-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathkeyVaultSecretProvider.ts
More file actions
83 lines (74 loc) · 3.83 KB
/
keyVaultSecretProvider.ts
File metadata and controls
83 lines (74 loc) · 3.83 KB
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
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import { KeyVaultOptions } from "./keyVaultOptions.js";
import { RefreshTimer } from "../refresh/refreshTimer.js";
import { ArgumentError } from "../common/errors.js";
import { SecretClient, KeyVaultSecretIdentifier } from "@azure/keyvault-secrets";
import { KeyVaultReferenceErrorMessages } from "../common/errorMessages.js";
export class AzureKeyVaultSecretProvider {
#keyVaultOptions: KeyVaultOptions | undefined;
#secretRefreshTimer: RefreshTimer | undefined;
#secretClients: Map<string, SecretClient>; // map key vault hostname to corresponding secret client
#cachedSecretValues: Map<string, any> = new Map<string, any>(); // map secret identifier to secret value
constructor(keyVaultOptions: KeyVaultOptions | undefined, refreshTimer?: RefreshTimer) {
if (keyVaultOptions?.secretRefreshIntervalInMs !== undefined) {
if (refreshTimer === undefined) {
throw new ArgumentError("Refresh timer must be specified when Key Vault secret refresh is enabled.");
}
if (refreshTimer.interval !== keyVaultOptions.secretRefreshIntervalInMs) {
throw new ArgumentError("Refresh timer does not match the secret refresh interval.");
}
}
this.#keyVaultOptions = keyVaultOptions;
this.#secretRefreshTimer = refreshTimer;
this.#secretClients = new Map();
for (const client of this.#keyVaultOptions?.secretClients ?? []) {
const clientUrl = new URL(client.vaultUrl);
this.#secretClients.set(clientUrl.host, client);
}
}
async getSecretValue(secretIdentifier: KeyVaultSecretIdentifier): Promise<unknown> {
const identifierKey = secretIdentifier.sourceId;
// If the refresh interval is not expired, return the cached value if available.
if (this.#cachedSecretValues.has(identifierKey) &&
(!this.#secretRefreshTimer || !this.#secretRefreshTimer.canRefresh())) {
return this.#cachedSecretValues.get(identifierKey);
}
// Fallback to fetching the secret value from Key Vault.
const secretValue = await this.#getSecretValueFromKeyVault(secretIdentifier);
this.#cachedSecretValues.set(identifierKey, secretValue);
return secretValue;
}
clearCache(): void {
this.#cachedSecretValues.clear();
}
async #getSecretValueFromKeyVault(secretIdentifier: KeyVaultSecretIdentifier): Promise<unknown> {
if (!this.#keyVaultOptions) {
throw new ArgumentError(KeyVaultReferenceErrorMessages.KEY_VAULT_OPTIONS_UNDEFINED);
}
const { name: secretName, vaultUrl, sourceId, version } = secretIdentifier;
// precedence: secret clients > custom secret resolver
const client = this.#getSecretClient(new URL(vaultUrl));
if (client) {
const secret = await client.getSecret(secretName, { version });
return secret.value;
}
if (this.#keyVaultOptions.secretResolver) {
return await this.#keyVaultOptions.secretResolver(new URL(sourceId));
}
// When code reaches here, it means that the key vault reference cannot be resolved in all possible ways.
throw new ArgumentError(KeyVaultReferenceErrorMessages.KEY_VAULT_REFERENCE_UNRESOLVABLE);
}
#getSecretClient(vaultUrl: URL): SecretClient | undefined {
let client = this.#secretClients.get(vaultUrl.host);
if (client !== undefined) {
return client;
}
if (this.#keyVaultOptions?.credential) {
client = new SecretClient(vaultUrl.toString(), this.#keyVaultOptions.credential, this.#keyVaultOptions.clientOptions);
this.#secretClients.set(vaultUrl.host, client);
return client;
}
return undefined;
}
}