From 15b154fc8d6453c8da5c438e574d9298974f5d81 Mon Sep 17 00:00:00 2001 From: William Mantly Date: Sat, 1 Aug 2026 12:29:22 -0400 Subject: [PATCH] v1.13.0: load secrets from OpenBao at boot via @simpleworkjs/bao-conf bin/www now defers require('../app') until bao-conf.init({ path: 'proxy' }) resolves, so models + createOidcClient see the OpenBao-merged config (the OIDC clientSecret is captured at require time). Authenticates to OpenBao with a scoped VAULT_TOKEN (policy proxy), never the root token; fail-soft to CONF_SECRETS if OpenBao is unreachable. config/proxy-secrets.js becomes an operator-edit seed artifact (OpenBao authoritative). README gains a Secrets section. Co-Authored-By: Claude --- CHANGELOG.md | 17 +++++ README.md | 17 +++++ nodejs/bin/www | 143 +++++++++++++++++++++------------------ nodejs/package-lock.json | 13 ++++ nodejs/package.json | 3 +- 5 files changed, 127 insertions(+), 66 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a2cb3a..37ef3fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,23 @@ correspond to git tags (`vX.Y.Z`) and `nodejs/package.json`'s `version`. ## [Unreleased] +## [1.13.0] - 2026-08-01 + +### Changed +- **Secrets now load from OpenBao at boot** via + [@simpleworkjs/bao-conf](https://simpleworkjs.github.io/bao-conf/), which + deep-merges `secret/proxy/conf` over the file-loaded config. The proxy + authenticates to OpenBao with a scoped `VAULT_TOKEN` (policy `proxy` — + read-only on its own path), never the root token. Because the OIDC + `clientSecret` is captured at require time inside `createOidcClient` (during + `require('../models')`, which `require('../app')` triggers transitively), + `bin/www` now defers `require('../app')` until after `bao-conf.init()` + resolves. Fail-soft: if OpenBao is unreachable, boot continues from + `CONF_SECRETS`. The `config/proxy-secrets.js` file is now an operator-edit + seed artifact (gitignored); OpenBao is authoritative. See theta-env's + [Secrets docs](https://theta42.github.io/theta-env/secrets/). +- Bumped package version to track the release tag. + ## [1.12.1] - 2026-08-01 ### Changed diff --git a/README.md b/README.md index 7472826..887b09a 100755 --- a/README.md +++ b/README.md @@ -162,6 +162,23 @@ docker compose exec proxy tail -f /var/log/nginx/error.log docker compose logs --tail=200 --since=10m proxy ``` +## Secrets + +Secrets are loaded from **OpenBao** at boot via +[@simpleworkjs/bao-conf](https://simpleworkjs.github.io/bao-conf/), which +deep-merges `secret/proxy/conf` over the file-loaded config. The proxy's OIDC +`clientSecret` is captured at require time (inside `createOidcClient` during +`require('../models')`), so `bin/www` runs `bao-conf.init()` **before** +`require('../app')` (which transitively loads models). Fail-soft: if OpenBao is +unreachable, boot continues from `CONF_SECRETS`. The proxy authenticates to +OpenBao with the scoped `VAULT_TOKEN` (env, policy `proxy` — read only +`secret/proxy/conf`), never the root token. + +The `config/proxy-secrets.js` file is an operator-edit seed artifact +(gitignored); the bootstrap writes the generated OAuth client creds into +OpenBao, which is authoritative. For the full architecture see theta-env's +**[Secrets docs](https://theta42.github.io/theta-env/secrets/)**. + ## Manual Installation For manual installation or other distributions, see the detailed steps below. diff --git a/nodejs/bin/www b/nodejs/bin/www index c94e422..b328101 100755 --- a/nodejs/bin/www +++ b/nodejs/bin/www @@ -4,34 +4,91 @@ * Module dependencies. */ -var app = require('../app'); -var debug = require('debug')('proxy-api:server'); -var http = require('http'); const conf = require('@simpleworkjs/conf'); +const debug = require('debug')('proxy-api:server'); +const http = require('http'); -/** - * Get port from environment and store in Express. - */ +// @simpleworkjs/conf loads ./config/proxy-secrets.js synchronously, then +// @simpleworkjs/bao-conf deep-merges secret/proxy/conf from OpenBao over it. +// The OIDC clientSecret is captured at require time inside models (via +// createOidcClient), and require('../app') transitively loads models, so the +// OpenBao fetch MUST resolve before require('../app'). Fail-soft: if OpenBao +// is unreachable, init() leaves conf as the file-loaded fallback and boot +// continues from ./config/proxy-secrets.js. +require('@simpleworkjs/bao-conf').init({ path: 'proxy', conf }).then(() => { + var app = require('../app'); // models + createOidcClient now see merged conf -var port = normalizePort(process.env.NODE_PORT || conf.port || '3000'); -app.set('port', port); + /** + * Get port from environment and store in Express. + */ -/** - * Create HTTP server. - */ + var port = normalizePort(process.env.NODE_PORT || conf.port || '3000'); + app.set('port', port); -var server = http.createServer(app); + /** + * Create HTTP server. + */ -var io = require('socket.io')(server); -app.io = io; + var server = http.createServer(app); -/** - * Listen on provided port, on all network interfaces. - */ + var io = require('socket.io')(server); + app.io = io; -server.listen(port); -server.on('error', onError); -server.on('listening', onListening); + /** + * Listen on provided port, on all network interfaces. + */ + + server.listen(port); + server.on('error', onError); + server.on('listening', onListening); + + /** + * Event listener for HTTP server "error" event. + */ + + function onError(error) { + if (error.syscall !== 'listen') { + throw error; + } + + var bind = typeof port === 'string' + ? 'Pipe ' + port + : 'Port ' + port; + + // handle specific listen errors with friendly messages + switch (error.code) { + case 'EACCES': + console.error(bind + ' requires elevated privileges'); + process.exit(1); + break; + case 'EADDRINUSE': + console.error(bind + ' is already in use'); + process.exit(1); + break; + default: + throw error; + } + } + + /** + * Event listener for HTTP server "listening" event. + */ + + function onListening() { + var addr = server.address(); + var bind = typeof addr === 'string' + ? 'pipe ' + addr + : 'port ' + addr.port; + console.log('Listening on ' + bind); + + for(let listener of app.onListen){ + listener() + } + } +}).catch(err => { + console.error('boot failed:', err); + process.exit(1); +}); /** * Normalize a port into a number, string, or false. @@ -51,48 +108,4 @@ function normalizePort(val) { } return false; -} - -/** - * Event listener for HTTP server "error" event. - */ - -function onError(error) { - if (error.syscall !== 'listen') { - throw error; - } - - var bind = typeof port === 'string' - ? 'Pipe ' + port - : 'Port ' + port; - - // handle specific listen errors with friendly messages - switch (error.code) { - case 'EACCES': - console.error(bind + ' requires elevated privileges'); - process.exit(1); - break; - case 'EADDRINUSE': - console.error(bind + ' is already in use'); - process.exit(1); - break; - default: - throw error; - } -} - -/** - * Event listener for HTTP server "listening" event. - */ - -function onListening() { - var addr = server.address(); - var bind = typeof addr === 'string' - ? 'pipe ' + addr - : 'port ' + addr.port; - console.log('Listening on ' + bind); - - for(let listener of app.onListen){ - listener() - } -} +} \ No newline at end of file diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index f7327fe..0cd4e16 100644 --- a/nodejs/package-lock.json +++ b/nodejs/package-lock.json @@ -12,6 +12,7 @@ "@fortawesome/fontawesome-free": "^7.3.0", "@popperjs/core": "^2.11.8", "@simpleworkjs/app-stack": "^1.0.0", + "@simpleworkjs/bao-conf": "^1.0.0", "@simpleworkjs/conf": "^1.2.0", "@simpleworkjs/frontend": "^0.2.7", "@simpleworkjs/ldap": "^1.0.0", @@ -297,6 +298,18 @@ "node": ">=18.0.0" } }, + "node_modules/@simpleworkjs/bao-conf": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@simpleworkjs/bao-conf/-/bao-conf-1.0.0.tgz", + "integrity": "sha512-HxB2ohFuDKbwTfNh5dXCot0dd6qoP+3Ebz1xKH0eOhKNVNMjHU1p7XZv2VTe+VnHn+DV22Eh8lAgWxnX/pkXUw==", + "license": "MIT", + "dependencies": { + "extend": "^3.0.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@simpleworkjs/conf": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@simpleworkjs/conf/-/conf-1.2.0.tgz", diff --git a/nodejs/package.json b/nodejs/package.json index 1fa0842..04e276e 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -1,6 +1,6 @@ { "name": "proxy-api", - "version": "1.9.0", + "version": "1.13.0", "author": [ { "name": "William Mantly", @@ -22,6 +22,7 @@ "@fortawesome/fontawesome-free": "^7.3.0", "@popperjs/core": "^2.11.8", "@simpleworkjs/app-stack": "^1.0.0", + "@simpleworkjs/bao-conf": "^1.0.0", "@simpleworkjs/conf": "^1.2.0", "@simpleworkjs/frontend": "^0.2.7", "@simpleworkjs/ldap": "^1.0.0",