Compare commits

...

10 Commits

Author SHA1 Message Date
wmantly b799d59672 chore: bump submodules to sso-manager-node v1.31.0 and theta-agent v1.6.0
CI/CD / build-theta-agent (push) Successful in 42s
CI/CD / docker-push (push) Failing after 12s
2026-08-07 23:43:08 -04:00
wmantly f46ef31ca9 Merge pull request #175 from theta42/release/v1.46.0
chore: release v1.46.0
2026-08-07 23:29:38 -04:00
wmantly 8451d3f12d feat: release v1.46.0 theta-suite with Zero-View secrets engine, theta-agent get-secret CLI, and LDAP tunnel
CI/CD / docker-push (push) Failing after 16s
CI/CD / build-theta-agent (push) Successful in 45s
2026-08-07 23:27:00 -04:00
wmantly 67e7e4eb34 Fix --seed-node-secret path/stdin bugs; docs title casing
seed_node_conf() (added for sso-manager-node's node-scoped secrets
engine, DESIGN.md §5) had two bugs that made it fail on every call:

- Path had an extra "data/" segment (secret/data/nodes/<id>/<name>).
  bao kv put takes the mount-relative path and inserts "data/" itself
  for KV v2 -- same convention seed_app_conf already uses just above it
  (secret/${vault_path}, not secret/data/${vault_path}). Fixed to
  secret/nodes/<id>/<name>, which resolves under the hood to the
  secret/data/nodes/<id>/* path api_agent_ops.js's node-scope check
  expects.
- It piped "key=value\n" lines to `bao kv put path -`, but `-` there
  means "read a JSON object from stdin", not KV lines -- failed with
  "invalid key/value pair \"-\"" before ever reaching OpenBao. Fixed to
  pass key=value pairs as ordinary CLI args.

Verified against a real OpenBao round-trip (write via the fixed
function, read back both via the CLI and the same HTTP path the SSO's
node-scope check uses).

docs/_config.yml: "theta-suite" -> "Theta Suite" in the Jekyll site title.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-07 17:20:01 -04:00
wmantly 403e1a3cd4 Merge pull request #174 from theta42/docs/index-page-update
Update docs home page copy
2026-08-07 00:10:34 -04:00
wmantly 28fad8a49e Update docs home page copy
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-07 00:09:42 -04:00
wmantly 70fc19e37f Merge pull request #173 from theta42/docs/readme-update
Update README intro and repo layout diagram
2026-08-06 23:58:15 -04:00
wmantly 5bb2c19fec Update README intro and repo layout diagram
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 23:54:02 -04:00
wmantly ff9b62d331 Merge pull request #172 from theta42/chore/add-site-analytics
Add site analytics tracking script
2026-08-06 23:48:05 -04:00
wmantly fabb250887 Add site analytics tracking script to docs layout
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 23:45:37 -04:00
9 changed files with 152 additions and 99 deletions
+11
View File
@@ -8,6 +8,17 @@ orchestration code; see each submodule's own `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.46.0] - 2026-08-07
Rolls up **theta-agent v1.6.0**, **sso-manager-node v1.31.0**, **jump-host v1.19.1**.
### Added
- **On-demand CLI Secret Fetching.** `theta-agent get-secret <key>` and `theta-agent get-secrets [--env|--json]` for dynamic secret resolution without plaintext files on disk.
- **Resource Secrets Engine & Zero-View Security.** OpenBao KV-v2 encrypted secrets for directory resources with strict regex validation (`^[A-Za-z0-9_]+$`), password generator, and multi-level hierarchy secret inheritance.
- **OpenBao `sso-broker` Policy.** Granted `secret/data/resources/*` and `secret/metadata/resources/*` permissions to `sso-broker`.
- **Zero-Trust LDAP WebSocket Tunnel.** Auto-starts local `/run/theta/ldap.sock` and `127.0.0.1:3890` loopback listeners on managed nodes.
- **Agent Self-Update & Service Control.** Added `theta-agent update` and `theta-agent reinitialize` CLI commands with automated service restarts (`sssd`, `sshd`).
## [v1.45.0] - 2026-08-06
Rolls up **sso-manager-node v1.30.2**, **proxy v1.35.1**, **jump-host v1.19.1**.
+29 -29
View File
@@ -1,8 +1,11 @@
# theta-suite
The whole theta42 identity + access stack in one repo, brought up with a single
command — for home labs and small businesses.
# Theta Suite
Theta Suite is your one-line solution to replacing fragmented, hard-to-wire
authentication setups with a unified security stack. It wires together OIDC
authentication, LDAP user directories, automated host enrollment, and
centralized secret management in a single command. It eliminates the manual
configuration friction so you get secure access, auditability, and multi-site
replication running in seconds.
It composes four applications around a shared [OpenBao](https://openbao.org/)
secrets store, brought up with one command:
@@ -20,7 +23,7 @@ secrets store, brought up with one command:
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-suite/](https://theta42.github.io/theta-suite/)
**Site:** [https://theta42.github.io/theta-suite/](https://theta42.github.io/theta-suite/)
## Screenshots
@@ -37,37 +40,31 @@ The SSO Manager and the proxy it fronts, both stood up by one `./setup.sh` run:
- Registers the proxy as an OIDC client of the SSO.
- Persists submodule commit hashes in `.env` for reproducibility (e.g., `SSO_GIT_COMMIT`, `PROXY_GIT_COMMIT`). This ensures future `docker compose` runs use the same submodule versions.
**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` handles this automatically and snapshots state before every rebuild — so you get a working SSO + proxy stack in one command and a safe way to upgrade it.
## Unified Release Status
-**Phase 1 (oidc-client)**: Complete.
-**Phases 2-5**: Pending (see [roadmap](#)).
```
┌──────────────────────────────────────────────────────────┐
│ browser / OIDC apps │ SSH clients │ Linux hosts
│ │ │ (PAM/SSSD, sudo, keys)
└───────┬────────────┴────────────┴──────────────────────┘
┌──────────────────────────────────────────────────────────
│ 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
▼ ▼
┌────────▼────────┐ ┌────────▼──────────┐ │
│ 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)
│ 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
│ openbao (KV-v2 at secret/) │ ← central secrets store
│ :8200 (internal) │ per-user + per-app KV
│ :8080 (operator UI/API) │
│ :8080 (operator UI/API)
└───────────────────────────────┘
```
@@ -154,7 +151,10 @@ Optional extra ports (only if you need them):
### 4. Docker + Docker Compose
You must use the modern Docker Compose v2 plugin (`docker compose`). The older v1 standalone (`docker-compose`) is not compatible with the BuildKit images generated by this suite and will fail with a `ContainerConfig` KeyError during deployment.
You must use the modern Docker Compose v2 plugin (`docker compose`). The older
v1 standalone (`docker-compose`) is not compatible with the BuildKit images
generated by this suite and will fail with a `ContainerConfig` KeyError during
deployment.
---
+6 -18
View File
@@ -642,24 +642,12 @@ async function seedDirectory(token, clientId, jumpClientId) {
requestable: false,
});
// OpenBao and its renewer sidecar are part of what the stack deploys, so
// they belong in the directory like every other component. Without entries
// their containers had nowhere to attach and showed up as parentless
// discoveries on a fresh install.
await ensure('service', 'OpenBao', 'openbao', host.id, {
address: 'http://openbao:8200',
port: 8200,
subType: 'vault',
icon: 'mdi:safe',
tagline: 'Secrets store for the stack.',
requestable: false,
});
await ensure('service', 'Bao Renewer', 'bao-renewer', host.id, {
subType: 'sidecar',
icon: 'mdi:autorenew',
tagline: 'Renews the stack service tokens against OpenBao.',
requestable: false,
});
// Remove legacy OpenBao/bao-renewer seed resources if present — OpenBao is an
// internal stack service, not a user-facing published directory service.
const openbaoRes = resources.find((r) => r.slug === 'openbao');
const renewerRes = resources.find((r) => r.slug === 'bao-renewer');
if (openbaoRes) await dirDelete(token, `resources/${openbaoRes.id}`);
if (renewerRes) await dirDelete(token, `resources/${renewerRes.id}`);
// SSH jump host service (core component — always registered).
let jumpSvc = null;
+1 -1
View File
@@ -1,4 +1,4 @@
title: theta-suite
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-suite"
+2
View File
@@ -11,6 +11,8 @@
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css">
<link rel="stylesheet" href="{{ '/assets/css/style.css' | relative_url }}">
<script defer src="https://tracking.718it.biz/script.js" data-website-id="a5df0dec-6c54-4c1a-a167-02867a56e2cc"></script>
</head>
<body class="d-flex flex-column min-vh-100">
+60 -48
View File
@@ -4,24 +4,28 @@ 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-suite
# Theta suite
The whole theta42 identity, access, and secrets stack in one repo, brought up
with a single command — for home labs and small businesses.
Theta Suite is your one-line solution to replacing fragmented, hard-to-wire
authentication setups with a unified security stack. It wires together OIDC
authentication, LDAP user directories, automated host enrollment, and
centralized secret management in a single command. It eliminates the manual
configuration friction so you get secure access, auditability, and multi-site
replication running in seconds.
It composes four applications around a shared secrets store:
[SSO Manager](https://theta42.github.io/sso-manager-node/) (OIDC provider +
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`.
## Who This Is For
* **Self-Hosters & Homelab Engineers:** Anyone running local bare metal,
Proxmox, or private VPS nodes who wants enterprise-grade OIDC, multi-master
LDAP, PAM/SSSD host enrollment, and OpenBao secret management without spending
days manually wiring glue code.
* **Small-to-Medium Businesses (SMBs):** Infrastructure teams that need a
unified, directory-driven access plane across both web apps and Linux boxes,
but want to bypass per-user SaaS taxes (Okta, Azure AD) and cloud vendor
lock-in.
* **DevOps & Systems Operators:** Engineers who value idempotent, single-command
deployments (`./setup.sh`) and need a production-grade baseline supporting
zero-trust proxying, SSH jump-host access control, and multi-site replication
out of the box.
## Screenshots
@@ -33,41 +37,49 @@ The SSO Manager and the proxy it fronts, both stood up by one `./setup.sh` run:
*(click either screenshot to view full size)*
## Why this over running them separately
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
snapshots state before every rebuild.
## What you get
- **SSO Manager**, fronted by the proxy under TLS — manage users, groups,
and OAuth clients.
- **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.
- **Hierarchical groups & permissions** — every adopted host and app gets its own
`admin`/`access`/`capability` groups, generated from the Directory; they double
as real POSIX groups for sudo/SSH. See
[Group & Permission Model](GROUPS.html).
- **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.<domain>` (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
- **Unified SSO Manager**: An OpenID Connect (OIDC) provider and OAuth 2.0
authorization server fronted by TLS. Includes a web dashboard for managing
users, groups, and OAuth apps, plus automated invitation and password reset
flows.
- **Identity-Aware Reverse Proxy**: Intercepts HTTP/HTTPS traffic to protect
upstream applications with OIDC login and direct LDAP group authorization,
featuring automatic TLS certificate issuance and automated host routing.
- **Embedded LDAPS Directory**: A bundled OpenLDAP core acting as your single
source of truth for POSIX accounts, SSH public keys, and sudo roles. Native
apps, legacy infrastructure, and Linux machines authenticate directly over
encrypted LDAPS (port 636) or StartTLS.
- **Hierarchical Directory Group & Permission Model**: Every adopted application
and machine automatically inherits dedicated `admin`, `access`, and
`capability` groups generated directly from the LDAP directory. These map
cleanly to real POSIX groups for fine-grained sudo and SSH privilege controls.
See [Group & Permission Model](GROUPS.html).
- **Automated Linux Host Enrollment (ldap-client)**: A lightweight host agent
that enrolls Linux machines into the central directory. It configures system
PAM/SSSD for login, applies sudo policies, distributes SSH public keys, and
registers host telemetry in the primary inventory dashboard.
- **Directory-Driven SSH Jump Host**: A centralized bastion host that routes
inbound terminal traffic (`ssh uid_-_host@jump.<domain>`) using active
directory group memberships. Supports WinSCP, file transfers, interactive
host pickers, and a dedicated audit interface for tracking user sessions and
connection metrics.
- **Central Secrets Engine (OpenBao integration)**: Bootstraps every component
against an embedded [OpenBao](https://openbao.org/) instance to load tokens
and cryptographic keys at runtime. Provides per-user secret vaults and enables
administrators to mint scoped API tokens for external services. 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.
- **Multi-target load balancing** — built-in proxy support for round-robin load balancing across multiple application servers.
- **Self-Service & CI/CD API Tokens**: Granular, personal access token
management built directly into the web interface, allowing operators to drive
system administration and automation pipelines programmatically without an
active browser session.
- **Multi-Site Geo-Replication**: Built-in support for N-Way Multi-Master LDAP
replication, allowing directory states to sync across geographically separated
physical hardware or remote data centers for high availability and low-latency
local reads.
- **Multi-Target Load Balancing**: Native reverse-proxy load balancing that
distributes traffic across multiple application backends using customizable
health checks and round-robin strategies.
## Get it
+41 -1
View File
@@ -78,10 +78,13 @@ die() { error "$*"; exit 1; }
# (stale policies/tokens causing vault 403s). The Redis vault-token cache is
# flushed once sso-manager is back up (see the OpenBao bootstrap section).
RESET_OPENBAO=0
SEED_NODE_SECRET=0
SEED_NODE_ARGS=()
for arg in "$@"; do
case "$arg" in
--reset-openbao) RESET_OPENBAO=1 ;;
*) warn "unknown argument: $arg (ignored)" ;;
--seed-node-secret) SEED_NODE_SECRET=1 ;;
*) if [[ "$SEED_NODE_SECRET" == 1 ]]; then SEED_NODE_ARGS+=("$arg"); else warn "unknown argument: $arg (ignored)"; fi ;;
esac
done
@@ -878,6 +881,29 @@ seed_app_conf() {
|| warn " could not seed secret/${vault_path} (continuing — app will use its file fallback)"
}
# Seed a node-scoped secret for a theta-agent (DESIGN.md §5). Node secrets live
# at secret/data/nodes/<agent-id>/* and are read by the agent (via the SSO's
# /api/v1/agent/secrets) on behalf of 3rd-party apps on the host. Agent ids are
# minted at enrollment, so this is a helper the operator calls per node, not a
# boot-time seed:
# ./setup.sh --seed-node-secret <agent-id> <name> <key>=<value> [<key>=<value>...]
seed_node_conf() {
local agent_id="$1" name="$2"; shift 2
[[ -n "$agent_id" && -n "$name" ]] || die "seed_node_conf: need <agent-id> <name>"
# CLI paths are mount-relative (no "data/" segment -- the CLI inserts that
# itself for KV v2, same as seed_app_conf's "secret/${vault_path}" above).
# The HTTP API path api_agent_ops.js checks against (secret/data/nodes/...)
# is what this resolves to underneath.
local path="secret/nodes/${agent_id}/${name}"
if bao_run kv get "$path" >/dev/null 2>&1; then
info " ${path} already seeded — keeping."
return 0
fi
info "Seeding ${path}..."
docker exec -e BAO_TOKEN="$VAULT_TOKEN" openbao bao kv put "$path" "$@" >/dev/null \
|| die "failed to seed ${path}"
}
info "Configuring OpenBao policies..."
# sso-broker — sso's authority to read/write its own conf, mint per-user and
# per-app tokens (auth/token/create/sso-broker), and create the matching
@@ -900,6 +926,13 @@ path "secret/metadata/plugins/*" { capabilities = ["list", "read", "delete"] }
# a key that changes on every boot makes signature verification meaningless.
path "secret/data/agent/*" { capabilities = ["create", "read", "update", "delete", "list"] }
path "secret/metadata/agent/*" { capabilities = ["list", "read", "delete"] }
# Node-scoped secrets for theta-agent (DESIGN.md §5): each node reads only its
# own secret/data/nodes/<agent-id>/* subtree via the SSO's /api/v1/agent/secrets
# endpoint. The SSO (sso-broker) must be able to read them on the agent's behalf.
path "secret/data/resources/*" { capabilities = ["create", "read", "update", "delete", "list"] }
path "secret/metadata/resources/*" { capabilities = ["list", "read", "delete"] }
path "secret/data/nodes/*" { capabilities = ["create", "read", "update", "delete", "list"] }
path "secret/metadata/nodes/*" { capabilities = ["list", "read", "delete"] }
path "auth/token/create/sso-broker" { capabilities = ["update"] }
path "auth/token/create/sso-app" { capabilities = ["update"] }
path "auth/token/renew-accessor" { capabilities = ["update"] }
@@ -980,6 +1013,13 @@ info " policies: sso-broker, sso-admin, proxy, jump-host (+ per-user/app cre
info " token roles: sso-broker (user-*/app-*/sso-admin, 24h period), sso-app (app-*, 768h period), theta-svc (service tokens, 768h period)"
info " app tokens: SSO_VAULT_TOKEN, PROXY_VAULT_TOKEN, JUMP_VAULT_TOKEN in .env (periodic; renewed by bao-renewer)"
# --seed-node-secret <agent-id> <name> <key>=<value>... : seed a node-scoped
# secret for a theta-agent (DESIGN.md §5). Runs after OpenBao is configured so
# the sso-broker policy (which grants secret/data/nodes/*) is in place.
if [[ "$SEED_NODE_SECRET" == 1 ]]; then
seed_node_conf "${SEED_NODE_ARGS[@]}"
fi
# bao-renewer: renews the three periodic service tokens every 12h so they never
# hit their period boundary while the stack is running. Recreated (not just
# started) so it always picks up freshly re-minted tokens from .env.