Skip to content

Commit

Permalink
API for Managed Identity to detect the current environment (#7093)
Browse files Browse the repository at this point in the history
This feature is requested by Azure Identity SDK. Please see the
description in the linked issue for more info.
  • Loading branch information
Robbie-Microsoft authored May 17, 2024
1 parent e1c27fa commit 8d19143
Show file tree
Hide file tree
Showing 16 changed files with 240 additions and 172 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "Added API for Managed Identity to detect the current environment #7093",
"packageName": "@azure/msal-node",
"email": "[email protected]",
"dependentChangeType": "patch"
}
17 changes: 14 additions & 3 deletions lib/msal-node/apiReview/msal-node.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -412,13 +412,11 @@ export { Logger }

export { LogLevel }

// Warning: (ae-missing-release-tag) "ManagedIdentityApplication" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public
export class ManagedIdentityApplication {
constructor(configuration?: ManagedIdentityConfiguration);
// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
acquireToken(managedIdentityRequestParams: ManagedIdentityRequestParams): Promise<AuthenticationResult>;
getManagedIdentitySource(): ManagedIdentitySourceNames;
}

// Warning: (ae-missing-release-tag) "ManagedIdentityConfiguration" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal)
Expand Down Expand Up @@ -450,6 +448,19 @@ export type ManagedIdentityRequestParams = {
resource: string;
};

// @public
export const ManagedIdentitySourceNames: {
readonly APP_SERVICE: "AppService";
readonly AZURE_ARC: "AzureArc";
readonly CLOUD_SHELL: "CloudShell";
readonly DEFAULT_TO_IMDS: "DefaultToImds";
readonly IMDS: "Imds";
readonly SERVICE_FABRIC: "ServiceFabric";
};

// @public
export type ManagedIdentitySourceNames = (typeof ManagedIdentitySourceNames)[keyof typeof ManagedIdentitySourceNames];

export { NetworkRequestOptions }

export { NetworkResponse }
Expand Down
19 changes: 17 additions & 2 deletions lib/msal-node/src/client/ManagedIdentityApplication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,14 @@ import { ClientCredentialClient } from "./ClientCredentialClient";
import { ManagedIdentityClient } from "./ManagedIdentityClient";
import { ManagedIdentityRequestParams } from "../request/ManagedIdentityRequestParams";
import { NodeStorage } from "../cache/NodeStorage";
import { DEFAULT_AUTHORITY_FOR_MANAGED_IDENTITY } from "../utils/Constants";
import {
DEFAULT_AUTHORITY_FOR_MANAGED_IDENTITY,
ManagedIdentitySourceNames,
} from "../utils/Constants";

/**
* Class to initialize a managed identity and identify the service
* @public
*/
export class ManagedIdentityApplication {
private config: ManagedIdentityNodeConfiguration;
Expand Down Expand Up @@ -113,7 +117,7 @@ export class ManagedIdentityApplication {

/**
* Acquire an access token from the cache or the managed identity
* @param managedIdentityRequest
* @param managedIdentityRequest - the ManagedIdentityRequestParams object passed in by the developer
* @returns the access token
*/
public async acquireToken(
Expand Down Expand Up @@ -183,4 +187,15 @@ export class ManagedIdentityApplication {
);
}
}

/**
* Determine the Managed Identity Source based on available environment variables. This API is consumed by Azure Identity SDK.
* @returns ManagedIdentitySourceNames - The Managed Identity source's name
*/
public getManagedIdentitySource(): ManagedIdentitySourceNames {
return (
ManagedIdentityClient.sourceName ||
this.managedIdentityClient.getManagedIdentitySource()
);
}
}
39 changes: 39 additions & 0 deletions lib/msal-node/src/client/ManagedIdentityClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { ManagedIdentityRequest } from "../request/ManagedIdentityRequest";
import { ManagedIdentityId } from "../config/ManagedIdentityId";
import { NodeStorage } from "../cache/NodeStorage";
import { BaseManagedIdentitySource } from "./ManagedIdentitySources/BaseManagedIdentitySource";
import { ManagedIdentitySourceNames } from "../utils/Constants";

