Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b799d59672 | |||
| f46ef31ca9 | |||
| 8451d3f12d | |||
| 67e7e4eb34 | |||
| 403e1a3cd4 | |||
| 28fad8a49e | |||
| 70fc19e37f | |||
| 5bb2c19fec | |||
| ff9b62d331 | |||
| fabb250887 |
@@ -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**.
|
||||
|
||||
@@ -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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Vendored
+6
-18
@@ -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
@@ -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"
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
+1
-1
Submodule sso-manager-node updated: 6e748bfa66...5b1302bc6f
+1
-1
Submodule theta-agent updated: d128807431...aa1f85a9d5
Reference in New Issue
Block a user