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

Not able to use blob storage operations using System Assigned Managed Identity in Azure


Getting "Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature." error while trying to use System Assigned Managed Identity in Azure using C# language.

The steps followed

  • Created a new VM with Enabled Identity (System Assigned)
  • Added the VM in IAM using Role Assignment in storage account
  • Able to generate token using C#
  • But getting exception while reading blob, below are the exception details
Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.
   at Microsoft.Azure.Storage.Core.Executor.Executor.<ExecuteAsync>d__1`1.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.Azure.Storage.Blob.CloudBlockBlob.<DownloadTextAsync>d__72.MoveNext()

The Program Class

    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                var blob = new AzureCloudBlob();
                Console.WriteLine(blob.ReadBlob());
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.StackTrace);
            }
        }
    }

The AzureCloudBlob class, used to connect and read a blob using System Assigned Managed Idenitity access token

    class AzureCloudBlob
    {
        public CloudBlockBlob CreateConnection()
        {
            var token = GetToken();
            var tokenCredentials = new TokenCredential(token);
            var storageCredentials = new StorageCredentials(tokenCredentials);
            var serviceUri = new Uri("https://mystorageacc.blob.core.windows.net/practice/blob.txt");

            return new CloudBlockBlob(serviceUri, storageCredentials);
        }

        public string ReadBlob()
        {
            var client = CreateConnection();
            var blobClient = client.DownloadTextAsync();
            var data = blobClient.Result;
            return data;
        }

        public string GetToken()
        {
            var request = (HttpWebRequest)WebRequest.Create("http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/");
            request.Headers["Metadata"] = "true";
            request.Method = "GET";

            try
            {
                var response = (HttpWebResponse)request.GetResponse();
                var streamResponse = new StreamReader(response.GetResponseStream());
                string stringResponse = streamResponse.ReadToEnd();
                var list = (Dictionary<string, string>)JsonConvert.DeserializeObject(stringResponse, typeof(Dictionary<string, string>));
                string accessToken = list["access_token"];
                Console.WriteLine(accessToken);
                return accessToken;
            }
            catch (Exception e)
            {
                string errorText = String.Format("{0} \n\n{1}", e.Message, e.InnerException != null ? e.InnerException.Message : "Acquire token failed");
                Console.WriteLine(errorText);
                return errorText;
            }
        }
    }


Solution

  • If you want to access Azure storage with Azure AD auth, we should use resouce=https://storage.azure.com/ to get Azure AD access token. But you use resource=https://management.azure.com/. Please replace it. Besides, please note that you need to assign right role to the MSI. The role should be Storage Blob Data Reader, Storage Blob Data Contributor or Storage Blob Data Owner.

    The detailed steps are as below

    1. Enable system-assigned managed identity on Azure VM

      $vm = Get-AzVM -ResourceGroupName myResourceGroup -Name myVM
      Update-AzVM -ResourceGroupName myResourceGroup -VM $vm -AssignIdentity:$SystemAssigned
      
    2. Assign role to MSI on storage account scope

      $sp =Get-AzADServicePrincipal -displayname "<your VM name>"
      New-AzRoleAssignment -ObjectId $sp.id `
          -RoleDefinitionName "Storage Blob Data Reader" `
          -Scope  "/subscriptions/<subscription>/resourceGroups/sample-resource-group/providers/Microsoft.Storage/storageAccounts/<storage-account>"
      
    3. code

      class Program
      {
          static void Main(string[] args)
          {
              //get token
              string accessToken = GetMSIToken("https://storage.azure.com/");
      
              //create token credential
              TokenCredential tokenCredential = new TokenCredential(accessToken);
      
              //create storage credentials
              StorageCredentials storageCredentials = new StorageCredentials(tokenCredential);
      
              Uri blobAddress = new Uri("<URI to blob file>");
      
              //create block blob using storage credentials
              CloudBlockBlob blob = new CloudBlockBlob(blobAddress, storageCredentials);
      
              //retrieve blob contents
              Console.WriteLine(blob.DownloadText());
              Console.ReadLine();
          }
      
          static string GetMSIToken(string resourceID)
          {
              string accessToken = string.Empty;
              // Build request to acquire MSI token
              HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=" + resourceID);
              request.Headers["Metadata"] = "true";
              request.Method = "GET";
      
              try
              {
                  // Call /token endpoint
                  HttpWebResponse response = (HttpWebResponse)request.GetResponse();
      
                  // Pipe response Stream to a StreamReader, and extract access token
                  StreamReader streamResponse = new StreamReader(response.GetResponseStream());
                  string stringResponse = streamResponse.ReadToEnd();
                  JavaScriptSerializer j = new JavaScriptSerializer();
                  Dictionary<string, string> list = (Dictionary<string, string>)j.Deserialize(stringResponse, typeof(Dictionary<string, string>));
                  accessToken = list["access_token"];
                  return accessToken;
              }
              catch (Exception e)
              {
                  string errorText = String.Format("{0} \n\n{1}", e.Message, e.InnerException != null ? e.InnerException.Message : "Acquire token failed");
                  return accessToken;
              }
          }
      }
      

    enter image description here