ianwremmel
1/1/2018 - 7:26 PM

Pull secrets from AWS

Pull secrets from AWS

'use strict';

// Run with e.g. `node env.js npm test`

/* eslint-disable require-jsdoc */
/* eslint-disable no-sync */
/* eslint-disable no-console */
/* eslint-disable no-process-exit */

const {spawn} = require('child_process');
const fs = require('fs');

const _ = require('lodash');
const AWS = require('aws-sdk');
const debug = require('debug')('displace:env');
const yaml = require('js-yaml');

AWS.config.update({region: 'us-east-1'});

const ssm = new AWS.SSM();

const ENV = process.env.NODE_ENV === 'prod' || process.env.NODE_ENV === 'production' ? 'production' : 'dev';

const raw = yaml.safeLoad(fs.readFileSync('./secrets.yml'));
const secrets = raw[ENV];
if (!secrets) {
  throw new Error(`No keys found for environement "${ENV}"`);
}

function getChunk(names) {
  return new Promise((resolve, reject) => {
    debug(`request ${names.join(', ')} from AWS SSM`);
    ssm.getParameters({Names: names}, (err, data) => {
      if (err) {
        reject(err);
        return;
      }
      debug(`received ${data.Parameters.length} params from AWS SSM`);
      resolve(data);
    });
  });
}

function getParameters(names) {
  const chunkSize = 10;
  debug(`Requesting ${names.length} parameters from AWS SSM in ${chunkSize} item chunks`);

  return Promise.all(_.chunk(names, chunkSize)
    .map(getChunk))
    .then((chunks) => _.mergeWith(...chunks, (obj, src) => _.isArray(obj) && obj.concat(src) || undefined))
    .then((data) => {
      for (const invalid of data.InvalidParameters) {
        console.warn(`Could not retrieve parameter named "${invalid}"`);
      }
      debug(`resolving with ${data.Parameters.length} parameters from AWS SSM`);
      return data.Parameters;
    });
}

Promise.resolve()
  .then(() => getParameters(Object.values(secrets)))
  .then((results) => results.reduce((acc, result) => {
    acc[result.Name] = result.Value;
    return acc;
  }, {}))
  .then((results) => {
    const env = {};
    for (const [
      envVar,
      keyPath
    ] of Object.entries(secrets)) {
      env[envVar] = results[keyPath];
    }
    return env;
  })
  .then((results) => new Promise((resolve) => {
    const [
      cmd,
      ...args
    ] = process.argv.slice(2);

    const child = spawn(cmd, args, {
      env: Object.assign({}, results, process.env),
      stdio: 'inherit'
    });

    child.on('close', (code) => {
      if (code) {
        debug(`Child process exited with code ${code}`);
        process.exit(code);
      }

      resolve();
    });
  }))
  .catch((err) => {
    console.error(err);
    process.exit(64);
  });

production:
  AUTH0_AUDIENCE: /displace/production/auth0_audience
  AUTH0_CONNECTION: /displace/production/auth0_connection
  AUTH0_BACKEND_CLIENT_ID: /displace/production/auth0_backend_client_id
  AUTH0_BACKEND_CLIENT_SECRET: /displace/production/auth0_backend_client_secret
  AUTH0_DOMAIN: /displace/production/auth0_domain
  AUTH0_FRONTEND_CLIENT_ID: /displace/production/auth0_frontend_client_id
  AUTH0_TEST_SUITE_CLIENT_ID: /displace/production/auth0_test_suite_client_id
  AUTH0_TEST_SUITE_CLIENT_SECRET: /displace/production/auth0_test_suite_client_secret
  GITHUB_CLIENT_ID: /displace/production/github_client_id
  GITHUB_CLIENT_SECRET: /displace/production/github_client_secret
  GOOGLE_ANALYTICS_ID: /displace/production/google_analytics_id
  SENTRY_AUTH_TOKEN: /displace/production/sentry_auth_token
  SENTRY_DSN: /displace/production/sentry_dsn
  SENTRY_DSN_PUBLIC: /displace/production/sentry_dsn_public
  SENTRY_ORGANIZATION: /displace/production/sentry_organization
  SENTRY_PROJECT: /displace/production/sentry_project
dev:
  AUTH0_AUDIENCE: /displace/dev/auth0_audience
  AUTH0_CONNECTION: /displace/dev/auth0_connection
  AUTH0_BACKEND_CLIENT_ID: /displace/dev/auth0_backend_client_id
  AUTH0_BACKEND_CLIENT_SECRET: /displace/dev/auth0_backend_client_secret
  AUTH0_DOMAIN: /displace/dev/auth0_domain
  AUTH0_FRONTEND_CLIENT_ID: /displace/dev/auth0_frontend_client_id
  AUTH0_TEST_SUITE_CLIENT_ID: /displace/dev/auth0_test_suite_client_id
  AUTH0_TEST_SUITE_CLIENT_SECRET: /displace/dev/auth0_test_suite_client_secret
  GITHUB_CLIENT_ID: /displace/dev/github_client_id
  GITHUB_CLIENT_SECRET: /displace/dev/github_client_secret
  GOOGLE_ANALYTICS_ID: /displace/dev/google_analytics_id
  SENTRY_AUTH_TOKEN: /displace/dev/sentry_auth_token
  SENTRY_DSN: /displace/dev/sentry_dsn
  SENTRY_DSN_PUBLIC: /displace/dev/sentry_dsn_public
  SENTRY_ORGANIZATION: /displace/dev/sentry_organization
  SENTRY_PROJECT: /displace/dev/sentry_project
  SAUCE_ACCESS_KEY: /displace/dev/sauce_access_key
  SAUCE_USERNAME: /displace/dev/sauce_username