Persist Redis + config in bind-mounted ./config/ (no .env); add backup/restore (#8)

Part A — lossless upgrades:
- Persist both bundled Redis stores via AOF+RDB on named volumes (sso-data,
  proxy-data) so OAuth clients, Host records, perms, DNS creds, and auto-ssl
  Let's Encrypt certs survive rebuilds.
- setup.sh: backup_before_rebuild() snapshots ./config/ + LDAP (slapcat) +
  both Redis (BGSAVE + compose cp) to ./backups/<ts>/ before each rebuild,
  keeps last BACKUP_KEEP (default 5). First run is a no-op.
- Restore runbook (README + docs): full / Redis-only / LDAP-only, with the
  AOF-vs-RDB note (delete the AOF before restoring an RDB).

Part B — eliminate .env / proxy.env:
- All config + secrets live in bind-mounted ./config/ (gitignored), read by each
  app's @simpleworkjs/conf from a symlinked secrets.js. Compose passes only
  NODE_ENV + NODE_PORT (no app_* env, which would override secrets.js).
- ./config/sso-secrets.js: app secrets + orchestrator-only stack/bootstrap/
  serviceAccountPass keys (app ignores the ones it doesn't use).
- ./config/proxy-secrets.js: oidc (clientId/clientSecret filled in by the
  bootstrap), ldap (bind creds), auth (admin groups/users).
- setup.sh ensure_config(): generates ./config/ with random secrets on first
  run (then exits for editing); one-time migration from .env/proxy.env
  preserving existing secrets (LDAP admin pass, JWT, OAuth client, service
  pass) so a running deployment keeps its directory + tokens + OAuth client.
- bootstrap/bootstrap.js: reads /config/*.js (not process.env), registers the
  proxy as an OIDC client, and writes the SSO-generated client id+secret back
  into ./config/proxy-secrets.js (sso mounts ./config RW, proxy RO).
- config.example/ holds committed annotated templates for manual reference.
- .gitignore: add config/, backups/, *.rdb, *.ldif.

Bump both gitlinks to the merged submodule tips:
- sso-manager-node -> 6920a9f (PR #34)
- proxy -> 8e78604 (PR #118)

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-12 13:16:17 -04:00
committed by GitHub
parent 2d2941e394
commit b5f24d40fc
13 changed files with 929 additions and 445 deletions
+49
View File
@@ -0,0 +1,49 @@
'use strict';
// Example proxy secrets for the theta-env unified stack. Copy to
// ./config/proxy-secrets.js (NOT this file — ./config/ is gitignored) and edit.
// `./setup.sh` generates ./config/proxy-secrets.js for you on first run and the
// bootstrap writes the OAuth client clientId/clientSecret back into it; this
// file documents the shape for manual editing / reference.
//
// The proxy app reads this via @simpleworkjs/conf (docker-entrypoint.sh
// symlinks it to /app/conf/secrets.js). Never commit ./config/.
module.exports = {
oidc: {
enabled: true,
issuer: 'https://sso.example.com',
authorizationEndpoint: 'https://sso.example.com/oauth/authorize',
// token/userinfo use the internal docker-network URL (no TLS hairpin):
tokenEndpoint: 'http://sso-manager:3001/oauth/token',
userinfoEndpoint: 'http://sso-manager:3001/oauth/userinfo',
endSessionEndpoint: 'https://sso.example.com/oauth/logout',
clientId: 'FILLED-IN-BY-BOOTSTRAP', // leave as-is; bootstrap sets it
clientSecret: 'FILLED-IN-BY-BOOTSTRAP', // leave as-is; bootstrap sets it
redirectUri: 'https://proxy.example.com/api/auth/oidc/callback',
scopes: ['openid', 'profile', 'email', 'groups'],
groupsClaim: 'groups',
usernameClaim: 'preferred_username',
},
ldap: {
// LDAPS over the docker network; the SSO's self-signed cert is trusted
// via tlsOptions.rejectUnauthorized:false.
url: 'ldaps://sso-manager:636',
bindDN: 'cn=ldapclient,ou=people,dc=example,dc=com',
// MUST equal serviceAccountPass in sso-secrets.js (the proxy binds as
// that service account). setup.sh keeps them in sync on generation.
bindPassword: 'CHANGE-ME',
searchBase: 'ou=people,dc=example,dc=com',
userFilter: '(objectClass=posixAccount)',
userNameAttribute: 'uid',
tlsOptions: { rejectUnauthorized: false },
},
auth: {
adminGroups: ['app_sso_admin'], // SSO group -> global proxy admin
adminUsers: ['proxyadmin2'], // local anti-lockout admin
groupRoleMap: {},
},
stack: {
ssoHost: 'sso.example.com',
proxyHost: 'proxy.example.com',
},
};
+45
View File
@@ -0,0 +1,45 @@
'use strict';
// Example SSO secrets for the theta-env unified stack. Copy to
// ./config/sso-secrets.js (NOT this file — ./config/ is gitignored) and edit.
// `./setup.sh` generates ./config/sso-secrets.js for you on first run; this file
// documents the shape for manual editing / reference.
//
// The SSO app reads this via @simpleworkjs/conf (docker-entrypoint.sh symlinks
// it to /app/conf/secrets.js). The app ignores the extra stack/bootstrap/
// serviceAccountPass keys (read by the orchestrator). Back this up off-host —
// it holds all SSO secrets. Never commit ./config/.
module.exports = {
name: 'SSO Manager', // shown in UI + outbound email
ldap: {
url: 'ldap://localhost:389', // the bundled slapd (in-container)
bindDN: 'cn=admin,dc=example,dc=com', // slapd root DN
bindPassword: 'CHANGE-ME', // slapd root + app bind password
userBase: 'ou=people,dc=example,dc=com',
groupBase: 'ou=groups,dc=example,dc=com',
},
smtp: { // optional; leave host '' to skip
host: '', port: 587, secure: false,
user: '', pass: '', from: '',
},
oauth: {
issuer: 'https://sso.example.com', // browser-facing SSO URL
jwtSecret: 'CHANGE-ME', // signs all tokens — keep secret
token_lifetime: { access_token: 3600, refresh_token: 2592000 },
},
// ── Orchestrator-only (ignored by the app; read by setup.sh + bootstrap) ──
stack: {
ldapBaseDn: 'dc=example,dc=com', // slapd suffix (drives seed OUs)
ldapDomain: 'example.com', // default cert CN + issuer host
ldapCertCn: '', // cert CN; '' -> defaults to ldapDomain
ssoHost: 'sso.example.com', // public SSO hostname
proxyHost: 'proxy.example.com', // public proxy hostname
},
bootstrap: {
adminUid: 'admin', // first SSO admin username
adminPass: 'CHANGE-ME', // first SSO admin password
adminEmail: 'admin@proxy.example.com', // first SSO admin email
},
serviceAccountPass: 'CHANGE-ME', // LDAP password the proxy binds with
};