A convenient npm package to handle multi-environment NodeJS configurations with encrypted secrets.
Note: This documentation refers to version 1.x of secure-config. For the latest version please click here.
Certainly you will have faced the situation that you need a lean and secure configuration management in your NodeJS app. “Lean” in the sense that you neither want to spend much time on it nor that you want to oversize your code for that. “Secure” in the sense of being able to carry confidential information like password and credentials without exposing them.
To meet that requirements we created the npm package secure-config. It is an easy to use and very basic configuration-management package. According to KISS principle it offers nearly no options and is focused on its main purpose – easy and secure provision of configurations in every environment. Just follow some basic guidelines like a common naming convention and you are in the game!
Table of Contents
Benefits
Usage
Suppose you have the following JSON configuration file config.json
with secret information about your database connection…
{
"database": {
"host": "127.0.0.1",
"user": "MySecretDbUser",
"pass": "MySecretDbPass"
}
}
Step 1 – Install secure-config-tool [optional]
[tsmx@localhost ] npm i -g @tsmx/secure-config-tool
Note: In the explanation I will use the provided tool secure-config-tool. You can do all that without the tool as it is NodeJS crypto standard, but it’s a lot more easier so…
Step 2 – Get a secret key and export it.
[tsmx@localhost ]$ secure-config-tool genkey
df9ed9002b...
[tsmx@localhost ]$ export CONFIG_ENCRYPTION_KEY=df9ed9002b...
Step 3 – Encrypt your configuration JSON values and generate a new, secure configuration file.
[tsmx@localhost ]$ secure-config-tool create-file config.json > conf/config.json
[tsmx@localhost ]$ cat conf/config.json
{
"database": {
"host": "127.0.0.1",
"user": "ENCRYPTED|50ceed2f97223100fbdf842ecbd4541f|df9ed9002bfc956eb14b1d2f8d960a11",
"pass": "ENCRYPTED|8fbf6ded36bcb15bd4734b3dc78f2890|7463b2ea8ed2c8d71272ac2e41761a35"
}
}
The generated file should be in the conf/
subfolder of your app. For details see naming conventions.
Step 4 – Use your configuration in the code
const conf = require('@tsmx/secure-config');
function MyFunc() {
let dbHost = conf.database.host; // = '127.0.0.1'
let dbUser = conf.database.user; // = 'MySecretDbUser'
let dbPass = conf.database.pass; // = 'MySecretDbPass'
//...
}
Step 5 – Run your app using the new encrypted configuration.
[tsmx@localhost ]$ export CONFIG_ENCRYPTION_KEY=df9ed9002b...
[tsmx@localhost ]$ node app.js
File name and directory conventions
You can have multiple configuration files for different environments or stages. They are distinguished by the environment variable NODE_ENV.
The basic configuration file name is config.json
if this environment variable is not present. If it is present, a configuration file with the name config-[NODE_ENV].json
is used. An exception will be thrown if no configuration file is found.
All configuration files must be located in a conf/
directory of the current running app, meaning a direct subdirectory of the current working directory (CWD/conf/
).
Example project structure
Development stage
Production stage
Test stage, e.g. Jest
path-to-your-app/
├── conf/
│ ├── config.json
│ ├── config-production.json
│ └── config-test.json
├── app.js
└── package.json
Injecting the decryption key
The key for decrypting the encrypted values is derived from an environment variable named CONFIG_ENCRYPTION_KEY
. You can set this variable whatever way is most suitable. Here are some examples for common use-cases.
The key length must be 32 bytes! The value set in CONFIG_ENCRYPTION_KEY
has to be:
Otherwise an error will be thrown.
Examples of valid key strings:
Different keys for each configuration environment are strongly recommended.
Generating encrypted entries
Option 1: secure-config-tool
For better convenience I provided a very basic secure-config-tool to easily generate the encrypted entries, process entire JSON files and getting keys.
Option 2: NodeJS crypto functions
You can simply use crypto
functions from NodeJS with the following snippet to create the encrypted entries for your configuration files:
const crypto = require('crypto');
const algorithm = 'aes-256-cbc';
function encrypt(value) {
let iv = crypto.randomBytes(16);
let key = Buffer.from('YOUR_KEY_HERE');
let cipher = crypto.createCipheriv(algorithm, key, iv);
let encrypted = cipher.update(value);
encrypted = Buffer.concat([encrypted, cipher.final()]);
return 'ENCRYPTED|' + iv.toString('hex') + '|' + encrypted.toString('hex');
}
The generated encrypted entry must always have the form: ENCRYPTED | IV | DATA
.
Part | Description |
ENCRYPTED | The prefix ENCRYPTED used to identify configuration values that must be decrypted. |
IV | The ciphers initialization vector (IV ) that was used for encryption. Hexadecimal value. |
DATA | The AES-256-CBC encrypted value. Hexadecimal value. |
Under the hood
This is how secure-config works: when importing the package via require('@tsmx/secure-config')
it…
- Retrieves the decryption key out of the environment variable
CONFIG_ENCRYPTION _KEY
- Loads the applicable JSON configuration file.
- Recursively iterates the loaded JSON and decrypts all encrypted entries that were found.
- Iteration is done with @tsmx/json-traverse
- Decryption is done with @tsmx/string-crypto
- Returns a simple JSON object with all secrets decrypted.
Important notes / good to know:
Sample project
To get familiar with the use of secure-config I provided a secure-config-test project on Github. For trying out with Docker and Kubernetes a public docker image is also available on Docker-Hub.
Test
The package contains a set of unit tests with a nearly 100% coverage of the code. To run them, install or clone the package and run the tests via npm:
npm run test
To output the code coverage run:
npm run test-coverage
Also check out the current coverage stats at Coveralls.