Azure Blob Container, Can't Generate Token - c#

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);
}

Related

Azure Blob Storage Sas Error: AuthenticationFailed

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;

Can't work with Azure Blob Storage from .Net Core App

I want to use Azure Blob Storage to save some files.
For each file I have a submission date, which I also want to use to create a Container. So I can organise the files by submission date.
I can access the Blob Storage and create the Container.
But I cannot set permissions on the Container, test if a particular Blob exists within it, or add a Blob to the Container.
What am I missing?
The innerException I'm getting in every case is:
{Microsoft.WindowsAzure.Storage.StorageException: This request is not authorized to perform this operation.
at Microsoft.WindowsAzure.Storage.Core.Executor.Executor.<ExecuteAsyncInternal>d__4`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task
task)
at Microsoft.WindowsAzure.Storage.Blob.CloudBlobContainer.<>c__DisplayClass64_0.<<SetPermissionsAsync>b__0>d.MoveNext()
Request Information
RequestID:XXXXXX
RequestDate:XXXXXX
StatusMessage:This request is not authorized to perform this operation.
ErrorCode:AuthorizationFailure
ErrorMessage:This request is not authorized to perform this operation.
RequestId:XXXXXX
Time:XXXXXX
}
Obviously an AuthorizationFailure as it says. But I can access the Blob Storage and create a Container. So what gives?
Here's the complete .Net Core 2 test program (minus a little redaction)
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Auth;
using Microsoft.WindowsAzure.Storage.Blob;
using System;
namespace AzureStorageTestApp
{
public class Program
{
public static void Main(string[] args)
{
var storageAccount = GetStorageAccount();
var policy = new SharedAccessAccountPolicy()
{
Permissions = SharedAccessAccountPermissions.Read | SharedAccessAccountPermissions.Write | SharedAccessAccountPermissions.List,
Services = SharedAccessAccountServices.Blob | SharedAccessAccountServices.File,
ResourceTypes = SharedAccessAccountResourceTypes.Service | SharedAccessAccountResourceTypes.Container,
SharedAccessStartTime = DateTime.UtcNow.AddMinutes(-15),
SharedAccessExpiryTime = DateTime.UtcNow.AddHours(24),
Protocols = SharedAccessProtocol.HttpsOnly
};
var sasToken = storageAccount.GetSharedAccessSignature(policy);
var creds = new StorageCredentials(sasToken);
var accountWithSAS = new CloudStorageAccount(creds, storageAccount.Credentials.AccountName, null, true);
var blobClient = accountWithSAS.CreateCloudBlobClient();
var containerName = DateTime.UtcNow.ToString("yyyy-MM-dd");
var container = blobClient.GetContainerReference(containerName);
if (container.CreateIfNotExistsAsync().Result)
{
var perms = new BlobContainerPermissions
{
PublicAccess = BlobContainerPublicAccessType.Blob
};
// Blows up here
container.SetPermissionsAsync(perms).Wait();
}
var fileName = "testfile.txt";
var fileData = "some file data";
var blob = container.GetBlockBlobReference(fileName);
bool shouldUpload = true;
// Blows up here
var blobExists = blob.ExistsAsync().Result;
if (blobExists)
{
blob.FetchAttributesAsync().Wait();
if (blob.Properties.Length == fileData.Length)
{
shouldUpload = false;
}
}
if (shouldUpload)
{
// Blows up here
blob.UploadTextAsync(fileData).Wait();
}
}
private static CloudStorageAccount GetStorageAccount()
{
var connectionString = "DefaultEndpointsProtocol=https;AccountName=<Redacted>;AccountKey=<Redacted>;EndpointSuffix=core.windows.net";
return CloudStorageAccount.Parse(connectionString);
}
}
}
The same story - it just doesn't work (fails with auth error).
Use CloudStorageAccount.Parse with BLOB connection string:
CloudStorageAccount.Parse("DefaultEndpointsProtocol=https;AccountName=***;AccountKey=***;EndpointSuffix=core.windows.net");
OK so here's "the answer".
If you're using a CloudStorageAccount set up with a SharedAccessAccountPolicy (instead of actual credentials) then that CloudStorageAccount can only be used for storage account management itself.
So for instance you can create a Blob Container, but you cannot set the permissions on that container, for that you'll need an appropriately created SharedAccessBlobPolicy or actual credentials.
So far my answer is to throw away all the stuff I had set up to use the SharedAccessAccountPolicy, ending up with the following code at the start:
var storageAccount = GetStorageAccount();
var blobClient = storageAccount.CreateCloudBlobClient();

Generate blob sasurl using SAS

I want to get a reference to the blob and generate a SAS URL for it.
How? Without exposing my storage account key?
What all have I tried? Getting the reference to blob by using SAS (of blob container or storage account).
My references: https://learn.microsoft.com/en-us/azure/storage/common/storage-dotnet-shared-access-signature-part-1?toc=%2fazure%2fstorage%2fblobs%2ftoc.json
The exception that I see: "Can not create Shared Access Signature unless Account Key credentials are used"
But I do not (obviously) want to expose my account key! Is this even possible? If not, is there any other way of doing it?
In short: no, there's no other way to do that besides using one of the keys. You need one of the Access Keys to be able to create a SAS token. Here's why you cannot do that with an existing SAS token:
The signature is an HMAC computed over the string-to-sign and key using the SHA256 algorithm, and then encoded using Base64 encoding.
This means the signature that is part of your SAS token is a calculated value. Part of that calculation is based on (one of the) key(s), since that is used to calculate the non-reversible hash. The fact that this hash is non-reversible means you cannot retrieve the Access Key used to calculate the hash. And therefor, you cannot use a SAS token to create another SAS token: you don't have an Access Key available to calculate the signature.
When you create a storage account, you get two storage access keys, which provide full control over the storage account contents. These keys are admin credentials.
More information: Constructing a Service SAS
This is an interpretation of this example
First we have this to get the accountKey:
public static async Task<StorageAccountKey> GetAccountKeys(string KeyName)
{
IAzure storageAccounts;
if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(#"AZURE_TENANT_ID"))
&& !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(#"AZURE_SUBSCRIPTION_ID")))
{
storageAccounts = GetStorageAccountWithTenantAndSubscription();
}
else
{
AzureCredentials credentials = SdkContext.AzureCredentialsFactory.FromSystemAssignedManagedServiceIdentity(MSIResourceType.AppService, AzureEnvironment.AzureGlobalCloud);
storageAccounts = Microsoft.Azure.Management.Fluent.Azure
.Authenticate(credentials)
.WithDefaultSubscription();
}
IStorageAccount storageAccount = await storageAccounts.StorageAccounts.GetByResourceGroupAsync(
Environment.GetEnvironmentVariable("STORAGE_ACCOUNT_GROUP"),
Environment.GetEnvironmentVariable("STORAGE_ACCOUNT_NAME")
);
IReadOnlyList<StorageAccountKey> accountKeys = storageAccount.GetKeys();
return accountKeys.FirstOrDefault(k => k.KeyName == KeyName);
}
private static IAzure GetStorageAccountWithTenantAndSubscription()
{
DefaultAzureCredential tokenCred = new DefaultAzureCredential(includeInteractiveCredentials: true);
string armToken = tokenCred.GetToken(new TokenRequestContext(scopes: new[] { "https://management.azure.com/.default" }, parentRequestId: null), default).Token;
TokenCredentials armCreds = new TokenCredentials(armToken);
string graphToken = tokenCred.GetToken(new TokenRequestContext(scopes: new[] { "https://graph.windows.net/.default" }, parentRequestId: null), default).Token;
TokenCredentials graphCreds = new TokenCredentials(graphToken);
AzureCredentials credentials = new AzureCredentials(armCreds, graphCreds, Environment.GetEnvironmentVariable(#"AZURE_TENANT_ID"), AzureEnvironment.AzureGlobalCloud);
return Microsoft.Azure.Management.Fluent.Azure
.Authenticate(credentials)
.WithSubscription(Environment.GetEnvironmentVariable(#"AZURE_SUBSCRIPTION_ID"));
}
Where you need to define the next environment variables:
AZURE_TENANT_ID
AZURE_SUBSCRIPTION_ID
STORAGE_ACCOUNT_GROUP
STORAGE_ACCOUNT_NAME
All of them can be found on the https://portal.azure.com/ and if you run az login
then you can do this to generate the connection string:
private static async Task<string> GetAccountSASToken()
{
StorageAccountKey accountKeyObj = await GetAccountKeys(Environment.GetEnvironmentVariable("STORAGE_ACCOUNT_KEY"));
string accountKey = accountKeyObj.Value;
string accountName = Environment.GetEnvironmentVariable("STORAGE_ACCOUNT_NAME");
StorageSharedKeyCredential key = new StorageSharedKeyCredential(accountName, accountKey);
AccountSasBuilder sasBuilder = new AccountSasBuilder()
{
Services = AccountSasServices.Blobs | AccountSasServices.Files,
ResourceTypes = AccountSasResourceTypes.Container | AccountSasResourceTypes.Object,
ExpiresOn = DateTimeOffset.UtcNow.AddHours(1),
Protocol = SasProtocol.Https
};
sasBuilder.SetPermissions(AccountSasPermissions.List | AccountSasPermissions.Read);
string sasToken = sasBuilder.ToSasQueryParameters(key).ToString();
return sasToken;
}
And that's all you need.

Programmatically get Azure storage account properties

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:

C# - Using Azure Key Vault with Azure Storage on Native App

I'm using the following code to upload to my image container in my Azure storange account. The connection string in app.config is:
<appSettings>
<add key="StorageConnectionString" value="MyConnectionString" />
</appSettings>
CloudStorageAccount storageAccount = CloudStorageAccount.Parse
CloudConfigurationManager.GetSetting("StorageConnectionString"));
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
// Retrieve reference to a previously created container.
CloudBlobContainer container = blobClient.GetContainerReference("imagestorage");
// Retrieve reference to a blob named "myblob".
CloudBlockBlob blockBlob = container.GetBlockBlobReference("IMG1.png");
// Create or overwrite the "myblob" blob with contents from a local file.
using (var fileStream = System.IO.File.OpenRead(#"D:\Untitled.png"))
{
blockBlob.UploadFromStream(fileStream);
}
Question is how do I intergrate Azure Key Vault into my native application so that my API keys will not be compromised by some annoying reverse engineers?
I've registered my app in Azure Active Directory and given permissions for Azure Key Vault.
Also, who ever tries to use my native desktop app has to log in to my ASP.NET Web API app with Individual Accounts and receive a token, before using any other features. All of my controllers require authorization.
I believe what you're trying to do is integrate your Azure KeyVault with your C# Application. You can do this my using 2 API. One being the Microsoft.Azure.KeyVault and the other being ADAL.
Following these steps may get you answer:
public async Task<string> GetToken(string authority, string resource, string scope)
{
var authContext = new AuthenticationContext(authority);
ClientCredential clientCred = new ClientCredential(ConfigurationManager.AppSettings["ClientID"], ConfigurationManager.AppSettings["ClientSecret"]);
AuthenticationResult result = await authContext.AcquireTokenAsync(resource, clientCred);
if(result == null)
{
throw new InvalidOperationException("Failed to obtain the JWT Token");
}
Console.WriteLine("Retrieved Password");
return result.AccessToken;
}
And then get the value of what you're trying to return by running this:
public async Task getvaluesAsync()
{
var kv = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(GetToken));
var sec = await kv.GetSecretAsync(ConfigurationManager.AppSettings["SecretURI"]);
EncryptSecret = sec.Value;
}
Replace the appropriate values of ClientID, Client Secret and SecretURI with your values in the App.config file. Use a getter and setter method with the "EcryptSecret" by doing something like,
public static string EncryptSecret { get; set; }
This will continuously store the password / DB connections for further use.
A few helpful articles would be:
https://learn.microsoft.com/en-us/azure/key-vault/key-vault-developers-guide

Categories