Search code examples
c#azureazure-functionsazure-storageazure-managed-identity

How can an Azure Function function get a reference to Azure Table storage using Managed Identity?


I have an Azure Function that has been assigned a System Identity:

System Assigned Identity

I would like the Azure Function to access a Storage account. The Function has the Reader & Data Access role on that storage account:

RBAC permissions

The Function has been configured with the name of the Storage account to use. The Function then tries to get an instance of CloudTableClient:

public async Task InitAsync(string accountsStorageName)
{
    var azureServiceTokenProvider = new AzureServiceTokenProvider();
    string accessToken = await azureServiceTokenProvider.GetAccessTokenAsync(accountsStorageName);
    
    var storageCredential = new StorageCredentials(accessToken);
    var storageAccount = new CloudStorageAccount(storageCredential, accountsStorageName, "core.windows.net", true);
                
     //  Gets the client to the account's Table storage.
    m_tableClient = storageAccount.CreateCloudTableClient();
}

Question

The above code fails because it is unable to get an access token:

Error

How can an Azure Function get a reference to Azure Table storage using Managed Identity?


Solution

  • Azure key vault can generate shared access signature tokens. A shared access signature provides delegated access to resources in your storage account. You can grant clients access to resources in your storage account without sharing your account keys. For more details, please refer to here

    1. Set an account shared access signature definition
    $storageAccountName = ""
    $keyVaultName = ""
    $storageContext = New-AzStorageContext -StorageAccountName $storageAccountName -Protocol Https -StorageAccountKey Key1
    $start = [System.DateTime]::Now.AddDays(-1)
    $end = [System.DateTime]::Now.AddMonths(1)
    
    $sasToken = New-AzStorageAccountSasToken -Service blob,file,Table,Queue -ResourceType Service,Container,Object -Permission "racwdlup" -Protocol HttpsOnly -StartTime $start -ExpiryTime $end -Context $storageContext
    
    
    Set-AzKeyVaultManagedStorageSasDefinition -AccountName $storageAccountName -VaultName $keyVaultName -Name "<YourSASDefinitionName>" -TemplateUri $sasToken -SasType 'account' -ValidityPeriod ([System.Timespan]::FromDays(30))
    

    enter image description here

    1. Configure access policy for Azure Function
    Set-AzKeyVaultAccessPolicy -VaultName $keyVaultName -ObjectId "Azure Function MSI object id" -PermissionsToSecrets get,list
    
    1. code
      public static class Http
        {
            [FunctionName("Http")]
            public static async Task<IActionResult> Run(
                [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
                ILogger log, ExecutionContext context)
            {
                var azureServiceTokenProvider = new AzureServiceTokenProvider();
                var kv = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
    
                SecretBundle sasToken =await kv.GetSecretAsync(secretIdentifier: "https://testkey02.vault.azure.net:443/secrets/teststorage08-sasToken");
                var storageCredential = new StorageCredentials(sasToken.Value);
                var accountsStorageName = "teststorage08";
                var storageAccount = new CloudStorageAccount(storageCredential, accountsStorageName, "core.windows.net", true);
    
                var tableClient = storageAccount.CreateCloudTableClient();
                var table =tableClient.GetTableReference("Customer");
                await table.CreateIfNotExistsAsync();
                CustomerEntity customer = new CustomerEntity("Harp", "Walter")
                {
                    Email = "[email protected]",
                    PhoneNumber = "425-555-0101"
                };
    
                TableOperation insertOrMergeOperation = TableOperation.InsertOrMerge(customer);
                TableResult result = await table.ExecuteAsync(insertOrMergeOperation);
                CustomerEntity insertedCustomer = result.Result as CustomerEntity;
    
                return new OkObjectResult(insertedCustomer);
    
            }       
        }
    
        public class CustomerEntity : TableEntity
        {
            public CustomerEntity()
            {
            }
    
            public CustomerEntity(string lastName, string firstName)
            {
                PartitionKey = lastName;
                RowKey = firstName;
            }
    
            public string Email { get; set; }
    
            public string PhoneNumber { get; set; }
        }
    

    enter image description here