-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathAzureKeyVaultKeyValueAdapter.ts
More file actions
94 lines (82 loc) · 4.25 KB
/
AzureKeyVaultKeyValueAdapter.ts
File metadata and controls
94 lines (82 loc) · 4.25 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
84
85
86
87
88
89
90
91
92
93
94
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import { ConfigurationSetting, isSecretReference, parseSecretReference } from "@azure/app-configuration";
import { IKeyValueAdapter } from "../IKeyValueAdapter.js";
import { KeyVaultOptions } from "./KeyVaultOptions.js";
import { ArgumentError, KeyVaultReferenceError } from "../common/error.js";
import { KeyVaultSecretIdentifier, SecretClient, parseKeyVaultSecretIdentifier } from "@azure/keyvault-secrets";
import { isRestError } from "@azure/core-rest-pipeline";
import { AuthenticationError } from "@azure/identity";
export class AzureKeyVaultKeyValueAdapter implements IKeyValueAdapter {
/**
* Map vault hostname to corresponding secret client.
*/
#secretClients: Map<string, SecretClient>;
#keyVaultOptions: KeyVaultOptions | undefined;
constructor(keyVaultOptions: KeyVaultOptions | undefined) {
this.#keyVaultOptions = keyVaultOptions;
}
canProcess(setting: ConfigurationSetting): boolean {
return isSecretReference(setting);
}
async processKeyValue(setting: ConfigurationSetting): Promise<[string, unknown]> {
// TODO: cache results to save requests.
if (!this.#keyVaultOptions) {
throw new ArgumentError("Failed to process the Key Vault reference because Key Vault options are not configured.");
}
let secretIdentifier: KeyVaultSecretIdentifier;
try {
secretIdentifier = parseKeyVaultSecretIdentifier(
parseSecretReference(setting).value.secretId
);
} catch (error) {
throw new KeyVaultReferenceError(buildKeyVaultReferenceErrorMessage("Invalid Key Vault reference.", setting), { cause: error });
}
try {
// precedence: secret clients > credential > secret resolver
const client = this.#getSecretClient(new URL(secretIdentifier.vaultUrl));
if (client) {
const secret = await client.getSecret(secretIdentifier.name, { version: secretIdentifier.version });
return [setting.key, secret.value];
}
if (this.#keyVaultOptions.secretResolver) {
return [setting.key, await this.#keyVaultOptions.secretResolver(new URL(secretIdentifier.sourceId))];
}
} catch (error) {
if (isRestError(error) || error instanceof AuthenticationError) {
throw new KeyVaultReferenceError(buildKeyVaultReferenceErrorMessage("Failed to resolve Key Vault reference.", setting, secretIdentifier.sourceId), { cause: error });
}
throw error;
}
// When code reaches here, it means that the key vault reference cannot be resolved in all possible ways.
throw new ArgumentError("Failed to process the key vault reference. No key vault secret client, credential or secret resolver callback is available to resolve the secret.");
}
/**
*
* @param vaultUrl - The url of the key vault.
* @returns
*/
#getSecretClient(vaultUrl: URL): SecretClient | undefined {
if (this.#secretClients === undefined) {
this.#secretClients = new Map();
for (const client of this.#keyVaultOptions?.secretClients ?? []) {
const clientUrl = new URL(client.vaultUrl);
this.#secretClients.set(clientUrl.host, client);
}
}
let client: SecretClient | undefined;
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;
}
}
function buildKeyVaultReferenceErrorMessage(message: string, setting: ConfigurationSetting, secretIdentifier?: string ): string {
return `${message} Key: '${setting.key}' Label: '${setting.label ?? ""}' ETag: '${setting.etag ?? ""}' ${secretIdentifier ? ` SecretIdentifier: '${secretIdentifier}'` : ""}`;
}