/*
* Class to initialize a managed identity and identify the service.
Expand All @@ -35,6 +36,7 @@ export class ManagedIdentityClient {
private cryptoProvider: CryptoProvider;

private static identitySource?: BaseManagedIdentitySource;
public static sourceName?: ManagedIdentitySourceNames;

constructor(
logger: Logger,
Expand Down Expand Up @@ -73,6 +75,43 @@ export class ManagedIdentityClient {
);
}

private allEnvironmentVariablesAreDefined(
environmentVariables: Array<string | undefined>
): boolean {
return Object.values(environmentVariables).every(
(environmentVariable) => {
return environmentVariable !== undefined;
}
);
}

/**
* Determine the Managed Identity Source based on available environment variables. This API is consumed by ManagedIdentityApplication's getManagedIdentitySource.
* @returns ManagedIdentitySourceNames - The Managed Identity source's name
*/
public getManagedIdentitySource(): ManagedIdentitySourceNames {
ManagedIdentityClient.sourceName =
this.allEnvironmentVariablesAreDefined(
ServiceFabric.getEnvironmentVariables()
)
? ManagedIdentitySourceNames.SERVICE_FABRIC
: this.allEnvironmentVariablesAreDefined(
AppService.getEnvironmentVariables()
)
? ManagedIdentitySourceNames.APP_SERVICE
: this.allEnvironmentVariablesAreDefined(
CloudShell.getEnvironmentVariables()
)
? ManagedIdentitySourceNames.CLOUD_SHELL
: this.allEnvironmentVariablesAreDefined(
AzureArc.getEnvironmentVariables()
)
? ManagedIdentitySourceNames.AZURE_ARC
: ManagedIdentitySourceNames.DEFAULT_TO_IMDS;

return ManagedIdentityClient.sourceName;
}

/**
* Tries to create a managed identity source for all sources
* @returns the managed identity Source
Expand Down
19 changes: 13 additions & 6 deletions lib/msal-node/src/client/ManagedIdentitySources/AppService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,7 @@ export class AppService extends BaseManagedIdentitySource {
this.identityHeader = identityHeader;
}

public static tryCreate(
logger: Logger,
nodeStorage: NodeStorage,
networkClient: INetworkModule,
cryptoProvider: CryptoProvider
): AppService | null {
public static getEnvironmentVariables(): Array<string | undefined> {
const identityEndpoint: string | undefined =
process.env[
ManagedIdentityEnvironmentVariableNames.IDENTITY_ENDPOINT
Expand All @@ -58,6 +53,18 @@ export class AppService extends BaseManagedIdentitySource {
ManagedIdentityEnvironmentVariableNames.IDENTITY_HEADER
];

return [identityEndpoint, identityHeader];
}

public static tryCreate(
logger: Logger,
nodeStorage: NodeStorage,
networkClient: INetworkModule,
cryptoProvider: CryptoProvider
): AppService | null {
const [identityEndpoint, identityHeader] =
AppService.getEnvironmentVariables();

// if either of the identity endpoint or identity header variables are undefined, this MSI provider is unavailable.
if (!identityEndpoint || !identityHeader) {
logger.info(
Expand Down
19 changes: 13 additions & 6 deletions lib/msal-node/src/client/ManagedIdentitySources/AzureArc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,26 @@ export class AzureArc extends BaseManagedIdentitySource {
this.identityEndpoint = identityEndpoint;
}

public static getEnvironmentVariables(): Array<string | undefined> {
const identityEndpoint: string | undefined =
process.env[
ManagedIdentityEnvironmentVariableNames.IDENTITY_ENDPOINT
];
const imdsEndpoint: string | undefined =
process.env[ManagedIdentityEnvironmentVariableNames.IMDS_ENDPOINT];

return [identityEndpoint, imdsEndpoint];
}

public static tryCreate(
logger: Logger,
nodeStorage: NodeStorage,
networkClient: INetworkModule,
cryptoProvider: CryptoProvider,
managedIdentityId: ManagedIdentityId
): AzureArc | null {
const identityEndpoint: string | undefined =
process.env[
ManagedIdentityEnvironmentVariableNames.IDENTITY_ENDPOINT
];
const imdsEndpoint: string | undefined =
process.env[ManagedIdentityEnvironmentVariableNames.IMDS_ENDPOINT];
const [identityEndpoint, imdsEndpoint] =
AzureArc.getEnvironmentVariables();

// if either of the identity or imds endpoints are undefined, this MSI provider is unavailable.
if (!identityEndpoint || !imdsEndpoint) {
Expand Down
10 changes: 8 additions & 2 deletions lib/msal-node/src/client/ManagedIdentitySources/CloudShell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,21 @@ export class CloudShell extends BaseManagedIdentitySource {
this.msiEndpoint = msiEndpoint;
}

public static getEnvironmentVariables(): Array<string | undefined> {
const msiEndpoint: string | undefined =
process.env[ManagedIdentityEnvironmentVariableNames.MSI_ENDPOINT];

return [msiEndpoint];
}

public static tryCreate(
logger: Logger,
nodeStorage: NodeStorage,
networkClient: INetworkModule,
cryptoProvider: CryptoProvider,
managedIdentityId: ManagedIdentityId
): CloudShell | null {
const msiEndpoint: string | undefined =
process.env[ManagedIdentityEnvironmentVariableNames.MSI_ENDPOINT];
const [msiEndpoint] = CloudShell.getEnvironmentVariables();

// if the msi endpoint environment variable is undefined, this MSI provider is unavailable.
if (!msiEndpoint) {
Expand Down
21 changes: 14 additions & 7 deletions lib/msal-node/src/client/ManagedIdentitySources/ServiceFabric.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,7 @@ export class ServiceFabric extends BaseManagedIdentitySource {
this.identityHeader = identityHeader;
}

public static tryCreate(
logger: Logger,
nodeStorage: NodeStorage,
networkClient: INetworkModule,
cryptoProvider: CryptoProvider,
managedIdentityId: ManagedIdentityId
): ServiceFabric | null {
public static getEnvironmentVariables(): Array<string | undefined> {
const identityEndpoint: string | undefined =
process.env[
ManagedIdentityEnvironmentVariableNames.IDENTITY_ENDPOINT
Expand All @@ -64,6 +58,19 @@ export class ServiceFabric extends BaseManagedIdentitySource {
.IDENTITY_SERVER_THUMBPRINT
];

return [identityEndpoint, identityHeader, identityServerThumbprint];
}

public static tryCreate(
logger: Logger,
nodeStorage: NodeStorage,
networkClient: INetworkModule,
cryptoProvider: CryptoProvider,
managedIdentityId: ManagedIdentityId
): ServiceFabric | null {
const [identityEndpoint, identityHeader, identityServerThumbprint] =
ServiceFabric.getEnvironmentVariables();

/*
* if either of the identity endpoint, identity header, or identity server thumbprint
* environment variables are undefined, this MSI provider is unavailable.
Expand Down
3 changes: 3 additions & 0 deletions lib/msal-node/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ export {
} from "./cache/serializer/SerializerTypes.js";
export { DistributedCachePlugin } from "./cache/distributed/DistributedCachePlugin.js";

// Constants
export { ManagedIdentitySourceNames } from "./utils/Constants.js";

// Crypto
export { CryptoProvider } from "./crypto/CryptoProvider.js";

Expand Down
16 changes: 11 additions & 5 deletions lib/msal-node/src/utils/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,20 @@ export type ManagedIdentityEnvironmentVariableNames =

/**
* Managed Identity Source Names
* @public
*/
export const ManagedIdentitySourceNames = {
APP_SERVICE: "App Service",
AZURE_ARC: "Azure Arc",
CLOUD_SHELL: "Cloud Shell",
IMDS: "IMDS",
SERVICE_FABRIC: "Service Fabric",
APP_SERVICE: "AppService",
AZURE_ARC: "AzureArc",
CLOUD_SHELL: "CloudShell",
DEFAULT_TO_IMDS: "DefaultToImds",
IMDS: "Imds",
SERVICE_FABRIC: "ServiceFabric",
} as const;
/**
* The ManagedIdentitySourceNames type
* @public
*/
export type ManagedIdentitySourceNames =
(typeof ManagedIdentitySourceNames)[keyof typeof ManagedIdentitySourceNames];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@ import {
} from "../../test_kit/StringConstants";

