UNPKG

n8n

Version:

n8n Workflow Automation Tool

237 lines • 9.38 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AzureKeyVault = void 0; const identity_1 = require("@azure/identity"); const backend_common_1 = require("@n8n/backend-common"); const di_1 = require("@n8n/di"); const ensure_error_1 = require("@n8n/utils/errors/ensure-error"); const n8n_workflow_1 = require("n8n-workflow"); const constants_1 = require("../../constants"); const secrets_provider_errors_1 = require("../../errors/secrets-provider-errors"); const types_1 = require("../../types"); class AzureKeyVault extends types_1.SecretsProvider { constructor(logger = di_1.Container.get(backend_common_1.Logger)) { super(); this.logger = logger; this.name = 'azureKeyVault'; this.displayName = 'Azure Key Vault'; this.properties = [ constants_1.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, }, ]; this.cachedSecrets = {}; this.logger = this.logger.scoped('external-secrets'); } async init(context) { this.settings = context.settings; this.logger.debug('Azure Key Vault provider initialized'); } async doConnect() { try { const { vaultName, tenantId, clientId, clientSecret } = this.settings; const { ClientSecretCredential } = await import('@azure/identity'); const { SecretClient } = await import('@azure/keyvault-secrets'); const credential = new ClientSecretCredential(tenantId, clientId, clientSecret); this.client = new SecretClient(`https://${vaultName}.vault.azure.net/`, credential); this.logger.debug('Azure Key Vault provider connected'); } catch (error) { this.logOperationFailure('Failed to connect Azure Key Vault provider', { operation: 'connect', error, context: this.azureErrorContext(error), }); throw error; } } async test() { if (!this.client) return [false, 'Failed to connect to Azure Key Vault']; try { await this.client.listPropertiesOfSecrets().next(); return [true]; } catch (error) { this.logOperationFailure('Azure Key Vault provider test failed', { operation: 'test', error, context: this.azureErrorContext(error), }); return [false, error instanceof Error ? error.message : 'Unknown error']; } } async disconnect() { } async update() { try { const secretNames = []; for await (const secret of this.client.listPropertiesOfSecrets()) { if (secret.enabled === false) continue; secretNames.push(secret.name); } const promises = await Promise.allSettled(secretNames.map(async (name) => { const { value } = await this.client.getSecret(name); return { name, value }; })); const updated = {}; const readErrors = []; const failedSecrets = []; for (const [index, promiseResult] of promises.entries()) { if (promiseResult.status === 'fulfilled') { const { name, value } = promiseResult.value; if (value !== undefined) updated[name] = value; } else { const error = (0, ensure_error_1.ensureError)(promiseResult.reason); readErrors.push(error); const secretName = secretNames[index]; const errorContext = this.azureErrorContext(error); this.logger.debug(`Could not read Azure Key Vault secret "${secretName}"`, { providerName: this.name, operation: 'update', vaultName: this.settings.vaultName, secretName, ...errorContext, }); failedSecrets.push({ name: secretName, errorCode: errorContext.errorCode ?? 'unknown', }); } } const failureSummary = (0, secrets_provider_errors_1.buildFailureSummaryLogContext)(failedSecrets); const isTotalFailure = secretNames.length > 0 && Object.keys(updated).length === 0 && readErrors.length > 0; if (failureSummary && !isTotalFailure) { this.logOperationFailure('Skipped unreadable Azure Key Vault secrets during update', { operation: 'update', error: readErrors[0] ?? new Error('One or more Azure Key Vault secrets could not be read'), context: { ...this.azureErrorContext(readErrors[0]), ...failureSummary, }, }); } if (isTotalFailure) { const error = readErrors[0]; this.logOperationFailure('Could not read any secrets from Azure Key Vault', { operation: 'update', error, context: { ...this.azureErrorContext(error), ...failureSummary, }, }); throw new n8n_workflow_1.UnexpectedError('Could not read any secrets from Azure Key Vault', { cause: error, }); } this.cachedSecrets = updated; this.logger.debug('Azure Key Vault provider secrets updated'); } catch (error) { if (error instanceof n8n_workflow_1.UnexpectedError) { throw error; } this.logOperationFailure('Failed to update Azure Key Vault provider secrets', { operation: 'update', error, context: this.azureErrorContext(error), }); throw error; } } getSecret(name) { return this.cachedSecrets[name]; } hasSecret(name) { return name in this.cachedSecrets; } getSecretNames() { return Object.keys(this.cachedSecrets); } isAzureHttpLikeError(error) { if (!(error instanceof Error)) return false; const candidate = error; return (error.name === 'RestError' || typeof candidate.statusCode === 'number' || typeof candidate.code === 'string'); } azureErrorContext(error) { if (error instanceof identity_1.AuthenticationError) { return { statusCode: error.statusCode, errorCode: error.errorResponse?.error, }; } if (this.isAzureHttpLikeError(error)) { return { statusCode: error.statusCode, errorCode: error.code, }; } if (error instanceof Error) { return { errorCode: error.name }; } return {}; } logOperationFailure(message, params) { const context = { ...params.context }; if (this.settings?.vaultName) { context.vaultName = this.settings.vaultName; } (0, secrets_provider_errors_1.logSecretsProviderOperationFailure)({ logger: this.logger, message, providerName: this.name, providerDisplayName: this.displayName, operation: params.operation, error: params.error, context, }); } } exports.AzureKeyVault = AzureKeyVault; //# sourceMappingURL=azure-key-vault.js.map