question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

DefaultAzureCredential fails on Azure App Service when using User-Assigned Identity

See original GitHub issue
  • Package Name: @azure/identity
  • Package Version: 1.1.0 / 1.2.0-beta.1
  • Operating system: Linux
  • nodejs
    • version: v12.13.0
  • browser
    • name/version:
  • typescript
    • version:
  • Is the bug related to documentation in

Describe the bug

When using DefaultAzureCredential on an Azure App Service that has only a User-Assigned Managed Identity, the call to getToken() fails with an exception and does not continue to the next entry in the chain, causing an unhandled exception in user code, despite being properly configured

To Reproduce Steps to reproduce the behavior:

  1. Create an Azure Web App with the Linux container image ghcr.io/noelbundick/credential-bug-repro

image

  1. Assign a User-Assigned identity only

System identity disabled image

One user-assigned identity image

  1. Set the identity’s clientId as the AZURE_CLIENT_ID app setting

User assigned identity clientId image

AZURE_CLIENT_ID in Application Settings

image

  1. Run the following commands from your local machine to witness the failure. You can see the exception details in the App Service Log stream (screenshots below). You may need to enable application file system logging on the site
  • curl https://<myapp>.azurewebsites.net/repro1 - fails despite setting the managedIdentityClientId option
  • curl https://<myapp>.azurewebsites.net/repro1 -fails despite having AZURE_CLIENT_ID properly configured

Expected behavior

I expect to be able to use DefaultAzureCredential as documented. I expect the initial usage of ManagedIdentityCredential without a clientId set to fail, and for the chain to continue down and use the one that is configured to use my User-Assigned Identity.

Additional context Stack trace from App Service w/ AZURE_LOG_LEVEL=verbose below. You can see the url being called by ManagedIdentityCredential does not include the clientId.

2020-10-01T22:37:14.635Z INFO  - Container mitestapp_1_8c954fb0 for site mitestapp initialized successfully and is ready to serve requests.
2020-10-01T22:36:59.890091434Z Hosting environment: Production
2020-10-01T22:36:59.890825444Z Content root path: /app
2020-10-01T22:36:59.891290350Z Now listening on: http://[::]:8081
2020-10-01T22:36:59.891301850Z Application started. Press Ctrl+C to shut down.
2020-10-01T22:37:50.740308426Z azure:identity:info EnvironmentCredential => Found the following environment variables: AZURE_CLIENT_ID
2020-10-01T22:37:50.742838359Z azure:core-http:info ServiceClient: using custom request policies
2020-10-01T22:37:50.743701971Z azure:core-http:info ServiceClient: using custom request policies
2020-10-01T22:37:50.744603182Z azure:core-http:info ServiceClient: using custom request policies
2020-10-01T22:37:50.746190703Z azure:identity:info EnvironmentCredential => getToken() => ERROR: EnvironmentCredential is unavailable. Environment variables are not fully configured.
2020-10-01T22:37:50.747910726Z azure:identity:info ManagedIdentityCredential => Using the endpoint and the secret coming form the environment variables: MSI_ENDPOINT=http://172.16.2.6:8081/msi/token and MSI_SECRET=[REDACTED].
2020-10-01T22:37:50.749844552Z azure:identity:info IdentityClient: sending token request to [http://172.16.2.6:8081/msi/token?resource=https%3A%2F%2Fmanagement.azure.com&api-version=2017-09-01]
2020-10-01T22:37:50.753594101Z azure:core-http:info Request: {
2020-10-01T22:37:50.753630001Z   "url": "http://172.16.2.6:8081/msi/token?resource=REDACTED&api-version=2017-09-01",
2020-10-01T22:37:50.753635701Z   "method": "GET",
2020-10-01T22:37:50.753639802Z   "headers": {
2020-10-01T22:37:50.753643502Z     "_headersMap": {
2020-10-01T22:37:50.753647302Z       "accept": "application/json",
2020-10-01T22:37:50.753651202Z       "secret": "REDACTED",
2020-10-01T22:37:50.753655102Z       "accept-language": "REDACTED",
2020-10-01T22:37:50.753658902Z       "x-ms-client-request-id": "8bba1e26-2750-463a-8ec7-3f70f04a987e",
2020-10-01T22:37:50.753662802Z       "content-type": "application/json; charset=utf-8",
2020-10-01T22:37:50.753666502Z       "user-agent": "core-http/1.1.9 Node/v12.13.0 OS/(x64-Linux-4.15.0-112-generic)"
2020-10-01T22:37:50.753670402Z     }
2020-10-01T22:37:50.753674002Z   },
2020-10-01T22:37:50.753677402Z   "query": {
2020-10-01T22:37:50.753681002Z     "resource": "REDACTED",
2020-10-01T22:37:50.753690202Z     "api-version": "2017-09-01"
2020-10-01T22:37:50.753694202Z   },
2020-10-01T22:37:50.753697702Z   "withCredentials": false,
2020-10-01T22:37:50.753701302Z   "timeout": 0,
2020-10-01T22:37:50.753704802Z   "keepAlive": true,
2020-10-01T22:37:50.753708402Z   "requestId": "8bba1e26-2750-463a-8ec7-3f70f04a987e"
2020-10-01T22:37:50.753712202Z }
2020-10-01T22:37:50.978830270Z azure:core-http:info Response status code: 400
2020-10-01T22:37:50.979770082Z azure:core-http:info Headers: {
2020-10-01T22:37:50.979786282Z   "_headersMap": {
2020-10-01T22:37:50.979790782Z     "content-type": "application/json; charset=utf-8",
2020-10-01T22:37:50.979861983Z     "date": "Thu, 01 Oct 2020 22:37:50 GMT",
2020-10-01T22:37:50.979867683Z     "server": "Kestrel",
2020-10-01T22:37:50.979882584Z     "transfer-encoding": "chunked"
2020-10-01T22:37:50.979942984Z   }
2020-10-01T22:37:50.979946784Z }
2020-10-01T22:37:50.983336129Z azure:identity:warning IdentityClient: authentication error. HTTP status: 400, An unknown error occurred and no additional details are available.
2020-10-01T22:37:50.984070639Z azure:identity:info ChainedTokenCredential => getToken() => ERROR: ManagedIdentityCredential authentication failed.(status code 400).
2020-10-01T22:37:50.984085239Z More details:
2020-10-01T22:37:50.984096339Z unknown_error(status code 400).
2020-10-01T22:37:50.984100039Z More details:
2020-10-01T22:37:50.984103439Z An unknown error occurred and no additional details are available.
2020-10-01T22:37:50.987213880Z (node:41) UnhandledPromiseRejectionWarning: AuthenticationError: ManagedIdentityCredential authentication failed.(status code 400).
2020-10-01T22:37:50.987228080Z More details:
2020-10-01T22:37:50.987232680Z unknown_error(status code 400).
2020-10-01T22:37:50.987236381Z More details:
2020-10-01T22:37:50.987239981Z An unknown error occurred and no additional details are available.
2020-10-01T22:37:50.987243381Z     at ManagedIdentityCredential.<anonymous> (/app/node_modules/@azure/identity/dist/index.js:1077:23)
2020-10-01T22:37:50.987247181Z     at Generator.throw (<anonymous>)
2020-10-01T22:37:50.987289681Z     at rejected (/app/node_modules/tslib/tslib.js:112:69)
2020-10-01T22:37:50.987294181Z     at processTicksAndRejections (internal/process/task_queues.js:93:5)
2020-10-01T22:37:50.989992617Z (node:41) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)
2020-10-01T22:37:50.990475023Z (node:41) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Inlined examples from the linked repo so readers don’t have to browse the code. These usages of DefaultAzureCredential should work, but they don’t:

