UNPKG

n8n

Version:

n8n Workflow Automation Tool

429 lines 17.2 kB
"use strict"; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; var _VaultProvider_currentToken, _VaultProvider_tokenInfo, _VaultProvider_http; Object.defineProperty(exports, "__esModule", { value: true }); exports.VaultProvider = void 0; const di_1 = require("@n8n/di"); const axios_1 = __importDefault(require("axios")); const n8n_core_1 = require("n8n-core"); const interfaces_1 = require("../../interfaces"); const constants_1 = require("../constants"); const external_secrets_helper_ee_1 = require("../external-secrets-helper.ee"); class VaultProvider extends interfaces_1.SecretsProvider { constructor(logger = di_1.Container.get(n8n_core_1.Logger)) { super(); this.logger = logger; this.properties = [ constants_1.DOCS_HELP_NOTICE, { displayName: 'Vault URL', name: 'url', type: 'string', required: true, noDataExpression: true, placeholder: 'e.g. https://example.com/v1/', default: '', }, { displayName: 'Vault Namespace (optional)', name: 'namespace', type: 'string', hint: 'Leave blank if not using namespaces', required: false, noDataExpression: true, placeholder: 'e.g. admin', default: '', }, { displayName: 'Authentication Method', name: 'authMethod', type: 'options', required: true, noDataExpression: true, options: [ { name: 'Token', value: 'token' }, { name: 'Username and Password', value: 'usernameAndPassword' }, { name: 'AppRole', value: 'appRole' }, ], default: 'token', }, { displayName: 'Token', name: 'token', type: 'string', default: '', required: true, noDataExpression: true, placeholder: 'e.g. hvs.2OCsZxZA6Z9lChbt0janOOZI', typeOptions: { password: true }, displayOptions: { show: { authMethod: ['token'], }, }, }, { displayName: 'Username', name: 'username', type: 'string', default: '', required: true, noDataExpression: true, placeholder: 'Username', displayOptions: { show: { authMethod: ['usernameAndPassword'], }, }, }, { displayName: 'Password', name: 'password', type: 'string', default: '', required: true, noDataExpression: true, placeholder: '***************', typeOptions: { password: true }, displayOptions: { show: { authMethod: ['usernameAndPassword'], }, }, }, { displayName: 'Role ID', name: 'roleId', type: 'string', default: '', required: true, noDataExpression: true, placeholder: '59d6d1ca-47bb-4e7e-a40b-8be3bc5a0ba8', displayOptions: { show: { authMethod: ['appRole'], }, }, }, { displayName: 'Secret ID', name: 'secretId', type: 'string', default: '', required: true, noDataExpression: true, placeholder: '84896a0c-1347-aa90-a4f6-aca8b7558780', typeOptions: { password: true }, displayOptions: { show: { authMethod: ['appRole'], }, }, }, ]; this.displayName = 'HashiCorp Vault'; this.name = 'vault'; this.state = 'initializing'; this.cachedSecrets = {}; _VaultProvider_currentToken.set(this, null); _VaultProvider_tokenInfo.set(this, null); _VaultProvider_http.set(this, void 0); this.refreshAbort = new AbortController(); this.tokenRefresh = async () => { var _a; if (this.refreshAbort.signal.aborted) { return; } try { await __classPrivateFieldGet(this, _VaultProvider_http, "f").post('auth/token/renew-self'); _a = this, [({ set value(_b) { __classPrivateFieldSet(_a, _VaultProvider_tokenInfo, _b, "f"); } }).value] = await this.getTokenInfo(); if (!__classPrivateFieldGet(this, _VaultProvider_tokenInfo, "f")) { this.logger.error('Failed to fetch token info during renewal. Cancelling all future renewals.'); return; } if (this.refreshAbort.signal.aborted) { return; } this.setupTokenRefresh(); } catch { this.logger.error('Failed to renew Vault token. Attempting to reconnect.'); void this.connect(); } }; this.logger = this.logger.scoped('external-secrets'); } async init(settings) { this.settings = settings.settings; const baseURL = new URL(this.settings.url); __classPrivateFieldSet(this, _VaultProvider_http, axios_1.default.create({ baseURL: baseURL.toString() }), "f"); if (this.settings.namespace) { __classPrivateFieldGet(this, _VaultProvider_http, "f").interceptors.request.use((config) => { config.headers['X-Vault-Namespace'] = this.settings.namespace; return config; }); } __classPrivateFieldGet(this, _VaultProvider_http, "f").interceptors.request.use((config) => { if (__classPrivateFieldGet(this, _VaultProvider_currentToken, "f")) { config.headers['X-Vault-Token'] = __classPrivateFieldGet(this, _VaultProvider_currentToken, "f"); } return config; }); this.logger.debug('Vault provider initialized'); } async connect() { var _a; if (this.settings.authMethod === 'token') { __classPrivateFieldSet(this, _VaultProvider_currentToken, this.settings.token, "f"); } else if (this.settings.authMethod === 'usernameAndPassword') { try { __classPrivateFieldSet(this, _VaultProvider_currentToken, await this.authUsernameAndPassword(this.settings.username, this.settings.password), "f"); } catch { this.state = 'error'; this.logger.error('Failed to connect to Vault using Username and Password credentials.'); return; } } else if (this.settings.authMethod === 'appRole') { try { __classPrivateFieldSet(this, _VaultProvider_currentToken, await this.authAppRole(this.settings.roleId, this.settings.secretId), "f"); } catch { this.state = 'error'; this.logger.error('Failed to connect to Vault using AppRole credentials.'); return; } } try { if (!(await this.test())[0]) { this.state = 'error'; } else { this.state = 'connected'; _a = this, [({ set value(_b) { __classPrivateFieldSet(_a, _VaultProvider_tokenInfo, _b, "f"); } }).value] = await this.getTokenInfo(); this.setupTokenRefresh(); } } catch (e) { this.state = 'error'; this.logger.error('Failed credentials test on Vault connect.'); } try { await this.update(); } catch { this.logger.warn('Failed to update Vault secrets'); } } async disconnect() { if (this.refreshTimeout !== null) { clearTimeout(this.refreshTimeout); } this.refreshAbort.abort(); } setupTokenRefresh() { if (!__classPrivateFieldGet(this, _VaultProvider_tokenInfo, "f")) { return; } if (__classPrivateFieldGet(this, _VaultProvider_tokenInfo, "f").expire_time === null) { return; } if (!__classPrivateFieldGet(this, _VaultProvider_tokenInfo, "f").renewable) { return; } const expireDate = new Date(__classPrivateFieldGet(this, _VaultProvider_tokenInfo, "f").expire_time); setTimeout(this.tokenRefresh, (expireDate.valueOf() - Date.now()) / 2); } async authUsernameAndPassword(username, password) { try { const resp = await __classPrivateFieldGet(this, _VaultProvider_http, "f").request({ method: 'POST', url: `auth/userpass/login/${username}`, responseType: 'json', data: { password }, }); return resp.data.auth.client_token; } catch { return null; } } async authAppRole(roleId, secretId) { try { const resp = await __classPrivateFieldGet(this, _VaultProvider_http, "f").request({ method: 'POST', url: 'auth/approle/login', responseType: 'json', data: { role_id: roleId, secret_id: secretId }, }); return resp.data.auth.client_token; } catch (e) { return null; } } async getTokenInfo() { const resp = await __classPrivateFieldGet(this, _VaultProvider_http, "f").request({ method: 'GET', url: 'auth/token/lookup-self', responseType: 'json', validateStatus: () => true, }); if (resp.status !== 200 || !resp.data.data) { return [null, resp]; } return [resp.data.data, resp]; } async getKVSecrets(mountPath, kvVersion, path) { this.logger.debug(`Getting kv secrets from ${mountPath}${path} (version ${kvVersion})`); let listPath = mountPath; if (kvVersion === '2') { listPath += 'metadata/'; } listPath += path; let listResp; try { const shouldPreferGet = (0, external_secrets_helper_ee_1.preferGet)(); const url = `${listPath}${shouldPreferGet ? '?list=true' : ''}`; const method = shouldPreferGet ? 'GET' : 'LIST'; listResp = await __classPrivateFieldGet(this, _VaultProvider_http, "f").request({ url, method, }); } catch { return null; } const data = Object.fromEntries((await Promise.allSettled(listResp.data.data.keys.map(async (key) => { if (key.endsWith('/')) { return await this.getKVSecrets(mountPath, kvVersion, path + key); } let secretPath = mountPath; if (kvVersion === '2') { secretPath += 'data/'; } secretPath += path + key; try { const secretResp = await __classPrivateFieldGet(this, _VaultProvider_http, "f").get(secretPath); this.logger.debug(`Vault provider retrieved secrets from ${secretPath}`); return [ key, kvVersion === '2' ? secretResp.data.data.data : secretResp.data.data, ]; } catch { return null; } }))) .map((i) => (i.status === 'rejected' ? null : i.value)) .filter((v) => v !== null)); const name = path.substring(0, path.length - 1); this.logger.debug(`Vault provider retrieved kv secrets from ${name}`); return [name, data]; } async update() { const mounts = await __classPrivateFieldGet(this, _VaultProvider_http, "f").get('sys/mounts'); const kvs = Object.entries(mounts.data.data).filter(([, v]) => v.type === 'kv'); const secrets = Object.fromEntries((await Promise.all(kvs.map(async ([basePath, data]) => { const value = await this.getKVSecrets(basePath, data.options.version, ''); if (value === null) { return null; } return [basePath.substring(0, basePath.length - 1), value[1]]; }))).filter((v) => v !== null)); this.cachedSecrets = secrets; this.logger.debug('Vault provider secrets updated'); } async test() { try { const [token, tokenResp] = await this.getTokenInfo(); if (token === null) { if (tokenResp.status === 404) { return [false, 'Could not find auth path. Try adding /v1/ to the end of your base URL.']; } return [false, 'Invalid credentials']; } const resp = await __classPrivateFieldGet(this, _VaultProvider_http, "f").request({ method: 'GET', url: 'sys/mounts', responseType: 'json', validateStatus: () => true, }); if (resp.status === 403) { return [ false, "Couldn't list mounts. Please give these credentials 'read' access to sys/mounts.", ]; } else if (resp.status !== 200) { return [ false, "Couldn't list mounts but wasn't a permissions issue. Please consult your Vault admin.", ]; } return [true]; } catch (e) { if (axios_1.default.isAxiosError(e)) { if (e.code === 'ECONNREFUSED') { return [ false, 'Connection refused. Please check the host and port of the server are correct.', ]; } } return [false]; } } getSecret(name) { return this.cachedSecrets[name]; } hasSecret(name) { return name in this.cachedSecrets; } getSecretNames() { const getKeys = ([k, v]) => { if (!constants_1.EXTERNAL_SECRETS_NAME_REGEX.test(k)) { return []; } if (typeof v === 'object') { const keys = []; for (const key of Object.keys(v)) { if (!constants_1.EXTERNAL_SECRETS_NAME_REGEX.test(key)) { continue; } const value = v[key]; if (typeof value === 'object' && value !== null) { keys.push(...getKeys([key, value]).map((ok) => `${k}.${ok}`)); } else { keys.push(`${k}.${key}`); } } return keys; } return [k]; }; return Object.entries(this.cachedSecrets).flatMap(getKeys); } } exports.VaultProvider = VaultProvider; _VaultProvider_currentToken = new WeakMap(), _VaultProvider_tokenInfo = new WeakMap(), _VaultProvider_http = new WeakMap(); //# sourceMappingURL=vault.js.map