feat(setup): enter the domain once via setup.env; stop drifting the secrets (#16)
The first-run flow generated ./config/*.js with example.com/dc=example,dc=com defaults and then exit 0'd, telling the operator to hand-edit. The domain/base DN was repeated across ~9 fields in each secrets file; on the deploy host only the stack block was updated (to dc=718it,dc=biz) while ldap.* DNs stayed at dc=example,dc=com, so slapd's root DN didn't match the app's bindDN and bootstrap failed with 401 Invalid Credentials. Enter the domain once: a new setup.env (gitignored; setup.env.example is the committed template) holds the essential, non-repeating first-run info — the domain as an LDAP base DN (CFG_BASE_DN). setup.sh reads it ONLY on first run (when ./config/*.js don't exist), derives hostnames (sso.<domain> / proxy.<domain>) and all LDAP DNs from it, generates both secrets files with the real domain filled in everywhere + random secrets, and proceeds to build in the same run (no edit-and-re-run step). After first run the secrets files are operator-owned and setup.env is ignored — the apps and secrets files are unchanged. - setup.sh ensure_config: source setup.env -> bind CFG_* to empty (set -u safe) -> unchanged .env/proxy.env legacy migration -> derive from base DN (no example.com defaults; die with a helpful msg if CFG_BASE_DN blank) -> generate + proceed. Header comments updated. - setup.env.example: committed template; secrets stay out (generated into ./config/*.js). - .gitignore: ignore setup.env (per-deployment). - README.md + docs/quickstart.md: Quickstart now cp setup.env.example -> set CFG_BASE_DN -> ./setup.sh; domain-asked-once note in "Before you begin". - Bump sso-manager-node gitlink to 11fb2c0 (docs PR #37: base-DN-is-the-one- domain-value note in sso-manager README/DEPLOYMENT/secrets.js.example). Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,11 @@ backups/
|
||||
.env
|
||||
proxy.env
|
||||
|
||||
# setup.env — operator's first-run domain config (copied from setup.env.example,
|
||||
# which IS committed). Holds the domain as a base DN; setup.env itself is
|
||||
# per-deployment and is not committed.
|
||||
setup.env
|
||||
|
||||
# Backup artifacts (hold secrets — the whole user directory + Redis dumps)
|
||||
*.rdb
|
||||
*.ldif
|
||||
|
||||
@@ -55,6 +55,11 @@ real TLS certificates for it via Let's Encrypt. A `.local` or made-up name only
|
||||
gets you a self-signed cert (browsers will warn — fine for testing, painful for
|
||||
daily use).
|
||||
|
||||
The domain is the **one** value you set in `setup.env` (as the LDAP base DN,
|
||||
e.g. `CFG_BASE_DN=dc=lab,dc=example,dc=com` for `lab.example.com`) — see
|
||||
*Quickstart*. The SSO/proxy hostnames default to `sso.<domain>` /
|
||||
`proxy.<domain>`, derived from it.
|
||||
|
||||
### 2. At least two hostnames, pointing at your public IP
|
||||
|
||||
You need a DNS **`A` record** for each hostname you want the stack to serve,
|
||||
@@ -109,15 +114,18 @@ standalone (`docker-compose`) both work.
|
||||
```bash
|
||||
git clone --recursive https://github.com/theta42/theta-env.git
|
||||
cd theta-env
|
||||
./setup.sh # generates ./config/ the first time — edit it, then re-run
|
||||
./setup.sh # builds + bootstraps + starts the stack
|
||||
cp setup.env.example setup.env # then edit setup.env: set CFG_BASE_DN to your domain
|
||||
./setup.sh # first run: generates ./config/ from setup.env, builds + bootstraps + starts
|
||||
```
|
||||
|
||||
The first `./setup.sh` generates `./config/sso-secrets.js` +
|
||||
`./config/proxy-secrets.js` with random secrets and **exits**, telling you to
|
||||
edit them. Set at least `stack.ssoHost`, `stack.proxyHost`, `stack.ldapBaseDn`,
|
||||
and `bootstrap.adminUid`/`adminPass` in `sso-secrets.js`, then re-run. The second
|
||||
run builds and brings up the stack.
|
||||
Your domain is entered **once**, as the LDAP base DN in `setup.env` (e.g.
|
||||
`CFG_BASE_DN=dc=lab,dc=example,dc=com` for the domain `lab.example.com`). The
|
||||
first `./setup.sh` reads `setup.env` and generates `./config/sso-secrets.js` +
|
||||
`./config/proxy-secrets.js` with that domain filled in everywhere (hostnames
|
||||
default to `sso.<domain>` / `proxy.<domain>`) plus random secrets, then builds
|
||||
and brings up the stack in the same run — no edit-and-re-run step.
|
||||
`setup.env` is used only on that first run; once `./config/*.js` exist they are
|
||||
operator-owned and `setup.env` is ignored.
|
||||
|
||||
`./setup.sh` is idempotent — re-run it any time to converge the stack to
|
||||
`./config/`. It:
|
||||
@@ -149,7 +157,8 @@ read by each app's `@simpleworkjs/conf` from a symlinked `secrets.js`:
|
||||
`clientId`/`clientSecret` — filled in by the bootstrap), `ldap` (bind creds,
|
||||
same `serviceAccountPass`), `auth` (admin groups/users).
|
||||
|
||||
`./setup.sh` generates both on first run with random secrets. There is **no
|
||||
`./setup.sh` generates both on first run from `./setup.env` (the one place the
|
||||
domain is entered — see *Quickstart*) with random secrets. There is **no
|
||||
`.env` / `proxy.env`** — edit `./config/*.js` directly. Compose only interpolates
|
||||
port defaults (`SSO_PORT`, `HTTP_PORT`, etc.), which you can override on the
|
||||
command line: `SSO_BIND=127.0.0.1 ./setup.sh`. See `config.example/` for the
|
||||
@@ -419,6 +428,7 @@ exactly in the bootstrap) so the SSO can verify them on bind.
|
||||
|
||||
```
|
||||
theta-env/
|
||||
├── setup.env.example # first-run config template — cp to setup.env, set CFG_BASE_DN
|
||||
├── config.example/ # committed annotated config templates (copy to ./config/)
|
||||
├── docker-compose.yml # sso-manager + proxy on one bridge net
|
||||
├── setup.sh # one-command idempotent bring-up (manages ./config/ + backups)
|
||||
@@ -428,9 +438,9 @@ theta-env/
|
||||
└── proxy/ # git submodule
|
||||
```
|
||||
|
||||
`./setup.sh` generates the gitignored `./config/` (`sso-secrets.js` +
|
||||
`proxy-secrets.js`) on first run and snapshots to the gitignored `./backups/`
|
||||
before each rebuild.
|
||||
`./setup.sh` reads the gitignored `setup.env` on first run to generate the
|
||||
gitignored `./config/` (`sso-secrets.js` + `proxy-secrets.js`) and snapshots to
|
||||
the gitignored `./backups/` before each rebuild.
|
||||
|
||||
`./setup.sh` updates both submodules to the latest of their tracked remote
|
||||
branch before building, so each run builds current upstream — no manual
|
||||
|
||||
+23
-16
@@ -32,32 +32,36 @@ step. If you forgot it:
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
## 2. Configure `./config/`
|
||||
## 2. Configure `setup.env` (enter your domain once)
|
||||
|
||||
```bash
|
||||
./setup.sh # generates ./config/ with random secrets, then exits
|
||||
cp setup.env.example setup.env
|
||||
$EDITOR setup.env # set CFG_BASE_DN to your domain, as a base DN
|
||||
```
|
||||
|
||||
The first `./setup.sh` generates `./config/sso-secrets.js` +
|
||||
`./config/proxy-secrets.js` and **exits**, telling you to edit. Edit
|
||||
`./config/sso-secrets.js` and at minimum set:
|
||||
Your domain is entered **once**, as the LDAP base DN. The SSO/proxy hostnames
|
||||
default to `sso.<domain>` / `proxy.<domain>`, derived from it, so for most setups
|
||||
`CFG_BASE_DN` is the only value you set:
|
||||
|
||||
| Key (in `sso-secrets.js`) | Example | Notes |
|
||||
| `setup.env` key | Example | Notes |
|
||||
|-----|---------|-------|
|
||||
| `stack.ldapBaseDn` | `dc=lab,dc=local` | your directory base |
|
||||
| `stack.ssoHost` | `sso.lab.local` | hostname the proxy serves the SSO UI at |
|
||||
| `stack.proxyHost` | `proxy.lab.local` | hostname the proxy serves its own UI at |
|
||||
| `bootstrap.adminUid` | `admin` | your first admin login |
|
||||
| `bootstrap.adminPass` | `...` | first admin password |
|
||||
| `CFG_BASE_DN` | `dc=lab,dc=local` | your directory base — **required** |
|
||||
| `CFG_SSO_HOST` | `sso.lab.local` | optional, defaults to `sso.<domain>` |
|
||||
| `CFG_PROXY_HOST` | `proxy.lab.local` | optional, defaults to `proxy.<domain>` |
|
||||
| `CFG_ADMIN_UID` | `admin` | optional, defaults to `admin` |
|
||||
| `CFG_ADMIN_EMAIL` | `admin@<proxyHost>` | optional |
|
||||
|
||||
Random secrets (`ldap.bindPassword`, `oauth.jwtSecret`, `serviceAccountPass`)
|
||||
are generated for you — change them in the file if you like. Optional:
|
||||
`bootstrap.adminEmail`, `smtp.*`, `stack.ldapCertCn`. See `config.example/` for
|
||||
the full annotated shape, and each submodule's `secrets.js.example`.
|
||||
`setup.env` is used **only on the first run** to generate `./config/`; after
|
||||
that `./config/*.js` are operator-owned and `setup.env` is ignored. Secrets
|
||||
(LDAP admin password, JWT, admin password, service-account password) are
|
||||
**generated** into `./config/*.js` on first run — do **not** put them in
|
||||
`setup.env`. See `setup.env.example` for the full annotated shape, and
|
||||
`config.example/` + each submodule's `secrets.js.example` for the generated
|
||||
file shape.
|
||||
|
||||
> **Migrating from an older `.env`-based deployment?** If `.env`/`proxy.env`
|
||||
> exist, `./setup.sh` migrates them into `./config/` preserving your existing
|
||||
> secrets — no need to reconfigure.
|
||||
> secrets — no need to write a `setup.env`.
|
||||
|
||||
## 3. Run
|
||||
|
||||
@@ -65,6 +69,9 @@ the full annotated shape, and each submodule's `secrets.js.example`.
|
||||
./setup.sh
|
||||
```
|
||||
|
||||
The first run reads `setup.env`, generates `./config/sso-secrets.js` +
|
||||
`./config/proxy-secrets.js` with your domain filled in everywhere plus random
|
||||
secrets, then builds and starts the stack in the same run (no edit-and-re-run).
|
||||
What happens:
|
||||
|
||||
1. Snapshots state to `./backups/<timestamp>/` before rebuilding (a no-op on the
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# setup.env — first-run setup for the theta-env stack.
|
||||
#
|
||||
# This file is used ONLY on the FIRST run of ./setup.sh, to generate
|
||||
# ./config/sso-secrets.js + ./config/proxy-secrets.js with your domain filled
|
||||
# in everywhere consistently. Once ./config/*.js exist they are operator-owned
|
||||
# (edit them directly; setup.env is ignored on later runs).
|
||||
#
|
||||
# cp setup.env.example setup.env
|
||||
# $EDITOR setup.env # set CFG_BASE_DN below to your domain
|
||||
# ./setup.sh # generates ./config/ and builds the stack
|
||||
#
|
||||
# Copying this file to setup.env (gitignored) keeps your domain out of git.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
# Your domain, as an LDAP base DN. THIS IS THE ONE PLACE THE DOMAIN IS ENTERED.
|
||||
# Everything else derives from it: the SSO/proxy hostnames default to
|
||||
# sso.<domain> / proxy.<domain>, and the LDAP DNs are cn=admin,<dn>,
|
||||
# ou=people,<dn>, ou=groups,<dn>. Required — setup.sh refuses to run without it.
|
||||
CFG_BASE_DN=dc=example,dc=com
|
||||
|
||||
# Public hostnames. Optional — default to sso.<domain> / proxy.<domain> derived
|
||||
# from CFG_BASE_DN above. Uncomment and set only if your hostnames differ
|
||||
# (e.g. a different subdomain, or the domain isn't the bare apex):
|
||||
#CFG_SSO_HOST=sso.example.com
|
||||
#CFG_PROXY_HOST=proxy.example.com
|
||||
|
||||
# Optional — sensible defaults if left blank:
|
||||
#CFG_ORG=SSO Manager # app display name + outbound email org
|
||||
#CFG_ADMIN_UID=admin # initial SSO admin username
|
||||
#CFG_ADMIN_EMAIL=admin@proxy.example.com # defaults to admin@<proxyHost>
|
||||
#CFG_LDAP_CERT_CN= # LDAP TLS cert CN; empty -> defaults to the domain
|
||||
|
||||
# Optional SMTP (outbound email from the SSO app). Leave blank to disable:
|
||||
#CFG_SMTP_HOST=smtp.example.com
|
||||
#CFG_SMTP_PORT=587
|
||||
#CFG_SMTP_USER=noreply@example.com
|
||||
#CFG_SMTP_PASS=your-smtp-password
|
||||
#CFG_SMTP_FROM=SSO Manager <noreply@example.com>
|
||||
|
||||
# ── DO NOT put secrets here ──────────────────────────────────────────────────
|
||||
# The LDAP admin password, JWT secret, admin password, and LDAP service-account
|
||||
# password are GENERATED (random) into ./config/sso-secrets.js on first run.
|
||||
# Change them later by editing ./config/sso-secrets.js directly. Do NOT set
|
||||
# CFG_LDAP_ADMIN_PASS / CFG_JWT_SECRET / CFG_ADMIN_PASS / CFG_SVC_PASS here.
|
||||
@@ -3,8 +3,9 @@
|
||||
# theta-env setup — one-command bring-up of the unified SSO Manager + Proxy stack.
|
||||
#
|
||||
# git clone --recursive <theta-env> && cd theta-env
|
||||
# ./setup.sh # generates ./config/ the first time — edit it, re-run
|
||||
# ./setup.sh # builds + bootstraps + starts the stack
|
||||
# cp setup.env.example setup.env # set CFG_BASE_DN to your domain (once)
|
||||
# ./setup.sh # first run: generates ./config/ from setup.env, builds + bootstraps + starts
|
||||
# ./setup.sh # later runs: rebuilds + bootstraps + starts (config left untouched)
|
||||
#
|
||||
# Idempotent: safe to re-run. It manages config in a bind-mounted ./config/
|
||||
# directory (sso-secrets.js + proxy-secrets.js — NO .env / proxy.env), snapshots
|
||||
@@ -18,10 +19,13 @@
|
||||
# (so each run builds the newest sso-manager-node + proxy). Skip with
|
||||
# SKIP_SUBMODULE_UPDATE=1.
|
||||
# 2. ensure_config: create ./config/sso-secrets.js + proxy-secrets.js if
|
||||
# missing. On a fresh clone they're generated with random secrets and you
|
||||
# must edit them + re-run. On an existing deployment with .env/proxy.env,
|
||||
# the secrets are migrated (preserved) into ./config. If ./config already
|
||||
# exists it is left untouched (the operator owns it).
|
||||
# missing. On a fresh clone the domain/hosts are read from ./setup.env
|
||||
# (the one place the domain is entered, as the LDAP base DN) and both
|
||||
# files are generated with that domain filled in everywhere + random
|
||||
# secrets, then the run proceeds to build (no edit-and-re-run step). On
|
||||
# an existing deployment with .env/proxy.env, the secrets are migrated
|
||||
# (preserved) into ./config. If ./config already exists it is left
|
||||
# untouched (the operator owns it; setup.env is ignored).
|
||||
# 3. backup_before_rebuild: snapshot ./config + LDAP (slapcat) + both Redis
|
||||
# (BGSAVE + dump.rdb) to ./backups/<ts>/ before the rebuild. No-op on the
|
||||
# very first run. Keeps the last BACKUP_KEEP (default 5).
|
||||
@@ -232,23 +236,35 @@ ensure_config() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Defaults for a fresh generation. Overridden below by .env/proxy.env if the
|
||||
# operator is migrating from the old .env-based setup.
|
||||
CFG_BASE_DN="${CFG_BASE_DN:-dc=example,dc=com}"
|
||||
# First run: read the domain/hosts from ./setup.env — the ONE place the
|
||||
# domain is entered, as the LDAP base DN (e.g. dc=718it,dc=biz). Hostnames
|
||||
# default to sso.<domain> / proxy.<domain>, derived from it. setup.env is
|
||||
# used ONLY on first run; once ./config/*.js exist they are operator-owned
|
||||
# and setup.env is ignored. Falls back to legacy .env/proxy.env migration
|
||||
# below for existing deployments.
|
||||
if [[ -f ./setup.env ]]; then
|
||||
info "Reading domain/hosts from ./setup.env ..."
|
||||
parse_kv_file ./setup.env
|
||||
fi
|
||||
|
||||
# Bind the CFG_* vars to empty where setup.env / the environment didn't set
|
||||
# them, so the .env migration's `${LDAP_X:-$CFG_X}` defaults below don't trip
|
||||
# `set -u`. Real values come from setup.env, the .env migration, or the
|
||||
# derivation block further down (no example.com placeholders here).
|
||||
CFG_BASE_DN="${CFG_BASE_DN:-}"
|
||||
CFG_DOMAIN="${CFG_DOMAIN:-}"
|
||||
CFG_ORG="${CFG_ORG:-SSO Manager}"
|
||||
CFG_SSO_HOST="${CFG_SSO_HOST:-sso.example.com}"
|
||||
CFG_PROXY_HOST="${CFG_PROXY_HOST:-proxy.example.com}"
|
||||
CFG_ADMIN_UID="${CFG_ADMIN_UID:-admin}"
|
||||
CFG_ORG="${CFG_ORG:-}"
|
||||
CFG_SSO_HOST="${CFG_SSO_HOST:-}"
|
||||
CFG_PROXY_HOST="${CFG_PROXY_HOST:-}"
|
||||
CFG_ADMIN_UID="${CFG_ADMIN_UID:-}"
|
||||
CFG_ADMIN_EMAIL="${CFG_ADMIN_EMAIL:-}"
|
||||
CFG_LDAP_CERT_CN="${CFG_LDAP_CERT_CN:-}"
|
||||
CFG_CLIENT_ID="${CFG_CLIENT_ID:-}"
|
||||
CFG_CLIENT_SECRET="${CFG_CLIENT_SECRET:-}"
|
||||
# Random secrets (generated fresh unless migrated from .env below).
|
||||
CFG_LDAP_ADMIN_PASS="${CFG_LDAP_ADMIN_PASS:-$(rand_hex 16)}"
|
||||
CFG_JWT_SECRET="${CFG_JWT_SECRET:-$(rand_hex 32)}"
|
||||
CFG_ADMIN_PASS="${CFG_ADMIN_PASS:-$(rand_hex 16)}"
|
||||
CFG_SVC_PASS="${CFG_SVC_PASS:-$(rand_hex 16)}"
|
||||
CFG_LDAP_ADMIN_PASS="${CFG_LDAP_ADMIN_PASS:-}"
|
||||
CFG_JWT_SECRET="${CFG_JWT_SECRET:-}"
|
||||
CFG_ADMIN_PASS="${CFG_ADMIN_PASS:-}"
|
||||
CFG_SVC_PASS="${CFG_SVC_PASS:-}"
|
||||
|
||||
# ── One-time migration from .env / proxy.env (existing deployments) ──
|
||||
# Preserve the operator's existing secrets so the running deployment keeps
|
||||
@@ -287,7 +303,26 @@ ensure_config() {
|
||||
[[ -n "$pbp" ]] && CFG_SVC_PASS="$pbp"
|
||||
migrated=1
|
||||
fi
|
||||
[[ -n "$CFG_ADMIN_EMAIL" ]] || CFG_ADMIN_EMAIL="admin@${CFG_PROXY_HOST}"
|
||||
|
||||
# Derive everything from the base DN — the one domain value. No example.com
|
||||
# defaults: a blank base DN means first-run setup hasn't been done yet.
|
||||
[[ -n "$CFG_BASE_DN" ]] \
|
||||
|| die "First run: 'cp setup.env.example setup.env', set CFG_BASE_DN to your domain (e.g. dc=718it,dc=biz), then re-run ./setup.sh"
|
||||
CFG_DOMAIN="${CFG_DOMAIN:-$(domain_from_dn "$CFG_BASE_DN")}"
|
||||
CFG_SSO_HOST="${CFG_SSO_HOST:-sso.$CFG_DOMAIN}"
|
||||
CFG_PROXY_HOST="${CFG_PROXY_HOST:-proxy.$CFG_DOMAIN}"
|
||||
CFG_ORG="${CFG_ORG:-SSO Manager}"
|
||||
CFG_ADMIN_UID="${CFG_ADMIN_UID:-admin}"
|
||||
CFG_ADMIN_EMAIL="${CFG_ADMIN_EMAIL:-admin@$CFG_PROXY_HOST}"
|
||||
CFG_LDAP_CERT_CN="${CFG_LDAP_CERT_CN:-}"
|
||||
CFG_CLIENT_ID="${CFG_CLIENT_ID:-}"
|
||||
CFG_CLIENT_SECRET="${CFG_CLIENT_SECRET:-}"
|
||||
# Random secrets (generated fresh unless sourced/migrated above). These do
|
||||
# NOT belong in setup.env — they're written into ./config/*.js only.
|
||||
CFG_LDAP_ADMIN_PASS="${CFG_LDAP_ADMIN_PASS:-$(rand_hex 16)}"
|
||||
CFG_JWT_SECRET="${CFG_JWT_SECRET:-$(rand_hex 32)}"
|
||||
CFG_ADMIN_PASS="${CFG_ADMIN_PASS:-$(rand_hex 16)}"
|
||||
CFG_SVC_PASS="${CFG_SVC_PASS:-$(rand_hex 16)}"
|
||||
|
||||
mkdir -p "$CONFIG_DIR" && chmod 700 "$CONFIG_DIR"
|
||||
write_sso_secrets
|
||||
@@ -298,13 +333,8 @@ ensure_config() {
|
||||
info "Migrated secrets into $CONFIG_DIR/ (existing LDAP dir / JWT / OAuth client preserved)."
|
||||
info "You may now delete .env and proxy.env — they are no longer used."
|
||||
else
|
||||
# Fresh generation with placeholder hostnames — the operator must edit.
|
||||
info "Generated $CONFIG_DIR/sso-secrets.js + proxy-secrets.js with random secrets."
|
||||
warn "EDIT $CONFIG_DIR/sso-secrets.js (set stack.ssoHost, stack.proxyHost, stack.ldapBaseDn,"
|
||||
warn " bootstrap.adminUid/adminPass to your values), then re-run ./setup.sh."
|
||||
info "Re-run ./setup.sh after editing. (LDAP admin pass + JWT were generated for you —"
|
||||
info "change them in the file if you like, or leave them.)"
|
||||
exit 0
|
||||
info "Generated $CONFIG_DIR/sso-secrets.js + proxy-secrets.js from ./setup.env (domain=$CFG_DOMAIN)."
|
||||
info "Edit $CONFIG_DIR/*.js to change secrets later; re-run ./setup.sh to rebuild."
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
+1
-1
Submodule sso-manager-node updated: 796e013234...11fb2c0a54
Reference in New Issue
Block a user