// Fails: explicitly set AZURE_CLIENT_ID
app.get('/repro1', async (req, res) => {

  const cred = new identity.DefaultAzureCredential({
    managedIdentityClientId: process.env.AZURE_CLIENT_ID
  });

  // This call will take ~120s and ultimately throw
  await cred.getToken('https://management.azure.com/.default'); 
  res.send('OK!');
});

// Fails: let DefaultAzureCredential handle it via env var
app.get('/repro2', async (req, res) => {

  const cred = new identity.DefaultAzureCredential();

  // This call will take ~120s and ultimately throw
  await cred.getToken('https://management.azure.com/.default');
  res.send('OK!');
});

cc @jongio - we should probably validate this scenario for other languages as well

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:3
  • Comments:16 (10 by maintainers)

github_iconTop GitHub Comments

1reaction
sadasantcommented, Oct 29, 2020

@noelbundick @JasonBeneteau Thank you for your feedback! I’m working on this right now.

1reaction
noelbundickcommented, Oct 20, 2020

I believe the root cause is in the error handler here: https://github.com/Azure/azure-sdk-for-js/blob/ff0ac514bcadd0791c9ed855312c18e526932838/sdk/identity/identity/src/credentials/managedIdentityCredential.ts#L481

I suspect this is yet another case of IMDS does one thing, and AppService does another (or maybe AppService does different things based on whether it gets passed the secret / x-identity-header headers??). It’s clear that the failure case here is throwing up 401 instead of 400 for me, but then it goes unhandled and we throw up an AuthenticationError instead of a CredentialUnavailable. When this happens, it kills the handling in ChainedTokenCredential and blows up my app even after we seemingly fixed it.

I notice that my original stack trace returned a 400 and the secret header. The newer one returns 401 and uses x-identity-header. Seem like a possible culprit.

Not sure what else this may affect, but the fix might just be err.statusCode === 400 || err.statuscode === 401 By throwing directly, it short-circuits the entire CTC chain and kills the app without continuing.

Read more comments on GitHub >

github_iconTop Results From Across the Web

DefaultAzureCredential doesn't work with User Assigned ...
DefaultAzureCredential doesn't work with User Assigned Managed Identity in Azure App Service while thats not the case with Azure VMSS.
Read more >
azure.identity.DefaultAzureCredential class - Microsoft Learn
A default credential capable of handling most Azure SDK authentication scenarios. The identity it uses depends on the environment.
Read more >
DefaultAzureCredential Class (Azure.Identity) - Microsoft Learn
Blobs client library using the DefaultAzureCredential, deployed to an Azure resource with a user assigned managed identity configured.
Read more >
Occassional error authenticating to KeyVault - Microsoft Q&A
When I put the code on Azure Synapse jobs, it fails. I am using a user-assigned managed identity and all privileges are granted....
Read more >
Managed identities - Azure App Service - Microsoft Learn
How to use managed identities for App Service and Azure Functions ... A user-assigned identity is a standalone Azure resource that can be ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found