1
votes

I am trying to get a msi token for a specific User defined identity. Our app service has 2 user defined identities and I want a token on behalf of one of the user assigned identity.

Here is the code:

        HttpWebRequest req = (HttpWebRequest)WebRequest.Create(
            "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/&object_id=<ObjectId>&client_id=<clientId>");

        req.Headers["Metadata"] = "true";
        req.Method = "GET";

        try
        {
            // Call /token endpoint
            HttpWebResponse response = (HttpWebResponse)req.GetResponse();

            // Pipe response Stream to a StreamReader, and extract access token
            StreamReader streamResponse = new StreamReader(response.GetResponseStream());
            string stringResponse = streamResponse.ReadToEnd();
            Dictionary<string, string> list =
                 JsonConvert.DeserializeObject<Dictionary<string, string>>(stringResponse);
            string accessToken = list["access_token"];

            System.IO.File.WriteAllText(@".\Log.txt", accessToken);
        }
        catch (Exception e)
        {
            string errorText = String.Format("{0} \n\n{1}", e.Message, e.InnerException != null ? e.InnerException.Message : "Acquire token failed");
            System.IO.File.WriteAllText(@".\Log.txt", errorText);
            throw;
        }

It is deployed in an azure app service. When I hit this section I see this error: An attempt was made to access a socket in a way forbidden by its access permissions

I tried connecting to http://169.254.169.254 to get the token using kudu console. But this endpoint does not seem to accessible there.

enter image description here

I did try to use AzureServiceTokenProvider from Microsoft.Azure.Services.AppAuthentication for generating msi token but could not find any documentation about how to use it for multiple user assigned identities.

Edit:

Update 1:

I tried to use endpoint from MSI_ENDPOINT environment variable instead of 169.254.169.254. But it looks like MSI_ENDPOINT value is not set when I run the app service. Here is the code I have tried:

   var endpoint = Environment.GetEnvironmentVariable("MSI_ENDPOINT");
    string apiVersion = "2018-02-01";
    string resource = "https://management.azure.com/";
    string objectId = "<objectid>";
    string clientId = "<clientId>";

        // Build request to acquire managed identities for Azure resources token
        //HttpWebRequest req = (HttpWebRequest)WebRequest.Create(
        //    "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/&object_id=4aef1720-b3b1-4935-8d68-e330508907fa&client_id=558ecc75-8697-4419-bab9-aa2c87043cfd");

        HttpWebRequest req = (HttpWebRequest)WebRequest.Create(
       String.Format(
            "{0}?resource={1}&api-version={2}&object_id={3}&client_id={4}",
            endpoint,
            resource,
            apiVersion,
            objectId,
            clientId));

        req.Headers["Metadata"] = "true";
        req.Method = "GET";

        try
        {
            // Call /token endpoint
            HttpWebResponse response = (HttpWebResponse)req.GetResponse();

            // Pipe response Stream to a StreamReader, and extract access token
            StreamReader streamResponse = new StreamReader(response.GetResponseStream());
            string stringResponse = streamResponse.ReadToEnd();
            Dictionary<string, string> list =
                 JsonConvert.DeserializeObject<Dictionary<string, string>>(stringResponse);
            string accessToken = list["access_token"];

            System.IO.File.WriteAllText(@".\Log.txt", accessToken);
        }
        catch (Exception e)
        {
            string errorText = String.Format("{0} \n\n{1}", e.Message, e.InnerException != null ? e.InnerException.Message : "Acquire token failed");

            string log = "MSI_ENDPOINT : " + endpoint + "\n";
            log += ("ErrorText : " + errorText + "\n");
            System.IO.File.WriteAllText(@".\Log.txt", errorText);
            throw;
        }
1
Since you are using MSI with App Service.. you should probably try using the environment variable MSI_ENDPOINT. Look at the C# code example here for how to construct the URL - docs.microsoft.com/en-us/azure/app-service/… .. Something like String.Format("{0}/?resource={1}&api-version={2}&object_id={3}&client_id={4}", Environment.GetEnvironmentVariable("MSI_ENDPOINT"), resource, apiversion, objectId, clientId));Rohit Saigal
I did look at MSI-Endpoint. Value is 127.0.0.1:41811/MSI/token . So I changed the URL to 127.0.0.1:41811/MSI/token? api-version=2018-02-01&resource=management.azure.com/…> Now I get error "Could not connect to remote server". Probably the endpoint is not reachable.Jitendra Singh Rathor
Ok got it.. I think the URL should be constructed at runtime using the variable like the code example in link shows.. instead of putting in 127.0.0.1 Also try passing only clientid in query string parameters for URL not the objectId.. I think it should be clientid= where you have client_id=Rohit Saigal
When I construct it at run time. MSI_ENDPOINT is not set. On Kudu console I see that value of MSI_ENDPOINT environment variable is set but but when when app service runs value of MSI_ENDPOINT is not set.Jitendra Singh Rathor
You may need to restart your app or redeploy the code.. See this note from Microsoft Docs.. Environment variables are set up when the process first starts, so after enabling a managed identity for your application, you may need to restart your application, or redeploy its code, before MSI_ENDPOINT and MSI_SECRET are available to your code.Rohit Saigal

1 Answers

5
votes

Firstly, this link How to use managed identities for App Service and Azure Functions provides good documentation specific to MSI for App Services.

Here is quick sample code.. to get token for a specific user assigned managed service identity as you've asked in your question.

  • resource - The AAD resource URI of the resource for which a token should be obtained.
  • apiversion - The version of the token API to be used. "2017-09-01" is currently the only version supported.
  • clientId - The ID of the user-assigned identity to be used. If omitted, the system-assigned identity is used.

    public static async Task<HttpResponseMessage> GetToken(string resource, string apiversion, string clientId)  
    {
        HttpClient client = new HttpClient();   
        client.DefaultRequestHeaders.Add("Secret", Environment.GetEnvironmentVariable("MSI_SECRET"));
        return await client.GetAsync(String.Format("{0}/?resource={1}&api-version={2}&clientid={3}", Environment.GetEnvironmentVariable("MSI_ENDPOINT"), resource, apiversion,clientId));
    }
    

    Overall I see a few changes that you should notice in the sample code above:

    1. Make use of MSI_ENDPOINT to construct URL at runtime
    2. parameter should be clientid and not client_id
    3. parameter object_id is not needed
    4. api version should be "2017-09-01" as documentation in above link says that's the only one supported.
  • About your issue with MSI_ENDPOINT value not being set when you run the app service, please take a look at this note from same link in Microsoft Docs

    enter image description here

  • Screenshot from documentation that is relevant for all parameters used

    enter image description here