Skip to content

Commit adb6155

Browse files
committed
update
1 parent de87ff5 commit adb6155

3 files changed

Lines changed: 56 additions & 41 deletions

File tree

src/common/errorMessages.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import { MIN_REFRESH_INTERVAL_IN_MS } from "../refresh/refreshOptions.js";
55
import { MIN_SECRET_REFRESH_INTERVAL_IN_MS } from "../keyvault/keyVaultOptions.js";
6+
import { ConfigurationSetting } from "@azure/app-configuration";
67

78
export const enum ErrorMessages {
89
INVALID_WATCHED_SETTINGS_KEY = "The characters '*' and ',' are not supported in key of watched settings.",
@@ -26,3 +27,14 @@ export const enum KeyVaultReferenceErrorMessages {
2627
KEY_VAULT_OPTIONS_UNDEFINED = "Failed to process the Key Vault reference because Key Vault options are not configured.",
2728
KEY_VAULT_REFERENCE_UNRESOLVABLE = "Failed to resolve the key vault reference. No key vault secret client, credential or secret resolver callback is available to resolve the secret."
2829
}
30+
31+
export function buildKeyVaultReferenceErrorMessage(message: string, secretIdentifier?: string, setting?: ConfigurationSetting): string {
32+
let errorMessage = message;
33+
if (secretIdentifier) {
34+
errorMessage += ` SecretIdentifier: '${secretIdentifier}'`;
35+
}
36+
if (setting) {
37+
errorMessage += ` Key: '${setting.key}' Label: '${setting.label ?? ""}' ETag: '${setting.etag ?? ""}'`;
38+
}
39+
return errorMessage;
40+
}

src/keyvault/keyVaultKeyValueAdapter.ts

Lines changed: 20 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,8 @@ import { AzureKeyVaultSecretProvider } from "./keyVaultSecretProvider.js";
77
import { KeyVaultOptions } from "./keyVaultOptions.js";
88
import { RefreshTimer } from "../refresh/refreshTimer.js";
99
import { ArgumentError, KeyVaultReferenceError } from "../common/errors.js";
10-
import { KeyVaultReferenceErrorMessages } from "../common/errorMessages.js";
10+
import { KeyVaultReferenceErrorMessages, buildKeyVaultReferenceErrorMessage } from "../common/errorMessages.js";
1111
import { KeyVaultSecretIdentifier, parseKeyVaultSecretIdentifier } from "@azure/keyvault-secrets";
12-
import { isRestError } from "@azure/core-rest-pipeline";
13-
import { AuthenticationError } from "@azure/identity";
1412

1513
export class AzureKeyVaultKeyValueAdapter implements IKeyValueAdapter {
1614
#keyVaultOptions: KeyVaultOptions | undefined;
@@ -29,22 +27,25 @@ export class AzureKeyVaultKeyValueAdapter implements IKeyValueAdapter {
2927
if (!this.#keyVaultOptions) {
3028
throw new ArgumentError(KeyVaultReferenceErrorMessages.KEY_VAULT_OPTIONS_UNDEFINED);
3129
}
32-
// Secret references are parsed, validated and resolved during preload; here we only read the
33-
// cached value. Parsing is guaranteed to succeed because preload runs first.
34-
const secretIdentifier = parseKeyVaultSecretIdentifier(
35-
parseSecretReference(setting).value.secretId
36-
);
37-
const secretValue = this.#keyVaultSecretProvider.getSecretValue(secretIdentifier);
30+
let secretIdentifier: KeyVaultSecretIdentifier;
31+
try {
32+
secretIdentifier = parseKeyVaultSecretIdentifier(
33+
parseSecretReference(setting).value.secretId
34+
);
35+
} catch (error) {
36+
throw new KeyVaultReferenceError(buildKeyVaultReferenceErrorMessage("Invalid Key Vault reference.", undefined, setting), { cause: error });
37+
}
38+
39+
const secretValue = await this.#keyVaultSecretProvider.getSecretValue(secretIdentifier);
3840
return [setting.key, secretValue];
3941
}
4042

4143
async preload(settings: ConfigurationSetting[]): Promise<void> {
4244
if (!this.#keyVaultOptions) {
4345
return; // no-op when keyVaultOptions is not configured
4446
}
45-
// Deduplicate references by secret identifier (sourceId)
46-
// ConfigurationSetting is for Key Vault reference error building.
47-
const uniqueSecrets = new Map<string, { secretIdentifier: KeyVaultSecretIdentifier; setting: ConfigurationSetting }>();
47+
// Deduplicate references by secret identifier (sourceId).
48+
const uniqueSecrets = new Map<string, KeyVaultSecretIdentifier>();
4849
for (const setting of settings) {
4950
if (!this.canProcess(setting)) {
5051
continue;
@@ -55,30 +56,20 @@ export class AzureKeyVaultKeyValueAdapter implements IKeyValueAdapter {
5556
parseSecretReference(setting).value.secretId
5657
);
5758
} catch (error) {
58-
throw new KeyVaultReferenceError(buildKeyVaultReferenceErrorMessage("Invalid Key Vault reference.", setting), { cause: error });
59+
throw new KeyVaultReferenceError(buildKeyVaultReferenceErrorMessage("Invalid Key Vault reference.", undefined, setting), { cause: error });
5960
}
6061
if (!uniqueSecrets.has(secretIdentifier.sourceId)) {
61-
uniqueSecrets.set(secretIdentifier.sourceId, { secretIdentifier, setting });
62+
uniqueSecrets.set(secretIdentifier.sourceId, secretIdentifier);
6263
}
6364
}
6465

65-
const loadSecret = async ({ secretIdentifier, setting }: { secretIdentifier: KeyVaultSecretIdentifier; setting: ConfigurationSetting }) => {
66-
try {
67-
await this.#keyVaultSecretProvider.loadSecretValue(secretIdentifier);
68-
} catch (error) {
69-
if (isRestError(error) || error instanceof AuthenticationError) {
70-
throw new KeyVaultReferenceError(buildKeyVaultReferenceErrorMessage("Failed to resolve Key Vault reference.", setting, secretIdentifier.sourceId), { cause: error });
71-
}
72-
throw error;
73-
}
74-
};
75-
76-
const uniqueSecretEntries = [...uniqueSecrets.values()];
66+
// Resolve failures surface as KeyVaultReferenceError from the provider, identified by secret identifier.
67+
const uniqueSecretIdentifiers = [...uniqueSecrets.values()];
7768
if (this.#keyVaultOptions.parallelSecretResolutionEnabled) {
78-
await Promise.all(uniqueSecretEntries.map(loadSecret));
69+
await Promise.all(uniqueSecretIdentifiers.map(secretIdentifier => this.#keyVaultSecretProvider.loadSecretValue(secretIdentifier)));
7970
} else {
80-
for (const entry of uniqueSecretEntries) {
81-
await loadSecret(entry);
71+
for (const secretIdentifier of uniqueSecretIdentifiers) {
72+
await this.#keyVaultSecretProvider.loadSecretValue(secretIdentifier);
8273
}
8374
}
8475
}
@@ -88,7 +79,3 @@ export class AzureKeyVaultKeyValueAdapter implements IKeyValueAdapter {
8879
return;
8980
}
9081
}
91-
92-
function buildKeyVaultReferenceErrorMessage(message: string, setting: ConfigurationSetting, secretIdentifier?: string ): string {
93-
return `${message} Key: '${setting.key}' Label: '${setting.label ?? ""}' ETag: '${setting.etag ?? ""}' ${secretIdentifier ? ` SecretIdentifier: '${secretIdentifier}'` : ""}`;
94-
}

src/keyvault/keyVaultSecretProvider.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33

44
import { KeyVaultOptions, MIN_SECRET_REFRESH_INTERVAL_IN_MS } from "./keyVaultOptions.js";
55
import { RefreshTimer } from "../refresh/refreshTimer.js";
6-
import { ArgumentError } from "../common/errors.js";
6+
import { ArgumentError, KeyVaultReferenceError } from "../common/errors.js";
77
import { SecretClient, KeyVaultSecretIdentifier } from "@azure/keyvault-secrets";
8-
import { KeyVaultReferenceErrorMessages } from "../common/errorMessages.js";
8+
import { KeyVaultReferenceErrorMessages, buildKeyVaultReferenceErrorMessage } from "../common/errorMessages.js";
9+
import { isRestError } from "@azure/core-rest-pipeline";
10+
import { AuthenticationError } from "@azure/identity";
911

1012
export class AzureKeyVaultSecretProvider {
1113
#keyVaultOptions: KeyVaultOptions | undefined;
@@ -33,18 +35,32 @@ export class AzureKeyVaultSecretProvider {
3335
}
3436
}
3537

36-
async loadSecretValue(secretIdentifier: KeyVaultSecretIdentifier): Promise<void> {
38+
async loadSecretValue(secretIdentifier: KeyVaultSecretIdentifier): Promise<unknown> {
3739
const identifierKey = secretIdentifier.sourceId;
3840
const shouldRefresh = this.#secretRefreshTimer?.canRefresh() ?? false;
3941
if (this.#cachedSecretValues.has(identifierKey) && !shouldRefresh) {
40-
return; // already cached and still fresh
42+
return this.#cachedSecretValues.get(identifierKey); // already cached and still fresh
4143
}
42-
this.#cachedSecretValues.set(identifierKey, await this.#getSecretValueFromKeyVault(secretIdentifier));
44+
let secretValue: unknown;
45+
try {
46+
secretValue = await this.#getSecretValueFromKeyVault(secretIdentifier);
47+
} catch (error) {
48+
if (isRestError(error) || error instanceof AuthenticationError) {
49+
throw new KeyVaultReferenceError(buildKeyVaultReferenceErrorMessage("Failed to resolve Key Vault reference.", secretIdentifier.sourceId), { cause: error });
50+
}
51+
throw error;
52+
}
53+
this.#cachedSecretValues.set(identifierKey, secretValue);
54+
return secretValue;
4355
}
4456

45-
// Reads a secret value that was fetched into the cache during preload. All network I/O happens in preload.
46-
getSecretValue(secretIdentifier: KeyVaultSecretIdentifier): unknown {
47-
return this.#cachedSecretValues.get(secretIdentifier.sourceId);
57+
// Serves the secret value from cache when available; otherwise loads it from Key Vault on demand.
58+
async getSecretValue(secretIdentifier: KeyVaultSecretIdentifier): Promise<unknown> {
59+
const identifierKey = secretIdentifier.sourceId;
60+
if (this.#cachedSecretValues.has(identifierKey)) {
61+
return this.#cachedSecretValues.get(identifierKey); // serve from cache
62+
}
63+
return this.loadSecretValue(secretIdentifier); // fallback: load on demand
4864
}
4965

5066
clearCache(): void {

0 commit comments

Comments
 (0)