-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
arcMsi.ts
108 lines (92 loc) · 3.54 KB
/
arcMsi.ts
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
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import { AccessToken, GetTokenOptions, RequestPrepareOptions } from "@azure/core-http";
import { MSI } from "./models";
import { credentialLogger } from "../../util/logging";
import { IdentityClient } from "../../client/identityClient";
import { msiGenericGetToken } from "./utils";
import { azureArcAPIVersion } from "./constants";
import { AuthenticationError } from "../../client/errors";
import { readFile } from "fs";
const logger = credentialLogger("ManagedIdentityCredential - ArcMSI");
// Azure Arc MSI doesn't have a special expiresIn parser.
const expiresInParser = undefined;
function prepareRequestOptions(resource?: string): RequestPrepareOptions {
const queryParameters: any = {
resource,
"api-version": azureArcAPIVersion
};
return {
// Should be similar to: http://localhost:40342/metadata/identity/oauth2/token
url: process.env.IDENTITY_ENDPOINT,
method: "GET",
queryParameters,
headers: {
Accept: "application/json",
Metadata: true
}
};
}
// Since "fs"'s readFileSync locks the thread, and to avoid extra dependencies.
function readFileAsync(path: string, options: { encoding: string }): Promise<string> {
return new Promise((resolve, reject) =>
readFile(path, options, (err, data) => {
if (err) {
reject(err);
}
resolve(data);
})
);
}
async function filePathRequest(
identityClient: IdentityClient,
requestPrepareOptions: RequestPrepareOptions
): Promise<string | undefined> {
const response = await identityClient.sendRequest(
identityClient.createWebResource(requestPrepareOptions)
);
if (response.status !== 401) {
let message = "";
if (response.bodyAsText) {
message = ` Response: ${response.bodyAsText}`;
}
throw new AuthenticationError(
response.status,
`To authenticate with Azure Arc MSI, status code 401 is expected on the first request.${message}`
);
}
const authHeader = response.headers.get("www-authenticate") || "";
return authHeader.split("=").slice(1)[0];
}
export const arcMsi: MSI = {
async isAvailable(): Promise<boolean> {
return Boolean(process.env.IMDS_ENDPOINT && process.env.IDENTITY_ENDPOINT);
},
async getToken(
identityClient: IdentityClient,
resource?: string,
clientId?: string,
getTokenOptions: GetTokenOptions = {}
): Promise<AccessToken | null> {
logger.info(`Using the Azure Arc MSI to authenticate.`);
if (clientId) {
throw new Error(
"User assigned identity is not supported by the Azure Arc Managed Identity Endpoint. To authenticate with the system assigned identity omit the client id when constructing the ManagedIdentityCredential, or if authenticating with the DefaultAzureCredential ensure the AZURE_CLIENT_ID environment variable is not set."
);
}
const requestOptions = {
disableJsonStringifyOnBody: true,
deserializationMapper: undefined,
abortSignal: getTokenOptions.abortSignal,
spanOptions: getTokenOptions.tracingOptions && getTokenOptions.tracingOptions.spanOptions,
...prepareRequestOptions(resource)
};
const filePath = await filePathRequest(identityClient, requestOptions);
if (!filePath) {
throw new Error("Azure Arc MSI failed to find the token file.");
}
const key = await readFileAsync(filePath, { encoding: "utf-8" });
requestOptions.headers!["Authorization"] = `Basic ${key}`;
return msiGenericGetToken(identityClient, requestOptions, expiresInParser, getTokenOptions);
}
};