0
votes

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

1

1 Answers

1
votes

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