From 621db00de7b5b233c5dedaa4265e0278a8f1853e Mon Sep 17 00:00:00 2001 From: Robbie Ginsburg Date: Fri, 15 Nov 2024 17:18:56 -0500 Subject: [PATCH 01/11] added token revocation funcitonality to managed identity --- .../src/client/ManagedIdentityApplication.ts | 5 ++- .../src/client/ManagedIdentityClient.ts | 6 ++- .../BaseManagedIdentitySource.ts | 14 +++++- lib/msal-node/src/config/Configuration.ts | 4 ++ .../ManagedIdentitySources/Imds.spec.ts | 44 ++++++++++++++++++- 5 files changed, 68 insertions(+), 5 deletions(-) diff --git a/lib/msal-node/src/client/ManagedIdentityApplication.ts b/lib/msal-node/src/client/ManagedIdentityApplication.ts index 9b2353a427..b2a673ae1b 100644 --- a/lib/msal-node/src/client/ManagedIdentityApplication.ts +++ b/lib/msal-node/src/client/ManagedIdentityApplication.ts @@ -140,6 +140,7 @@ export class ManagedIdentityApplication { ], authority: this.fakeAuthority.canonicalAuthority, correlationId: this.cryptoProvider.createNewGuid(), + claims: managedIdentityRequestParams.claims, }; if ( @@ -150,7 +151,9 @@ export class ManagedIdentityApplication { return this.managedIdentityClient.sendManagedIdentityTokenRequest( managedIdentityRequest, this.config.managedIdentityId, - this.fakeAuthority + this.fakeAuthority, + undefined, + this.config.clientCapabilities ); } diff --git a/lib/msal-node/src/client/ManagedIdentityClient.ts b/lib/msal-node/src/client/ManagedIdentityClient.ts index 555f116992..9890ef66da 100644 --- a/lib/msal-node/src/client/ManagedIdentityClient.ts +++ b/lib/msal-node/src/client/ManagedIdentityClient.ts @@ -54,7 +54,8 @@ export class ManagedIdentityClient { managedIdentityRequest: ManagedIdentityRequest, managedIdentityId: ManagedIdentityId, fakeAuthority: Authority, - refreshAccessToken?: boolean + refreshAccessToken?: boolean, + clientCapabilities?: Array ): Promise { if (!ManagedIdentityClient.identitySource) { ManagedIdentityClient.identitySource = @@ -71,7 +72,8 @@ export class ManagedIdentityClient { managedIdentityRequest, managedIdentityId, fakeAuthority, - refreshAccessToken + refreshAccessToken, + clientCapabilities ); } diff --git a/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts b/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts index 39d7a3e968..3bb424c6a1 100644 --- a/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts +++ b/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts @@ -125,7 +125,8 @@ export abstract class BaseManagedIdentitySource { managedIdentityRequest: ManagedIdentityRequest, managedIdentityId: ManagedIdentityId, fakeAuthority: Authority, - refreshAccessToken?: boolean + refreshAccessToken?: boolean, + clientCapabilities?: Array ): Promise { const networkRequest: ManagedIdentityRequestParameters = this.createRequest( @@ -133,6 +134,17 @@ export abstract class BaseManagedIdentitySource { managedIdentityId ); + // if claims are present, token proxies will clear their token cache and request a new token from ESTS + if (managedIdentityRequest.claims) { + networkRequest.queryParameters.bypass_cache = "true"; + } + + // an optional non-empty string query parameter used to send client capabilities to ESTS + if (clientCapabilities) { + networkRequest.queryParameters.xms_cc = + clientCapabilities.toString(); + } + const headers: Record = networkRequest.headers; headers[HeaderNames.CONTENT_TYPE] = Constants.URL_FORM_CONTENT_TYPE; diff --git a/lib/msal-node/src/config/Configuration.ts b/lib/msal-node/src/config/Configuration.ts index fbde1def2b..73bb3d5f64 100644 --- a/lib/msal-node/src/config/Configuration.ts +++ b/lib/msal-node/src/config/Configuration.ts @@ -136,6 +136,7 @@ export type ManagedIdentityIdParams = { /** @public */ export type ManagedIdentityConfiguration = { + clientCapabilities?: Array; managedIdentityIdParams?: ManagedIdentityIdParams; system?: NodeSystemOptions; }; @@ -247,6 +248,7 @@ export function buildAppConfiguration({ /** @internal */ export type ManagedIdentityNodeConfiguration = { + clientCapabilities: Array; managedIdentityId: ManagedIdentityId; system: Required< Pick @@ -254,6 +256,7 @@ export type ManagedIdentityNodeConfiguration = { }; export function buildManagedIdentityConfiguration({ + clientCapabilities, managedIdentityIdParams, system, }: ManagedIdentityConfiguration): ManagedIdentityNodeConfiguration { @@ -290,6 +293,7 @@ export function buildManagedIdentityConfiguration({ } return { + clientCapabilities: clientCapabilities || [], managedIdentityId: managedIdentityId, system: { loggerOptions, diff --git a/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts b/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts index 2e4318e3eb..edb6569aa4 100644 --- a/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts +++ b/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts @@ -55,6 +55,7 @@ import { } from "../../../src"; // NodeJS 16+ provides a built-in version of setTimeout that is promise-based import { setTimeout } from "timers/promises"; +import { CAE_CONSTANTS } from "../../test_kit/StringConstants.js"; describe("Acquires a token successfully via an IMDS Managed Identity", () => { // IMDS doesn't need environment variables because there is a default IMDS endpoint @@ -549,7 +550,44 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { ); }); - test("ignores a cached token when claims are provided", async () => { + test('ensures "xms=client-capabilites-string" was sent to ESTS', async () => { + const sendGetRequestAsyncSpy: jest.SpyInstance = jest.spyOn( + networkClient, + "sendGetRequestAsync" + ); + + const systemAssignedManagedIdentityApplicationWithClientCapabilities: ManagedIdentityApplication = + new ManagedIdentityApplication({ + ...systemAssignedConfig, + clientCapabilities: CAE_CONSTANTS.CLIENT_CAPABILITIES, + }); + + const networkManagedIdentityResult: AuthenticationResult = + await systemAssignedManagedIdentityApplicationWithClientCapabilities.acquireToken( + { + claims: TEST_CONFIG.CLAIMS, + resource: MANAGED_IDENTITY_RESOURCE, + } + ); + expect(networkManagedIdentityResult.accessToken).toEqual( + DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken + ); + + const tokenRequest = sendGetRequestAsyncSpy.mock.lastCall; + const url = tokenRequest[0]; + expect( + url.includes( + `xms_cc=${CAE_CONSTANTS.CLIENT_CAPABILITIES.toString()}` + ) + ).toBe(true); + }); + + test('ignores a cached token when claims are provided, and ensures "bypass_cache=true" was sent to ESTS', async () => { + const sendGetRequestAsyncSpy: jest.SpyInstance = jest.spyOn( + networkClient, + "sendGetRequestAsync" + ); + let networkManagedIdentityResult: AuthenticationResult = await systemAssignedManagedIdentityApplication.acquireToken({ resource: MANAGED_IDENTITY_RESOURCE, @@ -578,6 +616,10 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { expect(networkManagedIdentityResult.accessToken).toEqual( DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken ); + + const tokenRequest = sendGetRequestAsyncSpy.mock.lastCall; + const url = tokenRequest[0]; + expect(url.includes("bypass_cache=true")).toBe(true); }); test("ignores a cached token when forceRefresh is set to true", async () => { From eb8715684e91785e80a304f25e391c1d9391f4e1 Mon Sep 17 00:00:00 2001 From: Robbie Ginsburg Date: Fri, 15 Nov 2024 17:26:31 -0500 Subject: [PATCH 02/11] Change files --- ...ure-msal-node-20888f8d-8ed5-4418-80dd-03944efa17c7.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/@azure-msal-node-20888f8d-8ed5-4418-80dd-03944efa17c7.json diff --git a/change/@azure-msal-node-20888f8d-8ed5-4418-80dd-03944efa17c7.json b/change/@azure-msal-node-20888f8d-8ed5-4418-80dd-03944efa17c7.json new file mode 100644 index 0000000000..221a877da3 --- /dev/null +++ b/change/@azure-msal-node-20888f8d-8ed5-4418-80dd-03944efa17c7.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "added token revocation funcitonality to managed identity", + "packageName": "@azure/msal-node", + "email": "rginsburg@microsoft.com", + "dependentChangeType": "patch" +} From 122d4a2f262d65e0b498abffc780aeb70bb2aaf7 Mon Sep 17 00:00:00 2001 From: Robbie Ginsburg Date: Fri, 15 Nov 2024 17:27:14 -0500 Subject: [PATCH 03/11] beachball + msal-node.api.md --- .../@azure-msal-node-20888f8d-8ed5-4418-80dd-03944efa17c7.json | 2 +- lib/msal-node/apiReview/msal-node.api.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/change/@azure-msal-node-20888f8d-8ed5-4418-80dd-03944efa17c7.json b/change/@azure-msal-node-20888f8d-8ed5-4418-80dd-03944efa17c7.json index 221a877da3..905a07020a 100644 --- a/change/@azure-msal-node-20888f8d-8ed5-4418-80dd-03944efa17c7.json +++ b/change/@azure-msal-node-20888f8d-8ed5-4418-80dd-03944efa17c7.json @@ -1,6 +1,6 @@ { "type": "minor", - "comment": "added token revocation funcitonality to managed identity", + "comment": "Added token revocation functionality to managed identity (#7422)", "packageName": "@azure/msal-node", "email": "rginsburg@microsoft.com", "dependentChangeType": "patch" diff --git a/lib/msal-node/apiReview/msal-node.api.md b/lib/msal-node/apiReview/msal-node.api.md index 1a20e8c01e..02456267e7 100644 --- a/lib/msal-node/apiReview/msal-node.api.md +++ b/lib/msal-node/apiReview/msal-node.api.md @@ -389,6 +389,7 @@ export class ManagedIdentityApplication { // @public (undocumented) export type ManagedIdentityConfiguration = { + clientCapabilities?: Array; managedIdentityIdParams?: ManagedIdentityIdParams; system?: NodeSystemOptions; }; From 93f7c61f646dd0795f161540255a7bc994507063 Mon Sep 17 00:00:00 2001 From: Robbie Ginsburg Date: Fri, 15 Nov 2024 17:56:05 -0500 Subject: [PATCH 04/11] Improved code + unit tests --- .../src/client/ManagedIdentityApplication.ts | 7 +- .../src/client/ManagedIdentityClient.ts | 6 +- .../BaseManagedIdentitySource.ts | 11 ++- .../request/ManagedIdentityRequestParams.ts | 8 +- .../ManagedIdentitySources/Imds.spec.ts | 73 ++++++++++++++++++- 5 files changed, 86 insertions(+), 19 deletions(-) diff --git a/lib/msal-node/src/client/ManagedIdentityApplication.ts b/lib/msal-node/src/client/ManagedIdentityApplication.ts index b2a673ae1b..d072476def 100644 --- a/lib/msal-node/src/client/ManagedIdentityApplication.ts +++ b/lib/msal-node/src/client/ManagedIdentityApplication.ts @@ -141,6 +141,9 @@ export class ManagedIdentityApplication { authority: this.fakeAuthority.canonicalAuthority, correlationId: this.cryptoProvider.createNewGuid(), claims: managedIdentityRequestParams.claims, + clientCapabilities: + managedIdentityRequestParams.clientCapabilities || + this.config.clientCapabilities, }; if ( @@ -151,9 +154,7 @@ export class ManagedIdentityApplication { return this.managedIdentityClient.sendManagedIdentityTokenRequest( managedIdentityRequest, this.config.managedIdentityId, - this.fakeAuthority, - undefined, - this.config.clientCapabilities + this.fakeAuthority ); } diff --git a/lib/msal-node/src/client/ManagedIdentityClient.ts b/lib/msal-node/src/client/ManagedIdentityClient.ts index 9890ef66da..555f116992 100644 --- a/lib/msal-node/src/client/ManagedIdentityClient.ts +++ b/lib/msal-node/src/client/ManagedIdentityClient.ts @@ -54,8 +54,7 @@ export class ManagedIdentityClient { managedIdentityRequest: ManagedIdentityRequest, managedIdentityId: ManagedIdentityId, fakeAuthority: Authority, - refreshAccessToken?: boolean, - clientCapabilities?: Array + refreshAccessToken?: boolean ): Promise { if (!ManagedIdentityClient.identitySource) { ManagedIdentityClient.identitySource = @@ -72,8 +71,7 @@ export class ManagedIdentityClient { managedIdentityRequest, managedIdentityId, fakeAuthority, - refreshAccessToken, - clientCapabilities + refreshAccessToken ); } diff --git a/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts b/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts index 3bb424c6a1..4b4b119eb0 100644 --- a/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts +++ b/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts @@ -125,8 +125,7 @@ export abstract class BaseManagedIdentitySource { managedIdentityRequest: ManagedIdentityRequest, managedIdentityId: ManagedIdentityId, fakeAuthority: Authority, - refreshAccessToken?: boolean, - clientCapabilities?: Array + refreshAccessToken?: boolean ): Promise { const networkRequest: ManagedIdentityRequestParameters = this.createRequest( @@ -134,15 +133,15 @@ export abstract class BaseManagedIdentitySource { managedIdentityId ); - // if claims are present, token proxies will clear their token cache and request a new token from ESTS + // if claims are present, ESTS will get a new token if (managedIdentityRequest.claims) { networkRequest.queryParameters.bypass_cache = "true"; } - // an optional non-empty string query parameter used to send client capabilities to ESTS - if (clientCapabilities) { + // if client capabilities are present, send them to ESTS + if (managedIdentityRequest.clientCapabilities) { networkRequest.queryParameters.xms_cc = - clientCapabilities.toString(); + managedIdentityRequest.clientCapabilities.toString(); } const headers: Record = networkRequest.headers; diff --git a/lib/msal-node/src/request/ManagedIdentityRequestParams.ts b/lib/msal-node/src/request/ManagedIdentityRequestParams.ts index 9ba9ef4fcd..0ba1a3c8dc 100644 --- a/lib/msal-node/src/request/ManagedIdentityRequestParams.ts +++ b/lib/msal-node/src/request/ManagedIdentityRequestParams.ts @@ -5,13 +5,15 @@ /** * ManagedIdentityRequest - * - claims - a stringified claims request which will be used to determine whether or not the cache should be skipped - * - forceRefresh - forces managed identity requests to skip the cache and make network calls if true - * - resource - resource requested to access the protected API. It should be of the form "ResourceIdUri" or "ResourceIdUri/.default". For instance https://management.azure.net or, for Microsoft Graph, https://graph.microsoft.com/.default + * - claims - a stringified claims request which will be used to determine whether or not the cache should be skipped + * - clientCapabilities - an array of client capabilities to be sent to ESTS + * - forceRefresh - forces managed identity requests to skip the cache and make network calls if true + * - resource - resource requested to access the protected API. It should be of the form "ResourceIdUri" or "ResourceIdUri/.default". For instance https://management.azure.net or, for Microsoft Graph, https://graph.microsoft.com/.default * @public */ export type ManagedIdentityRequestParams = { claims?: string; + clientCapabilities?: Array; forceRefresh?: boolean; resource: string; }; diff --git a/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts b/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts index edb6569aa4..681edc5b03 100644 --- a/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts +++ b/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts @@ -550,7 +550,7 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { ); }); - test('ensures "xms=client-capabilites-string" was sent to ESTS', async () => { + test('ignores a cached token when claims are provided, ensures "bypass_cache=true" was sent to ESTS, and ensures "xms=client-capabilites-string" was sent to ESTS when client capabilities are provided', async () => { const sendGetRequestAsyncSpy: jest.SpyInstance = jest.spyOn( networkClient, "sendGetRequestAsync" @@ -562,19 +562,87 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { clientCapabilities: CAE_CONSTANTS.CLIENT_CAPABILITIES, }); - const networkManagedIdentityResult: AuthenticationResult = + let networkManagedIdentityResult: AuthenticationResult = + await systemAssignedManagedIdentityApplicationWithClientCapabilities.acquireToken( + { + resource: MANAGED_IDENTITY_RESOURCE, + } + ); + expect(networkManagedIdentityResult.fromCache).toBe(false); + expect(networkManagedIdentityResult.accessToken).toEqual( + DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken + ); + + const cachedManagedIdentityResult: AuthenticationResult = + await systemAssignedManagedIdentityApplicationWithClientCapabilities.acquireToken( + { + resource: MANAGED_IDENTITY_RESOURCE, + } + ); + expect(cachedManagedIdentityResult.fromCache).toBe(true); + expect(cachedManagedIdentityResult.accessToken).toEqual( + DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken + ); + + networkManagedIdentityResult = await systemAssignedManagedIdentityApplicationWithClientCapabilities.acquireToken( { claims: TEST_CONFIG.CLAIMS, resource: MANAGED_IDENTITY_RESOURCE, } ); + expect(networkManagedIdentityResult.fromCache).toBe(false); expect(networkManagedIdentityResult.accessToken).toEqual( DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken ); const tokenRequest = sendGetRequestAsyncSpy.mock.lastCall; const url = tokenRequest[0]; + expect(url.includes("bypass_cache=true")).toBe(true); + expect( + url.includes( + `xms_cc=${CAE_CONSTANTS.CLIENT_CAPABILITIES.toString()}` + ) + ).toBe(true); + }); + + test('does not ignore a cached token when claims are not provided, ensures "bypass_cache=true" was not sent to ESTS, and ensures "xms=client-capabilites-string" was sent to ESTS when client capabilities are provided', async () => { + const sendGetRequestAsyncSpy: jest.SpyInstance = jest.spyOn( + networkClient, + "sendGetRequestAsync" + ); + + const systemAssignedManagedIdentityApplicationWithClientCapabilities: ManagedIdentityApplication = + new ManagedIdentityApplication({ + ...systemAssignedConfig, + clientCapabilities: CAE_CONSTANTS.CLIENT_CAPABILITIES, + }); + + let networkManagedIdentityResult: AuthenticationResult = + await systemAssignedManagedIdentityApplicationWithClientCapabilities.acquireToken( + { + resource: MANAGED_IDENTITY_RESOURCE, + } + ); + expect(networkManagedIdentityResult.fromCache).toBe(false); + expect(networkManagedIdentityResult.accessToken).toEqual( + DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken + ); + + const cachedManagedIdentityResult: AuthenticationResult = + await systemAssignedManagedIdentityApplicationWithClientCapabilities.acquireToken( + { + resource: MANAGED_IDENTITY_RESOURCE, + } + ); + expect(cachedManagedIdentityResult.fromCache).toBe(true); + expect(cachedManagedIdentityResult.accessToken).toEqual( + DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken + ); + + const tokenRequest = sendGetRequestAsyncSpy.mock.lastCall; + const url = tokenRequest[0]; + expect(url.includes("bypass_cache=true")).toBe(false); expect( url.includes( `xms_cc=${CAE_CONSTANTS.CLIENT_CAPABILITIES.toString()}` @@ -593,7 +661,6 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { resource: MANAGED_IDENTITY_RESOURCE, }); expect(networkManagedIdentityResult.fromCache).toBe(false); - expect(networkManagedIdentityResult.accessToken).toEqual( DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken ); From b41584e1c399ac7914839f926f63a4d9abc088f1 Mon Sep 17 00:00:00 2001 From: Robbie Ginsburg Date: Fri, 15 Nov 2024 17:58:53 -0500 Subject: [PATCH 05/11] small improvement --- .../client/ManagedIdentitySources/BaseManagedIdentitySource.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts b/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts index 4b4b119eb0..b0f9de2bc1 100644 --- a/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts +++ b/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts @@ -139,7 +139,7 @@ export abstract class BaseManagedIdentitySource { } // if client capabilities are present, send them to ESTS - if (managedIdentityRequest.clientCapabilities) { + if (managedIdentityRequest.clientCapabilities.length) { networkRequest.queryParameters.xms_cc = managedIdentityRequest.clientCapabilities.toString(); } From 53b01bbc86d3874c0a1f8fb65468ad278313e28e Mon Sep 17 00:00:00 2001 From: Robbie Ginsburg Date: Fri, 15 Nov 2024 18:30:02 -0500 Subject: [PATCH 06/11] Improved unit tests --- .../BaseManagedIdentitySource.ts | 2 +- .../ManagedIdentitySources/Imds.spec.ts | 77 ++++++++++++------- 2 files changed, 49 insertions(+), 30 deletions(-) diff --git a/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts b/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts index b0f9de2bc1..cf3e5487d5 100644 --- a/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts +++ b/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts @@ -139,7 +139,7 @@ export abstract class BaseManagedIdentitySource { } // if client capabilities are present, send them to ESTS - if (managedIdentityRequest.clientCapabilities.length) { + if (managedIdentityRequest.clientCapabilities?.length) { networkRequest.queryParameters.xms_cc = managedIdentityRequest.clientCapabilities.toString(); } diff --git a/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts b/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts index 681edc5b03..71fdb69def 100644 --- a/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts +++ b/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts @@ -550,54 +550,52 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { ); }); - test('ignores a cached token when claims are provided, ensures "bypass_cache=true" was sent to ESTS, and ensures "xms=client-capabilites-string" was sent to ESTS when client capabilities are provided', async () => { + test('checks if a token was cached or not and if was "bypass_cache=true" was sent to ESTS depending on whether or not claims are provided, and ensures "xms=client-capabilites-string" was sent to ESTS when client capabilities are provided via the Managed Identity Request Parameters', async () => { const sendGetRequestAsyncSpy: jest.SpyInstance = jest.spyOn( networkClient, "sendGetRequestAsync" ); - const systemAssignedManagedIdentityApplicationWithClientCapabilities: ManagedIdentityApplication = - new ManagedIdentityApplication({ - ...systemAssignedConfig, + let networkManagedIdentityResult: AuthenticationResult = + await systemAssignedManagedIdentityApplication.acquireToken({ clientCapabilities: CAE_CONSTANTS.CLIENT_CAPABILITIES, + resource: MANAGED_IDENTITY_RESOURCE, }); - - let networkManagedIdentityResult: AuthenticationResult = - await systemAssignedManagedIdentityApplicationWithClientCapabilities.acquireToken( - { - resource: MANAGED_IDENTITY_RESOURCE, - } - ); expect(networkManagedIdentityResult.fromCache).toBe(false); expect(networkManagedIdentityResult.accessToken).toEqual( DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken ); + let tokenRequest = sendGetRequestAsyncSpy.mock.lastCall; + let url = tokenRequest[0]; + expect(url.includes("bypass_cache=true")).toBe(false); + expect( + url.includes( + `xms_cc=${CAE_CONSTANTS.CLIENT_CAPABILITIES.toString()}` + ) + ).toBe(true); const cachedManagedIdentityResult: AuthenticationResult = - await systemAssignedManagedIdentityApplicationWithClientCapabilities.acquireToken( - { - resource: MANAGED_IDENTITY_RESOURCE, - } - ); + await systemAssignedManagedIdentityApplication.acquireToken({ + clientCapabilities: CAE_CONSTANTS.CLIENT_CAPABILITIES, + resource: MANAGED_IDENTITY_RESOURCE, + }); expect(cachedManagedIdentityResult.fromCache).toBe(true); expect(cachedManagedIdentityResult.accessToken).toEqual( DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken ); networkManagedIdentityResult = - await systemAssignedManagedIdentityApplicationWithClientCapabilities.acquireToken( - { - claims: TEST_CONFIG.CLAIMS, - resource: MANAGED_IDENTITY_RESOURCE, - } - ); + await systemAssignedManagedIdentityApplication.acquireToken({ + claims: TEST_CONFIG.CLAIMS, + clientCapabilities: CAE_CONSTANTS.CLIENT_CAPABILITIES, + resource: MANAGED_IDENTITY_RESOURCE, + }); expect(networkManagedIdentityResult.fromCache).toBe(false); expect(networkManagedIdentityResult.accessToken).toEqual( DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken ); - - const tokenRequest = sendGetRequestAsyncSpy.mock.lastCall; - const url = tokenRequest[0]; + tokenRequest = sendGetRequestAsyncSpy.mock.lastCall; + url = tokenRequest[0]; expect(url.includes("bypass_cache=true")).toBe(true); expect( url.includes( @@ -606,7 +604,7 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { ).toBe(true); }); - test('does not ignore a cached token when claims are not provided, ensures "bypass_cache=true" was not sent to ESTS, and ensures "xms=client-capabilites-string" was sent to ESTS when client capabilities are provided', async () => { + test('checks if a token was cached or not and if was "bypass_cache=true" was sent to ESTS depending on whether or not claims are provided, and ensures "xms=client-capabilites-string" was sent to ESTS when client capabilities are provided via the Managed Identity configuration object', async () => { const sendGetRequestAsyncSpy: jest.SpyInstance = jest.spyOn( networkClient, "sendGetRequestAsync" @@ -629,6 +627,15 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken ); + let tokenRequest = sendGetRequestAsyncSpy.mock.lastCall; + let url = tokenRequest[0]; + expect(url.includes("bypass_cache=true")).toBe(false); + expect( + url.includes( + `xms_cc=${CAE_CONSTANTS.CLIENT_CAPABILITIES.toString()}` + ) + ).toBe(true); + const cachedManagedIdentityResult: AuthenticationResult = await systemAssignedManagedIdentityApplicationWithClientCapabilities.acquireToken( { @@ -640,9 +647,21 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken ); - const tokenRequest = sendGetRequestAsyncSpy.mock.lastCall; - const url = tokenRequest[0]; - expect(url.includes("bypass_cache=true")).toBe(false); + networkManagedIdentityResult = + await systemAssignedManagedIdentityApplicationWithClientCapabilities.acquireToken( + { + claims: TEST_CONFIG.CLAIMS, + resource: MANAGED_IDENTITY_RESOURCE, + } + ); + expect(networkManagedIdentityResult.fromCache).toBe(false); + expect(networkManagedIdentityResult.accessToken).toEqual( + DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken + ); + + tokenRequest = sendGetRequestAsyncSpy.mock.lastCall; + url = tokenRequest[0]; + expect(url.includes("bypass_cache=true")).toBe(true); expect( url.includes( `xms_cc=${CAE_CONSTANTS.CLIENT_CAPABILITIES.toString()}` From cd6981458fa76cc8dd6891321b8c78f7f3d10149 Mon Sep 17 00:00:00 2001 From: Robbie Ginsburg Date: Wed, 27 Nov 2024 12:12:30 -0500 Subject: [PATCH 07/11] Implemented GitHub Feedback --- .../src/client/ManagedIdentityApplication.ts | 4 +- .../BaseManagedIdentitySource.ts | 4 +- .../src/request/ManagedIdentityRequest.ts | 7 ++- .../request/ManagedIdentityRequestParams.ts | 8 +-- .../ManagedIdentitySources/Imds.spec.ts | 58 +------------------ 5 files changed, 12 insertions(+), 69 deletions(-) diff --git a/lib/msal-node/src/client/ManagedIdentityApplication.ts b/lib/msal-node/src/client/ManagedIdentityApplication.ts index d072476def..14a76ce06c 100644 --- a/lib/msal-node/src/client/ManagedIdentityApplication.ts +++ b/lib/msal-node/src/client/ManagedIdentityApplication.ts @@ -141,9 +141,7 @@ export class ManagedIdentityApplication { authority: this.fakeAuthority.canonicalAuthority, correlationId: this.cryptoProvider.createNewGuid(), claims: managedIdentityRequestParams.claims, - clientCapabilities: - managedIdentityRequestParams.clientCapabilities || - this.config.clientCapabilities, + clientCapabilities: this.config.clientCapabilities, }; if ( diff --git a/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts b/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts index cf3e5487d5..4a67869369 100644 --- a/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts +++ b/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts @@ -133,12 +133,12 @@ export abstract class BaseManagedIdentitySource { managedIdentityId ); - // if claims are present, ESTS will get a new token + // if claims are present, the MSI will get a new token if (managedIdentityRequest.claims) { networkRequest.queryParameters.bypass_cache = "true"; } - // if client capabilities are present, send them to ESTS + // if client capabilities are present, send them to the MSI if (managedIdentityRequest.clientCapabilities?.length) { networkRequest.queryParameters.xms_cc = managedIdentityRequest.clientCapabilities.toString(); diff --git a/lib/msal-node/src/request/ManagedIdentityRequest.ts b/lib/msal-node/src/request/ManagedIdentityRequest.ts index a99ae12b3a..a737d80ab8 100644 --- a/lib/msal-node/src/request/ManagedIdentityRequest.ts +++ b/lib/msal-node/src/request/ManagedIdentityRequest.ts @@ -8,8 +8,9 @@ import { ManagedIdentityRequestParams } from "./ManagedIdentityRequestParams.js" /** * ManagedIdentityRequest - * - forceRefresh - forces managed identity requests to skip the cache and make network calls if true - * - resource - resource requested to access the protected API. It should be of the form "{ResourceIdUri}" or {ResourceIdUri/.default}. For instance https://management.azure.net or, for Microsoft Graph, https://graph.microsoft.com/.default + * - clientCapabilities - an array of client capabilities to be sent to ESTS */ export type ManagedIdentityRequest = ManagedIdentityRequestParams & - CommonClientCredentialRequest; + CommonClientCredentialRequest & { + clientCapabilities?: Array; + }; diff --git a/lib/msal-node/src/request/ManagedIdentityRequestParams.ts b/lib/msal-node/src/request/ManagedIdentityRequestParams.ts index 0ba1a3c8dc..9ba9ef4fcd 100644 --- a/lib/msal-node/src/request/ManagedIdentityRequestParams.ts +++ b/lib/msal-node/src/request/ManagedIdentityRequestParams.ts @@ -5,15 +5,13 @@ /** * ManagedIdentityRequest - * - claims - a stringified claims request which will be used to determine whether or not the cache should be skipped - * - clientCapabilities - an array of client capabilities to be sent to ESTS - * - forceRefresh - forces managed identity requests to skip the cache and make network calls if true - * - resource - resource requested to access the protected API. It should be of the form "ResourceIdUri" or "ResourceIdUri/.default". For instance https://management.azure.net or, for Microsoft Graph, https://graph.microsoft.com/.default + * - claims - a stringified claims request which will be used to determine whether or not the cache should be skipped + * - forceRefresh - forces managed identity requests to skip the cache and make network calls if true + * - resource - resource requested to access the protected API. It should be of the form "ResourceIdUri" or "ResourceIdUri/.default". For instance https://management.azure.net or, for Microsoft Graph, https://graph.microsoft.com/.default * @public */ export type ManagedIdentityRequestParams = { claims?: string; - clientCapabilities?: Array; forceRefresh?: boolean; resource: string; }; diff --git a/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts b/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts index 71fdb69def..55bb673267 100644 --- a/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts +++ b/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts @@ -550,61 +550,7 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { ); }); - test('checks if a token was cached or not and if was "bypass_cache=true" was sent to ESTS depending on whether or not claims are provided, and ensures "xms=client-capabilites-string" was sent to ESTS when client capabilities are provided via the Managed Identity Request Parameters', async () => { - const sendGetRequestAsyncSpy: jest.SpyInstance = jest.spyOn( - networkClient, - "sendGetRequestAsync" - ); - - let networkManagedIdentityResult: AuthenticationResult = - await systemAssignedManagedIdentityApplication.acquireToken({ - clientCapabilities: CAE_CONSTANTS.CLIENT_CAPABILITIES, - resource: MANAGED_IDENTITY_RESOURCE, - }); - expect(networkManagedIdentityResult.fromCache).toBe(false); - expect(networkManagedIdentityResult.accessToken).toEqual( - DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken - ); - let tokenRequest = sendGetRequestAsyncSpy.mock.lastCall; - let url = tokenRequest[0]; - expect(url.includes("bypass_cache=true")).toBe(false); - expect( - url.includes( - `xms_cc=${CAE_CONSTANTS.CLIENT_CAPABILITIES.toString()}` - ) - ).toBe(true); - - const cachedManagedIdentityResult: AuthenticationResult = - await systemAssignedManagedIdentityApplication.acquireToken({ - clientCapabilities: CAE_CONSTANTS.CLIENT_CAPABILITIES, - resource: MANAGED_IDENTITY_RESOURCE, - }); - expect(cachedManagedIdentityResult.fromCache).toBe(true); - expect(cachedManagedIdentityResult.accessToken).toEqual( - DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken - ); - - networkManagedIdentityResult = - await systemAssignedManagedIdentityApplication.acquireToken({ - claims: TEST_CONFIG.CLAIMS, - clientCapabilities: CAE_CONSTANTS.CLIENT_CAPABILITIES, - resource: MANAGED_IDENTITY_RESOURCE, - }); - expect(networkManagedIdentityResult.fromCache).toBe(false); - expect(networkManagedIdentityResult.accessToken).toEqual( - DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken - ); - tokenRequest = sendGetRequestAsyncSpy.mock.lastCall; - url = tokenRequest[0]; - expect(url.includes("bypass_cache=true")).toBe(true); - expect( - url.includes( - `xms_cc=${CAE_CONSTANTS.CLIENT_CAPABILITIES.toString()}` - ) - ).toBe(true); - }); - - test('checks if a token was cached or not and if was "bypass_cache=true" was sent to ESTS depending on whether or not claims are provided, and ensures "xms=client-capabilites-string" was sent to ESTS when client capabilities are provided via the Managed Identity configuration object', async () => { + test('checks if a token was cached or not and if was "bypass_cache=true" was sent to the MSI depending on whether or not claims are provided, and ensures "xms=client-capabilites-string" was sent to ESTS when client capabilities are provided via the Managed Identity configuration object', async () => { const sendGetRequestAsyncSpy: jest.SpyInstance = jest.spyOn( networkClient, "sendGetRequestAsync" @@ -669,7 +615,7 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { ).toBe(true); }); - test('ignores a cached token when claims are provided, and ensures "bypass_cache=true" was sent to ESTS', async () => { + test('ignores a cached token when claims are provided, and ensures "bypass_cache=true" was sent to the MSI', async () => { const sendGetRequestAsyncSpy: jest.SpyInstance = jest.spyOn( networkClient, "sendGetRequestAsync" From 6abc6223c148e8ecc8c2f46a3a811af9e9ca0a65 Mon Sep 17 00:00:00 2001 From: Robbie Ginsburg Date: Fri, 6 Dec 2024 12:07:15 -0500 Subject: [PATCH 08/11] Addressed some GitHub feedback --- .../BaseManagedIdentitySource.ts | 15 +++++-- lib/msal-node/src/utils/Constants.ts | 10 +++++ .../ManagedIdentitySources/Imds.spec.ts | 45 +++++++++++++------ 3 files changed, 52 insertions(+), 18 deletions(-) diff --git a/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts b/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts index 4a67869369..6a5a23c0be 100644 --- a/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts +++ b/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts @@ -24,7 +24,11 @@ import { ManagedIdentityId } from "../../config/ManagedIdentityId.js"; import { ManagedIdentityRequestParameters } from "../../config/ManagedIdentityRequestParameters.js"; import { CryptoProvider } from "../../crypto/CryptoProvider.js"; import { ManagedIdentityRequest } from "../../request/ManagedIdentityRequest.js"; -import { HttpMethod, ManagedIdentityIdType } from "../../utils/Constants.js"; +import { + HttpMethod, + ManagedIdentityIdType, + ManagedIdentityQueryParameters, +} from "../../utils/Constants.js"; import { ManagedIdentityTokenResponse } from "../../response/ManagedIdentityTokenResponse.js"; import { NodeStorage } from "../../cache/NodeStorage.js"; import { @@ -135,13 +139,16 @@ export abstract class BaseManagedIdentitySource { // if claims are present, the MSI will get a new token if (managedIdentityRequest.claims) { - networkRequest.queryParameters.bypass_cache = "true"; + networkRequest.queryParameters[ + ManagedIdentityQueryParameters.BYPASS_CACHE + ] = "true"; } // if client capabilities are present, send them to the MSI if (managedIdentityRequest.clientCapabilities?.length) { - networkRequest.queryParameters.xms_cc = - managedIdentityRequest.clientCapabilities.toString(); + networkRequest.queryParameters[ + ManagedIdentityQueryParameters.XMS_CC + ] = managedIdentityRequest.clientCapabilities.toString(); } const headers: Record = networkRequest.headers; diff --git a/lib/msal-node/src/utils/Constants.ts b/lib/msal-node/src/utils/Constants.ts index efbffa267b..4aa8684f3e 100644 --- a/lib/msal-node/src/utils/Constants.ts +++ b/lib/msal-node/src/utils/Constants.ts @@ -16,6 +16,16 @@ export const DEFAULT_MANAGED_IDENTITY_ID = "system_assigned_managed_identity"; export const MANAGED_IDENTITY_DEFAULT_TENANT = "managed_identity"; export const DEFAULT_AUTHORITY_FOR_MANAGED_IDENTITY = `https://login.microsoftonline.com/${MANAGED_IDENTITY_DEFAULT_TENANT}/`; +/** + * Managed Identity Query Parameters + */ +export const ManagedIdentityQueryParameters = { + BYPASS_CACHE: "bypass_cache", + XMS_CC: "xms_cc", +} as const; +export type ManagedIdentityQueryParameters = + (typeof ManagedIdentityQueryParameters)[keyof typeof ManagedIdentityQueryParameters]; + /** * Managed Identity Environment Variable Names */ diff --git a/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts b/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts index 55bb673267..d9b540324c 100644 --- a/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts +++ b/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. */ -import { ManagedIdentityApplication } from "../../../src/client/ManagedIdentityApplication"; -import { ManagedIdentityConfiguration } from "../../../src/config/Configuration"; +import { ManagedIdentityApplication } from "../../../src/client/ManagedIdentityApplication.js"; +import { ManagedIdentityConfiguration } from "../../../src/config/Configuration.js"; import { DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT, DEFAULT_USER_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT, @@ -17,7 +17,7 @@ import { TEST_CONFIG, THREE_SECONDS_IN_MILLI, getCacheKey, -} from "../../test_kit/StringConstants"; +} from "../../test_kit/StringConstants.js"; import { ManagedIdentityNetworkClient, @@ -26,11 +26,12 @@ import { userAssignedClientIdConfig, managedIdentityRequestParams, systemAssignedConfig, -} from "../../test_kit/ManagedIdentityTestUtils"; +} from "../../test_kit/ManagedIdentityTestUtils.js"; import { DEFAULT_MANAGED_IDENTITY_ID, + ManagedIdentityQueryParameters, ManagedIdentitySourceNames, -} from "../../../src/utils/Constants"; +} from "../../../src/utils/Constants.js"; import { AccessTokenEntity, AuthenticationResult, @@ -42,17 +43,17 @@ import { ServerError, TimeUtils, } from "@azure/msal-common"; -import { ManagedIdentityClient } from "../../../src/client/ManagedIdentityClient"; +import { ManagedIdentityClient } from "../../../src/client/ManagedIdentityClient.js"; import { ManagedIdentityErrorCodes, createManagedIdentityError, -} from "../../../src/error/ManagedIdentityError"; -import { mockCrypto } from "../ClientTestUtils"; +} from "../../../src/error/ManagedIdentityError.js"; +import { mockCrypto } from "../ClientTestUtils.js"; import { CacheKVStore, ClientCredentialClient, NodeStorage, -} from "../../../src"; +} from "../../../src/index.js"; // NodeJS 16+ provides a built-in version of setTimeout that is promise-based import { setTimeout } from "timers/promises"; import { CAE_CONSTANTS } from "../../test_kit/StringConstants.js"; @@ -575,10 +576,16 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { let tokenRequest = sendGetRequestAsyncSpy.mock.lastCall; let url = tokenRequest[0]; - expect(url.includes("bypass_cache=true")).toBe(false); expect( url.includes( - `xms_cc=${CAE_CONSTANTS.CLIENT_CAPABILITIES.toString()}` + `${ManagedIdentityQueryParameters.BYPASS_CACHE}=true` + ) + ).toBe(false); + expect( + url.includes( + `${ + ManagedIdentityQueryParameters.XMS_CC + }=${CAE_CONSTANTS.CLIENT_CAPABILITIES.toString()}` ) ).toBe(true); @@ -607,10 +614,16 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { tokenRequest = sendGetRequestAsyncSpy.mock.lastCall; url = tokenRequest[0]; - expect(url.includes("bypass_cache=true")).toBe(true); expect( url.includes( - `xms_cc=${CAE_CONSTANTS.CLIENT_CAPABILITIES.toString()}` + `${ManagedIdentityQueryParameters.BYPASS_CACHE}=true` + ) + ).toBe(true); + expect( + url.includes( + `${ + ManagedIdentityQueryParameters.XMS_CC + }=${CAE_CONSTANTS.CLIENT_CAPABILITIES.toString()}` ) ).toBe(true); }); @@ -651,7 +664,11 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { const tokenRequest = sendGetRequestAsyncSpy.mock.lastCall; const url = tokenRequest[0]; - expect(url.includes("bypass_cache=true")).toBe(true); + expect( + url.includes( + `${ManagedIdentityQueryParameters.BYPASS_CACHE}=true` + ) + ).toBe(true); }); test("ignores a cached token when forceRefresh is set to true", async () => { From 1739bf3e5110f84120dd7914a3537c7f1788e202 Mon Sep 17 00:00:00 2001 From: Robbie Ginsburg Date: Fri, 6 Dec 2024 13:13:09 -0500 Subject: [PATCH 09/11] Addressed some GitHub feedback --- .../BaseManagedIdentitySource.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts b/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts index 6a5a23c0be..67f3270c17 100644 --- a/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts +++ b/lib/msal-node/src/client/ManagedIdentitySources/BaseManagedIdentitySource.ts @@ -139,6 +139,10 @@ export abstract class BaseManagedIdentitySource { // if claims are present, the MSI will get a new token if (managedIdentityRequest.claims) { + this.logger.info( + `[Managed Identity] The following claims are present in the request: ${managedIdentityRequest.claims}` + ); + networkRequest.queryParameters[ ManagedIdentityQueryParameters.BYPASS_CACHE ] = "true"; @@ -146,9 +150,16 @@ export abstract class BaseManagedIdentitySource { // if client capabilities are present, send them to the MSI if (managedIdentityRequest.clientCapabilities?.length) { + const clientCapabilities: string = + managedIdentityRequest.clientCapabilities.toString(); + + this.logger.info( + `[Managed Identity] The following claims are present in the request: ${clientCapabilities}` + ); + networkRequest.queryParameters[ ManagedIdentityQueryParameters.XMS_CC - ] = managedIdentityRequest.clientCapabilities.toString(); + ] = clientCapabilities; } const headers: Record = networkRequest.headers; From 53d12437f91bb8471d2f4375161d51a5f53a1087 Mon Sep 17 00:00:00 2001 From: Robbie Ginsburg Date: Fri, 6 Dec 2024 15:53:08 -0500 Subject: [PATCH 10/11] Upgraded min version for MSI V1 API and added relevant tests --- .../ManagedIdentitySources/AppService.spec.ts | 15 ++++++-- .../ManagedIdentitySources/AzureArc.spec.ts | 34 ++++++++---------- .../ManagedIdentitySources/Imds.spec.ts | 36 +++++++------------ .../ServiceFabric.spec.ts | 15 ++++++-- lib/msal-node/test/utils/ManagedIdentity.ts | 33 +++++++++++++++++ 5 files changed, 85 insertions(+), 48 deletions(-) create mode 100644 lib/msal-node/test/utils/ManagedIdentity.ts diff --git a/lib/msal-node/test/client/ManagedIdentitySources/AppService.spec.ts b/lib/msal-node/test/client/ManagedIdentitySources/AppService.spec.ts index a765bfcb37..8f2d4f8df6 100644 --- a/lib/msal-node/test/client/ManagedIdentitySources/AppService.spec.ts +++ b/lib/msal-node/test/client/ManagedIdentitySources/AppService.spec.ts @@ -28,6 +28,7 @@ import { ManagedIdentityEnvironmentVariableNames, ManagedIdentitySourceNames, } from "../../../src/utils/Constants"; +import { checkMSIV1MinimumVersion } from "../../utils/ManagedIdentity.js"; describe("Acquires a token successfully via an App Service Managed Identity", () => { beforeAll(() => { @@ -50,6 +51,8 @@ describe("Acquires a token successfully via an App Service Managed Identity", () // reset static variables after each test delete ManagedIdentityClient["identitySource"]; delete ManagedIdentityApplication["nodeStorage"]; + + jest.restoreAllMocks(); }); test("acquires a User Assigned Client Id token", async () => { @@ -80,7 +83,12 @@ describe("Acquires a token successfully via an App Service Managed Identity", () ); }); - test("acquires a token", async () => { + test("acquires a token and ensures the MSI V1 API minimum version is used", async () => { + const sendGetRequestAsyncSpy: jest.SpyInstance = jest.spyOn( + networkClient, + "sendGetRequestAsync" + ); + const networkManagedIdentityResult: AuthenticationResult = await managedIdentityApplication.acquireToken( managedIdentityRequestParams @@ -90,6 +98,9 @@ describe("Acquires a token successfully via an App Service Managed Identity", () expect(networkManagedIdentityResult.accessToken).toEqual( DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken ); + + const url: string = sendGetRequestAsyncSpy.mock.lastCall[0]; + checkMSIV1MinimumVersion(url); }); test("returns an already acquired token from the cache", async () => { @@ -154,8 +165,6 @@ describe("Acquires a token successfully via an App Service Managed Identity", () MANAGED_IDENTITY_APP_SERVICE_NETWORK_REQUEST_400_ERROR.correlation_id as string ) ).toBe(true); - - jest.restoreAllMocks(); }); }); }); diff --git a/lib/msal-node/test/client/ManagedIdentitySources/AzureArc.spec.ts b/lib/msal-node/test/client/ManagedIdentitySources/AzureArc.spec.ts index 7eb3a69245..f401eed4d0 100644 --- a/lib/msal-node/test/client/ManagedIdentitySources/AzureArc.spec.ts +++ b/lib/msal-node/test/client/ManagedIdentitySources/AzureArc.spec.ts @@ -31,15 +31,14 @@ import { ManagedIdentityErrorCodes, createManagedIdentityError, } from "../../../src/error/ManagedIdentityError"; -import { - ARC_API_VERSION, - SUPPORTED_AZURE_ARC_PLATFORMS, -} from "../../../src/client/ManagedIdentitySources/AzureArc"; +import { SUPPORTED_AZURE_ARC_PLATFORMS } from "../../../src/client/ManagedIdentitySources/AzureArc"; import * as fs from "fs"; import { + MSI_V1_MIN_VERSION, ManagedIdentityEnvironmentVariableNames, ManagedIdentitySourceNames, } from "../../../src/utils/Constants"; +import { checkMSIV1MinimumVersion } from "../../utils/ManagedIdentity.js"; jest.mock("fs"); @@ -84,6 +83,8 @@ describe("Acquires a token successfully via an Azure Arc Managed Identity", () = // reset static variables after each test delete ManagedIdentityClient["identitySource"]; delete ManagedIdentityApplication["nodeStorage"]; + + jest.restoreAllMocks(); }); const managedIdentityNetworkErrorClient401 = @@ -107,7 +108,12 @@ describe("Acquires a token successfully via an Azure Arc Managed Identity", () = ); }); - test("acquires a token", async () => { + test("acquires a token and ensures the MSI V1 API minimum version is used", async () => { + const sendGetRequestAsyncSpy: jest.SpyInstance = jest.spyOn( + networkClient, + "sendGetRequestAsync" + ); + const networkManagedIdentityResult: AuthenticationResult = await managedIdentityApplication.acquireToken( managedIdentityRequestParams @@ -117,6 +123,9 @@ describe("Acquires a token successfully via an Azure Arc Managed Identity", () = expect(networkManagedIdentityResult.accessToken).toEqual( DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken ); + + const url: string = sendGetRequestAsyncSpy.mock.lastCall[0]; + checkMSIV1MinimumVersion(url); }); test("acquires a token when both/either the identityEndpoint and/or imdsEndpoint environment variables are undefined, and the himds executable exists and its permissions allow it to be read", async () => { @@ -235,7 +244,7 @@ describe("Acquires a token successfully via an Azure Arc Managed Identity", () = ManagedIdentityEnvironmentVariableNames .IDENTITY_ENDPOINT ] - }?api-version=${ARC_API_VERSION}&resource=${MANAGED_IDENTITY_RESOURCE_BASE}`, + }?api-version=${MSI_V1_MIN_VERSION}&resource=${MANAGED_IDENTITY_RESOURCE_BASE}`, { headers: { Authorization: @@ -245,8 +254,6 @@ describe("Acquires a token successfully via an Azure Arc Managed Identity", () = }, } ); - - jest.restoreAllMocks(); }); }); @@ -305,8 +312,6 @@ describe("Acquires a token successfully via an Azure Arc Managed Identity", () = ManagedIdentityErrorCodes.invalidFileExtension ) ); - - jest.restoreAllMocks(); }); test("throws an error if the www-authenticate header has been returned from the azure arc managed identity, but the managed identity application is not being run on Windows or Linux", async () => { @@ -334,7 +339,6 @@ describe("Acquires a token successfully via an Azure Arc Managed Identity", () = Object.defineProperty(process, "platform", { value: "linux", }); - jest.restoreAllMocks(); }); test("throws an error if the www-authenticate header has been returned from the azure arc managed identity, but the path of the secret file from the www-authenticate header is not in the expected Windows or Linux formats", async () => { @@ -363,8 +367,6 @@ describe("Acquires a token successfully via an Azure Arc Managed Identity", () = ManagedIdentityErrorCodes.invalidFilePath ) ); - - jest.restoreAllMocks(); }); test("throws an error if the www-authenticate header has been returned from the azure arc managed identity, but the size of the secret file from the www-authenticate header is greater than 4096 bytes", async () => { @@ -388,8 +390,6 @@ describe("Acquires a token successfully via an Azure Arc Managed Identity", () = ManagedIdentityErrorCodes.invalidSecret ) ); - - jest.restoreAllMocks(); }); test("throws an error if the www-authenticate header is missing", async () => { @@ -492,8 +492,6 @@ describe("Acquires a token successfully via an Azure Arc Managed Identity", () = ManagedIdentityErrorCodes.unableToReadSecretFile ) ); - - jest.restoreAllMocks(); }); test("ensures that the error format is correct", async () => { @@ -551,8 +549,6 @@ describe("Acquires a token successfully via an Azure Arc Managed Identity", () = MANAGED_IDENTITY_AZURE_ARC_NETWORK_REQUEST_400_ERROR.correlation_id as string ) ).toBe(true); - - jest.restoreAllMocks(); }); }); }); diff --git a/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts b/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts index d9b540324c..0b7ef18af9 100644 --- a/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts +++ b/lib/msal-node/test/client/ManagedIdentitySources/Imds.spec.ts @@ -57,13 +57,17 @@ import { // NodeJS 16+ provides a built-in version of setTimeout that is promise-based import { setTimeout } from "timers/promises"; import { CAE_CONSTANTS } from "../../test_kit/StringConstants.js"; +import { checkMSIV1MinimumVersion } from "../../utils/ManagedIdentity.js"; describe("Acquires a token successfully via an IMDS Managed Identity", () => { // IMDS doesn't need environment variables because there is a default IMDS endpoint afterEach(() => { + // reset static variables after each test delete ManagedIdentityClient["identitySource"]; delete ManagedIdentityApplication["nodeStorage"]; + + jest.restoreAllMocks(); }); const managedIdentityNetworkErrorClientDefault500 = @@ -156,7 +160,12 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { ); }); - test("acquires a token", async () => { + test("acquires a token and ensures the MSI V1 API minimum version is used", async () => { + const sendGetRequestAsyncSpy: jest.SpyInstance = jest.spyOn( + networkClient, + "sendGetRequestAsync" + ); + const networkManagedIdentityResult: AuthenticationResult = await managedIdentityApplication.acquireToken( managedIdentityRequestParams @@ -166,6 +175,9 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { expect(networkManagedIdentityResult.accessToken).toEqual( DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken ); + + const url: string = sendGetRequestAsyncSpy.mock.lastCall[0]; + checkMSIV1MinimumVersion(url); }); test("returns an already acquired token from the cache", async () => { @@ -221,8 +233,6 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { expect(networkManagedIdentityResult.accessToken).toEqual( DEFAULT_USER_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken ); - - jest.restoreAllMocks(); }); test("returns a 500 error response from the network request permanently", async () => { @@ -249,8 +259,6 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { ).toBe(true); expect(sendGetRequestAsyncSpy).toHaveBeenCalledTimes(4); // request + 3 retries - - jest.restoreAllMocks(); }); }); @@ -294,8 +302,6 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { expect(networkManagedIdentityResult.accessToken).toEqual( DEFAULT_USER_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken ); - - jest.restoreAllMocks(); }); test("returns a 500 error response from the network request, just the first time, with a retry-after header of 3 seconds", async () => { @@ -333,8 +339,6 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { expect(networkManagedIdentityResult.accessToken).toEqual( DEFAULT_USER_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken ); - - jest.restoreAllMocks(); }); test("returns a 500 error response from the network request, just the first time, with a retry-after header of 3 seconds (extrapolated from an http-date)", async () => { @@ -376,8 +380,6 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { expect(networkManagedIdentityResult.accessToken).toEqual( DEFAULT_USER_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken ); - - jest.restoreAllMocks(); }); test("returns a 500 error response from the network request permanently", async () => { @@ -403,8 +405,6 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { ) ).toBe(true); expect(sendGetRequestAsyncSpy).toHaveBeenCalledTimes(4); // request + 3 retries - - jest.restoreAllMocks(); }); test("makes three acquireToken calls on the same managed identity application (which returns a 500 error response from the network request permanently) to ensure that retry policy lifetime is per request", async () => { @@ -438,8 +438,6 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { } catch (e) { expect(sendGetRequestAsyncSpyApp).toHaveBeenCalledTimes(12); // 12 total, 3 x (request + 3 retries) } - - jest.restoreAllMocks(); }, 15000); // triple the timeout value for this test because there are 3 acquireToken calls (3 x 1 second in between retries) test("ensures that a retry does not happen when the http status code from a failed network response is not included in the retry policy", async () => { @@ -465,8 +463,6 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { ) ).toBe(true); expect(sendGetRequestAsyncSpyApp).toHaveBeenCalledTimes(1); - - jest.restoreAllMocks(); }); test("ensures that a retry does not happen when the http status code from a failed network response is included in the retry policy, but the retry policy has been disabled", async () => { @@ -503,8 +499,6 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { ) ).toBe(true); expect(sendGetRequestAsyncSpy).toHaveBeenCalledTimes(1); - - jest.restoreAllMocks(); }); }); }); @@ -775,8 +769,6 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { DEFAULT_TOKEN_RENEWAL_OFFSET_SEC ) ).toBe(false); - - jest.restoreAllMocks(); }, 10000); // double the timeout value for this test because it waits two seconds in between the acquireToken call and the cache lookup test("requests three tokens with two different resources while switching between user and system assigned, then requests them again to verify they are retrieved from the cache, then verifies that their cache keys are correct", async () => { @@ -1017,8 +1009,6 @@ describe("Acquires a token successfully via an IMDS Managed Identity", () => { MANAGED_IDENTITY_IMDS_NETWORK_REQUEST_400_ERROR.correlation_id as string ) ).toBe(true); - - jest.restoreAllMocks(); }); }); }); diff --git a/lib/msal-node/test/client/ManagedIdentitySources/ServiceFabric.spec.ts b/lib/msal-node/test/client/ManagedIdentitySources/ServiceFabric.spec.ts index 1cc5a0e84f..49b6592282 100644 --- a/lib/msal-node/test/client/ManagedIdentitySources/ServiceFabric.spec.ts +++ b/lib/msal-node/test/client/ManagedIdentitySources/ServiceFabric.spec.ts @@ -28,6 +28,7 @@ import { ManagedIdentityEnvironmentVariableNames, ManagedIdentitySourceNames, } from "../../../src/utils/Constants"; +import { checkMSIV1MinimumVersion } from "../../utils/ManagedIdentity.js"; describe("Acquires a token successfully via an App Service Managed Identity", () => { beforeAll(() => { @@ -56,6 +57,8 @@ describe("Acquires a token successfully via an App Service Managed Identity", () // reset static variables after each test delete ManagedIdentityClient["identitySource"]; delete ManagedIdentityApplication["nodeStorage"]; + + jest.restoreAllMocks(); }); test("acquires a User Assigned Client Id token", async () => { @@ -86,7 +89,12 @@ describe("Acquires a token successfully via an App Service Managed Identity", () ); }); - test("acquires a token", async () => { + test("acquires a token and ensures the MSI V1 API minimum version is used", async () => { + const sendGetRequestAsyncSpy: jest.SpyInstance = jest.spyOn( + networkClient, + "sendGetRequestAsync" + ); + const networkManagedIdentityResult: AuthenticationResult = await managedIdentityApplication.acquireToken( managedIdentityRequestParams @@ -96,6 +104,9 @@ describe("Acquires a token successfully via an App Service Managed Identity", () expect(networkManagedIdentityResult.accessToken).toEqual( DEFAULT_SYSTEM_ASSIGNED_MANAGED_IDENTITY_AUTHENTICATION_RESULT.accessToken ); + + const url: string = sendGetRequestAsyncSpy.mock.lastCall[0]; + checkMSIV1MinimumVersion(url); }); test("returns an already acquired token from the cache", async () => { @@ -182,8 +193,6 @@ describe("Acquires a token successfully via an App Service Managed Identity", () MANAGED_IDENTITY_SERVICE_FABRIC_NETWORK_REQUEST_400_ERROR.correlation_id as string ) ).toBe(true); - - jest.restoreAllMocks(); }); }); }); diff --git a/lib/msal-node/test/utils/ManagedIdentity.ts b/lib/msal-node/test/utils/ManagedIdentity.ts new file mode 100644 index 0000000000..d8541fa499 --- /dev/null +++ b/lib/msal-node/test/utils/ManagedIdentity.ts @@ -0,0 +1,33 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + */ + +import { + MSI_V1_MIN_VERSION, + ManagedIdentityQueryParameters, +} from "../../src/utils/Constants.js"; + +export const checkMSIV1MinimumVersion = (url: string): void => { + const startIndex = + url.indexOf(ManagedIdentityQueryParameters.API_VERSION) + + ManagedIdentityQueryParameters.API_VERSION.length; + const endIndex = url.indexOf( + `&${ManagedIdentityQueryParameters.RESOURCE}`, + startIndex + 1 + ); + const msiVersionUsedDateString: string = url.slice( + startIndex + 1, + endIndex + ); + const msiVersionUsedDateObject: Date = new Date(msiVersionUsedDateString); + const msiVersionUsedDateMilliseconds = msiVersionUsedDateObject.getTime(); + + const msiV1MinVersionMilliseconds: number = new Date( + MSI_V1_MIN_VERSION + ).getTime(); + + expect(msiVersionUsedDateMilliseconds).toBeGreaterThanOrEqual( + msiV1MinVersionMilliseconds + ); +}; From f8f9b728c23a3fd672d45163aafd87458820c126 Mon Sep 17 00:00:00 2001 From: Robbie Ginsburg Date: Fri, 6 Dec 2024 15:57:59 -0500 Subject: [PATCH 11/11] Upgraded min version for MSI V1 API and added relevant tests --- .../src/client/ManagedIdentitySources/AppService.ts | 13 +++++-------- .../src/client/ManagedIdentitySources/AzureArc.ts | 11 +++++------ .../src/client/ManagedIdentitySources/CloudShell.ts | 4 ++-- .../src/client/ManagedIdentitySources/Imds.ts | 12 +++++------- .../client/ManagedIdentitySources/ServiceFabric.ts | 13 +++++-------- lib/msal-node/src/utils/Constants.ts | 5 +++-- 6 files changed, 25 insertions(+), 33 deletions(-) diff --git a/lib/msal-node/src/client/ManagedIdentitySources/AppService.ts b/lib/msal-node/src/client/ManagedIdentitySources/AppService.ts index a42df5d94c..759e83b325 100644 --- a/lib/msal-node/src/client/ManagedIdentitySources/AppService.ts +++ b/lib/msal-node/src/client/ManagedIdentitySources/AppService.ts @@ -8,20 +8,17 @@ import { BaseManagedIdentitySource } from "./BaseManagedIdentitySource.js"; import { HttpMethod, APP_SERVICE_SECRET_HEADER_NAME, - API_VERSION_QUERY_PARAMETER_NAME, - RESOURCE_BODY_OR_QUERY_PARAMETER_NAME, ManagedIdentityEnvironmentVariableNames, + ManagedIdentityQueryParameters, ManagedIdentitySourceNames, ManagedIdentityIdType, + MSI_V1_MIN_VERSION, } from "../../utils/Constants.js"; import { CryptoProvider } from "../../crypto/CryptoProvider.js"; import { ManagedIdentityRequestParameters } from "../../config/ManagedIdentityRequestParameters.js"; import { ManagedIdentityId } from "../../config/ManagedIdentityId.js"; import { NodeStorage } from "../../cache/NodeStorage.js"; -// MSI Constants. Docs for MSI are available here https://docs.microsoft.com/azure/app-service/overview-managed-identity -const APP_SERVICE_MSI_API_VERSION: string = "2019-08-01"; - /** * Original source of code: https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/identity/Azure.Identity/src/AppServiceManagedIdentitySource.cs */ @@ -107,9 +104,9 @@ export class AppService extends BaseManagedIdentitySource { request.headers[APP_SERVICE_SECRET_HEADER_NAME] = this.identityHeader; - request.queryParameters[API_VERSION_QUERY_PARAMETER_NAME] = - APP_SERVICE_MSI_API_VERSION; - request.queryParameters[RESOURCE_BODY_OR_QUERY_PARAMETER_NAME] = + request.queryParameters[ManagedIdentityQueryParameters.API_VERSION] = + MSI_V1_MIN_VERSION; + request.queryParameters[ManagedIdentityQueryParameters.RESOURCE] = resource; if ( diff --git a/lib/msal-node/src/client/ManagedIdentitySources/AzureArc.ts b/lib/msal-node/src/client/ManagedIdentitySources/AzureArc.ts index 96de28458c..1b1cd6c900 100644 --- a/lib/msal-node/src/client/ManagedIdentitySources/AzureArc.ts +++ b/lib/msal-node/src/client/ManagedIdentitySources/AzureArc.ts @@ -22,15 +22,15 @@ import { createManagedIdentityError, } from "../../error/ManagedIdentityError.js"; import { - API_VERSION_QUERY_PARAMETER_NAME, AUTHORIZATION_HEADER_NAME, AZURE_ARC_SECRET_FILE_MAX_SIZE_BYTES, HttpMethod, METADATA_HEADER_NAME, ManagedIdentityEnvironmentVariableNames, ManagedIdentityIdType, + ManagedIdentityQueryParameters, ManagedIdentitySourceNames, - RESOURCE_BODY_OR_QUERY_PARAMETER_NAME, + MSI_V1_MIN_VERSION, } from "../../utils/Constants.js"; import { NodeStorage } from "../../cache/NodeStorage.js"; import { @@ -43,7 +43,6 @@ import { ManagedIdentityTokenResponse } from "../../response/ManagedIdentityToke import { ManagedIdentityId } from "../../config/ManagedIdentityId.js"; import path from "path"; -export const ARC_API_VERSION: string = "2019-11-01"; export const DEFAULT_AZURE_ARC_IDENTITY_ENDPOINT: string = "http://127.0.0.1:40342/metadata/identity/oauth2/token"; const HIMDS_EXECUTABLE_HELPER_STRING = "N/A: himds executable exists"; @@ -194,9 +193,9 @@ export class AzureArc extends BaseManagedIdentitySource { request.headers[METADATA_HEADER_NAME] = "true"; - request.queryParameters[API_VERSION_QUERY_PARAMETER_NAME] = - ARC_API_VERSION; - request.queryParameters[RESOURCE_BODY_OR_QUERY_PARAMETER_NAME] = + request.queryParameters[ManagedIdentityQueryParameters.API_VERSION] = + MSI_V1_MIN_VERSION; + request.queryParameters[ManagedIdentityQueryParameters.RESOURCE] = resource; // bodyParameters calculated in BaseManagedIdentity.acquireTokenWithManagedIdentity diff --git a/lib/msal-node/src/client/ManagedIdentitySources/CloudShell.ts b/lib/msal-node/src/client/ManagedIdentitySources/CloudShell.ts index 065d85ea51..ea49a92fbd 100644 --- a/lib/msal-node/src/client/ManagedIdentitySources/CloudShell.ts +++ b/lib/msal-node/src/client/ManagedIdentitySources/CloudShell.ts @@ -13,8 +13,8 @@ import { METADATA_HEADER_NAME, ManagedIdentityEnvironmentVariableNames, ManagedIdentityIdType, + ManagedIdentityQueryParameters, ManagedIdentitySourceNames, - RESOURCE_BODY_OR_QUERY_PARAMETER_NAME, } from "../../utils/Constants.js"; import { ManagedIdentityErrorCodes, @@ -102,7 +102,7 @@ export class CloudShell extends BaseManagedIdentitySource { request.headers[METADATA_HEADER_NAME] = "true"; - request.bodyParameters[RESOURCE_BODY_OR_QUERY_PARAMETER_NAME] = + request.bodyParameters[ManagedIdentityQueryParameters.RESOURCE] = resource; return request; diff --git a/lib/msal-node/src/client/ManagedIdentitySources/Imds.ts b/lib/msal-node/src/client/ManagedIdentitySources/Imds.ts index 0559ea7cc9..183524a20e 100644 --- a/lib/msal-node/src/client/ManagedIdentitySources/Imds.ts +++ b/lib/msal-node/src/client/ManagedIdentitySources/Imds.ts @@ -9,13 +9,13 @@ import { ManagedIdentityRequestParameters } from "../../config/ManagedIdentityRe import { BaseManagedIdentitySource } from "./BaseManagedIdentitySource.js"; import { CryptoProvider } from "../../crypto/CryptoProvider.js"; import { - API_VERSION_QUERY_PARAMETER_NAME, HttpMethod, METADATA_HEADER_NAME, ManagedIdentityEnvironmentVariableNames, ManagedIdentityIdType, + ManagedIdentityQueryParameters, ManagedIdentitySourceNames, - RESOURCE_BODY_OR_QUERY_PARAMETER_NAME, + MSI_V1_MIN_VERSION, } from "../../utils/Constants.js"; import { NodeStorage } from "../../cache/NodeStorage.js"; @@ -23,8 +23,6 @@ import { NodeStorage } from "../../cache/NodeStorage.js"; const IMDS_TOKEN_PATH: string = "/metadata/identity/oauth2/token"; const DEFAULT_IMDS_ENDPOINT: string = `http://169.254.169.254${IMDS_TOKEN_PATH}`; -const IMDS_API_VERSION: string = "2018-02-01"; - // Original source of code: https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/identity/Azure.Identity/src/ImdsManagedIdentitySource.cs export class Imds extends BaseManagedIdentitySource { private identityEndpoint: string; @@ -104,9 +102,9 @@ export class Imds extends BaseManagedIdentitySource { request.headers[METADATA_HEADER_NAME] = "true"; - request.queryParameters[API_VERSION_QUERY_PARAMETER_NAME] = - IMDS_API_VERSION; - request.queryParameters[RESOURCE_BODY_OR_QUERY_PARAMETER_NAME] = + request.queryParameters[ManagedIdentityQueryParameters.API_VERSION] = + MSI_V1_MIN_VERSION; + request.queryParameters[ManagedIdentityQueryParameters.RESOURCE] = resource; if ( diff --git a/lib/msal-node/src/client/ManagedIdentitySources/ServiceFabric.ts b/lib/msal-node/src/client/ManagedIdentitySources/ServiceFabric.ts index 501d2f24ea..db0d9b6fcb 100644 --- a/lib/msal-node/src/client/ManagedIdentitySources/ServiceFabric.ts +++ b/lib/msal-node/src/client/ManagedIdentitySources/ServiceFabric.ts @@ -10,18 +10,15 @@ import { BaseManagedIdentitySource } from "./BaseManagedIdentitySource.js"; import { NodeStorage } from "../../cache/NodeStorage.js"; import { CryptoProvider } from "../../crypto/CryptoProvider.js"; import { - API_VERSION_QUERY_PARAMETER_NAME, HttpMethod, ManagedIdentityEnvironmentVariableNames, ManagedIdentityIdType, + ManagedIdentityQueryParameters, ManagedIdentitySourceNames, - RESOURCE_BODY_OR_QUERY_PARAMETER_NAME, + MSI_V1_MIN_VERSION, SERVICE_FABRIC_SECRET_HEADER_NAME, } from "../../utils/Constants.js"; -// MSI Constants. Docs for MSI are available here https://docs.microsoft.com/azure/app-service/overview-managed-identity -const SERVICE_FABRIC_MSI_API_VERSION: string = "2019-07-01-preview"; - /** * Original source of code: https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/identity/Azure.Identity/src/ServiceFabricManagedIdentitySource.cs */ @@ -125,9 +122,9 @@ export class ServiceFabric extends BaseManagedIdentitySource { request.headers[SERVICE_FABRIC_SECRET_HEADER_NAME] = this.identityHeader; - request.queryParameters[API_VERSION_QUERY_PARAMETER_NAME] = - SERVICE_FABRIC_MSI_API_VERSION; - request.queryParameters[RESOURCE_BODY_OR_QUERY_PARAMETER_NAME] = + request.queryParameters[ManagedIdentityQueryParameters.API_VERSION] = + MSI_V1_MIN_VERSION; + request.queryParameters[ManagedIdentityQueryParameters.RESOURCE] = resource; if ( diff --git a/lib/msal-node/src/utils/Constants.ts b/lib/msal-node/src/utils/Constants.ts index 4aa8684f3e..8e474a0be8 100644 --- a/lib/msal-node/src/utils/Constants.ts +++ b/lib/msal-node/src/utils/Constants.ts @@ -10,17 +10,18 @@ export const AUTHORIZATION_HEADER_NAME: string = "Authorization"; export const METADATA_HEADER_NAME: string = "Metadata"; export const APP_SERVICE_SECRET_HEADER_NAME: string = "X-IDENTITY-HEADER"; export const SERVICE_FABRIC_SECRET_HEADER_NAME: string = "secret"; -export const API_VERSION_QUERY_PARAMETER_NAME: string = "api-version"; -export const RESOURCE_BODY_OR_QUERY_PARAMETER_NAME: string = "resource"; export const DEFAULT_MANAGED_IDENTITY_ID = "system_assigned_managed_identity"; export const MANAGED_IDENTITY_DEFAULT_TENANT = "managed_identity"; export const DEFAULT_AUTHORITY_FOR_MANAGED_IDENTITY = `https://login.microsoftonline.com/${MANAGED_IDENTITY_DEFAULT_TENANT}/`; +export const MSI_V1_MIN_VERSION = "2024-09-30"; /** * Managed Identity Query Parameters */ export const ManagedIdentityQueryParameters = { + API_VERSION: "api-version", BYPASS_CACHE: "bypass_cache", + RESOURCE: "resource", XMS_CC: "xms_cc", } as const; export type ManagedIdentityQueryParameters =