I wrote in my C# web application a method that deletes old blobs from Azure storage account.
This is my code:
public void CleanupIotHubExpiredBlobs()
{
const string StorageAccountName = "storageName";
const string StorageAccountKey = "XXXXXXXXXX";
const string StorageContainerName = "outputblob";
string storageConnectionString = string.Format("DefaultEndpointsProtocol=https;AccountName={0};AccountKey={1}", StorageAccountName, StorageAccountKey);
// Retrieve storage account from connection string.
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(storageConnectionString);
// Create the blob client.
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
// select container in which to look for old blobs.
CloudBlobContainer container = blobClient.GetContainerReference(StorageContainerName);
// set up Blob access condition option which will filter all the blobs which are not modified for X (this.m_CleanupExpirationNumOfDays) amount of days
IEnumerable<IListBlobItem> blobs = container.ListBlobs("", true);
foreach (IListBlobItem blob in blobs)
{
CloudBlockBlob cloudBlob = blob as CloudBlockBlob;
Console.WriteLine(cloudBlob.Properties);
cloudBlob.DeleteIfExists(DeleteSnapshotsOption.None, AccessCondition.GenerateIfNotModifiedSinceCondition(DateTime.Now.AddDays(-1 * 0.04)), null, null);
}
LogMessageToFile("Remove old blobs from storage account");
}
as you can see, In order to achieve that The method has to receive StorageAccountName and StorageAccountKey parameters.
One way to do that is by configuring these parameters in a config file for the app to use, But this means the user has to manually insert these two parameters to the config file.
My question is:
is there a way to programmatically retrieve at least one of these parameters in my code, so that at least the user will have to insert only one parameters and not two? my goal is to make the user's life easier.
My question is: is there a way to programmatically retrieve at least one of these parameters in my code, so that at least the user will have to insert only one parameters and not two? my goal is to make the user's life easier.
According to your description, I suggest you could use azure rest api to get the storage account key by using account name.
Besides, we could also use rest api to list all the rescourse group's storage account name, but it still need to send the rescourse group name as parameter to the azure management url.
You could send the request to the azure management as below url:
POST: https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resrouceGroupName}/providers/Microsoft.Storage/storageAccounts/{storageAccountName}/listKeys?api-version=2016-01-01
Authorization: Bearer {token}
More details, you could refer to below codes:
Notice: Using this way, you need firstly create an Azure Active Directory application and service principal. After you generate the service principal, you could get the applicationid,access key and talentid. More details, you could refer to this article.
Code:
string tenantId = " ";
string clientId = " ";
string clientSecret = " ";
string subscription = " ";
string resourcegroup = "BrandoSecondTest";
string accountname = "brandofirststorage";
string authContextURL = "https://login.windows.net/" + tenantId;
var authenticationContext = new AuthenticationContext(authContextURL);
var credential = new ClientCredential(clientId, clientSecret);
var result = authenticationContext.AcquireTokenAsync(resource: "https://management.azure.com/", clientCredential: credential).Result;
if (result == null)
{
throw new InvalidOperationException("Failed to obtain the JWT token");
}
string token = result.AccessToken;
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(string.Format("https://management.azure.com/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Storage/storageAccounts/{2}/listKeys?api-version=2016-01-01", subscription, resourcegroup, accountname));
request.Method = "POST";
request.Headers["Authorization"] = "Bearer " + token;
request.ContentType = "application/json";
request.ContentLength = 0;
//Get the response
var httpResponse = (HttpWebResponse)request.GetResponse();
using (System.IO.StreamReader r = new System.IO.StreamReader(httpResponse.GetResponseStream()))
{
string jsonResponse = r.ReadToEnd();
Console.WriteLine(jsonResponse);
}
Result:
Related
I'm trying to get an accesstoken from a InteractiveBrowserCredential so I can make calls to the Azure Datalake, but when I try to do this I get this error message:
System.Net.Http.HttpRequestException: 'Response status code does not
indicate success: 403 (Server failed to authenticate the request. Make
sure the value of Authorization header is formed correctly including
the signature.).'
Below is my code, I sspect it might be the scopes I request but I have tried every value I could find online (https://storage.azure.com/user_impersonation, https://storage.azure.com/.default).
// setup authentication
var tenantId = "common";
var clientId = "xxx";
var options = new InteractiveBrowserCredentialOptions {
TenantId = tenantId,
ClientId = clientId,
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud,
RedirectUri = new Uri("http://localhost"),
};
// authenticate and request accesstoken
var interactiveCredential = new InteractiveBrowserCredential(options);
var token = interactiveCredential.GetToken(new TokenRequestContext(new[] { "https://storage.azure.com/.default" }));
// Create HttpClient
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Token); ;
string res = await client.GetStringAsync("https://xx.blob.core.windows.net/?comp=list"); // this line throws the error
textBox1.Text = res;
If I use the SDK it does work, so it does not appear to be security settings.
// connect to Azure Data Lake (this works fine)
string accountName = "xxx";
string dfsUri = "https://" + accountName + ".dfs.core.windows.net";
var dataLakeServiceClient = new DataLakeServiceClient(new Uri(dfsUri), interactiveCredential);
// get filesystems
var systems = dataLakeServiceClient.GetFileSystems().ToList();// works fine
The error "403 Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signatureerror" usually occurs if you have not x-ms-version header in your code.
I agree with Gaurav Mantri, To resolve the error make sure to pass header value.
I tried to reproduce the same in my environment and got the same error as below in Postman:
I generated the access token by using the below Parameters:
https://login.microsoftonline.com/common/oauth2/v2.0/token
grant_type:authorization_code
client_id:4b08ee93-f4c8-47e9-bb1e-XXXXXXX
client_secret:client_secret
scope:https://storage.azure.com/user_impersonation
redirect_uri:https://jwt.ms
code: code
When I included x-ms-version=2019-02-02, I am able to access the storage account successfully like below:
The x-ms-version header value must be in the format YYYY-MM-DD.
In your code, you can include the header below samples:
Client.DefaultRequestHeaders.Add("x-ms-version", "2019-02-02");
request.Headers.Add("x-ms-version", "2019-02-02");
Reference:
Versioning for the Azure Storage services | Microsoft Learn
I'm trying to use SAS tokens in Azure Blob Storage following this tutorial, but I hit this error:
<Error>
<Code>AuthenticationFailed</Code>
<Message>
Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature. RequestId:2aada4ff-901e-0011-116c-8bc84f000000 Time:2019-10-25T19:41:37.0381744Z
</Message>
<AuthenticationErrorDetail>
Signature did not match. String to sign used was r 2019-10-25T19:26:51Z 2019-10-25T20:31:51Z /blob/platinepersistencesg/$root/documents-legal-entity-01d6d631-bc1e-54e7-894e-f67297a2bae7 2019-02-02 b
</AuthenticationErrorDetail>
</Error>
Here is my code:
public async Task<IActionResult> GetSasToken()
{
const string containerName = "documents-legal-entity-01d6d631-bc1e-54e7-894e-f67297a2bae7";
const string blobName = "09578f41-e7fb-4765-bf41-869ea649f03a.pdf";
const SharedAccessBlobPermissions permissions = SharedAccessBlobPermissions.Read | SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.Create;
var blobClient = CloudStorageAccount
.Parse("DefaultEndpointsProtocol=https;AccountName=<account_name>;AccountKey=<account_key>;EndpointSuffix=core.windows.net")
.CreateCloudBlobClient();
var container = blobClient.GetContainerReference(containerName);
var blob = container.GetBlockBlobReference(blobName);
var policy = new SharedAccessBlobPolicy
{
SharedAccessExpiryTime = DateTime.UtcNow.AddHours(24),
Permissions = permissions
};
var sasToken = blob.GetSharedAccessSignature(policy);
var sasUri = container.Uri + sasToken;
return Ok(new { uri = sasUri });
}
I'm able to make it work following this answer, but I would like to use the Azure client instead for simplicity and to avoid carrying around the storage key.
Only thing you're missing is the blob name from your URL construction. Just need to change:
var sasUri = container.Uri + sasToken;
...to...
var sasUri = blob.Uri + sasToken;
I'm writing an Azure Function App that needs to provide a HTML page with a form. The form should post back to one of the endpoints of the App. The App shall be protected with Azure's Authorization Key functionality.
Azure allows the caller to provide his/her key in two ways:
With a code request query parameter.
With a x-functions-clientid HTTP header.
To make the call to the other endpoint of the Azure Function App successful, I will therefore need to provide the Host Key along with my request. For example like this:
<form method='post' action='DoSomething?code={{{WHERE TO GET THIS FROM?}}}'>
<input name='someInput' />
<input type='submit' />
</form>
I'm using C# to generate the HTML code. What's the most bullet-proof way to get the/a Host Key programmatically?
You could use Microsoft.Azure.Management.ResourceManager.Fluent and Microsoft.Azure.Management.Fluent to do that.
Refer this SO thread for more information.
string clientId = "client id";
string secret = "secret key";
string tenant = "tenant id";
var functionName ="functionName";
var webFunctionAppName = "functionApp name";
string resourceGroup = "resource group name";
var credentials = new AzureCredentials(new ServicePrincipalLoginInformation { ClientId = clientId, ClientSecret = secret}, tenant, AzureEnvironment.AzureGlobalCloud);
var azure = Azure
.Configure()
.Authenticate(credentials)
.WithDefaultSubscription();
var webFunctionApp = azure.AppServices.FunctionApps.GetByResourceGroup(resourceGroup, webFunctionAppName);
var ftpUsername = webFunctionApp.GetPublishingProfile().FtpUsername;
var username = ftpUsername.Split('\\').ToList()[1];
var password = webFunctionApp.GetPublishingProfile().FtpPassword;
var base64Auth = Convert.ToBase64String(Encoding.Default.GetBytes($"{username}:{password}"));
var apiUrl = new Uri($"https://{webFunctionAppName}.scm.azurewebsites.net/api");
var siteUrl = new Uri($"https://{webFunctionAppName}.azurewebsites.net");
string JWT;
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", $"Basic {base64Auth}");
var result = client.GetAsync($"{apiUrl}/functions/admin/token").Result;
JWT = result.Content.ReadAsStringAsync().Result.Trim('"'); //get JWT for call funtion key
}
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + JWT);
var key = client.GetAsync($"{siteUrl}/admin/functions/{functionName}/keys").Result.Content.ReadAsStringAsync().Result;
}
I'm working on azure storage but I cannot create a proper SAS token to pass to my frontend javascript. Following multiple tutorials and examples, I can't seem to get a working token for JS.
I'm validating my token at on the tutorial here so that my own javascript doesn't get in my way: https://dmrelease.blob.core.windows.net/azurestoragejssample/samples/sample-blob.html
I've spent hours trying out different solutions, but my token generated looks so similar to the one generated by azure. What am I missing?
code
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(connectionString);
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference(containerName);
//Set the expiry time and permissions for the container.
//In this case no start time is specified, so the shared access signature becomes valid immediately.
SharedAccessBlobPolicy sasConstraints = new SharedAccessBlobPolicy();
sasConstraints.SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddHours(24);
sasConstraints.Permissions = SharedAccessBlobPermissions.List | SharedAccessBlobPermissions.Write;
//Generate the shared access signature on the container, setting the constraints directly on the signature.
string sasContainerToken = container.GetSharedAccessSignature(sasConstraints);
//Return the URI string for the container, including the SAS token.
return sasContainerToken;
Based on my test, the code is ok for generating SAS token. If you want to list the blobs in the container, you need to add &comp=list&restype=container to your SAS URL. Then it should work.
Get https://xxxxx.blob.core.windows.net/test?sv=2018-03-28&sr=c&sig=xxxxxxxxx&sp=rwl&comp=list&restype=container
Azure Storage Service is not able to identify if the resource you're trying to access is a blob or a container and assumes it's a blob. Since it assumes the resource type is blob, it makes use of $root blob container for SAS calculation (which you can see from your error message). Since SAS was calculated for mark blob container, you get this Signature Does Not Match error. By specifying restype=container you're telling storage service to treat the resource as container. comp=list is required as per REST API specification.
For more information, please refer to another SO thread.
Regarding the issue, have you tried to use JS to create a SAS token.
var azure = require('azure-storage');
var fs = require('fs');
var SasConstants = azure.Constants.AccountSasConstants;
var blobService = azure.createBlobService();
var containerName = 'containername';
var blobName = 'blobname';
var startDate = new Date('');
var expiryDate = new Date(startDate);
expiryDate.setDate(startDate.getDate() + 1);
var sharedAccessPolicy = {
AccessPolicy: {
Permissions: azure.BlobUtilities.SharedAccessPermissions.READ + azure.BlobUtilities.SharedAccessPermissions.ADD + azure.BlobUtilities.SharedAccessPermissions.CREATE+ azure.BlobUtilities.SharedAccessPermissions.WRITE,
Start: startDate,
Expiry: expiryDate
},
};
var token = blobService.generateSharedAccessSignature(containerName, null, sharedAccessPolicy);
Generate a token for the storage account instead. The permissions in the tutorial listed are granted by the storage account policy.
public static string GenerateAccountSASToken(string connectionString)
{
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(connectionString);
SharedAccessAccountPolicy accountpolicy = new SharedAccessAccountPolicy();
accountpolicy.SharedAccessStartTime = DateTimeOffset.UtcNow.AddHours(-24);
accountpolicy.SharedAccessExpiryTime = DateTimeOffset.UtcNow.AddHours(24);
accountpolicy.Permissions = SharedAccessAccountPermissions.Add | SharedAccessAccountPermissions.Create | SharedAccessAccountPermissions.List | SharedAccessAccountPermissions.ProcessMessages | SharedAccessAccountPermissions.Read | SharedAccessAccountPermissions.Update | SharedAccessAccountPermissions.Write;
accountpolicy.Services = SharedAccessAccountServices.Blob;
accountpolicy.ResourceTypes = SharedAccessAccountResourceTypes.Container | SharedAccessAccountResourceTypes.Object | SharedAccessAccountResourceTypes.Service;
return storageAccount.GetSharedAccessSignature(accountpolicy);
}
I am trying to write a simple console app which will authenticate using OAUTH against Azure Graph without the need for username/password, but I'm receiving a 403 error when executing the WebClient.DownloadString method. Any help would be greatly appreciated.
static void Main(string[] args)
{
// Constants
var tenant = "mytenant.onmicrosoft.com";
var resource = "https://graph.microsoft.com/";
var clientID = "blah-blah-blah-blah-blah";
var secret = "blahblahblahblahblahblah";
// Ceremony
var authority = $"https://login.microsoftonline.com/{tenant}";
var authContext = new AuthenticationContext(authority);
var credentials = new ClientCredential(clientID, secret);
// Obtain Token
var authResult = authContext.AcquireToken(resource, credentials);
WebClient webClient1 = new WebClient();
webClient1.Headers[HttpRequestHeader.Authorization] = "Bearer " + authResult.AccessToken;
webClient1.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
webClient1.Headers[HttpRequestHeader.Accept] = "application/json";
string payload = webClient1.DownloadString("https://graph.microsoft.com/v1.0/users?$Select=givenName,surname");
}
}
This has now been resolved. The code above was correct, but there was a step I was missing, which is to configure the ServicePrincipal in Azure:-
Login with a Global Admin using the command Connect-Msolservice
Retrieve the ObjectID of the Service Principal > Get-MsolServicePrincipal –AppPrincipalId YOUR_APP_CLIENT_ID
Assign the role using > Add-MsolRoleMember -RoleMemberType ServicePrincipal -RoleName ‘Company Administrator’ -RoleMemberObjectId YOUR_OBJECT_ID
The following links were also very useful:-
https://developer.microsoft.com/en-us/graph/docs/concepts/overview (Click the arrow in the top left to show the full list and then scroll down to the appropriate operation)
https://msdn.microsoft.com/en-us/library/azure/ad/graph/howto/azure-ad-graph-api-error-codes-and-error-handling