diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e2e2d8c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 theta42 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index e6765ae..426c1e1 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,15 @@ Each project still runs **standalone** (`docker compose up` in its own folder); this repo just composes them and automates the first-run glue so they find each other. +**Why use this instead of running the two separately?** The two only become +useful once the proxy is registered as an OIDC client of the SSO and pointed at +the SSO's LDAP directory — and the SSO's domain has to match across half a dozen +config fields or logins silently fail with `Invalid Credentials`. Doing that by +hand is fiddly and easy to get wrong. `setup.sh` asks for your domain once (in +`setup.env`), generates both config files with it filled in everywhere, registers +the proxy as an OIDC client, and snapshots state before every rebuild — so you +get a working SSO + proxy stack in one command and a safe way to upgrade it. + ``` ┌──────────────────────────────────────────────┐ │ your browser / apps │ @@ -412,8 +421,12 @@ exactly in the bootstrap) so the SSO can verify them on bind. off-host (see *Backups and restore*). 3. **LDAPS uses the SSO's self-signed cert by default.** The proxy binds with `ldap.tlsOptions.rejectUnauthorized=false` (in `proxy-secrets.js`). For strict - trust, mount the SSO's cert (`ldap-certs` volume) into the proxy and set - `ldap.tlsOptions.ca=` in `./config/proxy-secrets.js`. + trust, this is a **two-step change, not config-only**: (a) edit + `docker-compose.yml` to also mount the `ldap-certs` volume into the `proxy` + service (it's currently only mounted into `sso-manager`) — see the + commented-out boilerplate in the `proxy` service's `volumes:` block — then + (b) set `ldap.tlsOptions.ca=` in `./config/proxy-secrets.js` to the + mounted cert path and `docker compose up -d proxy` to pick up the new mount. 4. **Re-running `setup.sh` resets the bootstrap admin + service passwords to the `./config/` values.** If you change a user's password in the SSO UI later, re-running `setup.sh` will reset the bootstrap admin's password back diff --git a/docker-compose.yml b/docker-compose.yml index c100bbf..d29158d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -103,6 +103,10 @@ services: - proxy-data:/data - proxy-cache:/var/cache/nginx/proxy - proxy-logs:/var/log/nginx + # OPTIONAL, for strict LDAPS trust (see README "Security notes"): mount + # the SSO's self-signed cert into the proxy read-only, then set + # ldap.tlsOptions.ca= in ./config/proxy-secrets.js. + # - ldap-certs:/etc/ssl/sso-ldap-certs:ro healthcheck: test: ["CMD", "curl", "-fsS", "http://localhost:3000/health"] interval: 30s diff --git a/docs/architecture.md b/docs/architecture.md index 48a75f0..4cb1685 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -11,6 +11,8 @@ theta-env is a **composition** repo: it builds the two existing projects from their git submodules and adds the glue that wires them together. It does not fork or patch them — both projects work unchanged on their own. +--- + ## The three repos | Repo | Role | @@ -22,6 +24,8 @@ fork or patch them — both projects work unchanged on their own. The two projects are pinned as **git submodules**. `git clone --recursive` fetches all three in one step; `git submodule update --remote` bumps them. +--- + ## The two containers ``` @@ -68,6 +72,8 @@ redis instances is the no-source-patch path and is fine at this scale. | `3001` (sso) | localhost only | SSO web UI (first-run convenience; fronted by the proxy normally) | | `389` (sso) | **no** | plain LDAP — internal only (app↔slapd over localhost) | +--- + ## The first-run bootstrap `./setup.sh` orchestrates first-run wiring; `bootstrap/bootstrap.js` does the @@ -127,6 +133,8 @@ admin creds from `./config/sso-secrets.js` sidesteps that entirely, and going through the HTTP API for the OAuth client validates the whole admin login path end-to-end. +--- + ## Idempotency Re-running `./setup.sh` converges to `./config/`: @@ -139,6 +147,8 @@ Re-running `./setup.sh` converges to `./config/`: So `setup.sh` is safe to re-run after editing `./config/`, after a `docker compose down`, or after restoring from backup. +--- + ## Backups and restore `./setup.sh` auto-snapshots `./config/` + LDAP + both Redis to diff --git a/docs/index.md b/docs/index.md index e125f88..dadd05d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -14,13 +14,15 @@ It exists for people whose needs are met by these two projects and who want to run them "very simply." Each project still works **standalone**; this repo just wires them together and automates the first-run glue. +--- + ## Quick start ```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 ``` You need **Docker** + **Docker Compose**. `./setup.sh` is idempotent — re-run any @@ -30,6 +32,8 @@ See the [Quickstart Guide](quickstart.html) for a walkthrough of `./config/` and what `setup.sh` does, [Architecture](architecture.html) for how the pieces fit together, and [Standalone](standalone.html) for running each project on its own. +--- + ## What you get - **SSO Manager** at `https://` — log in as your first admin to manage @@ -44,6 +48,8 @@ together, and [Standalone](standalone.html) for running each project on its own. creator (carrying their permissions); mint/rotate/revoke under **API Tokens** in each UI. See each submodule's DEPLOYMENT for the details. +--- + ## The `./config/` values you must set All config and secrets live in `./config/sso-secrets.js` + @@ -62,6 +68,8 @@ There is **no `.env`**. Set at least these in `./config/sso-secrets.js`: See `config.example/` for the full annotated shape (SMTP, LDAP cert CN, proxy OIDC/LDAP/auth, …). +--- + ## Architecture ``` @@ -88,6 +96,8 @@ The proxy is **both** an OIDC client of the SSO (for login) **and** a direct LDA client (for user lookups). See [Architecture](architecture.html) for the full diagram + the first-run bootstrap flow. +--- + ## Documentation - [Quickstart Guide](quickstart.html) — full walkthrough of `./config/` + `setup.sh`. @@ -95,11 +105,15 @@ diagram + the first-run bootstrap flow. design, and how the bootstrap wires the proxy into a fresh SSO. - [Standalone](standalone.html) — running SSO Manager or the proxy on its own. +--- + ## Community - [GitHub Repository](https://github.com/theta42/theta-env) - [Issue Tracker](https://github.com/theta42/theta-env/issues) +--- + ## License MIT License — see the repository for details. \ No newline at end of file diff --git a/docs/quickstart.md b/docs/quickstart.md index 36cf136..dca9539 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -7,6 +7,8 @@ title: Quickstart [← Back to Home](index.html) +--- + ## Prerequisites - A Linux host with **Docker** + **Docker Compose** (the v2 plugin `docker @@ -18,6 +20,8 @@ title: Quickstart certs; otherwise the proxy serves a self-signed fallback (browsers warn — expected for LAN use). +--- + ## 1. Clone ```bash @@ -32,6 +36,8 @@ step. If you forgot it: git submodule update --init --recursive ``` +--- + ## 2. Configure `setup.env` (enter your domain once) ```bash @@ -63,6 +69,8 @@ file shape. > exist, `./setup.sh` migrates them into `./config/` preserving your existing > secrets — no need to write a `setup.env`. +--- + ## 3. Run ```bash @@ -86,6 +94,8 @@ What happens: The first run builds two Docker images (a few minutes). Subsequent runs are fast. +--- + ## 4. Point DNS at the host `stack.ssoHost` and `stack.proxyHost` (from `./config/sso-secrets.js`) must @@ -98,6 +108,8 @@ echo "127.0.0.1 sso.lab.local proxy.lab.local" | sudo tee -a /etc/hosts (The proxy needs port 80 reachable for Let's Encrypt; on a LAN without that it serves a self-signed cert — browsers will warn, which is fine for home-lab use.) +--- + ## 5. Log in Open `https://` and log in as your bootstrap admin @@ -110,6 +122,8 @@ it). Add the Host records you want to protect with OIDC. First-run fallbacks (if DNS/TLS isn't ready yet): SSO UI at `http://127.0.0.1:3001`, proxy UI at `http://127.0.0.1:3000`. +--- + ## Re-running `./setup.sh` is **idempotent** — safe to re-run after editing `./config/`, after @@ -118,6 +132,8 @@ then converges the stack to your `./config/` values (LDAP service account + admi passwords are reset to the config; the OAuth client is kept if `proxy-secrets.js` already holds its creds). +--- + ## Direct LDAP for legacy apps Legacy apps bind LDAP directly over LDAPS: @@ -131,6 +147,8 @@ ldapsearch -x -H ldaps://:636 \ Use the `cn=ldapclient` service account (read-only, the bootstrap created it) or the admin DN. Use LDAPS (636), not plain LDAP. +--- + ## Backups and restore `./setup.sh` auto-snapshots `./config/` + LDAP + both Redis to `./backups//` @@ -145,6 +163,8 @@ docker compose exec sso-manager slapcat -f /etc/openldap/slapd.conf \ -b "" > backup-$(date +%F).ldif ``` +--- + ## Next steps - Add users / groups in the SSO UI. diff --git a/docs/standalone.md b/docs/standalone.md index 25a8273..621f987 100644 --- a/docs/standalone.md +++ b/docs/standalone.md @@ -11,6 +11,8 @@ theta-env composes the two projects but doesn't fork them — both work on their own. The submodules in this repo are normal clones; you can also clone them directly from GitHub. +--- + ## SSO Manager alone The all-in-one image (`Dockerfile.openldap`) bundles the app + OpenLDAP + Redis: @@ -46,6 +48,8 @@ sudo systemctl enable --now sso-manager Idempotent — re-run to update. See the SSO Manager [deployment guide](https://theta42.github.io/sso-manager-node/deployment.html). +--- + ## Proxy alone The all-in-one image (`Dockerfile`) bundles OpenResty + the Node app + Redis: @@ -70,6 +74,33 @@ so `app_*` would silently override your file. Requires `@simpleworkjs/conf` >= 1.1.0. Full reference: [proxy deployment docs](https://theta42.github.io/proxy/docker.html). +### The `auth.adminUsers` anti-lockout account + +Both `setup.sh` and `config.example/proxy-secrets.js.example` write +`auth.adminUsers: ['proxyadmin2']` into `proxy-secrets.js`. This is a +**local, config-driven admin bypass** — the proxy grants full admin rights to +any logged-in OIDC user whose username (the `preferred_username` claim from +the SSO) matches an entry in `auth.adminUsers`, regardless of their LDAP group +membership (see `proxy/nodejs/utils/roles.js`, `resolveEffective()`). It exists +so an operator can't lock themselves out of the proxy mgmt UI if the SSO's +`app_sso_admin` group is ever misconfigured, deleted, or otherwise broken. + +It is **not** derived from any `setup.env` value, and it does **not** create a +user by itself — the name is only a username match. To actually use the +bypass, create a user with uid `proxyadmin2` in the SSO (it does not need to +be in `app_sso_admin` or any other group) and log in through the proxy as that +user. + +To change or disable it, edit `auth.adminUsers` directly in +`./config/proxy-secrets.js` after the first `./setup.sh` run (re-running +`setup.sh` will not overwrite an existing `proxy-secrets.js`): + +- **Rename** it to a less guessable username: `adminUsers: ['your-break-glass-uid']`. +- **Add more** anti-lockout accounts: `adminUsers: ['proxyadmin2', 'another-admin']`. +- **Disable** it entirely: `adminUsers: []` (global admin then comes only from + `auth.adminGroups` membership — make sure at least one real admin group is + reachable before doing this). + ### Bare metal ```bash @@ -80,6 +111,8 @@ See the proxy [Docker guide](https://theta42.github.io/proxy/docker.html) / [installation guide](https://theta42.github.io/proxy/installation.html). +--- + ## Mixing and matching theta-env isn't required to use the two together — the four wiring steps are