From 3287777b9b806c428afa36f4b69b4650d6ea4207 Mon Sep 17 00:00:00 2001 From: William Mantly Date: Sat, 1 Aug 2026 18:46:10 -0400 Subject: [PATCH] v1.30.0: rename theta-env -> theta-suite + docs rewrite + sso v1.16.1 (#126) Rename the project to theta-suite (it is now an integrated suite of four apps around a shared OpenBao secrets store, not a two-project env). - theta-env -> theta-suite across the superproject: _config.yml (title + baseurl /theta-suite + repo URLs), README, setup.sh (incl. the THETA_SUITE_REEXECED self-update sentinel), docker-compose.yml, bootstrap.js, lint.yml, config.example/*, docs/robots.txt, all docs, this changelog. - architecture.md rewritten: real 4-service + ldap-client topology, OpenBao secrets section, OpenBao-aware config flow; removed "two containers" / "three repos" / LDAP-"legacy" framing. - index.md: integrated-suite framing + secrets/OpenBao + ldap-client. - standalone.md + README: standalone reframed as advanced opt-in. - sso-manager-node submodule -> v1.16.1 (401 fix on /conf and /vault). Co-authored-by: Claude --- .env.example | 2 +- .github/workflows/lint.yml | 2 +- CHANGELOG.md | 127 +++++++++----- README.md | 50 +++--- bootstrap/bootstrap.js | 10 +- config.example/proxy-secrets.js.example | 2 +- config.example/sso-secrets.js.example | 2 +- docker-compose.yml | 2 +- docs/_config.yml | 14 +- docs/architecture.md | 217 ++++++++++++++++-------- docs/index.md | 47 +++-- docs/quickstart.md | 13 +- docs/robots.txt | 2 +- docs/secrets.md | 4 +- docs/standalone.md | 31 ++-- pr_url.txt | 2 +- setup.env.example | 2 +- setup.sh | 18 +- sso-manager-node | 2 +- test-integration.sh | 4 +- 20 files changed, 355 insertions(+), 198 deletions(-) diff --git a/.env.example b/.env.example index d08e31f..cd862b1 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,4 @@ -# theta-env — unified SSO Manager + Proxy deployment. +# theta-suite — unified SSO Manager + Proxy deployment. # # Copy this file to `.env` and fill in the values, then run `./setup.sh`. # All values are read by setup.sh / docker-compose / the bootstrap. diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 10acbcc..8a2f2b7 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,6 +1,6 @@ name: Lint -# theta-env has no app code of its own to unit-test (it orchestrates the +# theta-suite has no app code of its own to unit-test (it orchestrates the # proxy/sso-manager-node submodules) -- this checks the one thing that can # actually break silently: setup.sh and bootstrap.js, plus a static # consistency check on the config bootstrap.js generates for jump-host diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a784f3..db5ff23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,18 +2,65 @@ All notable changes to this project are documented here. Format loosely follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions -correspond to git tags (`vX.Y.Z`). Entries here cover theta-env's own +correspond to git tags (`vX.Y.Z`). Entries here cover theta-suite's own orchestration code; see each submodule's own `CHANGELOG.md` ([proxy](https://github.com/theta42/proxy/blob/master/CHANGELOG.md), [sso-manager-node](https://github.com/theta42/sso-manager-node/blob/master/CHANGELOG.md)) for what changed inside the apps it composes. +## [v1.30.0] - 2026-08-01 + +The project is renamed **theta-env → theta-suite** — it has grown from a +docker-compose wiring two projects into an integrated suite of four +applications around a shared OpenBao secrets store, and the name should reflect +that. The GitHub repository is renamed `theta42/theta-env` → +`theta42/theta-suite` (old URLs redirect), and the docs site moves to +`https://theta42.github.io/theta-suite/`. + +### Changed (theta-suite orchestration) +- **Renamed theta-env → theta-suite** across the superproject: `docs/_config.yml` + (`title` + `baseurl: /theta-suite` + repo URLs), `README.md`, `setup.sh` + (incl. the `THETA_SUITE_REEXECED` self-update sentinel), `docker-compose.yml`, + `bootstrap/bootstrap.js`, `.github/workflows/lint.yml`, `config.example/*`, + `docs/robots.txt`, all docs pages, and this changelog. +- **Compose project name note:** docker compose derives the project name from + the clone directory, so named volumes follow it (`_openbao-data`). + A fresh `git clone` of `theta-suite` uses the `theta-suite` project name; an + existing deployment that keeps its `theta-env` directory keeps its + `theta-env_*` volumes — no data migration is required, just don't mix the two. +- **Docs site baseurl** is now `/theta-suite`, matching the renamed repo's + GitHub Pages URL. + +### Docs +- **`architecture.md` rewritten.** Replaced the outdated "The two containers" + / "The three repos" framing with the actual topology — four always-on + services (`openbao`, `sso-manager`, `proxy`, `jump-host`) plus the + `ldap-client` host-enrollment tool — a real diagram, a full **Secrets + (OpenBao)** section (central store, scoped per-app policies/tokens, the + `@simpleworkjs/bao-conf` boot overlay, per-user KV, external-app minting), + and an OpenBao-aware "how config reaches the apps". Removed the + LDAP-"legacy apps" wording (direct LDAP binds are first-class: Linux hosts + PAM/SSSD, sudo, SSH keys). +- **`index.md`** — integrated-suite framing; added **Central secrets (OpenBao)** + and **ldap-client** to "What you get" and "Related projects". +- **`standalone.md` + `README.md`** — standalone is now framed as an advanced + opt-in; the integrated `./setup.sh` stack is the supported path. + +### Submodule bump +- **sso-manager-node → v1.16.1** — fixes the **401 on `/conf` and `/vault`** for + a logged-in admin. Both view routes 401'd because this app's auth-token is a + header set by client JS (localStorage), not a cookie, so `req.user` is + undefined on a browser navigation; the routes now render the shell and gate + client-side (`app.auth.forceLogin`), with `/api/conf` + `/api/vault` still + enforcing auth + OpenBao scope server-side. See the + [sso v1.16.1 release](https://github.com/theta42/sso-manager-node/releases/tag/v1.16.1). + ## [v1.29.0] - 2026-08-01 Two fixes for a fresh `./setup.sh` install, plus the SSH jump host promoted from an opt-in component to a core part of the stack. -### Fixed (theta-env orchestration) +### Fixed (theta-suite orchestration) - **`setup.sh`** — fresh installs aborted silently right after `Minting per-app OpenBao tokens`. The `env_get` helper's `grep | cut` pipeline returns non-zero under `set -euo pipefail` when `.env` exists (it's created earlier @@ -26,7 +73,7 @@ from an opt-in component to a core part of the stack. - **`setup.sh`** — `JUMP_VAULT_TOKEN` is now always minted (jump host is core; the mint was already unconditional, this just documents it). -### Changed (theta-env orchestration) +### Changed (theta-suite orchestration) - **jump host is no longer optional** — it is built + started on every run, with no `CFG_JUMP_HOST_ENABLED` flag. - `docker-compose.yml`: removed `profiles: ["jump-host"]` from the @@ -53,7 +100,7 @@ apps get scoped `secret/apps//*` access. `setup.sh` mints the policies and scoped tokens, `bootstrap.js` writes generated creds into OpenBao, and a new `docs/secrets.md` documents the architecture. -### Changed (theta-env orchestration) +### Changed (theta-suite orchestration) - **`setup.sh`** — after the KV-v2 enable, a new idempotent block writes four OpenBao policies (`sso-broker`, `sso-admin`, `proxy`, `jump-host`) via heredocs, a `sso-broker` token role (`allowed_policies_glob` `user-*`/`app-*`, @@ -146,7 +193,7 @@ and exposes a fixed, role-scoped personal-secrets UI. proxy exit at boot in any deployment without an OpenBao sidecar (standalone Docker, bare metal). 1.0.1 makes `init()` fail-soft on a missing token (warn + continue from `CONF_SECRETS`), matching the documented contract. The - theta-env stack is unaffected (it always sets a scoped `VAULT_TOKEN`). + theta-suite stack is unaffected (it always sets a scoped `VAULT_TOKEN`). ##### v1.13.0 — Changed - **Secrets now load from OpenBao at boot** via @@ -171,7 +218,7 @@ and exposes a fixed, role-scoped personal-secrets UI. jump host exit at boot in any deployment without an OpenBao sidecar (standalone Docker, bare metal). 1.0.1 makes `init()` fail-soft on a missing token (warn + continue from `CONF_SECRETS`), matching the documented - contract. The theta-env stack is unaffected (it always sets a scoped + contract. The theta-suite stack is unaffected (it always sets a scoped `VAULT_TOKEN`). ##### v1.14.0 — Changed @@ -542,7 +589,7 @@ No `setup.sh` or compose change. ## [1.10.0] - 2026-07-27 ### Fixed -- **`bootstrap/bootstrap.js`'s jump-secrets.js template now points jump-host at `ldaps://sso-manager:636`**, not `ldap://sso-manager:389`. The plain-port URL combined with jump-host's `tlsOptions` made `ldapts` attempt implicit TLS against a port serving plaintext LDAP — slapd dropped every connection before any LDAP message parsed, so SSH password login failed for every account, with any password, indistinguishable from a wrong credential. Root-caused by standing up a local jump-host, editing its config, and calling `getUser`/`checkPassword` directly inside the container. **Existing deployments must edit `./config/jump-secrets.js` themselves** (this template only affects fresh bootstraps) — see theta42/theta-env#99. Companion defensive fix: [simpleworkjs/ldap v1.0.2](https://github.com/simpleworkjs/ldap/releases/tag/v1.0.2) now rejects this `ldap://` + `tlsOptions` combination outright. +- **`bootstrap/bootstrap.js`'s jump-secrets.js template now points jump-host at `ldaps://sso-manager:636`**, not `ldap://sso-manager:389`. The plain-port URL combined with jump-host's `tlsOptions` made `ldapts` attempt implicit TLS against a port serving plaintext LDAP — slapd dropped every connection before any LDAP message parsed, so SSH password login failed for every account, with any password, indistinguishable from a wrong credential. Root-caused by standing up a local jump-host, editing its config, and calling `getUser`/`checkPassword` directly inside the container. **Existing deployments must edit `./config/jump-secrets.js` themselves** (this template only affects fresh bootstraps) — see theta42/theta-suite#99. Companion defensive fix: [simpleworkjs/ldap v1.0.2](https://github.com/simpleworkjs/ldap/releases/tag/v1.0.2) now rejects this `ldap://` + `tlsOptions` combination outright. ### Bumped - jump-host -> [v1.7.0](https://github.com/theta42/jump-host/releases/tag/v1.7.0) — adds self-service API tokens (create/list/rotate/revoke from its dashboard); jump-host previously had none. @@ -629,14 +676,14 @@ Jump-host gains **standalone mode**: it can now run with no LDAP directory and no SSO Manager at all, storing users and hosts itself via `@simpleworkjs/orm` (Sequelize; SQLite by default, any Sequelize-supported dialect). This is an app-internal capability, opt-in via -`standalone.enabled` in jump-host's own config — the bundled theta-env stack +`standalone.enabled` in jump-host's own config — the bundled theta-suite stack is unaffected and continues to wire jump-host to the shared LDAP directory and SSO Manager as before. Two bugs were also fixed in jump-host's SSH server: an ephemeral listen port (`0`) was silently overridden back to the default, and session listeners could miss a client's immediate `exec`/`shell` request. -No `setup.sh`, compose, or config change on the theta-env side. +No `setup.sh`, compose, or config change on the theta-suite side. ## [1.5.0] - 2026-07-26 @@ -690,7 +737,7 @@ sso-manager-node 1.5.0: - `GET /api/user/me` now also reports `isAdmin` (membership in `app_sso_admin`), the single effective-rights flag the shared UI shell gates the update banner on. Group-level gating still reads `memberOf`. ### Verified -- Browser-verified against a full theta-env stack (sso-manager + proxy + jump-host): every top-level page renders with a clean console; nav gating is correct for admin and non-admin; `forceLogin`'s onboarding and group gates fire; `val.js` blocks a weak password and accepts a strong one through a real form submit; the DELETE-method forms work; and the OIDC login round trip (authorize with PKCE -> login -> consent -> callback -> token fragment) completes on both OIDC clients. +- Browser-verified against a full theta-suite stack (sso-manager + proxy + jump-host): every top-level page renders with a clean console; nav gating is correct for admin and non-admin; `forceLogin`'s onboarding and group gates fire; `val.js` blocks a weak password and accepts a strong one through a real form submit; the DELETE-method forms work; and the OIDC login round trip (authorize with PKCE -> login -> consent -> callback -> token fragment) completes on both OIDC clients. proxy 1.4.0: @@ -713,7 +760,7 @@ proxy 1.4.0: - Admin-only nav items lost their inline `display: none` in favour of that class, and the brand link points at `/` instead of `#`. ### Verified -- Browser-verified against a full theta-env stack (sso-manager + proxy + jump-host): every top-level page renders with a clean console; nav gating is correct for admin and non-admin; `forceLogin`'s onboarding and group gates fire; `val.js` blocks a weak password and accepts a strong one through a real form submit; the DELETE-method forms work; and the OIDC login round trip (authorize with PKCE -> login -> consent -> callback -> token fragment) completes on both OIDC clients. +- Browser-verified against a full theta-suite stack (sso-manager + proxy + jump-host): every top-level page renders with a clean console; nav gating is correct for admin and non-admin; `forceLogin`'s onboarding and group gates fire; `val.js` blocks a weak password and accepts a strong one through a real form submit; the DELETE-method forms work; and the OIDC login round trip (authorize with PKCE -> login -> consent -> callback -> token fragment) completes on both OIDC clients. jump-host 1.3.0: @@ -736,7 +783,7 @@ jump-host 1.3.0: - `#spa-shell` dropped its inline `margin-top`; `styles.css` already sets it and the shared shell adjusts it when a banner is shown. ### Verified -- Browser-verified against a full theta-env stack (sso-manager + proxy + jump-host): every top-level page renders with a clean console; nav gating is correct for admin and non-admin; `forceLogin`'s onboarding and group gates fire; `val.js` blocks a weak password and accepts a strong one through a real form submit; the DELETE-method forms work; and the OIDC login round trip (authorize with PKCE -> login -> consent -> callback -> token fragment) completes on both OIDC clients. +- Browser-verified against a full theta-suite stack (sso-manager + proxy + jump-host): every top-level page renders with a clean console; nav gating is correct for admin and non-admin; `forceLogin`'s onboarding and group gates fire; `val.js` blocks a weak password and accepts a strong one through a real form submit; the DELETE-method forms work; and the OIDC login round trip (authorize with PKCE -> login -> consent -> callback -> token fragment) completes on both OIDC clients. ## [1.4.0] - 2026-07-25 @@ -834,12 +881,12 @@ sso-manager-node 1.3.2: sso-manager-node 1.3.1: ### Added -- The Directory documentation (`docs/directory.md`) is now surfaced: registered in-app at `/docs/directory` ("Directory & Inventory"), help-linked from the Directory page header, and linked from the docs-site index. Extended with the shared slug conventions (`site_`, `host_` — as used by ldap-client and the theta-env seed), the automatic-registration story (theta-env stack seeding, ldap-client Linux host enrollment), and the API surface (admin at `/api/directory-admin`, read-only graph at `/api/discovery`). +- The Directory documentation (`docs/directory.md`) is now surfaced: registered in-app at `/docs/directory` ("Directory & Inventory"), help-linked from the Directory page header, and linked from the docs-site index. Extended with the shared slug conventions (`site_`, `host_` — as used by ldap-client and the theta-suite seed), the automatic-registration story (theta-suite stack seeding, ldap-client Linux host enrollment), and the API surface (admin at `/api/directory-admin`, read-only graph at `/api/discovery`). ### Changed - Direct LDAP binds are described as first-class, not "legacy", across README, DEPLOYMENT.md, docs, and the Dockerfile: Linux hosts are a primary consumer of the directory (PAM/SSSD login, LDAP-backed `sudo` via `sudoRole`, SSH public keys via openssh-lpk) — exactly what the custom schemas exist for. -### theta-env own changes +### theta-suite own changes ### Added - `CFG_SITE_NAME` in `setup.env` (right below `CFG_DOMAIN`, default `local`): names the SSO directory site the stack registers itself under — slug `site_`, matching the `parentSlug` convention ldap-client-joined Linux hosts use, so they land under the same site. @@ -915,7 +962,7 @@ sso-manager-node: ### Changed - Refreshed all README screenshots (dashboard, users, groups, OAuth apps) against the current UI, and added a new Sites & Replication screenshot. -### theta-env own changes +### theta-suite own changes - Refreshed `docs/images/sso-dashboard.png` and `docs/images/proxy-hosts.png` to match the submodules' updated screenshots. ## [1.1.20] - 2026-07-20 @@ -945,7 +992,7 @@ sso-manager-node: - `routes/index.js` now derives the displayed LDAPS URL from `conf.ldap.ldapsHost`/`ldapsPort` with fallback to the OAuth issuer host. - `docs/configuration.md`, `docs/ldap.md`, `DEPLOYMENT.md`, and `secrets.js.example` document the new `ldapsHost`/`ldapsPort` options and recommended network layouts. -### theta-env own changes +### theta-suite own changes - `setup.env.example` adds optional `CFG_LDAPS_HOST` for the internal LDAPS hostname. - `setup.sh` passes `CFG_LDAPS_HOST` into the generated `./config/sso-secrets.js` as `ldap.ldapsHost`. - `config.example/sso-secrets.js.example` documents `ldap.ldapsHost` / `ldap.ldapsPort`. @@ -989,7 +1036,7 @@ sso-manager-node: ### Fixed - `models/email.js`: fixed from-address template rendering bug. -### theta-env own changes +### theta-suite own changes - `CHANGELOG.md` now embeds the full app-level release notes for each submodule bump, not just links. - `.env.example` no longer ships realistic-looking default passwords; values are clearly placeholders. - `config.example/*.js.example` comments now describe the actual `CONF_SECRETS` env-var loading mechanism. @@ -1129,10 +1176,10 @@ sso-manager-node: - proxy -> [v1.1.7](https://github.com/theta42/proxy/releases/tag/v1.1.7) - sso-manager-node -> [v1.1.6](https://github.com/theta42/sso-manager-node/releases/tag/v1.1.6) -Both: redesigned the GitHub Pages docs site to match each app's own look (dark navbar/footer, Bootstrap 5, Font Awesome) instead of the generic `jekyll-theme-cayman` theme, added a real cross-page nav, SEO (`jekyll-seo-tag` + `jekyll-sitemap`), and mobile-responsive layout. theta-env's own docs site got the same treatment in this release too (see below). +Both: redesigned the GitHub Pages docs site to match each app's own look (dark navbar/footer, Bootstrap 5, Font Awesome) instead of the generic `jekyll-theme-cayman` theme, added a real cross-page nav, SEO (`jekyll-seo-tag` + `jekyll-sitemap`), and mobile-responsive layout. theta-suite's own docs site got the same treatment in this release too (see below). ### Changed -- theta-env's own docs site redesigned the same way -- dark navbar/footer using the shared theta42 logo (this repo has no app UI of its own), cross-page nav, SEO, mobile-responsive. `docs/index.md`'s "More docs" section removed (redundant with the new nav). +- theta-suite's own docs site redesigned the same way -- dark navbar/footer using the shared theta42 logo (this repo has no app UI of its own), cross-page nav, SEO, mobile-responsive. `docs/index.md`'s "More docs" section removed (redundant with the new nav). - Added `docs/_site` to `.gitignore` (missing entirely before). ## [1.1.6] - 2026-07-16 @@ -1165,7 +1212,7 @@ Both: bumped `jq-repeat` 2.0.1 -> 2.1.0. proxy fixed real breakage from the remo ## [1.1.3] - 2026-07-16 ### Added -- `CHANGELOG.md` (this file). Closes [#43](https://github.com/theta42/theta-env/issues/43). +- `CHANGELOG.md` (this file). Closes [#43](https://github.com/theta42/theta-suite/issues/43). ### Bumped - proxy -> [v1.1.3](https://github.com/theta42/proxy/releases/tag/v1.1.3) @@ -1200,23 +1247,23 @@ First tagged release. Establishes the `vX.Y.Z` tag convention going forward. - proxy -> [v1.1.0](https://github.com/theta42/proxy/releases/tag/v1.1.0) - sso-manager-node -> [v1.1.0](https://github.com/theta42/sso-manager-node/releases/tag/v1.1.0) -[Unreleased]: https://github.com/theta42/theta-env/compare/v1.4.0...HEAD -[1.4.0]: https://github.com/theta42/theta-env/compare/v1.3.7...v1.4.0 -[1.1.17]: https://github.com/theta42/theta-env/compare/v1.1.16...v1.1.17 -[1.1.16]: https://github.com/theta42/theta-env/compare/v1.1.15...v1.1.16 -[1.1.15]: https://github.com/theta42/theta-env/compare/v1.1.14...v1.1.15 -[1.1.14]: https://github.com/theta42/theta-env/compare/v1.1.13...v1.1.14 -[1.1.13]: https://github.com/theta42/theta-env/compare/v1.1.12...v1.1.13 -[1.1.12]: https://github.com/theta42/theta-env/compare/v1.1.11...v1.1.12 -[1.1.11]: https://github.com/theta42/theta-env/compare/v1.1.10...v1.1.11 -[1.1.10]: https://github.com/theta42/theta-env/compare/v1.1.9...v1.1.10 -[1.1.9]: https://github.com/theta42/theta-env/compare/v1.1.8...v1.1.9 -[1.1.8]: https://github.com/theta42/theta-env/compare/v1.1.7...v1.1.8 -[1.1.7]: https://github.com/theta42/theta-env/compare/v1.1.6...v1.1.7 -[1.1.6]: https://github.com/theta42/theta-env/compare/v1.1.5...v1.1.6 -[1.1.5]: https://github.com/theta42/theta-env/compare/v1.1.4...v1.1.5 -[1.1.4]: https://github.com/theta42/theta-env/compare/v1.1.3...v1.1.4 -[1.1.3]: https://github.com/theta42/theta-env/compare/v1.1.2...v1.1.3 -[1.1.2]: https://github.com/theta42/theta-env/compare/v1.1.1...v1.1.2 -[1.1.1]: https://github.com/theta42/theta-env/compare/v1.1.0...v1.1.1 -[1.1.0]: https://github.com/theta42/theta-env/releases/tag/v1.1.0 +[Unreleased]: https://github.com/theta42/theta-suite/compare/v1.4.0...HEAD +[1.4.0]: https://github.com/theta42/theta-suite/compare/v1.3.7...v1.4.0 +[1.1.17]: https://github.com/theta42/theta-suite/compare/v1.1.16...v1.1.17 +[1.1.16]: https://github.com/theta42/theta-suite/compare/v1.1.15...v1.1.16 +[1.1.15]: https://github.com/theta42/theta-suite/compare/v1.1.14...v1.1.15 +[1.1.14]: https://github.com/theta42/theta-suite/compare/v1.1.13...v1.1.14 +[1.1.13]: https://github.com/theta42/theta-suite/compare/v1.1.12...v1.1.13 +[1.1.12]: https://github.com/theta42/theta-suite/compare/v1.1.11...v1.1.12 +[1.1.11]: https://github.com/theta42/theta-suite/compare/v1.1.10...v1.1.11 +[1.1.10]: https://github.com/theta42/theta-suite/compare/v1.1.9...v1.1.10 +[1.1.9]: https://github.com/theta42/theta-suite/compare/v1.1.8...v1.1.9 +[1.1.8]: https://github.com/theta42/theta-suite/compare/v1.1.7...v1.1.8 +[1.1.7]: https://github.com/theta42/theta-suite/compare/v1.1.6...v1.1.7 +[1.1.6]: https://github.com/theta42/theta-suite/compare/v1.1.5...v1.1.6 +[1.1.5]: https://github.com/theta42/theta-suite/compare/v1.1.4...v1.1.5 +[1.1.4]: https://github.com/theta42/theta-suite/compare/v1.1.3...v1.1.4 +[1.1.3]: https://github.com/theta42/theta-suite/compare/v1.1.2...v1.1.3 +[1.1.2]: https://github.com/theta42/theta-suite/compare/v1.1.1...v1.1.2 +[1.1.1]: https://github.com/theta42/theta-suite/compare/v1.1.0...v1.1.1 +[1.1.0]: https://github.com/theta42/theta-suite/releases/tag/v1.1.0 diff --git a/README.md b/README.md index 55c623c..ea57c42 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ -# theta-env +# theta-suite The whole theta42 identity + access stack in one repo, brought up with a single command — for home labs and small businesses. -It wires together two projects that already work on their own: +It composes four applications around a shared [OpenBao](https://openbao.org/) +secrets store, brought up with one command: - **[SSO Manager](https://github.com/theta42/sso-manager-node)** — an OIDC provider with a built-in LDAP directory (OpenLDAP) and a web UI for managing @@ -11,12 +12,15 @@ It wires together two projects that already work on their own: - **[theta42/proxy](https://github.com/theta42/proxy)** — an OIDC-protected reverse proxy (OpenResty) that puts any of your apps behind SSO login and can look users up directly in LDAP. +- **[Jump Host](https://github.com/theta42/jump-host)** — directory-driven SSH + access to your machines through one public entry point. +- **[ldap-client](https://github.com/theta42/ldap-client)** — enrolls Linux + hosts into the directory for PAM/SSSD login, sudo, and SSH keys. -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. +All four load their secrets from OpenBao at boot; `setup.sh` automates the +first-run glue so they find each other and the secrets store. -**Documentation:** [https://theta42.github.io/theta-env/](https://theta42.github.io/theta-env/) +**Documentation:** [https://theta42.github.io/theta-suite/](https://theta42.github.io/theta-suite/) ## Screenshots @@ -125,9 +129,10 @@ see browser warnings.) Optional extra ports (only if you need them): - **4443** — alternate HTTPS listener (e.g. if 443 is taken by something else). -- **636** (LDAPS) — only if a legacy app on another machine binds to LDAP - directly over the network. The proxy itself reaches LDAP over the internal - Docker network, so you do **not** need to expose 636 for the stack to work. +- **636** (LDAPS) — for direct-LDAP clients on other machines (Linux hosts + via PAM/SSSD, LDAP-native apps). The proxy itself reaches LDAP over the + internal Docker network, so you do **not** need to expose 636 for the stack + to work. **Do not forward 636 to the public internet.** If you need LAN clients to bind LDAP, set `CFG_LDAPS_HOST=ldap.internal.example.com` (or `sso-manager` for same-host Docker clients) in `setup.env` and use an internal DNS record / cert @@ -143,8 +148,8 @@ standalone (`docker-compose`) both work. ## Quickstart ```bash -git clone --recursive https://github.com/theta42/theta-env.git -cd theta-env +git clone --recursive https://github.com/theta42/theta-suite.git +cd theta-suite cp setup.env.example setup.env # then edit setup.env: set CFG_DOMAIN to your domain ./setup.sh # first run: generates ./config/ from setup.env, builds + bootstraps + starts ``` @@ -233,9 +238,10 @@ override on the command line: `SSO_BIND=127.0.0.1 ./setup.sh`. See - **Proxy mgmt UI**: `https://` — add the Host records you want to protect with OIDC. (First-run fallback: `http://:3000`, reachable on the LAN by default.) -- **Direct LDAP for legacy apps**: bind to `ldaps://:636` as - `cn=admin,` (admin) or `cn=ldapclient,ou=people,` (read-only - service account the bootstrap created). Use LDAPS, not plain LDAP. +- **Direct LDAP for LDAP-native clients and Linux hosts**: bind to + `ldaps://:636` as `cn=admin,` (admin) or + `cn=ldapclient,ou=people,` (read-only service account the bootstrap + created). Use LDAPS, not plain LDAP. ### API tokens (personal access tokens) @@ -346,7 +352,7 @@ docker compose cp proxy:/data/dump.rdb proxy.rdb # Secrets — ./config/ (the seed/fallback; OpenBao is authoritative) cp -a ./config config-backup && chmod 700 config-backup # OpenBao (the authoritative secret store — back up its data volume) -docker run --rm -v theta-env_openbao-data:/data -v "$PWD":/backup alpine \ +docker run --rm -v theta-suite_openbao-data:/data -v "$PWD":/backup alpine \ tar czf /backup/openbao-data.tgz -C /data . ``` @@ -398,9 +404,15 @@ Redis and are preserved by the volume. --- -## Running each project standalone +## Running a component individually -The two submodules work on their own — this repo just composes them: +> The integrated stack (`./setup.sh`) is the supported path. The per-project +> commands below are for the advanced case of running one component on its own +> (separate host, no orchestrator) — you then manage secrets from the +> `config/*-secrets.js` file only (no shared OpenBao) and do the OIDC/LDAP wiring +> by hand. See [docs/standalone.md](docs/standalone.md). + +Each submodule builds and runs on its own: - **SSO Manager alone**: ```bash @@ -486,7 +498,7 @@ exactly in the bootstrap) so the SSO can verify them on bind. ## Repo layout ``` -theta-env/ +theta-suite/ ├── setup.env.example # first-run config template — cp to setup.env, set CFG_DOMAIN ├── config.example/ # committed annotated config templates (copy to ./config/) ├── docker-compose.yml # sso-manager + proxy on one bridge net @@ -507,7 +519,7 @@ tagged release of each app, not whatever's most recently merged upstream. To lock to the pinned commits (offline rebuild, or a deliberate pin), run `SKIP_SUBMODULE_UPDATE=1 ./setup.sh`. -See [CHANGELOG.md](CHANGELOG.md) for what changed in each theta-env release +See [CHANGELOG.md](CHANGELOG.md) for what changed in each theta-suite release (and each submodule's own `CHANGELOG.md` — [proxy](https://github.com/theta42/proxy/blob/master/CHANGELOG.md), [sso-manager-node](https://github.com/theta42/sso-manager-node/blob/master/CHANGELOG.md) diff --git a/bootstrap/bootstrap.js b/bootstrap/bootstrap.js index c46b827..c873ef1 100644 --- a/bootstrap/bootstrap.js +++ b/bootstrap/bootstrap.js @@ -1,6 +1,6 @@ #!/usr/bin/env node /* - * theta-env bootstrap — runs inside the sso-manager container to wire the + * theta-suite bootstrap — runs inside the sso-manager container to wire the * proxy into a (fresh or existing) SSO Manager. Invoked by setup.sh: * * docker compose exec sso-manager node /bootstrap/bootstrap.js @@ -275,7 +275,7 @@ async function listClients(token) { } async function createClient(token, opts) { - const o = opts || { name: CLIENT_NAME, description: 'theta-env proxy (auto-registered)', redirect_uris: [REDIRECT_URI] }; + const o = opts || { name: CLIENT_NAME, description: 'theta-suite proxy (auto-registered)', redirect_uris: [REDIRECT_URI] }; const res = await fetch(`${SSO_INTERNAL}/api/oauth/client`, { method: 'POST', headers: { 'auth-token': token, 'Content-Type': 'application/json' }, @@ -543,7 +543,7 @@ async function mintApiToken(token, name) { const res = await fetch(`${SSO_INTERNAL}/api/api-token`, { method: 'POST', headers: { 'auth-token': token, 'Content-Type': 'application/json' }, - body: JSON.stringify({ name, description: 'theta-env jump host (auto-registered)' }), + body: JSON.stringify({ name, description: 'theta-suite jump host (auto-registered)' }), }); if (!res.ok) throw new Error(`mint API token failed (${res.status}): ${await res.text().catch(() => '')}`); const data = await res.json(); @@ -568,7 +568,7 @@ function writeJumpSecrets(apiToken, oidc, localAdminPass) { const siteName = (sso.stack && sso.stack.siteName) || 'local'; const ldapsHost = (sso.ldap && sso.ldap.ldapsHost) || SSO_HOST; const body = `'use strict'; -// Generated by theta-env bootstrap. The jump host reads this via +// Generated by theta-suite bootstrap. The jump host reads this via // @simpleworkjs/conf (CONF_SECRETS). Binds as cn=admin so it can write the // sshPublicKey attribute (key injection); for a hardened deployment use a // scoped account with an sshPublicKey write-ACL instead (see jump-host README). @@ -653,7 +653,7 @@ async function provisionJumpHost(token) { } else { oidc = await createClient(token, { name: JUMP_CLIENT_NAME, - description: 'theta-env jump host web UI (auto-registered)', + description: 'theta-suite jump host web UI (auto-registered)', redirect_uris: [JUMP_REDIRECT_URI], }); } diff --git a/config.example/proxy-secrets.js.example b/config.example/proxy-secrets.js.example index d53312a..ae39d75 100644 --- a/config.example/proxy-secrets.js.example +++ b/config.example/proxy-secrets.js.example @@ -1,5 +1,5 @@ 'use strict'; -// Example proxy secrets for the theta-env unified stack. Copy to +// Example proxy secrets for the theta-suite 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 diff --git a/config.example/sso-secrets.js.example b/config.example/sso-secrets.js.example index 32c36cc..5a33cdd 100644 --- a/config.example/sso-secrets.js.example +++ b/config.example/sso-secrets.js.example @@ -1,5 +1,5 @@ 'use strict'; -// Example SSO secrets for the theta-env unified stack. Copy to +// Example SSO secrets for the theta-suite 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. diff --git a/docker-compose.yml b/docker-compose.yml index 7bd3bb4..e93adfb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,4 @@ -# theta-env — unified SSO Manager + Proxy. +# theta-suite — unified SSO Manager + Proxy. # # Brings up the two all-in-one images on one bridge network so the proxy can # reach the SSO internally (http://sso-manager:3001 for token/userinfo, diff --git a/docs/_config.yml b/docs/_config.yml index 71d5dc1..d99df5a 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,7 +1,7 @@ -title: theta-env +title: theta-suite description: A unified, one-command SSO Manager + OIDC proxy stack for home labs and small businesses. url: "https://theta42.github.io" -baseurl: "/theta-env" +baseurl: "/theta-suite" logo: /assets/img/theta42.svg lang: en_US @@ -10,10 +10,10 @@ plugins: - jekyll-sitemap github: - repository_url: https://github.com/theta42/theta-env - zip_url: https://github.com/theta42/theta-env/archive/refs/heads/master.zip - tar_url: https://github.com/theta42/theta-env/archive/refs/heads/master.tar.gz - repository_name: theta42/theta-env + repository_url: https://github.com/theta42/theta-suite + zip_url: https://github.com/theta42/theta-suite/archive/refs/heads/master.zip + tar_url: https://github.com/theta42/theta-suite/archive/refs/heads/master.tar.gz + repository_name: theta42/theta-suite nav: - title: Home @@ -32,7 +32,7 @@ nav: page: /standalone.html icon: fa-puzzle-piece - title: Changelog - url: https://github.com/theta42/theta-env/blob/master/CHANGELOG.md + url: https://github.com/theta42/theta-suite/blob/master/CHANGELOG.md icon: fa-list defaults: diff --git a/docs/architecture.md b/docs/architecture.md index 3b123ed..c627972 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,77 +1,131 @@ --- layout: default title: Architecture -description: How theta-env composes the SSO Manager and proxy submodules — the OIDC/LDAP wiring setup.sh generates from one domain. +description: How theta-suite composes the SSO Manager, proxy, jump host, and ldap-client around a shared OpenBao secrets store — the OIDC/LDAP/secrets wiring setup.sh generates from one domain. --- # Architecture [← Back to Home](index.html) -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. +theta-suite is a **composition** repo: it builds four applications from their +git submodules and adds the glue that wires them together — plus a shared +[OpenBao](https://openbao.org/) secrets store — on one Docker network. It +does not fork or patch the components; it composes and configures them. --- -## The three repos +## Components -| Repo | Role | +| Repo / image | Role | |------|------| | [`theta42/sso-manager-node`](https://github.com/theta42/sso-manager-node) | OIDC provider + OpenLDAP directory + web UI. All-in-one image (`Dockerfile.openldap`). | | [`theta42/proxy`](https://github.com/theta42/proxy) | OIDC-protected reverse proxy (OpenResty + Node mgmt app + Redis). All-in-one image (`Dockerfile`). | -| `theta42/theta-env` (this repo) | Composes the two on one Docker network + automates first-run wiring. | +| [`theta42/jump-host`](https://github.com/theta42/jump-host) | Directory-driven SSH jump host (sshd + Node web UI). Image (`Dockerfile`). | +| [`theta42/ldap-client`](https://github.com/theta42/ldap-client) | Enrolls real Linux hosts into the directory (SSSD + AuthorizedKeysCommand). Also the opt-in `ldap-test-host` fixture. | +| `quay.io/openbao/openbao` | Central secrets store (Vault fork), KV-v2 at `secret/`. | +| `theta42/theta-suite` (this repo) | Composes all of the above on one network + automates first-run wiring. | -The two projects are pinned as **git submodules**. `git clone --recursive` -fetches all three in one step; `git submodule update --remote` bumps them. +The four applications are pinned as **git submodules**; OpenBao uses the +upstream image. `git clone --recursive` fetches the submodules in one step; +`git submodule update --remote` bumps them. --- -## The two containers +## The stack ``` - ┌──────────────────────────────────────────────┐ - │ your browser / apps / direct LDAP clients │ - └───────────────┬──────────────────────────────┘ - │ https (:443) ldaps (:636) - ┌─────────▼─────────┐ - │ proxy container │ OpenResty :80/:443/:4443 - │ (OIDC + LDAP) │ Node mgmt app :3000 (localhost only) - │ │ bundled Redis (127.0.0.1:6379) - └─────────┬─────────┘ - ┌─────────────┼────────────────────────────┐ - │ ldaps:636 │ http:3001 (internal) │ OIDC token + userinfo - │ (docker net)│ (docker net, not published)│ (server-to-server) - ▼ ▼ │ - ┌──────────────────────────────┐ │ - │ sso-manager container │◄──────────────────┘ - │ OIDC provider (Express) │ bundled Redis (127.0.0.1:6379) - │ OpenLDAP (slapd) │ web UI :3001 (localhost only) - │ ldaps :636 (published) │ - └───────────────────────────────┘ - ▲ - │ ldaps :636 (published to host) — legacy apps bind directly - │ - ┌──────────────────────────────┐ - │ legacy apps (Gitea, Emby, …)│ - └──────────────────────────────┘ + ┌──────────────────────────────────────────────────────────┐ + │ browser / OIDC apps │ SSH clients │ Linux hosts │ + │ │ │ (PAM/SSSD, sudo, keys) │ + └────────┬────────────┴──────┬──────┴───────────┬───────────┘ + https (:443) ssh (:2222) ldaps (:636) + │ │ │ + ┌────────▼────────┐ ┌──────────▼────────┐ │ + │ proxy │ │ jump-host │ │ + │ OpenResty │ │ sshd :2222 │ │ + │ :80/:443/:4443 │ │ web UI :3002 │ │ + │ mgmt app :3000 │ └────────┬──────────┘ │ + └────────┬─────────┘ │ OIDC + LDAP │ + │ http:3001 (internal)│ via sso-manager │ + ▼ ▼ ▼ + ┌───────────────────────────────────────────────────────┐ + │ sso-manager (Express + OpenLDAP + Redis) │ + │ OIDC provider + LDAP directory │ + │ web UI :3001 (internal) ldaps :636 (published) │ + └───────────────────────────────────────────────────────┘ + ▲ loads secrets at boot (scoped token each) + ┌───────────┴───────────────────┐ + │ openbao (KV-v2 at secret/) │ ← central secrets store + │ :8200 (internal) │ per-user + per-app KV + │ :8080 (operator UI/API) │ + └───────────────────────────────┘ + + ldap-client — enrolls real Linux hosts into the directory above + (PAM/SSSD login, sudo, SSH-key serving); also the + `ldap-test-host` fixture (opt-in: `--profile ldap-test`). ``` -Both containers bundle their **own Redis** (the proxy hardcodes `127.0.0.1:6379` -in three places that ignore config; the SSO's models default to the same). Two -redis instances is the no-source-patch path and is fine at this scale. +All four services bundle their **own Redis** (sso-manager, proxy, jump-host +each run a 127.0.0.1:6379 instance) and share the `openbao` secrets store. +Direct LDAP binds against `:636` are first-class — that's how Linux hosts do +PAM/SSSD login, sudo, and SSH-key serving, and how LDAP-native apps +authenticate — not a fallback path. ### What's exposed, what's not -| Port | On host? | Purpose | -|------|----------|---------| -| `443` (proxy) | **yes** | the public entry point — OIDC login + proxied apps + the SSO/proxy UIs | -| `80` (proxy) | **yes** | HTTP-01 for Let's Encrypt (and redirect to 443) | -| `4443` (proxy) | yes (optional) | alt HTTPS listener | -| `3000` (proxy) | localhost only | proxy mgmt UI/API (first-run convenience; fronted by 443 normally) | -| `636` (sso) | yes | LDAPS for legacy direct-LDAP clients | -| `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) | +| Port | Service | On host? | Purpose | +|------|---------|----------|---------| +| `443` | proxy | **yes** | public entry point — OIDC login + proxied apps + the SSO/proxy UIs | +| `80` | proxy | **yes** | HTTP-01 for Let's Encrypt (and redirect to 443) | +| `4443` | proxy | yes (optional) | alt HTTPS listener | +| `3000` | proxy | localhost/LAN | proxy mgmt UI/API (fronted by 443 normally) | +| `3001` | sso-manager | localhost/LAN | SSO web UI (fronted by the proxy normally) | +| `636` | sso-manager | **yes** | LDAPS for direct-LDAP clients (Linux hosts, LDAP-native apps) | +| `389` | sso-manager | **no** | plain LDAP — internal only (app↔slapd over localhost) | +| `2222` | jump-host | **yes** | SSH front door | +| `3002` | jump-host | **yes** | jump-host web UI/API | +| `8080` | openbao | yes | OpenBao UI/API for the operator (apps use `openbao:8200` internally) | + +--- + +## Secrets (OpenBao) + +Every component loads its secrets from one OpenBao instance at boot, not +from scattered config files. OpenBao runs as the `openbao` container +(`http://openbao:8200` on theta-net, KV-v2 at `secret/`); each app gets a +**scoped token** (never the root token) whose OpenBao policy confines it to +the paths it needs: + +| Service | env var | Policy | Access | +|---------|---------|--------|--------| +| sso-manager | `SSO_VAULT_TOKEN` | `sso-broker` | `secret/sso-manager/conf`, `secret/users/*`, `secret/apps/*`; also mints per-user + per-app tokens | +| proxy | `PROXY_VAULT_TOKEN` | `proxy` | `secret/proxy/conf` (read) | +| jump-host | `JUMP_VAULT_TOKEN` | `jump-host` | `secret/jump-host/conf` (read) | + +At boot each app calls `@simpleworkjs/bao-conf`'s `init()`, which deep-merges +its OpenBao path over the file-loaded `@simpleworkjs/conf` object — so OpenBao +is authoritative at runtime, with the `./config/*-secrets.js` file kept only as +an operator-edited seed and a fail-soft fallback (`init()` is fail-soft, so the +app still boots from the file if OpenBao is unreachable). The proxy and +jump-host consume `conf.oidc.clientSecret` at `require` time, so `init()` +runs *before* their models load (see each app's `bin/www`). + +Beyond app config, OpenBao holds: + +- **Per-user secret storage** — `secret/users//*`, browsed and edited in + the SSO UI's **My Secrets** page. Each user is confined to their own + namespace by a `user-` policy; admins see all of `secret/`. +- **External-app tokens** — an admin mints a scoped `app-` token + (confined to `secret/apps//*`) from the SSO UI's **Apps** tab, so an + external app can read its own secrets over the OpenBao HTTP API. + +`setup.sh` creates the policies + a `sso-broker` token role and mints the +per-app tokens on first run; the root token stays in `setup.env` for +seeding/maintenance only and is never passed to a service container. Full +details — the policy model, the `secret/apps//conf` convention, `curl` ++ Node examples, and the operator rotation procedure — are in +[Secrets](secrets.html). --- @@ -82,7 +136,14 @@ actual work, running **inside the sso-manager container** (bind-mounted read-only from this repo). It's deliberately self-contained — only Node built-ins (`child_process`, `crypto`, `fs`) + global `fetch`, and it reads its inputs from the bind-mounted `./config/sso-secrets.js` + `./config/proxy-secrets.js` -(not from env): +(not from env). + +OpenBao comes up first: `setup.sh` initializes and unseals it, writes the +policies and the `sso-broker` token role, mints the per-app scoped tokens into +`setup.env`, and idempotently seeds `secret/sso-manager/conf`, +`secret/proxy/conf`, and `secret/jump-host/conf` from the corresponding +`./config/*-secrets.js` files. The app containers then start with their scoped +`VAULT_TOKEN`. The SSO/LDAP/OIDC wiring that follows: 1. **Build + start sso-manager**, wait for `/health`. 2. **LDAP service account** — `ldapadd` `cn=ldapclient,ou=people,` (an @@ -97,14 +158,16 @@ inputs from the bind-mounted `./config/sso-secrets.js` + `./config/proxy-secrets 5. **Register the proxy as an OIDC client** via `POST /api/oauth/client` (gated by `app_sso_oauth_admin`, satisfied by step 3). The SSO **generates** the `client_id`/`client_secret` (UUIDs) — supplied creds are ignored — so the - bootstrap writes the generated creds **back into `./config/proxy-secrets.js`** - (the sso-manager mounts `./config` read-write for this; the proxy mounts it - read-only). If `proxy-secrets.js` already holds a `clientId`+`clientSecret` - matching an existing client, they are kept; if the client exists but the file - has no usable secret, the secret is rotated and written back. -6. **Build + start the proxy**, wait for `/health`. The proxy entrypoint points - `CONF_SECRETS` at `./config/proxy-secrets.js`, so `@simpleworkjs/conf` - (≥1.2.0) reads the OAuth creds + LDAP bind creds from the file. + bootstrap writes the generated creds **back into `./config/proxy-secrets.js` + and into OpenBao at `secret/proxy/conf`** (the sso-manager mounts `./config` + read-write for this; the proxy mounts it read-only). If `proxy-secrets.js` + already holds a `clientId`+`clientSecret` matching an existing client, they + are kept; if the client exists but the file has no usable secret, the + secret is rotated and written back. +6. **Build + start the proxy + jump-host**, wait for `/health`. Each + entrypoint points `CONF_SECRETS` at its `./config/*-secrets.js`, then + `@simpleworkjs/bao-conf` overlays the OpenBao path over it (the OAuth + clientSecret + LDAP bind creds come from OpenBao at runtime). 7. **Register `` and `` as Host records in the proxy** — `setup.sh` runs a short script inside the proxy container that calls its Host model directly (`Host.create({host, ip, targetPort, ...})`), rather @@ -120,20 +183,25 @@ inputs from the bind-mounted `./config/sso-secrets.js` + `./config/proxy-secrets `setup.sh` then prints the first-admin login + the public URLs. -### How config reaches the apps (no `.env`) +### How config reaches the apps -All config and secrets live in `./config/` (gitignored, bind-mounted). Each -entrypoint points the `CONF_SECRETS` env var (`@simpleworkjs/conf` >= 1.2.0) -at its file early, before the app starts: +Config and secrets live in two layers: an operator-edited +`./config/*-secrets.js` file (gitignored, bind-mounted) and the OpenBao +overlay over it. Each entrypoint points the `CONF_SECRETS` env var +(`@simpleworkjs/conf` >= 1.2.0) at its file early, before the app starts: ``` CONF_SECRETS=/config/sso-secrets.js (sso-manager, ./config RW) CONF_SECRETS=/config/proxy-secrets.js (proxy, ./config RO) +CONF_SECRETS=/config/jump-secrets.js (jump-host, ./config RO) ``` `@simpleworkjs/conf` loads `conf/base.js → .js → secrets file → app_* -env`, where **env beats the secrets file**. So compose passes **no `app_*` env -vars** (only `NODE_ENV`, `NODE_PORT`) — that makes the secrets file +env`, where **env beats the secrets file**. Then `@simpleworkjs/bao-conf` +deep-merges the app's OpenBao path over the result at boot — OpenBao is the +authoritative runtime layer; the file is the seed and fail-soft fallback. So +compose passes **no `app_*` config env vars** (only `NODE_ENV`, `NODE_PORT`, +`VAULT_ADDR`, and a scoped `VAULT_TOKEN`) — that keeps the secrets file + OpenBao authoritative. The SSO entrypoint reads the few values it needs at startup (LDAP base DN, admin password, JWT secret, cert CN) from `sso-secrets.js` via an in-container `node` call. @@ -151,12 +219,15 @@ end-to-end. ## Idempotency -Re-running `./setup.sh` converges to `./config/`: +Re-running `./setup.sh` converges to `./config/` + OpenBao: - The LDAP service account + admin passwords are **reset to `./config/`**. - Group membership is ensured (add is a no-op if already a member). - The OAuth client is kept if `proxy-secrets.js` already holds its creds; - created or rotated otherwise, and the new creds written back. + created or rotated otherwise, and the new creds written back (to the file + and to OpenBao). +- OpenBao policies, token role, per-app tokens, and `secret//conf` seeds + are ensured (created if absent, left alone if present). So `setup.sh` is safe to re-run after editing `./config/`, after a `docker compose down`, or after restoring from backup. @@ -165,14 +236,18 @@ compose down`, or after restoring from backup. ## Backups and restore -`./setup.sh` auto-snapshots `./config/` + LDAP + both Redis to +`./setup.sh` auto-snapshots `./config/` + LDAP + all the Redis instances to `./backups//` before each rebuild (keeps the last `BACKUP_KEEP`, -default 5). State lives on named volumes (`ldap-data`, `sso-data`, `proxy-data`) -and survives recreation; `down -v` wipes them. Redis is persisted with AOF + -RDB on those volumes. For the full manual-backup + restore runbook (full / -Redis-only / LDAP-only, with the AOF-vs-RDB note), see the *Backups and -restore* section of the [README](https://github.com/theta42/theta-env#backups-and-restore). -Quick LDAP backup: +default 5). State lives on named volumes (`ldap-data`, `ldap-certs`, +`sso-data`, `proxy-data`, `proxy-cache`, `proxy-logs`, `jump-data`, +`jump-redis-data`, `openbao-data`) and survives recreation; `down -v` wipes +them. Redis is persisted with AOF + RDB on those volumes. For the full +manual-backup + restore runbook (full / Redis-only / LDAP-only, with the +AOF-vs-RDB note), see the *Backups and restore* section of the +[README](https://github.com/theta42/theta-suite#backups-and-restore). OpenBao +holds the live secrets, so back up its volume too (`_openbao-data`, +where `` is your clone directory name — `theta-suite` for a fresh +clone). Quick LDAP backup: ```bash docker compose exec sso-manager slapcat -f /etc/openldap/slapd.conf -b "" > backup.ldif diff --git a/docs/index.md b/docs/index.md index b048113..a71fb4e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,20 +4,24 @@ title: Home description: A unified, one-command SSO Manager + OIDC proxy stack for home labs and small businesses. Wires together a self-hosted identity provider and a reverse proxy with one setup.sh. --- -# theta-env +# theta-suite -The whole theta42 identity + access stack in one repo, brought up with a -single command — for home labs and small businesses. +The whole theta42 identity, access, and secrets stack in one repo, brought up +with a single command — for home labs and small businesses. -It wires together two projects that already work on their own — +It composes four applications around a shared secrets store: [SSO Manager](https://theta42.github.io/sso-manager-node/) (OIDC provider + -LDAP directory) and [Proxy](https://theta42.github.io/proxy/) (an -OIDC-protected reverse proxy that can also look users up directly in LDAP) — -and automates the fiddly part: registering the proxy as an OIDC client of the -SSO and pointing it at the right LDAP directory, with hostnames and secrets -generated from one `setup.env`. A third component, the -[Jump Host](https://theta42.github.io/jump-host/), adds directory-driven SSH -access to your machines through one public entry point. +LDAP directory), [Proxy](https://theta42.github.io/proxy/) (an OIDC-protected +reverse proxy that can also look users up directly in LDAP), +[Jump Host](https://theta42.github.io/jump-host/) (directory-driven SSH access +through one public entry point), and +[ldap-client](https://theta42.github.io/ldap-client/) (enrolls your Linux +hosts into the directory for PAM/SSSD, sudo, and SSH keys). All of them read +their secrets at boot from [OpenBao](https://openbao.org/), the central secrets +store. `setup.sh` automates the fiddly part: registering the proxy as an OIDC +client of the SSO, pointing every component at the right LDAP directory and +the OpenBao token it needs, and generating hostnames and secrets from one +`setup.env`. ## Screenshots @@ -31,9 +35,9 @@ The SSO Manager and the proxy it fronts, both stood up by one `./setup.sh` run: ## Why this over running them separately -Each project works standalone, but they only become useful together once the -proxy is registered as an OIDC client of the SSO *and* pointed at the SSO's -LDAP directory — and the domain has to match across half a dozen config +The components are designed to integrate — they're only useful together once +the proxy is registered as an OIDC client of the SSO *and* pointed at the +SSO's LDAP directory — and the domain has to match across half a dozen config fields, or logins silently fail. Doing that by hand is fiddly. `setup.sh` asks for your domain once, generates both apps' config with it filled in everywhere, registers the proxy as an OIDC client automatically, and @@ -46,9 +50,16 @@ snapshots state before every rebuild. - **Proxy** — add the hosts you want to protect with OIDC login. - **LDAPS** for direct binds — Linux hosts (PAM/SSSD, sudo, SSH keys) and LDAP-native apps authenticate against the same directory. +- **ldap-client** — enroll Linux hosts into the directory (PAM/SSSD login, + sudo, SSH keys); the host inventory shows up in the SSO UI and drives + jump-host routing. - **SSH Jump Host** — `ssh uid_-_host@jump.` (WinSCP-friendly) or an interactive picker; access is driven by directory group membership, with a web UI for audit + metrics. +- **Central secrets (OpenBao)** — every component loads its secrets from one + [OpenBao](https://openbao.org/) instance at boot; each user gets personal + secret storage, and admins mint scoped tokens for external apps. See + [Secrets](secrets.html). - **Self-service API tokens** in both apps' UIs, for scripting/CI without a browser session. - **Multi-Site Support (Geo-Location Scaling)** — built-in support for N-Way Multi-Master LDAP replication across physical locations. @@ -57,8 +68,8 @@ snapshots state before every rebuild. ## Get it ```bash -git clone --recursive https://github.com/theta42/theta-env.git -cd theta-env +git clone --recursive https://github.com/theta42/theta-suite.git +cd theta-suite cp setup.env.example setup.env # then edit setup.env: set CFG_DOMAIN to your domain ./setup.sh ``` @@ -66,7 +77,7 @@ cp setup.env.example setup.env # then edit setup.env: set CFG_DOMAIN to your You need **Docker** + **Docker Compose**. `./setup.sh` is idempotent — re-run any time to converge the stack to `./config/`. For the full config reference, architecture, and running each project standalone, see the -**[GitHub repository](https://github.com/theta42/theta-env)**. +**[GitHub repository](https://github.com/theta42/theta-suite)**. ## Related projects @@ -76,3 +87,5 @@ architecture, and running each project standalone, see the stack runs in front of it. - **[Jump Host](https://theta42.github.io/jump-host/)** — the SSH jump host this stack brings up. +- **[ldap-client](https://theta42.github.io/ldap-client/)** — enrolls Linux + hosts into the directory this stack serves. diff --git a/docs/quickstart.md b/docs/quickstart.md index 01c2fb7..7491ac0 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -1,7 +1,7 @@ --- layout: default title: Quickstart -description: Step-by-step first run for theta-env — prerequisites, setup.env, and bringing up the stack with ./setup.sh. +description: Step-by-step first run for theta-suite — prerequisites, setup.env, and bringing up the stack with ./setup.sh. --- # Quickstart Guide @@ -26,8 +26,8 @@ description: Step-by-step first run for theta-env — prerequisites, setup.env, ## 1. Clone ```bash -git clone --recursive https://github.com/theta42/theta-env.git -cd theta-env +git clone --recursive https://github.com/theta42/theta-suite.git +cd theta-suite ``` `--recursive` fetches the two submodules (`sso-manager-node`, `proxy`) in one @@ -143,9 +143,10 @@ already holds its creds). --- -## Direct LDAP for legacy apps +## Direct LDAP for LDAP-native clients and Linux hosts -Legacy apps bind LDAP directly over LDAPS: +LDAP-native apps and Linux hosts (PAM/SSSD, sudo, SSH keys) bind LDAP directly +over LDAPS: ```bash ldapsearch -x -H ldaps://:636 \ @@ -164,7 +165,7 @@ or the admin DN. Use LDAPS (636), not plain LDAP. before each rebuild (keeps the last `BACKUP_KEEP`, default 5). For manual backups and the full restore runbook (full / Redis-only / LDAP-only, with the AOF-vs-RDB note), see the *Backups and restore* section of the -[README](https://github.com/theta42/theta-env#backups-and-restore). Quick LDAP +[README](https://github.com/theta42/theta-suite#backups-and-restore). Quick LDAP backup: ```bash diff --git a/docs/robots.txt b/docs/robots.txt index 0b4daa1..cba2b62 100644 --- a/docs/robots.txt +++ b/docs/robots.txt @@ -1,4 +1,4 @@ User-agent: * Allow: / -Sitemap: https://theta42.github.io/theta-env/sitemap.xml +Sitemap: https://theta42.github.io/theta-suite/sitemap.xml diff --git a/docs/secrets.md b/docs/secrets.md index 3393eb8..4993a2b 100644 --- a/docs/secrets.md +++ b/docs/secrets.md @@ -1,12 +1,12 @@ --- layout: default title: Secrets (OpenBao) -description: theta-env's central secrets architecture — OpenBao as the single store for all app, per-user, and external-app secrets, with scoped tokens and policies. +description: theta-suite's central secrets architecture — OpenBao as the single store for all app, per-user, and external-app secrets, with scoped tokens and policies. --- # Secrets — OpenBao as the central store -theta-env keeps **every secret in one place: [OpenBao](https://openbao.org/)** +theta-suite keeps **every secret in one place: [OpenBao](https://openbao.org/)** (a Vault-community fork), running on the `theta-net` docker network at `http://openbao:8200`. The three apps (SSO Manager, proxy, jump host) load their boot secrets from it; end users get personal per-user secret storage diff --git a/docs/standalone.md b/docs/standalone.md index 0ae170f..71a0d3b 100644 --- a/docs/standalone.md +++ b/docs/standalone.md @@ -1,16 +1,23 @@ --- layout: default title: Standalone -description: Running the SSO Manager or the proxy on their own, without theta-env's orchestration. +description: Running a component individually, without theta-suite's orchestration — an advanced path; the integrated stack is the supported one. --- -# Running each project standalone +# Running a component individually [← Back to Home](index.html) -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. +> **The integrated stack is the supported path.** `./setup.sh` wiring all four +> components together around a shared OpenBao secrets store is what's tested and +> released. The steps below are for the advanced case where you want to run one +> component on its own — a separate host, a different network, or without the +> orchestrator. Running standalone means managing secrets from the +> `config/*-secrets.js` file only (no shared OpenBao) and doing the OIDC/LDAP +> wiring by hand. + +The submodules in this repo are normal clones; you can also clone them directly +from GitHub. Each component builds and runs on its own. --- @@ -114,10 +121,12 @@ See the proxy --- -## Mixing and matching +## Wiring components together by hand -theta-env isn't required to use the two together — the four wiring steps are -documented in both projects' deployment guides: +If you have a specific reason to run the components on separate hosts instead +of through `./setup.sh` (and accept that you lose the shared OpenBao secrets +store), the four wiring steps are documented in both projects' deployment +guides: 1. One Docker network (or reachable hostnames) so the proxy can reach the SSO internally for token/userinfo + LDAPS. @@ -129,8 +138,8 @@ documented in both projects' deployment guides: 4. Point the proxy's `ldap.url` at the SSO's LDAPS + create a dedicated `cn=ldapclient` service account; set the same password as `bindPassword`. -theta-env just automates those four steps with `./setup.sh`. If you prefer to -do them by hand (or want the two on separate hosts), follow the standalone -guides above. +`./setup.sh` exists to do all of this for you — and to add the OpenBao secrets +store, jump host, and ldap-client on top. Unless you need the components on +separate hosts, prefer the integrated stack. [← Back to Home](index.html) \ No newline at end of file diff --git a/pr_url.txt b/pr_url.txt index 202ace2..5401c44 100644 --- a/pr_url.txt +++ b/pr_url.txt @@ -1 +1 @@ -https://github.com/theta42/theta-env/pull/75 +https://github.com/theta42/theta-suite/pull/75 diff --git a/setup.env.example b/setup.env.example index fba575d..0b2927e 100644 --- a/setup.env.example +++ b/setup.env.example @@ -1,5 +1,5 @@ # ───────────────────────────────────────────────────────────────────────────── -# setup.env — first-run setup for the theta-env stack. +# setup.env — first-run setup for the theta-suite 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 diff --git a/setup.sh b/setup.sh index 499af09..b4d7f7e 100755 --- a/setup.sh +++ b/setup.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash # -# theta-env setup — one-command bring-up of the unified SSO Manager + Proxy stack. +# theta-suite setup — one-command bring-up of the unified SSO Manager + Proxy stack. # -# git clone --recursive && cd theta-env +# git clone --recursive && cd theta-suite # cp setup.env.example setup.env # set CFG_DOMAIN 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) @@ -19,7 +19,7 @@ # no manual `git pull` needed first. # # What it does, in order: -# 0. Pull theta-env's own latest commit (fast-forward only) and, if it +# 0. Pull theta-suite's own latest commit (fast-forward only) and, if it # moved, re-exec so the rest of this run uses the new script. Never # blocks the run — skips silently with no upstream, warns and continues # on any other pull failure (offline, local changes). Skip with @@ -141,7 +141,7 @@ parse_kv_file() { done < "$file" } -# ── 0. Self-update: pull theta-env itself, then restart with the new version ── +# ── 0. Self-update: pull theta-suite itself, then restart with the new version ── # Step 1 below only refreshes the proxy/sso-manager-node submodules — it never # updates setup.sh or this repo's own files. Pull the current branch's # upstream (fast-forward only) before anything else, and if it moved, re-exec @@ -151,7 +151,7 @@ parse_kv_file() { # warns (but continues on the current checkout) if the pull fails for any # other reason (offline, local changes that prevent a fast-forward). Skip # entirely with SKIP_SELF_UPDATE=1. -if [[ "${SKIP_SELF_UPDATE:-0}" != "1" && "${THETA_ENV_REEXECED:-0}" != "1" ]] \ +if [[ "${SKIP_SELF_UPDATE:-0}" != "1" && "${THETA_SUITE_REEXECED:-0}" != "1" ]] \ && command -v git >/dev/null 2>&1 && git rev-parse --is-inside-work-tree >/dev/null 2>&1 \ && git rev-parse --abbrev-ref --symbolic-full-name '@{u}' >/dev/null 2>&1 then @@ -161,11 +161,11 @@ then AFTER_REV="$(git rev-parse HEAD)" if [[ "$BEFORE_REV" != "$AFTER_REV" ]]; then AFTER_VER="$(git describe --tags "$AFTER_REV" 2>/dev/null || echo "${AFTER_REV:0:12}")" - info "Updated theta-env (${BEFORE_VER} -> ${AFTER_VER}) — restarting setup.sh with the new version..." - THETA_ENV_REEXECED=1 exec "$0" "$@" + info "Updated theta-suite (${BEFORE_VER} -> ${AFTER_VER}) — restarting setup.sh with the new version..." + THETA_SUITE_REEXECED=1 exec "$0" "$@" fi else - warn "Could not fast-forward theta-env to the latest upstream (offline, or local changes) — continuing with the current checkout." + warn "Could not fast-forward theta-suite to the latest upstream (offline, or local changes) — continuing with the current checkout." fi fi @@ -565,7 +565,7 @@ backup_before_rebuild() { # bind-mount was added), then fall back to reading it inside the container. # Use `docker exec ` (not `docker-compose exec`) so the snapshot works # no matter which compose project brought the container up — the unified - # theta-env stack (project "theta-env") and the standalone submodule stack + # theta-suite stack (project "theta-suite") and the standalone submodule stack # (project "sso-manager-node") both name it "sso-manager". `docker-compose # exec` from the superproject otherwise exits 1 silently (wrong project) and # the snapshot silently no-ops. diff --git a/sso-manager-node b/sso-manager-node index ebb5b2c..21a56dc 160000 --- a/sso-manager-node +++ b/sso-manager-node @@ -1 +1 @@ -Subproject commit ebb5b2c2a7ae9e78c9c3f2726c6cd32dfd0bc6e5 +Subproject commit 21a56dce50f6d91b7eac0df911cf0eba75fc9893 diff --git a/test-integration.sh b/test-integration.sh index 9d7bcdf..61fbd18 100755 --- a/test-integration.sh +++ b/test-integration.sh @@ -1,7 +1,7 @@ #!/bin/bash set -e -echo "=== Starting theta-env Integration Tests ===" +echo "=== Starting theta-suite Integration Tests ===" echo "=> Cleaning up any existing containers and volumes..." docker-compose down -v @@ -11,7 +11,7 @@ echo "=> Running setup.sh to initialize environment..." # setup.sh uses dialog, which requires a terminal, but it falls back to defaults if not interactive? # Actually setup.sh has a dialog UI. Let's just run it or provide a seeded config. # If setup.sh is strictly interactive, we might need to bypass it or provide answers. -# Let's try running docker-compose up directly if setup.sh is too interactive, but the user explicitly said "Make sure setup.sh like your change, then do a full release. Make sure each repo has a current change log, is pushed and and merged." and "Automated testing in theta-env to test integration between all the include projects". +# Let's try running docker-compose up directly if setup.sh is too interactive, but the user explicitly said "Make sure setup.sh like your change, then do a full release. Make sure each repo has a current change log, is pushed and and merged." and "Automated testing in theta-suite to test integration between all the include projects". # Wait, setup.sh has no silent mode out of the box unless we provide answers. echo "=> Initializing OpenBao manually for tests (simulating setup.sh)"