Press n or j to go to the next uncovered block, b, p or k for the previous block.
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 | 428x 428x 428x 428x 428x 428x 428x 88x 88x 48x 12x 12x 108x 68x 428x 1028x 1028x 1028x 1028x 592x 436x 8x 428x 196x 232x 4x 228x 228x 4x 224x 428x 1224x 1224x 1224x 1224x 428x 1224x 1224x 1224x 1224x 428x 1224x 1224x 1224x 1028x 1000x 196x 804x 428x 68x 28x | import { type Document } from '../../bson'; import { MongoNetworkTimeoutError } from '../../error'; import { get } from '../../utils'; import { MongoCryptAzureKMSRequestError } from '../errors'; import { type KMSProviders } from './index'; const MINIMUM_TOKEN_REFRESH_IN_MILLISECONDS = 6000; /** Base URL for getting Azure tokens. */ export const AZURE_BASE_URL = 'http://169.254.169.254/metadata/identity/oauth2/token?'; /** * The access token that libmongocrypt expects for Azure kms. */ interface AccessToken { accessToken: string; } /** * The response from the azure idms endpoint, including the `expiresOnTimestamp`. * `expiresOnTimestamp` is needed for caching. */ interface AzureTokenCacheEntry extends AccessToken { accessToken: string; expiresOnTimestamp: number; } /** * @internal */ export class AzureCredentialCache { cachedToken: AzureTokenCacheEntry | null = null; async getToken(): Promise<AccessToken> { Eif (this.cachedToken == null || this.needsRefresh(this.cachedToken)) { this.cachedToken = await this._getToken(); } return { accessToken: this.cachedToken.accessToken }; } needsRefresh(token: AzureTokenCacheEntry): boolean { const timeUntilExpirationMS = token.expiresOnTimestamp - Date.now(); return timeUntilExpirationMS <= MINIMUM_TOKEN_REFRESH_IN_MILLISECONDS; } /** * exposed for testing */ resetCache() { this.cachedToken = null; } /** * exposed for testing */ _getToken(): Promise<AzureTokenCacheEntry> { return fetchAzureKMSToken(); } } /** @internal */ export const tokenCache = new AzureCredentialCache(); /** @internal */ async function parseResponse(response: { body: string; status?: number; }): Promise<AzureTokenCacheEntry> { const { status, body: rawBody } = response; const body: { expires_in?: number; access_token?: string } = (() => { try { return JSON.parse(rawBody); } catch { throw new MongoCryptAzureKMSRequestError('Malformed JSON body in GET request.'); } })(); if (status !== 200) { throw new MongoCryptAzureKMSRequestError('Unable to complete request.', body); } if (!body.access_token) { throw new MongoCryptAzureKMSRequestError( 'Malformed response body - missing field `access_token`.' ); } if (!body.expires_in) { throw new MongoCryptAzureKMSRequestError( 'Malformed response body - missing field `expires_in`.' ); } const expiresInMS = Number(body.expires_in) * 1000; if (Number.isNaN(expiresInMS)) { throw new MongoCryptAzureKMSRequestError( 'Malformed response body - unable to parse int from `expires_in` field.' ); } return { accessToken: body.access_token, expiresOnTimestamp: Date.now() + expiresInMS }; } /** * @internal * * exposed for CSFLE * [prose test 18](https://github.com/mongodb/specifications/tree/master/source/client-side-encryption/tests#azure-imds-credentials) */ export interface AzureKMSRequestOptions { headers?: Document; url?: URL | string; } /** * @internal * Get the Azure endpoint URL. */ export function addAzureParams(url: URL, resource: string, username?: string): URL { url.searchParams.append('api-version', '2018-02-01'); url.searchParams.append('resource', resource); Iif (username) { url.searchParams.append('client_id', username); } return url; } /** * @internal * * parses any options provided by prose tests to `fetchAzureKMSToken` and merges them with * the default values for headers and the request url. */ export function prepareRequest(options: AzureKMSRequestOptions): { headers: Document; url: URL; } { const url = new URL(options.url?.toString() ?? AZURE_BASE_URL); addAzureParams(url, 'https://vault.azure.net'); const headers = { ...options.headers, 'Content-Type': 'application/json', Metadata: true }; return { headers, url }; } /** * @internal * * `AzureKMSRequestOptions` allows prose tests to modify the http request sent to the idms * servers. This is required to simulate different server conditions. No options are expected to * be set outside of tests. * * exposed for CSFLE * [prose test 18](https://github.com/mongodb/specifications/tree/master/source/client-side-encryption/tests#azure-imds-credentials) */ export async function fetchAzureKMSToken( options: AzureKMSRequestOptions = {} ): Promise<AzureTokenCacheEntry> { const { headers, url } = prepareRequest(options); try { const response = await get(url, { headers }); return await parseResponse(response); } catch (error) { if (error instanceof MongoNetworkTimeoutError) { throw new MongoCryptAzureKMSRequestError(`[Azure KMS] ${error.message}`); } throw error; } } /** * @internal * * @throws Will reject with a `MongoCryptError` if the http request fails or the http response is malformed. */ export async function loadAzureCredentials(kmsProviders: KMSProviders): Promise<KMSProviders> { const azure = await tokenCache.getToken(); return { ...kmsProviders, azure }; } |