UNPKG

@azure/msal-node

Version:
249 lines (246 loc) 15.5 kB
/*! @azure/msal-node v5.0.6 2026-03-02 */ 'use strict'; import { Constants, AuthError, createClientAuthError, ClientAuthErrorCodes } from '@azure/msal-common/node'; import { ManagedIdentityRequestParameters } from '../../config/ManagedIdentityRequestParameters.mjs'; import { BaseManagedIdentitySource } from './BaseManagedIdentitySource.mjs'; import { createManagedIdentityError } from '../../error/ManagedIdentityError.mjs'; import { ManagedIdentityEnvironmentVariableNames, ManagedIdentitySourceNames, ManagedIdentityIdType, HttpMethod, ManagedIdentityHeaders, ManagedIdentityQueryParameters, AZURE_ARC_SECRET_FILE_MAX_SIZE_BYTES } from '../../utils/Constants.mjs'; import { accessSync, constants, statSync, readFileSync } from 'fs'; import path from 'path'; import { unableToCreateAzureArc, wwwAuthenticateHeaderMissing, wwwAuthenticateHeaderUnsupportedFormat, platformNotSupported, invalidFileExtension, invalidFilePath, unableToReadSecretFile, invalidSecret } from '../../error/ManagedIdentityErrorCodes.mjs'; /* * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. */ const ARC_API_VERSION = "2019-11-01"; const DEFAULT_AZURE_ARC_IDENTITY_ENDPOINT = "http://127.0.0.1:40342/metadata/identity/oauth2/token"; const HIMDS_EXECUTABLE_HELPER_STRING = "N/A: himds executable exists"; const SUPPORTED_AZURE_ARC_PLATFORMS = { win32: `${process.env["ProgramData"]}\\AzureConnectedMachineAgent\\Tokens\\`, linux: "/var/opt/azcmagent/tokens/", }; const AZURE_ARC_FILE_DETECTION = { win32: `${process.env["ProgramFiles"]}\\AzureConnectedMachineAgent\\himds.exe`, linux: "/opt/azcmagent/bin/himds", }; /** * Azure Arc managed identity source implementation for acquiring tokens from Azure Arc-enabled servers. * * This class provides managed identity authentication for applications running on Azure Arc-enabled servers * by communicating with the local Hybrid Instance Metadata Service (HIMDS). It supports both environment * variable-based configuration and automatic detection through the HIMDS executable. * * Original source of code: https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/identity/Azure.Identity/src/AzureArcManagedIdentitySource.cs */ class AzureArc extends BaseManagedIdentitySource { /** * Creates a new instance of the AzureArc managed identity source. * * @param logger - Logger instance for capturing telemetry and diagnostic information * @param nodeStorage - Storage implementation for caching tokens and metadata * @param networkClient - Network client for making HTTP requests to the identity endpoint * @param cryptoProvider - Cryptographic operations provider for token validation and encryption * @param disableInternalRetries - Flag to disable automatic retry logic for failed requests * @param identityEndpoint - The Azure Arc identity endpoint URL for token requests */ constructor(logger, nodeStorage, networkClient, cryptoProvider, disableInternalRetries, identityEndpoint) { super(logger, nodeStorage, networkClient, cryptoProvider, disableInternalRetries); this.identityEndpoint = identityEndpoint; } /** * Retrieves and validates Azure Arc environment variables for managed identity configuration. * * This method checks for IDENTITY_ENDPOINT and IMDS_ENDPOINT environment variables. * If either is missing, it attempts to detect the Azure Arc environment by checking for * the HIMDS executable at platform-specific paths. On successful detection, it returns * the default identity endpoint and a helper string indicating file-based detection. * * @returns An array containing [identityEndpoint, imdsEndpoint] where both values are * strings if Azure Arc is available, or undefined if not available. */ static getEnvironmentVariables() { let identityEndpoint = process.env[ManagedIdentityEnvironmentVariableNames.IDENTITY_ENDPOINT]; let imdsEndpoint = process.env[ManagedIdentityEnvironmentVariableNames.IMDS_ENDPOINT]; // if either of the identity or imds endpoints are undefined, check if the himds executable exists if (!identityEndpoint || !imdsEndpoint) { // get the expected Windows or Linux file path of the himds executable const fileDetectionPath = AZURE_ARC_FILE_DETECTION[process.platform]; try { /* * check if the himds executable exists and its permissions allow it to be read * returns undefined if true, throws an error otherwise */ accessSync(fileDetectionPath, constants.F_OK | constants.R_OK); identityEndpoint = DEFAULT_AZURE_ARC_IDENTITY_ENDPOINT; imdsEndpoint = HIMDS_EXECUTABLE_HELPER_STRING; } catch (err) { /* * do nothing * accessSync returns undefined on success, and throws an error on failure */ } } return [identityEndpoint, imdsEndpoint]; } /** * Attempts to create an AzureArc managed identity source instance. * * Validates the Azure Arc environment by checking environment variables * and performing file-based detection. It ensures that only system-assigned managed identities * are supported for Azure Arc scenarios. The method performs comprehensive validation of * endpoint URLs and logs detailed information about the detection process. * * @param logger - Logger instance for capturing creation and validation steps * @param nodeStorage - Storage implementation for the managed identity source * @param networkClient - Network client for HTTP communication * @param cryptoProvider - Cryptographic operations provider * @param disableInternalRetries - Whether to disable automatic retry mechanisms * @param managedIdentityId - The managed identity configuration, must be system-assigned * * @returns AzureArc instance if the environment supports Azure Arc managed identity, null otherwise * * @throws {ManagedIdentityError} When a user-assigned managed identity is specified (not supported for Azure Arc) */ static tryCreate(logger, nodeStorage, networkClient, cryptoProvider, disableInternalRetries, managedIdentityId) { const [identityEndpoint, imdsEndpoint] = AzureArc.getEnvironmentVariables(); // if either of the identity or imds endpoints are undefined (even after himds file detection) if (!identityEndpoint || !imdsEndpoint) { logger.info(`[Managed Identity] ${ManagedIdentitySourceNames.AZURE_ARC} managed identity is unavailable through environment variables because one or both of '${ManagedIdentityEnvironmentVariableNames.IDENTITY_ENDPOINT}' and '${ManagedIdentityEnvironmentVariableNames.IMDS_ENDPOINT}' are not defined. ${ManagedIdentitySourceNames.AZURE_ARC} managed identity is also unavailable through file detection.`, ""); return null; } // check if the imds endpoint is set to the default for file detection if (imdsEndpoint === HIMDS_EXECUTABLE_HELPER_STRING) { logger.info(`[Managed Identity] ${ManagedIdentitySourceNames.AZURE_ARC} managed identity is available through file detection. Defaulting to known ${ManagedIdentitySourceNames.AZURE_ARC} endpoint: ${DEFAULT_AZURE_ARC_IDENTITY_ENDPOINT}. Creating ${ManagedIdentitySourceNames.AZURE_ARC} managed identity.`, ""); } else { // otherwise, both the identity and imds endpoints are defined without file detection; validate them const validatedIdentityEndpoint = AzureArc.getValidatedEnvVariableUrlString(ManagedIdentityEnvironmentVariableNames.IDENTITY_ENDPOINT, identityEndpoint, ManagedIdentitySourceNames.AZURE_ARC, logger); // remove trailing slash validatedIdentityEndpoint.endsWith("/") ? validatedIdentityEndpoint.slice(0, -1) : validatedIdentityEndpoint; AzureArc.getValidatedEnvVariableUrlString(ManagedIdentityEnvironmentVariableNames.IMDS_ENDPOINT, imdsEndpoint, ManagedIdentitySourceNames.AZURE_ARC, logger); logger.info(`[Managed Identity] Environment variables validation passed for ${ManagedIdentitySourceNames.AZURE_ARC} managed identity. Endpoint URI: ${validatedIdentityEndpoint}. Creating ${ManagedIdentitySourceNames.AZURE_ARC} managed identity.`, ""); } if (managedIdentityId.idType !== ManagedIdentityIdType.SYSTEM_ASSIGNED) { throw createManagedIdentityError(unableToCreateAzureArc); } return new AzureArc(logger, nodeStorage, networkClient, cryptoProvider, disableInternalRetries, identityEndpoint); } /** * Creates a properly formatted HTTP request for acquiring tokens from the Azure Arc identity endpoint. * * This method constructs a GET request to the Azure Arc HIMDS endpoint with the required metadata header * and query parameters. The endpoint URL is normalized to use 127.0.0.1 instead of localhost for * consistency. Additional body parameters are calculated by the base class during token acquisition. * * @param resource - The target resource/scope for which to request an access token (e.g., "https://graph.microsoft.com/.default") * * @returns A configured ManagedIdentityRequestParameters object ready for network execution */ createRequest(resource) { const request = new ManagedIdentityRequestParameters(HttpMethod.GET, this.identityEndpoint.replace("localhost", "127.0.0.1")); request.headers[ManagedIdentityHeaders.METADATA_HEADER_NAME] = "true"; request.queryParameters[ManagedIdentityQueryParameters.API_VERSION] = ARC_API_VERSION; request.queryParameters[ManagedIdentityQueryParameters.RESOURCE] = resource; // bodyParameters calculated in BaseManagedIdentity.acquireTokenWithManagedIdentity return request; } /** * Processes the server response and handles Azure Arc-specific authentication challenges. * * This method implements the Azure Arc authentication flow which may require reading a secret file * for authorization. When the initial request returns HTTP 401 Unauthorized, it extracts the file * path from the WWW-Authenticate header, validates the file location and size, reads the secret, * and retries the request with Basic authentication. The method includes comprehensive security * validations to prevent path traversal and ensure file integrity. * * @param originalResponse - The initial HTTP response from the identity endpoint * @param networkClient - Network client for making the retry request if needed * @param networkRequest - The original request parameters (modified with auth header for retry) * @param networkRequestOptions - Additional options for network requests * * @returns A promise that resolves to the server token response with access token and metadata * * @throws {ManagedIdentityError} When: * - WWW-Authenticate header is missing or has unsupported format * - Platform is not supported (not Windows or Linux) * - Secret file has invalid extension (not .key) * - Secret file path doesn't match expected platform path * - Secret file cannot be read or is too large (>4096 bytes) * @throws {ClientAuthError} When network errors occur during retry request */ async getServerTokenResponseAsync(originalResponse, networkClient, networkRequest, networkRequestOptions) { let retryResponse; if (originalResponse.status === Constants.HTTP_UNAUTHORIZED) { const wwwAuthHeader = originalResponse.headers["www-authenticate"]; if (!wwwAuthHeader) { throw createManagedIdentityError(wwwAuthenticateHeaderMissing); } if (!wwwAuthHeader.includes("Basic realm=")) { throw createManagedIdentityError(wwwAuthenticateHeaderUnsupportedFormat); } const secretFilePath = wwwAuthHeader.split("Basic realm=")[1]; // throw an error if the managed identity application is not being run on Windows or Linux if (!SUPPORTED_AZURE_ARC_PLATFORMS.hasOwnProperty(process.platform)) { throw createManagedIdentityError(platformNotSupported); } // get the expected Windows or Linux file path const expectedSecretFilePath = SUPPORTED_AZURE_ARC_PLATFORMS[process.platform]; // throw an error if the file in the file path is not a .key file const fileName = path.basename(secretFilePath); if (!fileName.endsWith(".key")) { throw createManagedIdentityError(invalidFileExtension); } /* * throw an error if the file path from the www-authenticate header does not match the * expected file path for the platform (Windows or Linux) the managed identity application * is running on */ if (expectedSecretFilePath + fileName !== secretFilePath) { throw createManagedIdentityError(invalidFilePath); } let secretFileSize; // attempt to get the secret file's size, in bytes try { secretFileSize = await statSync(secretFilePath).size; } catch (e) { throw createManagedIdentityError(unableToReadSecretFile); } // throw an error if the secret file's size is greater than 4096 bytes if (secretFileSize > AZURE_ARC_SECRET_FILE_MAX_SIZE_BYTES) { throw createManagedIdentityError(invalidSecret); } // attempt to read the contents of the secret file let secret; try { secret = readFileSync(secretFilePath, Constants.EncodingTypes.UTF8); } catch (e) { throw createManagedIdentityError(unableToReadSecretFile); } const authHeaderValue = `Basic ${secret}`; this.logger.info(`[Managed Identity] Adding authorization header to the request.`, ""); networkRequest.headers[ManagedIdentityHeaders.AUTHORIZATION_HEADER_NAME] = authHeaderValue; try { retryResponse = await networkClient.sendGetRequestAsync(networkRequest.computeUri(), networkRequestOptions); } catch (error) { if (error instanceof AuthError) { throw error; } else { throw createClientAuthError(ClientAuthErrorCodes.networkError); } } } return this.getServerTokenResponse(retryResponse || originalResponse); } } export { ARC_API_VERSION, AZURE_ARC_FILE_DETECTION, AzureArc, DEFAULT_AZURE_ARC_IDENTITY_ENDPOINT, SUPPORTED_AZURE_ARC_PLATFORMS }; //# sourceMappingURL=AzureArc.mjs.map