azure-sdk-for-js: DefaultAzureCredential fails on Azure App Service when using User-Assigned Identity

  • 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

About this issue

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

Commits related to this issue

Most upvoted comments

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

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.

@noelbundick - Did this resolve this issue for you?

@noelbundick Thank you, Noel! We’ll most likely be releasing today. I’ll send you instructions a bit later. After that, if you have the time to try again, please let us know. Otherwise, I’ll go more in depth later this week.

Note: I believe this will be resolved via https://github.com/Azure/azure-sdk-for-js/pull/11426