diff --git a/docs/deployment.md b/docs/deployment.md index c6fff19..bbfe469 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -7,211 +7,15 @@ title: Deployment [← Back to Home](index.html) -Two supported methods: +The full deployment guide — Docker (all-in-one image), bare-metal install, +the `app_*` env reference, backups, and the security notes (including why +LDAPS shouldn't be port-forwarded to the internet) — lives in one place to +avoid two copies drifting out of sync: -1. **Docker** — a single all-in-one image bundling the app + OpenLDAP + Redis. -2. **Bare metal** — `install.sh` on Debian/Ubuntu (Node.js, OpenLDAP, Redis, systemd unit). +**[DEPLOYMENT.md on GitHub](https://github.com/theta42/sso-manager-node/blob/master/DEPLOYMENT.md)** -## Method 1: Docker (all-in-one) +See also [Configuration](configuration.html) for the config layer merge +order, and [LDAP](ldap.html) for the directory layout and connecting a +3rd-party app. -The image (`Dockerfile.openldap`) bundles OpenLDAP + the app + Redis in one -container. The app connects to the bundled slapd over `localhost:389` -automatically; you only need to set a few secrets. - -```bash -# Minimal: an LDAP admin password + a JWT secret, then build + start. -LDAP_ADMIN_PASS='choose-a-strong-password' \ -JWT_SECRET="$(openssl rand -hex 32)" \ -docker compose up -d --build -``` - -For a customized deployment, put overrides in a `.env` next to -`docker-compose.yml`: - -```env -LDAP_BASE_DN=dc=yourdomain,dc=com -LDAP_DOMAIN=yourdomain.com -LDAP_ADMIN_PASS=your-admin-password -ORG_NAME=Your Org -JWT_SECRET=your-jwt-secret -OAUTH_ISSUER=https://sso.yourdomain.com # browser-facing URL the proxy serves -LDAP_CERT_CN=sso.yourdomain.com # hostname LDAPS clients verify -SMTP_HOST=smtp.yourdomain.com -SMTP_PORT=587 -SMTP_USER=noreply@yourdomain.com -SMTP_PASS=your-smtp-password -SMTP_FROM=Your Org -PORT=3001 -LDAPS_PORT=636 -``` - -Then `docker compose up -d --build`. - -### Access - -- SSO Manager UI: `http://localhost:3001` (HTTP — put a TLS-terminating proxy in front) -- Health: `http://localhost:3001/health` → `{"status":"ok"}` -- OIDC discovery: `http://localhost:3001/.well-known/openid-configuration` -- LDAPS (legacy apps / direct binds): `ldaps://:636` - -### API tokens (personal access tokens) - -Any logged-in user can mint a long-lived bearer token to call the management -API from scripts/CI without a browser session. Self-service; authenticates -**as the creator** (carries their LDAP group permissions, re-resolved live). - -Create one under **API Tokens** in the UI (shown once), then: - -```bash -curl -H "Authorization: Bearer sso__" https://sso.example.com/api/user -``` - -Rotate/revoke from the same page (immediate effect). Optional expiry at -creation. Tokens persist in Redis (AOF) and survive rebuilds. - -### Logs - -The all-in-one image runs the Node app and slapd (OpenLDAP) in one container, -both writing to the container's stdout/stderr, so `docker compose logs` is the -primary view (slapd runs with `-d 0`, so LDAP output is there too). - -```bash -docker compose logs -f sso-manager # app + slapd (stdout/stderr) -docker compose logs --tail=200 --since=10m sso-manager # recent context -docker compose exec sso-manager ldapsearch -x -H ldap://localhost:389 \ - -D "cn=admin,$LDAP_BASE_DN" -W -b "$LDAP_BASE_DN" # LDAP health check -``` - -### Environment variables - -| Variable | Default | Description | -|----------|---------|-------------| -| `LDAP_BASE_DN` | `dc=example,dc=com` | slapd suffix + app user/group base | -| `LDAP_DOMAIN` | derived from base DN | DNS domain; default for cert CN + issuer | -| `LDAP_ADMIN_PASS` | `admin` | slapd root password + app bind password | -| `ORG_NAME` | `SSO Manager` | org name in UI/email/group descriptions | -| `JWT_SECRET` | auto-generated | OAuth JWT signing secret (**persist it**) | -| `OAUTH_ISSUER` | `https://sso.` | OIDC issuer (browser-facing URL) | -| `LDAP_CERT_CN` | `LDAP_DOMAIN` | CN/SAN on the LDAPS cert | -| `LDAP_CERT_DIR` | `/etc/openldap/certs` | look for `ldap.crt`+`ldap.key` here (mount your own) | -| `SMTP_HOST`/`SMTP_PORT`/`SMTP_USER`/`SMTP_PASS`/`SMTP_FROM` | localhost / 587 / empty | outbound email | -| `PORT` | `3001` | host port mapped to the UI | -| `LDAPS_PORT` | `636` | host port mapped to LDAPS | - -Any `app_*` var may also be set directly to override any config value — see -[Configuration](configuration.html). - -### LDAP TLS (LDAPS / StartTLS) - -The bundled slapd generates a **self-signed cert** on first start (CN = -`LDAP_CERT_CN`, valid 10y, SAN = CN + `localhost` + `127.0.0.1`) and listens on -`ldaps:///` (636) + StartTLS on `ldap:///` (389). The cert lives on the -`ldap-certs` volume so it persists across container recreation. - -- **Trust it** (clients): copy the cert out and add it to the client's CA store, - or set `TLS_REQCERT never` for quick LAN use: - ```bash - docker compose cp sso-manager:/etc/openldap/certs/ldap.crt ./ldap.crt - ``` -- **Use your own cert**: replace the `ldap-certs` named volume with a bind mount - containing your `ldap.crt` + `ldap.key`. The entrypoint leaves existing certs - untouched. - -> Port 389 (plain LDAP) is **not** mapped to the host by default — direct-LDAP -> clients should use LDAPS (636) or StartTLS. - -### Backups and restore - -LDAP, Redis (OAuth clients + tokens), and the `./config/` secrets are all -persisted and restorable. Redis is now AOF+RDB persisted to the `sso-data` -volume (not in-memory) so OAuth clients survive rebuilds. - -See the **Backups and restore** section of `DEPLOYMENT.md` for the full runbook -(what lives where, manual backup, full / Redis-only / LDAP-only restore, and -the AOF-vs-RDB note). Quick LDAP backup: - -```bash -docker compose exec sso-manager slapcat -f /etc/openldap/slapd.conf \ - -b "dc=yourdomain,dc=com" > ldap-backup-$(date +%F).ldif -``` - -## Method 2: Bare metal (Debian/Ubuntu) - -`install.sh` is an idempotent installer: Node.js 20.x, OpenLDAP (modules + -overlays + custom schema + directory tree + required groups), the app at -`/opt/sso-manager`, and a systemd unit. - -```bash -sudo ./install.sh \ - -p 'your-ldap-password' \ - -b 'dc=yourdomain,dc=com' \ - -n 'Your Org' \ - -o 3001 -``` - -| Flag | Description | -|------|-------------| -| `-p, --admin-pass` | LDAP admin password (required) | -| `-b, --base-dn` | Base DN (default `dc=example,dc=com`) | -| `-n, --org-name` | Org name (default `SSO Manager`) | -| `-o, --port` | HTTP port (default `3001`) | -| `-j, --jwt-secret` | JWT secret (default auto-generated) | -| `-s, --smtp-config` | SMTP as `host:port:user:pass` | -| `--skip-ldap` | Skip LDAP setup (use existing) | -| `--skip-app` | LDAP setup only | -| `--dry-run` | Show actions without making changes | - -Post-install: - -```bash -sudo systemctl enable --now sso-manager -curl http://localhost:3001/health # -> {"status":"ok"} -``` - -For an existing LDAP server, run `sudo ./install.sh --skip-ldap …` and point the -app at it. To (re)configure overlays on an already-installed slapd, prefer -`ops/ldap-setup.sh` (idempotent, auto-detects the user database). - -## Fronting with a reverse proxy - -The SSO runs HTTP inside the container; terminate TLS at a front proxy. The -[`theta42/proxy`](https://github.com/theta42/proxy) is an OIDC-protected reverse -proxy and a natural fit — it's **both** an OIDC client of this SSO *and* a -direct LDAP client for user lookups. - -1. **One Docker network** so the proxy reaches the SSO internally at - `http://sso-manager:3001` (token/userinfo, server-to-server) without exposing - the SSO's HTTP port. -2. **Set the SSO's `OAUTH_ISSUER`** to the *browser-facing* HTTPS URL the proxy - serves the SSO at (e.g. `https://sso.yourdomain.com`). -3. **Register the proxy as an OIDC client** in the SSO UI, with `redirectUri` - matching the proxy's callback (`https://proxy.yourdomain.com/api/auth/oidc/callback`). -4. **LDAP for the proxy**: point `ldap.url` at `ldaps://sso-manager:636` and - create a dedicated service account under `ou=people` (e.g. - `cn=ldapclient,ou=people,…`) — don't reuse the admin DN. - -The [`theta42/theta-env`](https://github.com/theta42/theta-env) unified repo -automates all four steps with `./setup.sh` — see -[theta-env docs](https://theta42.github.io/theta-env/). - -## Security notes - -1. **Never commit `secrets.js`** — it's in `.gitignore`. -2. **Use LDAPS / StartTLS** for any LDAP that crosses the network. Port 389 is - not mapped to the host by default so LAN clients can't bind in cleartext. -3. **Persist `JWT_SECRET`** — an auto-generated one invalidates all tokens on - container recreation. -4. **Don't expose the UI's HTTP port to the internet** — terminate TLS at a - front proxy and keep `3001` on the Docker network / localhost only. -5. **Don't port-forward LDAPS (636) to the internet either.** It's mapped to - the host by default for LAN/VPN clients that bind LDAP directly (other - hosts running `ldap-client`, apps with their own LDAP auth settings) — not - for exposure through your router/firewall. LDAP simple-bind is a - brute-force target and there's no rate limiting in front of it the way - there is for the HTTP login endpoints. If you need a remote host to bind - LDAP, put it behind a VPN (Tailscale, WireGuard, …) instead of forwarding - 636 publicly. -6. The all-in-one image runs slapd as the `ldap` user but the app as root - (matches the bare-metal unit). Harden to a non-root user for production. - -[← Back to Home](index.html) \ No newline at end of file +[← Back to Home](index.html)