import {
ManagedIdentityTestUtils,
userAssignedClientIdConfig,
managedIdentityRequestParams,
systemAssignedConfig,
} from "../../test_kit/ManagedIdentityTestUtils";
import { AuthenticationResult } from "@azure/msal-common";
import { ManagedIdentityClient } from "../../../src/client/ManagedIdentityClient";
import { ManagedIdentityEnvironmentVariableNames } from "../../../src/utils/Constants";
import {
ManagedIdentityEnvironmentVariableNames,
ManagedIdentitySourceNames,
} from "../../../src/utils/Constants";

describe("Acquires a token successfully via an App Service Managed Identity", () => {
beforeAll(() => {
Expand All @@ -44,10 +46,11 @@ describe("Acquires a token successfully via an App Service Managed Identity", ()
});

test("acquires a User Assigned Client Id token", async () => {
expect(ManagedIdentityTestUtils.isAppService()).toBe(true);

const managedIdentityApplication: ManagedIdentityApplication =
new ManagedIdentityApplication(userAssignedClientIdConfig);
expect(managedIdentityApplication.getManagedIdentitySource()).toBe(
ManagedIdentitySourceNames.APP_SERVICE
);

const networkManagedIdentityResult: AuthenticationResult =
await managedIdentityApplication.acquireToken(
Expand All @@ -65,11 +68,12 @@ describe("Acquires a token successfully via an App Service Managed Identity", ()
managedIdentityApplication = new ManagedIdentityApplication(
systemAssignedConfig
);
expect(managedIdentityApplication.getManagedIdentitySource()).toBe(
ManagedIdentitySourceNames.APP_SERVICE
);
});

test("acquires a token", async () => {
expect(ManagedIdentityTestUtils.isAppService()).toBe(true);

const networkManagedIdentityResult: AuthenticationResult =
await managedIdentityApplication.acquireToken(
managedIdentityRequestParams
Expand All @@ -82,8 +86,6 @@ describe("Acquires a token successfully via an App Service Managed Identity", ()
});

test("returns an already acquired token from the cache", async () => {
expect(ManagedIdentityTestUtils.isAppService()).toBe(true);

const networkManagedIdentityResult: AuthenticationResult =
await managedIdentityApplication.acquireToken({
resource: MANAGED_IDENTITY_RESOURCE,
Expand Down
Loading

0 comments on commit 8d19143

Please sign in to comment.