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
- README.md
- source code documentation
- SDK API docs on https://docs.microsoft.com
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:
- Create an Azure Web App with the Linux container image
ghcr.io/noelbundick/credential-bug-repro
- Corresponding code here: https://github.com/noelbundick/credential-bug-repro
- Assign a User-Assigned identity only
System identity disabled
One user-assigned identity
- Set the identity’s clientId as the
AZURE_CLIENT_ID
app setting
User assigned identity clientId
AZURE_CLIENT_ID
in Application Settings
- 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 themanagedIdentityClientId
optioncurl https://<myapp>.azurewebsites.net/repro1
-fails despite havingAZURE_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:
- Created 3 years ago
- Reactions:3
- Comments:16 (10 by maintainers)
Top GitHub 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 up401
instead of400
for me, but then it goes unhandled and we throw up anAuthenticationError
instead of aCredentialUnavailable
. When this happens, it kills the handling inChainedTokenCredential
and blows up my app even after we seemingly fixed it.I notice that my original stack trace returned a
400
and thesecret
header. The newer one returns401
and usesx-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.