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

API for Managed Identity to detect the current environment #7093

Merged
merged 17 commits into from
May 17, 2024
Merged
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 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
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
Loading