From 1b6c2d3a37a78ed07ada93be2a57e4b7f7149e58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Thu, 18 Jul 2024 15:51:48 +0200 Subject: [PATCH] feat: Introduce Azure Key Vault as external secrets provider (#10054) --- packages/cli/package.json | 2 + .../ExternalSecretsProviders.ee.ts | 2 + packages/cli/src/ExternalSecrets/constants.ts | 11 ++ .../__tests__/azure-key-vault.test.ts | 70 ++++++++++ .../aws-secrets/aws-secrets-manager.ts | 11 +- .../azure-key-vault/azure-key-vault.ts | 131 ++++++++++++++++++ .../providers/azure-key-vault/types.ts | 8 ++ .../ExternalSecrets/providers/infisical.ts | 10 +- .../src/ExternalSecrets/providers/vault.ts | 10 +- .../src/assets/images/azure-key-vault.svg | 23 +++ .../ExternalSecretsProviderImage.ee.vue | 2 + pnpm-lock.yaml | 127 ++++++++++++++--- 12 files changed, 361 insertions(+), 46 deletions(-) create mode 100644 packages/cli/src/ExternalSecrets/providers/__tests__/azure-key-vault.test.ts create mode 100644 packages/cli/src/ExternalSecrets/providers/azure-key-vault/azure-key-vault.ts create mode 100644 packages/cli/src/ExternalSecrets/providers/azure-key-vault/types.ts create mode 100644 packages/editor-ui/src/assets/images/azure-key-vault.svg diff --git a/packages/cli/package.json b/packages/cli/package.json index 7772bbfc91..90eebaed21 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -81,6 +81,8 @@ "ts-essentials": "^7.0.3" }, "dependencies": { + "@azure/identity": "^4.3.0", + "@azure/keyvault-secrets": "^4.8.0", "@n8n/client-oauth2": "workspace:*", "@n8n/config": "workspace:*", "@n8n/localtunnel": "2.1.0", diff --git a/packages/cli/src/ExternalSecrets/ExternalSecretsProviders.ee.ts b/packages/cli/src/ExternalSecrets/ExternalSecretsProviders.ee.ts index f7367064a3..33228673c3 100644 --- a/packages/cli/src/ExternalSecrets/ExternalSecretsProviders.ee.ts +++ b/packages/cli/src/ExternalSecrets/ExternalSecretsProviders.ee.ts @@ -3,6 +3,7 @@ import { Service } from 'typedi'; import { InfisicalProvider } from './providers/infisical'; import { VaultProvider } from './providers/vault'; import { AwsSecretsManager } from './providers/aws-secrets/aws-secrets-manager'; +import { AzureKeyVault } from './providers/azure-key-vault/azure-key-vault'; @Service() export class ExternalSecretsProviders { @@ -10,6 +11,7 @@ export class ExternalSecretsProviders { awsSecretsManager: AwsSecretsManager, infisical: InfisicalProvider, vault: VaultProvider, + azureKeyVault: AzureKeyVault, }; getProvider(name: string): { new (): SecretsProvider } | null { diff --git a/packages/cli/src/ExternalSecrets/constants.ts b/packages/cli/src/ExternalSecrets/constants.ts index d62dd1e459..e3ea016262 100644 --- a/packages/cli/src/ExternalSecrets/constants.ts +++ b/packages/cli/src/ExternalSecrets/constants.ts @@ -1,5 +1,16 @@ +import type { INodeProperties } from 'n8n-workflow'; + export const EXTERNAL_SECRETS_DB_KEY = 'feature.externalSecrets'; export const EXTERNAL_SECRETS_INITIAL_BACKOFF = 10 * 1000; export const EXTERNAL_SECRETS_MAX_BACKOFF = 5 * 60 * 1000; export const EXTERNAL_SECRETS_NAME_REGEX = /^[a-zA-Z0-9\-\_\/]+$/; + +export const DOCS_HELP_NOTICE: INodeProperties = { + displayName: + 'Need help filling out these fields? Open docs', + name: 'notice', + type: 'notice', + default: '', + noDataExpression: true, +}; diff --git a/packages/cli/src/ExternalSecrets/providers/__tests__/azure-key-vault.test.ts b/packages/cli/src/ExternalSecrets/providers/__tests__/azure-key-vault.test.ts new file mode 100644 index 0000000000..329bfca2ac --- /dev/null +++ b/packages/cli/src/ExternalSecrets/providers/__tests__/azure-key-vault.test.ts @@ -0,0 +1,70 @@ +import { SecretClient } from '@azure/keyvault-secrets'; +import type { KeyVaultSecret } from '@azure/keyvault-secrets'; +import { AzureKeyVault } from '../azure-key-vault/azure-key-vault'; +import { mock } from 'jest-mock-extended'; +import type { AzureKeyVaultContext } from '../azure-key-vault/types'; + +jest.mock('@azure/identity'); +jest.mock('@azure/keyvault-secrets'); + +describe('AzureKeyVault', () => { + const azureKeyVault = new AzureKeyVault(); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should update cached secrets', async () => { + /** + * Arrange + */ + await azureKeyVault.init( + mock({ + settings: { + vaultName: 'my-vault', + tenantId: 'my-tenant-id', + clientId: 'my-client-id', + clientSecret: 'my-client-secret', + }, + }), + ); + + const listSpy = jest + .spyOn(SecretClient.prototype, 'listPropertiesOfSecrets') + // @ts-expect-error Partial mock + .mockImplementation(() => ({ + async *[Symbol.asyncIterator]() { + yield { name: 'secret1' }; + yield { name: 'secret2' }; + yield { name: 'secret3' }; // no value + yield { name: '#@&' }; // invalid name + }, + })); + + const getSpy = jest + .spyOn(SecretClient.prototype, 'getSecret') + .mockImplementation(async (name: string) => { + return mock({ value: { secret1: 'value1', secret2: 'value2' }[name] }); + }); + + /** + * Act + */ + await azureKeyVault.connect(); + await azureKeyVault.update(); + + /** + * Assert + */ + expect(listSpy).toHaveBeenCalled(); + expect(getSpy).toHaveBeenCalledWith('secret1'); + expect(getSpy).toHaveBeenCalledWith('secret2'); + expect(getSpy).toHaveBeenCalledWith('secret3'); + expect(getSpy).not.toHaveBeenCalledWith('#@&'); + + expect(azureKeyVault.getSecret('secret1')).toBe('value1'); + expect(azureKeyVault.getSecret('secret2')).toBe('value2'); + expect(azureKeyVault.getSecret('secret3')).toBeUndefined(); // no value + expect(azureKeyVault.getSecret('#@&')).toBeUndefined(); // invalid name + }); +}); diff --git a/packages/cli/src/ExternalSecrets/providers/aws-secrets/aws-secrets-manager.ts b/packages/cli/src/ExternalSecrets/providers/aws-secrets/aws-secrets-manager.ts index 986b2fcadc..b7b5f52d7b 100644 --- a/packages/cli/src/ExternalSecrets/providers/aws-secrets/aws-secrets-manager.ts +++ b/packages/cli/src/ExternalSecrets/providers/aws-secrets/aws-secrets-manager.ts @@ -1,6 +1,6 @@ import { AwsSecretsClient } from './aws-secrets-client'; import { UnknownAuthTypeError } from '@/errors/unknown-auth-type.error'; -import { EXTERNAL_SECRETS_NAME_REGEX } from '@/ExternalSecrets/constants'; +import { DOCS_HELP_NOTICE, EXTERNAL_SECRETS_NAME_REGEX } from '@/ExternalSecrets/constants'; import type { SecretsProvider, SecretsProviderState } from '@/Interfaces'; import type { INodeProperties } from 'n8n-workflow'; import type { AwsSecretsManagerContext } from './types'; @@ -13,14 +13,7 @@ export class AwsSecretsManager implements SecretsProvider { state: SecretsProviderState = 'initializing'; properties: INodeProperties[] = [ - { - displayName: - 'Need help filling out these fields? Open docs', - name: 'notice', - type: 'notice', - default: '', - noDataExpression: true, - }, + DOCS_HELP_NOTICE, { displayName: 'Region', name: 'region', diff --git a/packages/cli/src/ExternalSecrets/providers/azure-key-vault/azure-key-vault.ts b/packages/cli/src/ExternalSecrets/providers/azure-key-vault/azure-key-vault.ts new file mode 100644 index 0000000000..c51e54e799 --- /dev/null +++ b/packages/cli/src/ExternalSecrets/providers/azure-key-vault/azure-key-vault.ts @@ -0,0 +1,131 @@ +import { ClientSecretCredential } from '@azure/identity'; +import { SecretClient } from '@azure/keyvault-secrets'; +import type { SecretsProvider, SecretsProviderState } from '@/Interfaces'; +import type { INodeProperties } from 'n8n-workflow'; +import type { AzureKeyVaultContext } from './types'; +import { DOCS_HELP_NOTICE, EXTERNAL_SECRETS_NAME_REGEX } from '@/ExternalSecrets/constants'; + +export class AzureKeyVault implements SecretsProvider { + name = 'azureKeyVault'; + + displayName = 'Azure Key Vault'; + + state: SecretsProviderState = 'initializing'; + + properties: INodeProperties[] = [ + DOCS_HELP_NOTICE, + { + displayName: 'Vault Name', + hint: 'The name of your existing Azure Key Vault.', + name: 'vaultName', + type: 'string', + default: '', + required: true, + placeholder: 'e.g. my-vault', + noDataExpression: true, + }, + { + displayName: 'Tenant ID', + name: 'tenantId', + hint: 'In Azure, this can be called "Directory (Tenant) ID".', + type: 'string', + default: '', + required: true, + placeholder: 'e.g. 7dec9324-7074-72b7-a3ca-a9bb3012f466', + noDataExpression: true, + }, + { + displayName: 'Client ID', + name: 'clientId', + hint: 'In Azure, this can be called "Application (Client) ID".', + type: 'string', + default: '', + required: true, + placeholder: 'e.g. 7753d8c2-e41f-22ed-3dd7-c9e96463622c', + typeOptions: { password: true }, + noDataExpression: true, + }, + { + displayName: 'Client Secret', + name: 'clientSecret', + hint: 'The client secret value of your registered application.', + type: 'string', + default: '', + required: true, + typeOptions: { password: true }, + noDataExpression: true, + }, + ]; + + private cachedSecrets: Record = {}; + + private client: SecretClient; + + private settings: AzureKeyVaultContext['settings']; + + async init(context: AzureKeyVaultContext) { + this.settings = context.settings; + } + + async connect() { + const { vaultName, tenantId, clientId, clientSecret } = this.settings; + + try { + const credential = new ClientSecretCredential(tenantId, clientId, clientSecret); + this.client = new SecretClient(`https://${vaultName}.vault.azure.net/`, credential); + this.state = 'connected'; + } catch (error) { + this.state = 'error'; + } + } + + async test(): Promise<[boolean] | [boolean, string]> { + if (!this.client) return [false, 'Failed to connect to Azure Key Vault']; + + try { + await this.client.listPropertiesOfSecrets().next(); + return [true]; + } catch (error: unknown) { + return [false, error instanceof Error ? error.message : 'unknown error']; + } + } + + async disconnect() { + // unused + } + + async update() { + const secretNames: string[] = []; + + for await (const secret of this.client.listPropertiesOfSecrets()) { + secretNames.push(secret.name); + } + + const promises = secretNames + .filter((name) => EXTERNAL_SECRETS_NAME_REGEX.test(name)) + .map(async (name) => { + const { value } = await this.client.getSecret(name); + return { name, value }; + }); + + const secrets = await Promise.all(promises); + + this.cachedSecrets = secrets.reduce>((acc, cur) => { + if (cur.value === undefined) return acc; + acc[cur.name] = cur.value; + return acc; + }, {}); + } + + getSecret(name: string) { + return this.cachedSecrets[name]; + } + + hasSecret(name: string) { + return name in this.cachedSecrets; + } + + getSecretNames() { + return Object.keys(this.cachedSecrets); + } +} diff --git a/packages/cli/src/ExternalSecrets/providers/azure-key-vault/types.ts b/packages/cli/src/ExternalSecrets/providers/azure-key-vault/types.ts new file mode 100644 index 0000000000..cd229a2589 --- /dev/null +++ b/packages/cli/src/ExternalSecrets/providers/azure-key-vault/types.ts @@ -0,0 +1,8 @@ +import type { SecretsProviderSettings } from '@/Interfaces'; + +export type AzureKeyVaultContext = SecretsProviderSettings<{ + vaultName: string; + tenantId: string; + clientId: string; + clientSecret: string; +}>; diff --git a/packages/cli/src/ExternalSecrets/providers/infisical.ts b/packages/cli/src/ExternalSecrets/providers/infisical.ts index 0d700486a7..d9a2f93cda 100644 --- a/packages/cli/src/ExternalSecrets/providers/infisical.ts +++ b/packages/cli/src/ExternalSecrets/providers/infisical.ts @@ -3,7 +3,7 @@ import InfisicalClient from 'infisical-node'; import { populateClientWorkspaceConfigsHelper } from 'infisical-node/lib/helpers/key'; import { getServiceTokenData } from 'infisical-node/lib/api/serviceTokenData'; import { ApplicationError, type IDataObject, type INodeProperties } from 'n8n-workflow'; -import { EXTERNAL_SECRETS_NAME_REGEX } from '../constants'; +import { DOCS_HELP_NOTICE, EXTERNAL_SECRETS_NAME_REGEX } from '../constants'; export interface InfisicalSettings { token: string; @@ -24,13 +24,7 @@ interface InfisicalServiceToken { export class InfisicalProvider implements SecretsProvider { properties: INodeProperties[] = [ - { - displayName: - 'Need help filling out these fields? Open docs', - name: 'notice', - type: 'notice', - default: '', - }, + DOCS_HELP_NOTICE, { displayName: 'Service Token', name: 'token', diff --git a/packages/cli/src/ExternalSecrets/providers/vault.ts b/packages/cli/src/ExternalSecrets/providers/vault.ts index 43c6ae9f74..6735d26d6d 100644 --- a/packages/cli/src/ExternalSecrets/providers/vault.ts +++ b/packages/cli/src/ExternalSecrets/providers/vault.ts @@ -4,7 +4,7 @@ import type { IDataObject, INodeProperties } from 'n8n-workflow'; import type { AxiosInstance, AxiosResponse } from 'axios'; import axios from 'axios'; import { Logger } from '@/Logger'; -import { EXTERNAL_SECRETS_NAME_REGEX } from '../constants'; +import { DOCS_HELP_NOTICE, EXTERNAL_SECRETS_NAME_REGEX } from '../constants'; import { preferGet } from '../externalSecretsHelper.ee'; import { Container } from 'typedi'; @@ -85,13 +85,7 @@ interface VaultSecretList { export class VaultProvider extends SecretsProvider { properties: INodeProperties[] = [ - { - displayName: - 'Need help filling out these fields? Open docs', - name: 'notice', - type: 'notice', - default: '', - }, + DOCS_HELP_NOTICE, { displayName: 'Vault URL', name: 'url', diff --git a/packages/editor-ui/src/assets/images/azure-key-vault.svg b/packages/editor-ui/src/assets/images/azure-key-vault.svg new file mode 100644 index 0000000000..68a9c17c4e --- /dev/null +++ b/packages/editor-ui/src/assets/images/azure-key-vault.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/editor-ui/src/components/ExternalSecretsProviderImage.ee.vue b/packages/editor-ui/src/components/ExternalSecretsProviderImage.ee.vue index 0d6b308d83..dc40fed88c 100644 --- a/packages/editor-ui/src/components/ExternalSecretsProviderImage.ee.vue +++ b/packages/editor-ui/src/components/ExternalSecretsProviderImage.ee.vue @@ -6,6 +6,7 @@ import infisical from '../assets/images/infisical.webp'; import doppler from '../assets/images/doppler.webp'; import vault from '../assets/images/hashicorp.webp'; import awsSecretsManager from '../assets/images/aws-secrets-manager.svg'; +import azureKeyVault from '../assets/images/azure-key-vault.svg'; const props = defineProps<{ provider: ExternalSecretsProvider; @@ -18,6 +19,7 @@ const image = computed( infisical, vault, awsSecretsManager, + azureKeyVault, })[props.provider.name], ); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 15800ee6d3..8cfa83da0c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -538,6 +538,12 @@ importers: packages/cli: dependencies: + '@azure/identity': + specifier: ^4.3.0 + version: 4.3.0 + '@azure/keyvault-secrets': + specifier: ^4.8.0 + version: 4.8.0 '@n8n/client-oauth2': specifier: workspace:* version: link:../@n8n/client-oauth2 @@ -1978,14 +1984,18 @@ packages: resolution: {integrity: sha512-3X9wzaaGgRaBCwhLQZDtFp5uLIXCPrGbwJNWPPugvL4xbIGgScv77YzzxToKGLAKvG9amDoofMoP+9hsH1vs1w==} engines: {node: '>=18.0.0'} - '@azure/core-client@1.6.1': - resolution: {integrity: sha512-mZ1MSKhZBYoV8GAWceA+PEJFWV2VpdNSpxxcj1wjIAOi00ykRuIQChT99xlQGZWLY3/NApWhSImlFwsmCEs4vA==} - engines: {node: '>=12.0.0'} + '@azure/core-client@1.9.2': + resolution: {integrity: sha512-kRdry/rav3fUKHl/aDLd/pDLcB+4pOFwPPTVEExuMyaI5r+JBbMWqRbCY1pn5BniDaU3lRxO9eaQ1AmSMehl/w==} + engines: {node: '>=18.0.0'} '@azure/core-http-compat@1.3.0': resolution: {integrity: sha512-ZN9avruqbQ5TxopzG3ih3KRy52n8OAbitX3fnZT5go4hzu0J+KVPSzkL+Wt3hpJpdG8WIfg1sBD1tWkgUdEpBA==} engines: {node: '>=12.0.0'} + '@azure/core-http-compat@2.1.2': + resolution: {integrity: sha512-5MnV1yqzZwgNLLjlizsU3QqOeQChkIXw781Fwh1xdAqJR5AA32IUaq6xv1BICJvfbHoa+JYcaij2HFkhLbNTJQ==} + engines: {node: '>=18.0.0'} + '@azure/core-http@2.3.2': resolution: {integrity: sha512-Z4dfbglV9kNZO177CNx4bo5ekFuYwwsvjLiKdZI4r84bYGv3irrbQz7JC3/rUfFH2l4T/W6OFleJaa2X0IaQqw==} engines: {node: '>=14.0.0'} @@ -2018,10 +2028,18 @@ packages: resolution: {integrity: sha512-0q5DL4uyR0EZ4RXQKD8MadGH6zTIcloUoS/RVbCpNpej4pwte0xpqYxk8K97Py2RiuUvI7F4GXpoT4046VfufA==} engines: {node: '>=14.0.0'} + '@azure/identity@4.3.0': + resolution: {integrity: sha512-LHZ58/RsIpIWa4hrrE2YuJ/vzG1Jv9f774RfTTAVDZDriubvJ0/S5u4pnw4akJDlS0TiJb6VMphmVUFsWmgodQ==} + engines: {node: '>=18.0.0'} + '@azure/keyvault-keys@4.6.0': resolution: {integrity: sha512-0112LegxeR03L8J4k+q6HwBVvrpd9y+oInG0FG3NaHXN7YUubVBon/eb5jFI6edGrvNigpxSR0XIsprFXdkzCQ==} engines: {node: '>=12.0.0'} + '@azure/keyvault-secrets@4.8.0': + resolution: {integrity: sha512-RGfpFk6XUXHfWuTAiokOe8t6ej5C4ijf4HVyJUmTfN6VjDBVPvTtoiOi/C5072/ENHScYZFhiYOgIjLgYjfJ/A==} + engines: {node: '>=18.0.0'} + '@azure/logger@1.0.3': resolution: {integrity: sha512-aK4s3Xxjrx3daZr3VylxejK3vG5ExXck5WOHDJ8in/k9AqlfIyFMMT1uG7u8mNjX+QRILTIn0/Xgschfh/dQ9g==} engines: {node: '>=12.0.0'} @@ -2030,10 +2048,22 @@ packages: resolution: {integrity: sha512-mnmi8dCXVNZI+AGRq0jKQ3YiodlIC4W9npr6FCB9WN6NQT+6rq+cIlxgUb//BjLyzKsnYo+i4LROGeMyU+6v1A==} engines: {node: '>=0.8.0'} + '@azure/msal-browser@3.19.0': + resolution: {integrity: sha512-3unHlh3qWtXbqks/TLq3qGWzxfmwRfk9tXSGvVCcHHnCH5QKtcg/JiDIeP/1B2qFlqnSgtYY0JPLy9EIVoZ7Ag==} + engines: {node: '>=0.8.0'} + + '@azure/msal-common@14.13.0': + resolution: {integrity: sha512-b4M/tqRzJ4jGU91BiwCsLTqChveUEyFK3qY2wGfZ0zBswIBZjAxopx5CYt5wzZFKuN15HqRDYXQbztttuIC3nA==} + engines: {node: '>=0.8.0'} + '@azure/msal-common@14.7.1': resolution: {integrity: sha512-v96btzjM7KrAu4NSEdOkhQSTGOuNUIIsUdB8wlyB9cdgl5KqEKnTonHUZ8+khvZ6Ap542FCErbnTyDWl8lZ2rA==} engines: {node: '>=0.8.0'} + '@azure/msal-node@2.11.0': + resolution: {integrity: sha512-yNRCp4Do4CGSBe1WXq4DWhfa/vYZCUgGrweYLC5my/6eDnYMt0fYGPHuTMw0iRslQGXF3CecGAxXp7ab57V4zg==} + engines: {node: '>=16'} + '@azure/msal-node@2.6.4': resolution: {integrity: sha512-nNvEPx009/80UATCToF+29NZYocn01uKrB91xtFr7bSqkqO1PuQGXRyYwryWRztUrYZ1YsSbw9A+LmwOhpVvcg==} engines: {node: '>=16'} @@ -14146,9 +14176,9 @@ snapshots: '@azure/core-util': 1.7.0 tslib: 2.6.2 - '@azure/core-client@1.6.1': + '@azure/core-client@1.9.2': dependencies: - '@azure/abort-controller': 1.1.0 + '@azure/abort-controller': 2.0.0 '@azure/core-auth': 1.6.0 '@azure/core-rest-pipeline': 1.9.2 '@azure/core-tracing': 1.0.1 @@ -14161,7 +14191,15 @@ snapshots: '@azure/core-http-compat@1.3.0': dependencies: '@azure/abort-controller': 1.1.0 - '@azure/core-client': 1.6.1 + '@azure/core-client': 1.9.2 + '@azure/core-rest-pipeline': 1.9.2 + transitivePeerDependencies: + - supports-color + + '@azure/core-http-compat@2.1.2': + dependencies: + '@azure/abort-controller': 2.0.0 + '@azure/core-client': 1.9.2 '@azure/core-rest-pipeline': 1.9.2 transitivePeerDependencies: - supports-color @@ -14229,7 +14267,7 @@ snapshots: dependencies: '@azure/abort-controller': 1.1.0 '@azure/core-auth': 1.6.0 - '@azure/core-client': 1.6.1 + '@azure/core-client': 1.9.2 '@azure/core-rest-pipeline': 1.9.2 '@azure/core-tracing': 1.0.1 '@azure/core-util': 1.7.0 @@ -14244,11 +14282,30 @@ snapshots: transitivePeerDependencies: - supports-color + '@azure/identity@4.3.0': + dependencies: + '@azure/abort-controller': 1.1.0 + '@azure/core-auth': 1.6.0 + '@azure/core-client': 1.9.2 + '@azure/core-rest-pipeline': 1.9.2 + '@azure/core-tracing': 1.0.1 + '@azure/core-util': 1.7.0 + '@azure/logger': 1.0.3 + '@azure/msal-browser': 3.19.0 + '@azure/msal-node': 2.11.0 + events: 3.3.0 + jws: 4.0.0 + open: 8.4.0 + stoppable: 1.1.0 + tslib: 2.6.2 + transitivePeerDependencies: + - supports-color + '@azure/keyvault-keys@4.6.0': dependencies: '@azure/abort-controller': 1.1.0 '@azure/core-auth': 1.6.0 - '@azure/core-client': 1.6.1 + '@azure/core-client': 1.9.2 '@azure/core-http-compat': 1.3.0 '@azure/core-lro': 2.4.0 '@azure/core-paging': 1.3.0 @@ -14260,6 +14317,22 @@ snapshots: transitivePeerDependencies: - supports-color + '@azure/keyvault-secrets@4.8.0': + dependencies: + '@azure/abort-controller': 1.1.0 + '@azure/core-auth': 1.6.0 + '@azure/core-client': 1.9.2 + '@azure/core-http-compat': 2.1.2 + '@azure/core-lro': 2.4.0 + '@azure/core-paging': 1.3.0 + '@azure/core-rest-pipeline': 1.9.2 + '@azure/core-tracing': 1.0.1 + '@azure/core-util': 1.7.0 + '@azure/logger': 1.0.3 + tslib: 2.6.2 + transitivePeerDependencies: + - supports-color + '@azure/logger@1.0.3': dependencies: tslib: 2.6.2 @@ -14268,8 +14341,20 @@ snapshots: dependencies: '@azure/msal-common': 14.7.1 + '@azure/msal-browser@3.19.0': + dependencies: + '@azure/msal-common': 14.13.0 + + '@azure/msal-common@14.13.0': {} + '@azure/msal-common@14.7.1': {} + '@azure/msal-node@2.11.0': + dependencies: + '@azure/msal-common': 14.13.0 + jsonwebtoken: 9.0.2 + uuid: 8.3.2 + '@azure/msal-node@2.6.4': dependencies: '@azure/msal-common': 14.7.1 @@ -14320,7 +14405,7 @@ snapshots: '@babel/traverse': 7.24.0 '@babel/types': 7.24.0 convert-source-map: 2.0.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.5(supports-color@8.1.1) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 7.6.0 @@ -14441,7 +14526,7 @@ snapshots: '@babel/core': 7.24.6 '@babel/helper-compilation-targets': 7.24.6 '@babel/helper-plugin-utils': 7.24.6 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.5(supports-color@8.1.1) lodash.debounce: 4.0.8 resolve: 1.22.8 transitivePeerDependencies: @@ -14452,7 +14537,7 @@ snapshots: '@babel/core': 7.24.6 '@babel/helper-compilation-targets': 7.24.6 '@babel/helper-plugin-utils': 7.24.6 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.5(supports-color@8.1.1) lodash.debounce: 4.0.8 resolve: 1.22.8 transitivePeerDependencies: @@ -15336,7 +15421,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.22.6 '@babel/parser': 7.24.0 '@babel/types': 7.24.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.5(supports-color@8.1.1) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -15351,7 +15436,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.24.6 '@babel/parser': 7.24.6 '@babel/types': 7.24.6 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.5(supports-color@8.1.1) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -18923,7 +19008,7 @@ snapshots: dependencies: '@typescript-eslint/types': 6.7.5 '@typescript-eslint/visitor-keys': 6.7.5 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.5(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 semver: 7.6.0 @@ -19344,7 +19429,7 @@ snapshots: agent-base@7.1.0: dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.5(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -20790,7 +20875,7 @@ snapshots: detect-port@1.5.1: dependencies: address: 1.2.2 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.5(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -21168,7 +21253,7 @@ snapshots: esbuild-register@3.5.0(esbuild@0.20.2): dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.5(supports-color@8.1.1) esbuild: 0.20.2 transitivePeerDependencies: - supports-color @@ -22405,7 +22490,7 @@ snapshots: dependencies: '@tootallnate/once': 1.1.2 agent-base: 6.0.2 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.5(supports-color@8.1.1) transitivePeerDependencies: - supports-color optional: true @@ -22414,7 +22499,7 @@ snapshots: dependencies: '@tootallnate/once': 2.0.0 agent-base: 6.0.2 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.5(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -22823,7 +22908,7 @@ snapshots: istanbul-lib-source-maps@4.0.1: dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.5(supports-color@8.1.1) istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: @@ -26241,7 +26326,7 @@ snapshots: socks-proxy-agent@6.2.1: dependencies: agent-base: 6.0.2 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.5(supports-color@8.1.1) socks: 2.7.1 transitivePeerDependencies: - supports-color