n8n
Version:
n8n Workflow Automation Tool
237 lines • 9.38 kB
JavaScript
"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