Sheena Agrawal
Sheena Agrawal

Reputation: 740

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?

Upvotes: 0

Views: 2151

Answers (2)

Ernesto Alfonso
Ernesto Alfonso

Reputation: 725

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.

Upvotes: 0

rickvdbosch
rickvdbosch

Reputation: 15561

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

Upvotes: 6

Related Questions