From 135bfc44bfef027db713544e5bedf47744de9723 Mon Sep 17 00:00:00 2001 From: Paul Beaudoin Date: Wed, 24 May 2023 21:50:04 -0400 Subject: [PATCH] Add docker-compose, encrypted config --- .env-docker | 24 +++++++++++++ README.md | 21 +++--------- app.js | 77 +++++++++++++++++++++++------------------- docker-compose.yml | 12 +++++++ lib/load-config.js | 26 ++++++++++++++ lib/preflight_check.js | 20 +++++++---- 6 files changed, 121 insertions(+), 59 deletions(-) create mode 100644 .env-docker create mode 100644 docker-compose.yml create mode 100644 lib/load-config.js diff --git a/.env-docker b/.env-docker new file mode 100644 index 00000000..3e9570d3 --- /dev/null +++ b/.env-docker @@ -0,0 +1,24 @@ +# Greg built self-hosted domain: +ENCRYPTED_ELASTICSEARCH_URI=AQECAHh7ea2tyZ6phZgT4B9BDKwguhlFtRC6hgt+7HbmeFsrsgAAAJgwgZUGCSqGSIb3DQEHBqCBhzCBhAIBADB/BgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDCxC3qeBv9AR85VT0AIBEIBSIz3EXDTPVzM43QJ8DfS4/cw3Mq2Sg9uLltQedZosCcSgmk33ZswF7uUQt7WWN/OijtCrgspWZEqPtug0gwG25u/zxi0ONQm3cGy3NC/tVo8EbA== +ENCRYPTED_RESOURCES_INDEX=AQECAHh7ea2tyZ6phZgT4B9BDKwguhlFtRC6hgt+7HbmeFsrsgAAAHIwcAYJKoZIhvcNAQcGoGMwYQIBADBcBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDHTQSgIcKoLOPFzVvgIBEIAv4+WtxKMeahIFRtdB64DfQAdAtN7DyujwxRBnrhdAqX5RBMXqGpfvUheXMoWlVN4= + +ENCRYPTED_SCSB_URL=AQECAHh7ea2tyZ6phZgT4B9BDKwguhlFtRC6hgt+7HbmeFsrsgAAAH8wfQYJKoZIhvcNAQcGoHAwbgIBADBpBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDGdkVdF5qRl8uYWPoQIBEIA87iTS5cOoPOH3LJA7ggi5Euz6hjEAXYUfWf2M5kkb+kpW0s3sGCbiY3j7OZKi631Wy3eSQ01ZQ7vtflF+ +ENCRYPTED_SCSB_API_KEY=AQECAHh7ea2tyZ6phZgT4B9BDKwguhlFtRC6hgt+7HbmeFsrsgAAAGMwYQYJKoZIhvcNAQcGoFQwUgIBADBNBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDNw8KXkyN8HvtjAX0gIBEIAgX+XG2fxTj6kSchrd/dfHB05KU5pkT0LtPxUTuNCXoLc= + +NYPL_API_BASE_URL=https://platform.nypl.org/api/v0.1/ +NYPL_OAUTH_URL=https://isso.nypl.org/ +ENCRYPTED_NYPL_OAUTH_ID=AQECAHh7ea2tyZ6phZgT4B9BDKwguhlFtRC6hgt+7HbmeFsrsgAAAGswaQYJKoZIhvcNAQcGoFwwWgIBADBVBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDMLKVUQA58B6vprNcAIBEIAoaz0lI9EL2M9NyTuEwT8JDmPBt6aXfMiFs027DEuwsCN0wS0qWeFL1g== +ENCRYPTED_NYPL_OAUTH_SECRET=AQECAHh7ea2tyZ6phZgT4B9BDKwguhlFtRC6hgt+7HbmeFsrsgAAAIcwgYQGCSqGSIb3DQEHBqB3MHUCAQAwcAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAyWz91LOP2YP5fg0q0CARCAQ9inO9SV1M8R0Pkkx84r7UdwlU1FxfXvIjk/z6Qs81KBAVELhby2iD5LawQyDrR9tjhuMbotS6QnydwwMR/p8+qJXHI= + +NYPL_CORE_VERSION=v2.0 +ROMCOM_MAX_XA_BNUM=b0 + +LOG_LEVEL=debug +FEATURES=on-site-edd + +SEARCH_ITEMS_SIZE=100 +PORT=8082 + +HIDE_NYPL_SOURCE= + +BIB_HAS_VOLUMES_THRESHOLD=0.01 diff --git a/README.md b/README.md index 49b35023..a44f9090 100644 --- a/README.md +++ b/README.md @@ -6,27 +6,14 @@ This is the API providing most of bibliographic data to the [NYPL Research Catal ## Installing & Running Locally -Use [nvm](https://github.com/creationix/nvm) to set your Node version: - -``` -nvm use +Start the container with AWS creds so that the app can decrypt config from `.env-docker`: ``` - -Install dependencies: - + AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=... docker-compose up ``` -npm i -``` - -Create a `.env` based on `.env.example`. Fill it with values from the appropriate `config/[environment].env` file (`qa.env` is probably sensible). Note - if using values from `config/[environment].env` - you must decrypt the following keys using [`aws kms decrypt`](https://github.com/NYPL/engineering-general/blob/main/security/secrets.md#encryptingdecrypting) (or [kms-util](https://github.com/NYPL-discovery/kms-util)) (i.e. all values in `.env` must be decrypted): - * `SCSB_URL` - * `SCSCB_API_KEY` - * `NYPL_OAUTH_SECRET` - -Now start the app: +After making changes, rebuild the image: ``` -npm start +docker-compose build ``` Note that when developing locally, you may need to [add your IP to the access control policy of the relevant ES domain](https://github.com/NYPL/aws/blob/b5c0af0ec8357af9a645d8b47a5dbb0090966071/common/elasticsearch.md#2-make-the-domain-public-restrict-by-ip). diff --git a/app.js b/app.js index f004ca65..57f61456 100644 --- a/app.js +++ b/app.js @@ -1,55 +1,62 @@ const config = require('config') +const express = require('express') + +const esClient = require('./lib/es-client') +const { loadConfig } = require('./lib/load-config') +const { preflightCheck } = require('./lib/preflight_check') + const swaggerDocs = require('./swagger.v1.1.x.json') const pjson = require('./package.json') -require('dotenv').config() -// Load logger after running above to ensure we respect LOG_LEVEL if set -const logger = require('./lib/logger') +const app = express() -require('./lib/preflight_check') +const run = async () => { + await loadConfig() -const express = require('express') -const esClient = require('./lib/es-client') + preflightCheck() -const app = express() + // Load logger after running above to ensure we respect LOG_LEVEL if set + app.logger = require('./lib/logger') -app.logger = logger -app.thesaurus = config.thesaurus + app.thesaurus = config.thesaurus -require('./lib/resources')(app) + require('./lib/resources')(app) -// routes -require('./routes/resources')(app) -require('./routes/misc')(app) + // routes + require('./routes/resources')(app) + require('./routes/misc')(app) -app.esClient = esClient + app.esClient = esClient -app.all('*', function (req, res, next) { - res.header('Access-Control-Allow-Origin', '*') - res.header('Access-Control-Allow-Methods', 'GET, OPTIONS') - res.header('Access-Control-Allow-Headers', 'Content-Type') - next() -}) + app.all('*', function (req, res, next) { + res.header('Access-Control-Allow-Origin', '*') + res.header('Access-Control-Allow-Methods', 'GET, OPTIONS') + res.header('Access-Control-Allow-Headers', 'Content-Type') + next() + }) -app.get('/', function (req, res) { - res.send(pjson.version) -}) + app.get('/', function (req, res) { + res.send(pjson.version) + }) -// Just testing route -app.get('/api/v0.1/discovery', function (req, res) { - res.send(pjson.version) -}) + // Just testing route + app.get('/api/v0.1/discovery', function (req, res) { + res.send(pjson.version) + }) -app.get('/api/v0.1/discovery/swagger', function (req, res) { - res.send(swaggerDocs) -}) + app.get('/api/v0.1/discovery/swagger', function (req, res) { + res.send(swaggerDocs) + }) -const port = process.env.PORT || config['port'] + const port = process.env.PORT || config['port'] -require('./lib/globals')(app).then((app) => { - app.listen(port, function () { - app.logger.info('Server started on port ' + port) + require('./lib/globals')(app).then((app) => { + app.listen(port, function () { + app.logger.info('Server started on port ' + port) + }) }) -}) +} + +run() module.exports = app diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..f5c6732e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,12 @@ +version: '3' +services: + app: + build: + context: . + volumes: + - ./:/app + ports: + - '8082:8082' + environment: + - AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID + - AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY diff --git a/lib/load-config.js b/lib/load-config.js new file mode 100644 index 00000000..ec6b1570 --- /dev/null +++ b/lib/load-config.js @@ -0,0 +1,26 @@ +const { decrypt } = require('./kms-helper') + +const loadConfig = async () => { + require('dotenv').config({ path: '.env-docker' }) + + // Identify env vars that begin with "ENCRYPTED_" + const encryptedKeys = Object.keys(process.env) + .filter((key) => /^ENCRYPTED_/.test(key)) + + const logger = require('./logger') + // Decrypt all encrypted env vars, setting a new decrypted env var without + // the ENCRYPTED_ prefix: + return Promise.all( + encryptedKeys + .map(async (key) => { + const keyWithoutPrefix = key.replace(/^ENCRYPTED_/, '') + const decrypted = await decrypt(process.env[key]) + logger.debug('Load-config: Decrypted ' + key) + process.env[keyWithoutPrefix] = decrypted + }) + ) +} + +module.exports = { + loadConfig +} diff --git a/lib/preflight_check.js b/lib/preflight_check.js index 77a15c05..93df3b96 100644 --- a/lib/preflight_check.js +++ b/lib/preflight_check.js @@ -11,12 +11,18 @@ const requiredEnvVars = [ 'NYPL_OAUTH_SECRET', ] -const undefinedVars = requiredEnvVars - .filter((varName) => !process.env[varName]) +const preflightCheck = () => { + const undefinedVars = requiredEnvVars + .filter((varName) => !process.env[varName]) -if (undefinedVars.length > 0) { - let message = `The following ENV_VAR(S) must be defined: ${undefinedVars.join(', ')}.` - console.log(message) - logger.error(message) - throw new Error(message) + if (undefinedVars.length > 0) { + let message = `The following ENV_VAR(S) must be defined: ${undefinedVars.join(', ')}.` + console.log(message) + logger.error(message) + throw new Error(message) + } +} + +module.exports = { + preflightCheck }