From 67e7e4eb347fb4952845fedc3c60e5bdbbba918e Mon Sep 17 00:00:00 2001 From: William Mantly Date: Fri, 7 Aug 2026 17:20:01 -0400 Subject: [PATCH 1/2] Fix --seed-node-secret path/stdin bugs; docs title casing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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//). 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//, which resolves under the hood to the secret/data/nodes//* 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 --- docs/_config.yml | 2 +- setup.sh | 40 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/docs/_config.yml b/docs/_config.yml index 8e129ea..a9049d1 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -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" diff --git a/setup.sh b/setup.sh index 5cc4150..7c6b91a 100755 --- a/setup.sh +++ b/setup.sh @@ -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//* 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 = [=...] +seed_node_conf() { + local agent_id="$1" name="$2"; shift 2 + [[ -n "$agent_id" && -n "$name" ]] || die "seed_node_conf: need " + # 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,11 @@ 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//* 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/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 +1011,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 =... : 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. From 8451d3f12d6025d37615846174ffc893b603ca14 Mon Sep 17 00:00:00 2001 From: William Mantly Date: Fri, 7 Aug 2026 23:27:00 -0400 Subject: [PATCH 2/2] feat: release v1.46.0 theta-suite with Zero-View secrets engine, theta-agent get-secret CLI, and LDAP tunnel --- CHANGELOG.md | 11 +++++++++++ bootstrap/bootstrap.js | 24 ++++++------------------ setup.sh | 2 ++ 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2da02b..80fe7bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 ` 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**. diff --git a/bootstrap/bootstrap.js b/bootstrap/bootstrap.js index c3a3986..d2826e9 100644 --- a/bootstrap/bootstrap.js +++ b/bootstrap/bootstrap.js @@ -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; diff --git a/setup.sh b/setup.sh index 7c6b91a..08d0652 100755 --- a/setup.sh +++ b/setup.sh @@ -929,6 +929,8 @@ 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//* 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"] }