Compare commits

..

4 Commits

Author SHA1 Message Date
wmantly 0c4abd82be Merge pull request #157 from theta42/release/v1.35.14-token-lifecycle
CI/CD / build-theta-agent (push) Successful in 43s
CI/CD / docker-push (push) Failing after 17s
feat: OpenBao token lifecycle + bump sso to v1.23.0 (v1.35.14)
2026-08-04 00:25:16 -04:00
wmantly c27e8c7867 feat: OpenBao token lifecycle + bump sso to v1.23.0 (v1.35.14)
- theta-svc token role (periodic 768h): SSO/PROXY/JUMP_VAULT_TOKEN now minted
  through it; ensure_token renews periodic tokens on every setup.sh re-run and
  detects/revokes/re-mints valid-but-non-periodic tokens from older installs.
- bao-renewer sidecar (docker-compose): renews the three service tokens every
  12h while the stack runs.
- sso-app token role (periodic 768h) + sso-broker policy grants for
  auth/token/create/sso-app and renew/revoke/lookup-accessor.
- docs/secrets.md rewritten around the new lifecycle.
- Bump sso-manager-node gitlink to v1.23.0 (real vault-403 fix + app-token
  lifecycle).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-04 00:21:51 -04:00
wmantly 9750413178 Merge pull request #156 from theta42/release/v1.35.13-agents-page
CI/CD / build-theta-agent (push) Successful in 44s
CI/CD / docker-push (push) Failing after 18s
feat: bump sso to v1.22.0 (Agents page + secure /api/agent) (v1.35.13)
2026-08-03 23:16:39 -04:00
wmantly c1a9d8f059 feat: bump sso to v1.22.0 (Agents page + secure /api/agent) (v1.35.13) 2026-08-03 23:15:19 -04:00
5 changed files with 156 additions and 22 deletions
+18
View File
@@ -8,6 +8,24 @@ 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.35.14] - 2026-08-04
### Fixed
- **The recurring `/vault` 403 "permission denied" is actually dead this time — it was never a policy problem.** sso's `/api/vault` proxy declared its request hook with http-proxy-middleware **v3** syntax (`on: { proxyReq }`) while the app installs HPM **v2**, which silently ignores the unknown key — so `X-Vault-Token` was never injected and every vault call reached OpenBao unauthenticated. All the policy work of v1.35.10/v1.31.1 was correct and is unchanged; the requests just never carried a token. Ships as **sso v1.23.0** (see its changelog for the companion `fixRequestBody` header-ordering fix and the initORM schema heal that unbreaks the plugin scheduler on upgraded databases).
### Added
- **OpenBao token lifecycle — nothing expires by surprise anymore.**
- New **`theta-svc` token role** (periodic 768h): `SSO/PROXY/JUMP_VAULT_TOKEN` are now minted through it instead of as plain orphan tokens with a hard ~32-day death date. `ensure_token` renews periodic tokens on every `setup.sh` re-run and detects, revokes, and re-mints valid-but-non-periodic tokens from older installs (detection is the token's `role` — OpenBao token lookup does not expose a `period` field).
- New **`bao-renewer` sidecar** (docker-compose): renews the three service tokens every 12h while the stack runs, logging each result. Recreated on every `setup.sh` run so it always holds the current tokens.
- New **`sso-app` token role** (periodic 768h): external-app tokens minted from the vault UI go through it instead of the broker's 24h role, and sso now stores each app token's *accessor* and auto-renews it (boot + every 6h) — a downstream app's credential stays valid as long as sso runs, with no renewal code in the downstream app.
- `sso-broker` policy gained `update` on `auth/token/create/sso-app` and `auth/token/renew-accessor`/`revoke-accessor`/`lookup-accessor`.
- `docs/secrets.md` rewritten around the new lifecycle (roles table, renewal layers, disaster recovery).
## [v1.35.13] - 2026-08-04
### Added
- **sso v1.22.0** — new **Agents** page (live list of connected theta-agent hosts with CPU/RAM/disk/ZFS/GPU telemetry + online status) and a security fix gating the `/api/agent` REST routes. Bumped the sso-manager-node gitlink to v1.22.0.
## [v1.35.12] - 2026-08-04
### Fixed
+36
View File
@@ -238,6 +238,42 @@ services:
- ./config/ldap-test-host.vars:/config/ldap.vars:ro
- ./config/ldap-ca.crt:/config/ldap-ca.crt:ro
# Renews the three periodic service tokens (theta-svc role, 768h period)
# every 12h. Periodic tokens live forever ONLY while something renews them —
# this sidecar is that something, so the stack survives arbitrarily long
# uptimes and the tokens in .env never silently expire. If a token is missing
# or already dead it just logs and moves on (setup.sh re-mints on next run).
bao-renewer:
image: quay.io/openbao/openbao:latest
container_name: bao-renewer
restart: unless-stopped
depends_on:
- openbao
environment:
- BAO_ADDR=http://openbao:8200
- SSO_VAULT_TOKEN=${SSO_VAULT_TOKEN:-}
- PROXY_VAULT_TOKEN=${PROXY_VAULT_TOKEN:-}
- JUMP_VAULT_TOKEN=${JUMP_VAULT_TOKEN:-}
entrypoint: ["/bin/sh", "-c"]
command:
- |
renew() {
if [ -z "$$2" ]; then return 0; fi
if BAO_TOKEN="$$2" bao token renew > /dev/null 2>&1; then
echo "[bao-renewer] renewed $$1"
else
echo "[bao-renewer] FAILED to renew $$1 (expired/revoked? re-run setup.sh to re-mint)"
fi
}
while true; do
renew SSO_VAULT_TOKEN "$$SSO_VAULT_TOKEN"
renew PROXY_VAULT_TOKEN "$$PROXY_VAULT_TOKEN"
renew JUMP_VAULT_TOKEN "$$JUMP_VAULT_TOKEN"
sleep 43200
done
networks:
- theta-net
openbao:
image: quay.io/openbao/openbao:latest
container_name: openbao
+36 -10
View File
@@ -60,23 +60,49 @@ never passed to a service container.
| Policy | Capabilities | Held by |
|---|---|---|
| `sso-broker` | read/write `secret/sso-manager/conf`, `secret/users/*`, `secret/apps/*`, `secret/plugins/*`; `update` on `auth/token/create/sso-broker`; `update` on `sys/policies/acl/user-*`, `app-*`, `sso-admin` | SSO (`SSO_VAULT_TOKEN`) |
| `sso-broker` | read/write `secret/sso-manager/conf`, `secret/users/*`, `secret/apps/*`, `secret/plugins/*`; `update` on `auth/token/create/sso-broker` + `create/sso-app` and `auth/token/renew-accessor`/`revoke-accessor`/`lookup-accessor`; `update` on `sys/policies/acl/user-*`, `app-*`, `sso-admin` | SSO (`SSO_VAULT_TOKEN`) |
| `sso-admin` | read/write/list all of `secret/*` | admin UI sessions (minted by the broker) |
| `proxy` | read `secret/proxy/conf` | proxy (`PROXY_VAULT_TOKEN`) |
| `jump-host` | read `secret/jump-host/conf` | jump host (`JUMP_VAULT_TOKEN`) |
| `user-<uid>` | read/write `secret/users/<uid>/*` | per-user tokens (minted lazily by the broker) |
| `app-<name>` | read/write `secret/apps/<name>/*` | per-external-app tokens (minted by an admin) |
**Token role `sso-broker`**`allowed_policies=sso-admin`,
`allowed_policies_glob=user-*,app-*`, orphan, renewable, `token_period=24h`.
The SSO mints per-user, per-admin, and per-app tokens *through* this role at
runtime, so it never needs the root token to issue scoped access.
**Token roles** — three, all orphan + renewable:
The three per-app tokens (`SSO_VAULT_TOKEN`, `PROXY_VAULT_TOKEN`,
`JUMP_VAULT_TOKEN`) are minted orphan + renewable and stored in `./.env` by
`setup.sh`. They use OpenBao's default service-token TTL; if one expires,
re-run `./setup.sh` and the `ensure_token` helper re-mints it (the old one
expires on its own). Automated renewal is a planned follow-up, not yet built.
- `sso-broker``allowed_policies=sso-admin`, `allowed_policies_glob=user-*,app-*`,
`token_period=24h`. The SSO mints per-user and per-admin tokens *through*
this role at runtime, so it never needs the root token to issue scoped
access. The 24h period is fine here because the broker re-mints these from
its Redis cache transparently.
- `sso-app``allowed_policies_glob=app-*`, `token_period=768h`. External-app
tokens minted from the vault UI's Apps tab go through this role: they are
long-lived credentials, so they get a monthly period instead of a daily one.
- `theta-svc``allowed_policies=sso-broker,proxy,jump-host`,
`token_period=768h`. The services' own tokens (below).
### Token lifecycle — nothing expires by surprise
Periodic tokens never hit a max TTL, but they die if nothing renews them
inside a period window. Renewal is automated at every layer:
- **Service tokens** (`SSO_VAULT_TOKEN`, `PROXY_VAULT_TOKEN`,
`JUMP_VAULT_TOKEN`, minted via `theta-svc`, stored in `./.env`): the
`bao-renewer` sidecar (docker-compose) renews all three every 12 hours, and
every `setup.sh` re-run renews them too. A valid-but-non-periodic token from
an older install is detected, revoked, and re-minted as periodic on the next
`setup.sh` run.
- **External-app tokens** (minted in the SSO vault UI): the SSO stores each
token's **accessor** (which can renew/revoke but not authenticate) and
renews it every 6 hours and at boot — a downstream app's credential stays
valid as long as the SSO is running, with no renewal code in the downstream
app. Re-minting an app's token revokes the previous one via its accessor, so
exactly one credential per app is ever live.
- **Per-user / admin tokens**: 24h TTL by design; the broker re-mints them
transparently, so there is nothing to renew.
Worst case (the whole stack was down for >32 days): re-run `./setup.sh` — it
re-mints anything that lapsed; external-app tokens are re-minted from the
Apps tab (the app's policy and stored secrets are kept).
## Seeding
+65 -11
View File
@@ -810,17 +810,34 @@ ensure_policy() {
}
# Mint an orphan, renewable token for `policy` and persist it to .env as `key`,
# OR reuse the token already in .env if it is still valid (re-mint on expiry).
# Mint a PERIODIC service token (theta-svc role: orphan, renewable, 768h
# period) for `policy` and persist it to .env as `key`. Periodic tokens have no
# max-TTL death date — each renewal resets the clock — unlike the plain orphan
# tokens minted before this (creation_ttl 768h, dead ~32 days after mint no
# matter what). The bao-renewer sidecar renews them every 12h while the stack
# runs, and every setup.sh re-run renews here too. A valid-but-non-periodic
# token from an older setup.sh is revoked and re-minted as periodic.
ensure_token() {
local key="$1" policy="$2" existing tok
local key="$1" policy="$2" existing tok lookup
existing="$(env_get "$key")"
if [[ -n "$existing" ]] && docker exec -e BAO_TOKEN="$existing" openbao bao token lookup >/dev/null 2>&1; then
info " ${key} already minted + valid — keeping."
return 0
if [[ -n "$existing" ]]; then
lookup="$(docker exec -e BAO_TOKEN="$existing" openbao bao token lookup -format=json 2>/dev/null || true)"
if [[ -n "$lookup" ]]; then
# Periodic = minted through the theta-svc role. (OpenBao token lookup
# does not expose a `period` field — the role is the reliable marker;
# renewal behavior confirms the 768h period resets past the original
# creation TTL.)
if echo "$lookup" | grep -q '"role": *"theta-svc"'; then
info " ${key} already minted + periodic (theta-svc) — renewing to reset its clock."
docker exec -e BAO_TOKEN="$existing" openbao bao token renew >/dev/null 2>&1 || true
return 0
fi
info " ${key} is valid but NOT periodic (pre-theta-svc mint; dies at its max TTL) — revoking + re-minting."
bao_run token revoke "$existing" >/dev/null 2>&1 || true
fi
fi
info " minting ${key} (policy=${policy})..."
tok="$(bao_run token create -policy="$policy" -orphan=true -field=token)" \
info " minting ${key} (policy=${policy}, role=theta-svc, periodic 768h)..."
tok="$(bao_run token create -role=theta-svc -policy="$policy" -field=token)" \
|| die "failed to mint ${key} (policy=${policy})"
env_upsert "$key" "$tok"
}
@@ -858,6 +875,10 @@ path "secret/metadata/apps/*" { capabilities = ["list", "read", "delete"] }
path "secret/data/plugins/*" { capabilities = ["create", "read", "update", "delete", "list"] }
path "secret/metadata/plugins/*" { 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"] }
path "auth/token/revoke-accessor" { capabilities = ["update"] }
path "auth/token/lookup-accessor" { capabilities = ["update"] }
path "sys/policies/acl/user-*" { capabilities = ["create", "read", "update", "delete", "list"] }
path "sys/policies/acl/app-*" { capabilities = ["create", "read", "update", "delete", "list"] }
path "sys/policies/acl/sso-admin" { capabilities = ["create", "read", "update", "delete", "list"] }
@@ -896,15 +917,48 @@ else
info " token role sso-broker already exists — keeping."
fi
# sso-app token role: external-app tokens minted from the sso vault UI. Periodic
# 768h (NOT the broker's 24h) — an app token is a long-lived credential; with a
# 24h period any downstream app that didn't renew daily silently died. A 768h
# period keeps it alive as long as the app renews (or is re-minted) at least
# monthly: `bao token renew-self` / POST /v1/auth/token/renew-self.
info "Configuring sso-app token role..."
if ! bao_run read auth/token/roles/sso-app >/dev/null 2>&1; then
docker exec -i -e BAO_TOKEN="$VAULT_TOKEN" openbao bao write auth/token/roles/sso-app - <<'JSON' >/dev/null
{"allowed_policies_glob":["app-*"],"orphan":true,"renewable":true,"token_period":"768h"}
JSON
else
info " token role sso-app already exists — keeping."
fi
# theta-svc token role: the services' own tokens (SSO/PROXY/JUMP_VAULT_TOKEN).
# Periodic 768h so they can be renewed forever (the bao-renewer sidecar renews
# every 12h; each setup.sh re-run renews too). allowed_policies is exact-match:
# exactly the three service policies, nothing else.
info "Configuring theta-svc token role..."
if ! bao_run read auth/token/roles/theta-svc >/dev/null 2>&1; then
docker exec -i -e BAO_TOKEN="$VAULT_TOKEN" openbao bao write auth/token/roles/theta-svc - <<'JSON' >/dev/null
{"allowed_policies":["sso-broker","proxy","jump-host"],"orphan":true,"renewable":true,"token_period":"768h"}
JSON
else
info " token role theta-svc already exists — keeping."
fi
info "Minting per-app OpenBao tokens (stored in .env, passed to containers as VAULT_TOKEN)..."
ensure_token SSO_VAULT_TOKEN sso-broker
ensure_token PROXY_VAULT_TOKEN proxy
ensure_token JUMP_VAULT_TOKEN jump-host
info "OpenBao secrets configured:"
info " policies: sso-broker, sso-admin, proxy, jump-host (+ per-user/app created lazily by sso)"
info " token role: sso-broker (mints user-*/app-*/sso-admin tokens, 24h period)"
info " app tokens: SSO_VAULT_TOKEN, PROXY_VAULT_TOKEN, JUMP_VAULT_TOKEN in .env"
info " policies: sso-broker, sso-admin, proxy, jump-host (+ per-user/app created lazily by sso)"
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)"
# 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.
info "Starting bao-renewer (service-token renewal sidecar)..."
"${COMPOSE[@]}" up -d --force-recreate bao-renewer
# ── 4. Start SSO Manager, wait for health ─────────────────────────────────────
# SSO_GIT_COMMIT: sso-manager-node is a git submodule here, so its .git is a