diff --git a/docker/images/n8n/README.md b/docker/images/n8n/README.md index 84fee8a6f9..2f1eec51d3 100644 --- a/docker/images/n8n/README.md +++ b/docker/images/n8n/README.md @@ -73,6 +73,11 @@ By default n8n uses SQLite to save credentials, past executions and workflows. n8n however also supports MongoDB and PostgresDB. To use them simply a few environment variables have to be set. +To avoid passing sensitive information via environment variables "_FILE" may be +appended to the database environment variables (for example "DB_POSTGRESDB_PASSWORD_FILE"). +It will then load the data from a file with the given name. That makes it possible to +load data easily from Docker- and Kubernetes-Secrets. + It is important to still persist the data in the `/root/.n8` folder. The reason is that it contains n8n user data. That is the name of the webhook (in case) the n8n tunnel gets used and even more important the encryption key diff --git a/packages/cli/README.md b/packages/cli/README.md index 430ceecf54..1b7039d601 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -74,6 +74,11 @@ By default n8n uses SQLite to save credentials, past executions and workflows. n8n however also supports MongoDB and PostgresDB. To use them simply a few environment variables have to be set. +To avoid passing sensitive information via environment variables "_FILE" may be +appended to the database environment variables (for example "DB_POSTGRESDB_PASSWORD_FILE"). +It will then load the data from a file with the given name. That makes it possible to +load data easily from Docker- and Kubernetes-Secrets. + #### Start with MongoDB as Database diff --git a/packages/cli/src/Db.ts b/packages/cli/src/Db.ts index a51a92b675..73462f8c4b 100644 --- a/packages/cli/src/Db.ts +++ b/packages/cli/src/Db.ts @@ -1,4 +1,5 @@ import { + GenericHelpers, IDatabaseCollections, DatabaseType, } from './'; @@ -31,7 +32,7 @@ export let collections: IDatabaseCollections = { import * as path from 'path'; export async function init(): Promise { - const dbType = config.get('database.type') as DatabaseType; + const dbType = await GenericHelpers.getConfigValue('database.type') as DatabaseType; const n8nFolder = UserSettings.getUserN8nFolderPath(); let entities; @@ -41,18 +42,18 @@ export async function init(): Promise { entities = MongoDb; connectionOptions = { type: 'mongodb', - url: config.get('database.mongodb.connectionUrl') as string, + url: await GenericHelpers.getConfigValue('database.mongodb.connectionUrl') as string, useNewUrlParser: true, }; } else if (dbType === 'postgresdb') { entities = PostgresDb; connectionOptions = { type: 'postgres', - database: config.get('database.postgresdb.database'), - host: config.get('database.postgresdb.host'), - password: config.get('database.postgresdb.password'), - port: config.get('database.postgresdb.port'), - username: config.get('database.postgresdb.user'), + database: await GenericHelpers.getConfigValue('database.postgresdb.database') as string, + host: await GenericHelpers.getConfigValue('database.postgresdb.host') as string, + password: await GenericHelpers.getConfigValue('database.postgresdb.password') as string, + port: await GenericHelpers.getConfigValue('database.postgresdb.port') as number, + username: await GenericHelpers.getConfigValue('database.postgresdb.user') as string, }; } else if (dbType === 'sqlite') { entities = SQLite; diff --git a/packages/cli/src/GenericHelpers.ts b/packages/cli/src/GenericHelpers.ts index ff32510a1e..53cbcefc6d 100644 --- a/packages/cli/src/GenericHelpers.ts +++ b/packages/cli/src/GenericHelpers.ts @@ -1,6 +1,12 @@ import * as config from '../config'; import * as express from 'express'; +import { + readFile as fsReadFile, +} from 'fs'; +import { promisify } from "util"; +import { IDataObject } from 'n8n-workflow'; +const fsReadFileAsync = promisify(fsReadFile); /** * Displays a message to the user @@ -46,3 +52,54 @@ export function getBaseUrl(): string { export function getSessionId(req: express.Request): string | undefined { return req.headers.sessionid as string | undefined; } + + +/** + * Gets value from config with support for "_FILE" environment variables + * + * @export + * @param {string} configKey The key of the config data to get + * @returns {(Promise)} + */ +export async function getConfigValue(configKey: string): Promise { + const configKeyParts = configKey.split('.'); + + // Get the environment variable + const configSchema = config.getSchema(); + let currentSchema = configSchema.properties as IDataObject; + for (const key of configKeyParts) { + if (currentSchema[key] === undefined) { + throw new Error(`Key "${key}" of ConfigKey "${configKey}" does not exist`); + } else if ((currentSchema[key]! as IDataObject).properties === undefined) { + currentSchema = currentSchema[key] as IDataObject; + } else { + currentSchema = (currentSchema[key] as IDataObject).properties as IDataObject; + } + } + + // Check if environment variable is defined for config key + if (currentSchema.env === undefined) { + // No environment variable defined, so return value from config + return config.get(configKey); + } + + // Check if special file enviroment variable exists + const fileEnvironmentVariable = process.env[currentSchema.env + '_FILE']; + if (fileEnvironmentVariable === undefined) { + // Does not exist, so return value from config + return config.get(configKey); + } + + let data; + try { + data = await fsReadFileAsync(fileEnvironmentVariable, 'utf8') as string; + } catch (error) { + if (error.code === 'ENOENT') { + throw new Error(`The file "${fileEnvironmentVariable}" could not be found.`); + } + + throw error; + } + + return data; +}