Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added token revocation functionality to Managed Identity #7422

Open
wants to merge 17 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "Added token revocation functionality to managed identity (#7422)",
"packageName": "@azure/msal-node",
"email": "[email protected]",
"dependentChangeType": "patch"
}
1 change: 1 addition & 0 deletions lib/msal-node/apiReview/msal-node.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@ export class ManagedIdentityApplication {

// @public (undocumented)
export type ManagedIdentityConfiguration = {
clientCapabilities?: Array<string>;
managedIdentityIdParams?: ManagedIdentityIdParams;
system?: NodeSystemOptions;
};
Expand Down
2 changes: 2 additions & 0 deletions lib/msal-node/src/client/ManagedIdentityApplication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ export class ManagedIdentityApplication {
],
authority: this.fakeAuthority.canonicalAuthority,
correlationId: this.cryptoProvider.createNewGuid(),
claims: managedIdentityRequestParams.claims,
clientCapabilities: this.config.clientCapabilities,
};

if (
Expand Down
13 changes: 5 additions & 8 deletions lib/msal-node/src/client/ManagedIdentitySources/AppService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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 (
Expand Down
11 changes: 5 additions & 6 deletions lib/msal-node/src/client/ManagedIdentitySources/AzureArc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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";
Expand Down Expand Up @@ -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;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Service Fabric will be the only resource that supports Token Revocation for now. For the private preview let's only focus on Service Fabric and not change the api-version for other Azure resources

request.queryParameters[ManagedIdentityQueryParameters.RESOURCE] =
resource;

// bodyParameters calculated in BaseManagedIdentity.acquireTokenWithManagedIdentity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -133,6 +137,31 @@ export abstract class BaseManagedIdentitySource {
managedIdentityId
);

// if claims are present, the MSI will get a new token
if (managedIdentityRequest.claims) {
Robbie-Microsoft marked this conversation as resolved.
Show resolved Hide resolved
Robbie-Microsoft marked this conversation as resolved.
Show resolved Hide resolved
this.logger.info(
`[Managed Identity] The following claims are present in the request: ${managedIdentityRequest.claims}`
);

networkRequest.queryParameters[
ManagedIdentityQueryParameters.BYPASS_CACHE
] = "true";
}

// 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
] = clientCapabilities;
}

const headers: Record<string, string> = networkRequest.headers;
headers[HeaderNames.CONTENT_TYPE] = Constants.URL_FORM_CONTENT_TYPE;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import {
METADATA_HEADER_NAME,
ManagedIdentityEnvironmentVariableNames,
ManagedIdentityIdType,
ManagedIdentityQueryParameters,
ManagedIdentitySourceNames,
RESOURCE_BODY_OR_QUERY_PARAMETER_NAME,
} from "../../utils/Constants.js";
import {
ManagedIdentityErrorCodes,
Expand Down Expand Up @@ -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;
Expand Down
12 changes: 5 additions & 7 deletions lib/msal-node/src/client/ManagedIdentitySources/Imds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,20 @@ 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";

// IMDS constants. Docs for IMDS are available here https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http
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;
Expand Down Expand Up @@ -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 (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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 (
Expand Down
4 changes: 4 additions & 0 deletions lib/msal-node/src/config/Configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export type ManagedIdentityIdParams = {

/** @public */
export type ManagedIdentityConfiguration = {
clientCapabilities?: Array<string>;
managedIdentityIdParams?: ManagedIdentityIdParams;
system?: NodeSystemOptions;
};
Expand Down Expand Up @@ -247,13 +248,15 @@ export function buildAppConfiguration({

/** @internal */
export type ManagedIdentityNodeConfiguration = {
clientCapabilities: Array<string>;
managedIdentityId: ManagedIdentityId;
system: Required<
Pick<NodeSystemOptions, "loggerOptions" | "networkClient">
>;
};

export function buildManagedIdentityConfiguration({
clientCapabilities,
managedIdentityIdParams,
system,
}: ManagedIdentityConfiguration): ManagedIdentityNodeConfiguration {
Expand Down Expand Up @@ -290,6 +293,7 @@ export function buildManagedIdentityConfiguration({
}

return {
clientCapabilities: clientCapabilities || [],
managedIdentityId: managedIdentityId,
system: {
loggerOptions,
Expand Down
7 changes: 4 additions & 3 deletions lib/msal-node/src/request/ManagedIdentityRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>;
};
15 changes: 13 additions & 2 deletions lib/msal-node/src/utils/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,22 @@ 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 =
(typeof ManagedIdentityQueryParameters)[keyof typeof ManagedIdentityQueryParameters];

/**
* Managed Identity Environment Variable Names
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
ManagedIdentityEnvironmentVariableNames,
ManagedIdentitySourceNames,
} from "../../../src/utils/Constants.js";
import { checkMSIV1MinimumVersion } from "../../utils/ManagedIdentity.js";

describe("Acquires a token successfully via an App Service Managed Identity", () => {
beforeAll(() => {
Expand All @@ -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 () => {
Expand Down Expand Up @@ -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,
<any>"sendGetRequestAsync"
);

const networkManagedIdentityResult: AuthenticationResult =
await managedIdentityApplication.acquireToken(
managedIdentityRequestParams
Expand All @@ -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 () => {
Expand Down Expand Up @@ -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();
});
});
});
Loading
Loading