Environment Variable files

How do we get key settings into the code when some of the information in those settings needs to be kept private?

Config Modules

The Voluntarily.nz platform provides key values to the server web application and browser components through two config files. /config/serverConfig.js and /config/clientConfig.js

These modules return basically the same data structure except that the server version can include secrets used to access third party services such as AWS SMTP server. The client side version should not include any secrets as these would be able to be extracted from the browser.

The config file includes some hard coded settings and some variable settings that are read from the process environment - often with a fallback default value

e.g

appName: process.env.APPNAME || 'Voluntarily NZ',

databaseUrl: process.env.MONGODB_URI || `mongodb://localhost/${databaseName}`,

The databaseURL is a good example of a value that falls back to the localhost mongo database if no remote database URI is provided.

Most modules, therefore, should import the appropriate config module and use the structure values provided rather than accessing the process.env directly.

example usage:

const { config } = require('../config/serverConfig')

mongoose.connect(config.databaseUrl, etc.

Setting Environment Variables

The variables used by the platform may be placed in the run time environment prior to running the application in the usual way.

  • including them on the command line

  • exporting them into the shell environment

Example command-line - sets NODE_ENV to test and PORT to 8080

npx cross-env NODE_ENV=test PORT=8080 ENV_ENVIRONMENT='development' ENV_SECRET='mwmBqNcTWiKxDHygMiKhwyffaKQCVoRQ' nyc --reporter=lcov --reporter=text ava",

Example export - use vly-sparedb instead of the default vly2 database on localhost

export DBNAME=vly-sparedb npm run dev

Using Environment files

While setting command-line environment variables is convenient for a one off line it will get tedious if there are a range of settings to configure. We can list all the values in a text file called <something>.env and then just pass in a single value indicating the environment ENV_ENVIRONMENT=development or ENV_ENVIRONMENT=alpha etc.

This uses the library dotenv to read the values into the environment.

e.g. development.env

# Application APPNAME=Voluntarily NZ PORT=3122 APP_URL=http://localhost:${PORT} # Database DBNAME=vly2 MONGODB_URI=mongodb://localhost/${DBNAME} # Authentication AUTH0_CLIENT_ID=S4yd4VgZ92NIjhwO3vt4h0Gifb9mXv1k AUTH0_CLIENT_DOMAIN=voluntarily.au.auth0.com etc.

Using encrypted environment files

Using env files like this allows us to have one place where we store the settings for the development, alpha, beta, and live deployments and can include production secrets such as the production MONGODB_URI

If we checked these into the code base - which is public then we would be making our secrets public, if we don’t then we have to maintain the values somewhere else - e.g in a separate application with separate access rules and would need an operating process to move the secrets into the run time environment on deployment. - we used to do this which meant having to add settings into the AWS service running the docker containers running the application - which is complicated.

So instead we encrypt the .env files with a symmetric key - a single string that is used to encode and decode the .env files into xxx.env.enc files.

To update the settings you decrypt the .enc files using the appropriate key. The key for development.env.enc is available to everyone - its in the commands in package.json.

ENV_SECRET='mwmBqNcTWiKxDHygMiKhwyffaKQCVoRQ'

to decode the file run the package nodecipher.

or if you prefer to pass in the password on the command line

see https://www.npmjs.com/package/node-cipher for more details on nodecipher

Of course, the passwords for the alpha, beta, gamma, and live env files are not public and are held in the voluntarily secure password management. This means that if you want to add a new secret to the env files you need to get a core administrator to do it for you prior to deployment.

Once you have updated the development.env file you then re-encrypt it with

and commit the updated development.env.enc.

you should not commit the in the clear development.env and .gitignore is set to avoid this.

The module config/importEncryptedEnv.js handles reading the .enc file, decoding it to a .env file reading that into the process environment and deleting the .env file afterwards.

 

Conclusion

This all may seem a little roundabout - if we are using encrypted config files then why use env vars? why not just use json files for example?

  • Env vars give us the ability to locally override the settings in the file from the command line.

  • Env vars are available to any libraries that we include that don’t know about our config file.

 

TLDR;

  1. The system settings are in development.env.enc

  2. decode using npx nodecipher dec development.env.enc development.env

  3. encode using npx nodecipher enc development.env development.env.enc

  4. read the process.env.VALUE into config for server or client for general use.

  5. any new values need to be added to alpha.env and live.env by an admin