azure-storage-net: Cannot generate SAS token for Blob using GetSharedAccessSignature(policy) and Azure Managed Identity.

Which service(blob, file, queue, table) does this issue concern?

Microsoft.Azure.Storage.Blob;

Which version of the SDK was used?

Azure Tools 2.9 Microsoft.Azure.Storage.Blob 10.0.3 Microsoft.Azure.Services.App.Authentication 1.2.0-preview3

Which platform are you using? (ex: .NET Core 2.1)

.NET Core 2.2

What problem was encountered?

Cannot generate SAS token when using Managed Identity. I have App Service on Azure trying to generate SAS token using the RBAC role Assignment. For the time being, I even assigned the identity as “Owner” role but still it cannot generate SAS token. It says, I need a Account Key Credentials. If I have to provide Account Key in the code, then doesn’t it defeat the purpose of Managed Identity. We want to avoid using Storage Key in our solution and use Managed Identity.

Below is the error. 2019-05-22 15:15:19.283 +00:00 [Error] Microsoft.AspNetCore.Server.IIS.Core.IISHttpServer: Connection ID "16861477006485750114", Request ID "80000165-0000-ea00-b63f-84710c7967bb": An unhandled exception was thrown by the application.System.InvalidOperationException: Cannot create Shared Access Signature unless Account Key credentials are used.at Microsoft.Azure.Storage.Blob.CloudBlob.GetSharedAccessSignature(SharedAccessBlobPolicy policy,

How can we reproduce the problem in the simplest way?

const string blobName = “https://yourcontainer.blob.core.windows.net/images/image1.jpg”;

        var azureServiceTokenProvider = new AzureServiceTokenProvider();
        string accessToken = (azureServiceTokenProvider.GetAccessTokenAsync("https://storage.azure.com/")).Result;

  
        AccountKeyCredentials accountKeyCredentials;

        TokenCredential tokenCredential = new TokenCredential(accessToken);

        StorageCredentials storageCredentials = new StorageCredentials(tokenCredential);

        
        CloudBlockBlob blob = new CloudBlockBlob(new Uri(blobName),
                                                    storageCredentials);
     
        SharedAccessBlobPolicy policy = new SharedAccessBlobPolicy()
        {                
            Permissions = SharedAccessBlobPermissions.Read,                                
            SharedAccessExpiryTime = DateTime.UtcNow.AddDays(24),
        };

        var sasToken = blob.GetSharedAccessSignature(policy);

Have you found a mitigation/solution?

No.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 21
  • Comments: 17

Most upvoted comments

@tomgallard I can’t get it to work. GetUserDelegationKey throws “Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.” in GetUserDelegationKey. Note that I’m using a SAS to a container, not the account.

Code:

Microsoft.Azure.Storage.Blob.CloudBlobContainer container = new Microsoft.Azure.Storage.Blob.CloudBlobContainer(new Uri("url+sas goes here"));
var blockBlob = container.GetBlockBlobReference("folder/file.txt");
//The following line fails with message: "Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature."
var delegationKey = blockBlob.ServiceClient.GetUserDelegationKey(DateTimeOffset.UtcNow.AddMinutes(-1), DateTimeOffset.UtcNow.AddMinutes(15));
var sas = blockBlob.GetUserDelegationSharedAccessSignature(delegationKey, new Microsoft.Azure.Storage.Blob.SharedAccessBlobPolicy()
                    {
                        SharedAccessStartTime = DateTime.UtcNow.AddHours(-1),
                        SharedAccessExpiryTime = DateTime.UtcNow.AddHours(16),
                        Permissions = Microsoft.Azure.Storage.Blob.SharedAccessBlobPermissions.Read
                    });
return blockBlob.Uri.ToString() + sas;

Any ideas?

Yes- before you start accessing containers you need to instantiate a blob client passing in a set of TokenCredentials- something like t his:

AzureServiceTokenProvider azureServiceTokenProvider = new AzureServiceTokenProvider();
           //this comes from local cache if not expired- not expensive, no need to cache
           
string accessToken = await azureServiceTokenProvider.GetAccessTokenAsync("https://storage.azure.com/");
TokenCredential tokenCredential = new TokenCredential(accessToken);
StorageCredentials storageCredentials = new StorageCredentials(tokenCredential);
var cloudBlobClient = new CloudBlobClient(new StorageUri(new Uri($"https://{_accountName.ToLower()}.blob.core.windows.net")), storageCredentials);
var container = cloudBlobClient.GetContainerReference("containerName")

@Xiaoxin4396 Its been a long time since I probably fixed it, so I don’t remember exactly the cause of the exception. It probably was related to usin GetSharedAccessSignature method, rather I used BlobSasBuilder in my final code. I am sharing the code, which is running fine currently, hope it helps you in some way

         
        private static string connectionString = ConfigurationManager.AppSettings["AzureStorageConnectionString"];
        private static BlobContainerClient containerClient;
        private static string accountName = ConfigurationManager.AppSettings["AzureStorageAccountName"];
        private static string accountKey = ConfigurationManager.AppSettings["AzureStorageAccountKey"];

        /**
         *  Generates PreSigned URL for sharing the bucket objects
         */
        public string GeneratePreSignedURLAzure(string bucketName, string objectKey, int timeInMinutes = 5)
        {
            // Construct the blob endpoint from the account name.
            string blobEndpoint = string.Format("https://{0}.blob.core.windows.net", accountName);

            StorageSharedKeyCredential sharedKeyCredential = new StorageSharedKeyCredential(accountName, accountKey);

            // Create a new Blob service client with Azure AD credentials.  
            BlobServiceClient blobClient = new BlobServiceClient(connectionString);

            // Create a SAS token that's valid for one hour.
            BlobSasBuilder sasBuilder = new BlobSasBuilder()
            {
                BlobContainerName = bucketName,
                BlobName = objectKey,
                Resource = "b",
            };

            string storedPolicyName = null;

            if (storedPolicyName == null)
            {
                sasBuilder.StartsOn = DateTimeOffset.UtcNow;
                sasBuilder.ExpiresOn = DateTimeOffset.UtcNow.AddHours(1);
                sasBuilder.SetPermissions(BlobContainerSasPermissions.Read);
            }
            else
            {
                sasBuilder.Identifier = storedPolicyName;
            }

            // Use the key to get the SAS token.
            string sasToken = sasBuilder.ToSasQueryParameters(sharedKeyCredential).ToString();

            Console.WriteLine("SAS token for blob container is: {0}", sasToken);
            Console.WriteLine();

            return $"{blobEndpoint}/{bucketName}/{objectKey}?{sasToken}";
        }

@tomgallard I can’t get it to work. GetUserDelegationKey throws “Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.” in GetUserDelegationKey. Note that I’m using a SAS to a container, not the account.

Code:

Microsoft.Azure.Storage.Blob.CloudBlobContainer container = new Microsoft.Azure.Storage.Blob.CloudBlobContainer(new Uri("url+sas goes here"));
var blockBlob = container.GetBlockBlobReference("folder/file.txt");
//The following line fails with message: "Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature."
var delegationKey = blockBlob.ServiceClient.GetUserDelegationKey(DateTimeOffset.UtcNow.AddMinutes(-1), DateTimeOffset.UtcNow.AddMinutes(15));
var sas = blockBlob.GetUserDelegationSharedAccessSignature(delegationKey, new Microsoft.Azure.Storage.Blob.SharedAccessBlobPolicy()
                    {
                        SharedAccessStartTime = DateTime.UtcNow.AddHours(-1),
                        SharedAccessExpiryTime = DateTime.UtcNow.AddHours(16),
                        Permissions = Microsoft.Azure.Storage.Blob.SharedAccessBlobPermissions.Read
                    });
return blockBlob.Uri.ToString() + sas;

Any ideas?

As with most Azure APIs, you need Fiddler to see the REAL exception. The exception message was clear for me. Fiddler came the rescue as always:

<?xml version="1.0" encoding="utf-8"?><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:A-guid
Time:2020-07-14T14:06:04.7963646Z</Message>
<AuthenticationErrorDetail>Only authentication scheme Bearer is supported</AuthenticationErrorDetail></Error>

@mikeblakeuk Did you figure the cause of this exception?

I am facing same exception with message “Only authentication scheme Bearer is supported” on my local development environment, while creating the BlobServiceClient object, below is the code. BlobServiceClientntials. blobClient = new BlobServiceClient(connectionString); UserDelegationKey key = blobClient.GetUserDelegationKey(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(7));

My local machine’s timezone/time is not off, I have checked thoroughly

@arkiaconsulting I had the same requirement for container only access. I followed this blog to work with setting up a service-principal with roles. You don’t need to do the custom roles, but the blog is a good reference is walking through setting up the needed resources.

My first run was to assign the “Contributor” and “Storage Blob Data Contributor” to the container-id, but later had to assign those roles to the storage-account-id so that the could use the service-principal login way to generate the SAS.

az login #at the subscription level you need
az ad sp credential reset --name $SP_NAME
az logout 

This gives you the login credentials you need.

After I do a login using the service-principal-id/secret 
az login --service-principal --username YOUR_SERVICE_PRINCIPAL_CLIENT_ID --password YOUR_SERVICE_PRINCIPAL_CLIENT_SECRET --tenant YOUR_TENANT_ID

end=`date -u -d "30 minutes" '+%Y-%m-%dT%H:%MZ'`  
sas=`az storage container generate-sas \
    -n $CONTAINER_NAME \
    --account-name $STORAGE_ACCOUNT_NAME \
    --permissions "acdlrw" \
    --https-only --expiry $end \
    -o tsv`

given the same service-principal-id/secret

public static async Task<string> GetAccessToken(string tenantId, string clientId, string clientSecret)
{
    var authContext = new AuthenticationContext($"https://login.windows.net/{tenantId}");
    var credential = new ClientCredential(clientId, clientSecret);
    var result = await authContext.AcquireTokenAsync("https://storage.azure.com", credential);

    if (result == null)
    {
        throw new Exception("Failed to authenticate via ADAL");
    }

    return result.AccessToken;
}
public static async Task<string> GetContainerSasTokenAsync(string storageAccountName, string containerName, string access_token)
{
    TokenCredential tokenCredential = new Microsoft.Azure.Storage.Auth.TokenCredential(access_token);
    StorageCredentials storageCredentials = new StorageCredentials(tokenCredential);
    CloudBlobClient client = new CloudBlobClient(new Uri($"https://{storageAccountName}.blob.core.windows.net"), storageCredentials);
    CloudBlobContainer container = client.GetContainerReference(containerName);

    var delegationKey = await client.GetUserDelegationKeyAsync(DateTimeOffset.UtcNow.AddMinutes(-1), DateTimeOffset.UtcNow.AddMinutes(15));
    var sas = container.GetUserDelegationSharedAccessSignature(delegationKey, new SharedAccessBlobPolicy()
    {
        SharedAccessStartTime = DateTime.UtcNow.AddHours(-1),
        SharedAccessExpiryTime = DateTime.UtcNow.AddHours(16),
        Permissions = SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.List
    });

    return sas;
}