wangzhibo
2026-05-09 53418655c061ac26f66058253f39200aced1ab1d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
/*! @azure/msal-node v5.1.3 2026-04-16 */
'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