Azure App with Managed Identity enabled. Can't get Access Token

Lucian Hanga 20 Reputation points
2024-09-22T22:16:59.79+00:00

I provisioned with terraform the following resources:

  • Azure Database for MySQL server
  • Azure App Service Plan
  • Azure WebApp
  • Azure KeyVault

I enabled the managed identity on the webapp and in the Key Vault defined a policy to allow the webapp to access the KeyVault secrets. First I tried to deploy a simple php application which tries to get the database credentials from the KeyVault and connect to the database. However, I got an error that the webapp cannot access the KeyVault.
Then I tried to use the Webapp advanced tools and curl from the webapp console to get the access token from the metadata service:

curl 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2023-01-01&resource=https://vault.azure.net' \
-H 'Metadata:true'

I got the following error:

__ __          __      __    _ __     
   / //_/_  _ ____/ /_  __/ /   (_) /____ 
  / ,< / / / / __  / / / / /   / / __/ _  
 / /| / /_/ / /_/ / /_/ / /___/ / /_/  __/
/_/ |_\__,_/\__,_/\__,_/_____/_/\__/\___/ 

                                  

DEBUG CONSOLE | AZURE APP SERVICE ON LINUX
Documentation: [http://aka.ms/webapp-linux]()
Kudu Version : 20240822.2
Commit       : a86ad9d31002b0e2111a20f21ebaeae4be986b94
kudu_ssh_user@webapp-web_kudu_6306b30e78:/$  curl -H "Metadata: true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fvault.azure.net"
curl: (7) Failed to connect to 169.254.169.254 port 80 after 0 ms: Couldn't connect to server
kudu_ssh_user@webapp-web_kudu_6306b30e78:/$ 

In the Azure console CLI I was able to run the curl command and get the access token for the keyvault, and use the token then in the php application to get the secrets from the KeyVault. It worked like a charm. Here is the terraform code fir the app:


locals {

app_service_plan_name = "asp-${var.project_name}"
webapp_name = "webapp-${var.project_name}"    
}
#
# create the service plan for the webapp
#
resource "azurerm_service_plan" "webapp_serviceplan" {
  name                = local.app_service_plan_name
  location            = var.location
  resource_group_name = var.resource_group_name
  os_type             = "Linux"
  sku_name            = "F1" # Free tier
}

#create the webapp using the "azurerm_linux_web_app" resource
resource "azurerm_linux_web_app" "webapp" {
  name                = local.webapp_name
  location            = var.location
  resource_group_name = var.resource_group_name
  service_plan_id = azurerm_service_plan.webapp_serviceplan.id
  site_config {
always_on = false
application_stack {
    php_version = "8.3"
}
}
  # enable the managed identity for the webapp
identity {
    type = "SystemAssigned"
}
}

Azure Key Vault
Azure Key Vault
An Azure service that is used to manage and protect cryptographic keys and other secrets used by cloud apps and services.
1,293 questions
Azure App Service
Azure App Service
Azure App Service is a service used to create and deploy scalable, mission-critical web apps.
7,807 questions
{count} votes

Accepted answer
  1. TP 96,331 Reputation points
    2024-09-24T12:46:35.4+00:00

    Hi Lucian,

    You aren't using correct IMDS endpoint for app service. That is why you are seeing "Failed to connect to 169.254.169.254 port 80 after 0 ms: Couldn't connect to server" error.

    You need to use IDENTITY_ENDPOINT and IDENTITY_HEADER environment variables. Below is example using curl:

    curl -H X-IDENTITY-HEADER:$IDENTITY_HEADER "$IDENTITY_ENDPOINT?resource=https://vault.azure.net&api-version=2019-08-01"
    
    

    You would use similar technique in your php code by referencing the above two environment variables.

    REST endpoint reference

    https://learn.microsoft.com/en-us/azure/app-service/overview-managed-identity#rest-endpoint-reference

    Please click Accept Answer and upvote if the above was helpful.

    Thanks.

    -TP

    1 person found this answer helpful.

2 additional answers

Sort by: Most helpful
  1. Lucian Hanga 20 Reputation points
    2024-09-24T21:04:03.0733333+00:00

    hi Ben,

    I followed your advice and I used RBAC and User-Assigned Managed Identity and with the answer from TP it worked!

    Thanks a lot for your help!


  2. Lucian Hanga 20 Reputation points
    2024-09-24T21:12:13.9866667+00:00

    the curl code which I ran from the webapp container.

    curl -H "X-IDENTITY-HEADER: $IDENTITY_HEADER" \
         -G \
         --data-urlencode "resource=https://mykeyvalut.vault.azure.net/" \
         --data-urlencode "api-version=2019-08-01" \
         --data-urlencode "client_id=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
         "$IDENTITY_ENDPOINT"
    
    

    and the python code which I used for testing the Azure API

    from flask import Flask
    from azure.identity import ManagedIdentityCredential
    import os
    
    app = Flask(__name__)
    
    @app.route('/')
    def get_access_token():
        # Prepare an HTML string which will be returned as a response
        response = """
        <html>
            <head>
                <title>Azure Managed Identity Access Token</title>
                <style>
                    body { font-family: Arial, sans-serif; }
                    h1 { color: #2c3e50; }
                    p { font-size: 14px; line-height: 1.6; }
                    .error { color: red; }
                    .token { color: green; word-wrap: break-word; }
                </style>
            </head>
            <body>
                <h1>Azure Managed Identity Access Token</h1>
        """
    
        # Managed Identity Client ID (User-Assigned Managed Identity)
        managed_identity_client_id = "xxxxxxxxxxxxxxxxxxxxxxx"  # Replace with your client ID
    
        # Resource URL for which to obtain the token
        resource_url = "https://mykeyvalut.vault.azure.net/"  # Replace with your required resource URL
    
        try:
            # Use ManagedIdentityCredential with the specified client ID
            credential = ManagedIdentityCredential(client_id=managed_identity_client_id)
    
            # Obtain an access token for the specified resource
            token = credential.get_token(resource_url)
    
            # Replace the first 100 characters of the token with 'xxxx...'
    		masked_token = 'x' * 100 + token.token[100:]
    
    
            # Add the masked token to the response
            response += f"<p><strong>Access Token:</strong> <span class='token'>{masked_token}</span></p>"
    
        except Exception as ex:
            # If there is an error in the process, return the error message
            response += f"<p class='error'><strong>An error occurred while getting the access token:</strong> {ex}</p>"
    
        response += """
            </body>
        </html>
        """
    
        return response
    
    if __name__ == '__main__':
        # Run the Flask app
        app.run(host='0.0.0.0', port=8000)
    

    Important Note

    There’s a grace period after enabling the managed identity where you need to wait for everything to align internally, including permission propagation, before you can successfully retrieve the token. I was impatient and made several attempts that resulted in errors, which I could have avoided by simply waiting about 5 minutes between each try.

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.