From 5001269b374c35cab5cd2b6984680dd9c29bbad7 Mon Sep 17 00:00:00 2001 From: Emi Griffith Date: Thu, 30 Jul 2026 19:49:28 -0700 Subject: [PATCH] openbao: derive the AppRole credential path per environment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit render-secrets-openbao.sh resolved the AppRole file to a single host-wide default, /etc/thermograph/openbao-approle — which is prod's. On vps2 that made beta's render authenticate as tg-prod, and tg-host-prod.hcl denies thermograph/data/env/beta, so beta could never render from OpenBao. It surfaces as `cannot read thermograph/env/beta`: the policy working correctly against the wrong identity. env-topology.sh exists precisely because vps2 runs two environments and one host-wide marker cannot answer "which environment is this". The AppRole path is the same question and had been given the same host-wide answer. It is now derived per environment alongside host, checkout, branch, ports and DB role, and the renderer resolves THERMOGRAPH_BAO_APPROLE, then TG_BAO_APPROLE, then the conventional path — the same precedence render-secrets.sh:44 already uses for the backend selector. Only beta differs. prod and dev keep the conventional path; dev is alone on vps1 so there is no collision there. Beta parity passes when the path is supplied by hand (24 keys = 8 + 16 shared); this makes it automatic. Derivation confirmed by sourcing env-topology.sh for all three environments. --- infra/deploy/env-topology.sh | 16 +++++++++++++++- infra/deploy/render-secrets-openbao.sh | 8 +++++++- infra/openbao/README.md | 9 ++++----- infra/openbao/bootstrap.sh | 5 ++--- 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/infra/deploy/env-topology.sh b/infra/deploy/env-topology.sh index 9e2b7c9..d57d148 100644 --- a/infra/deploy/env-topology.sh +++ b/infra/deploy/env-topology.sh @@ -76,6 +76,16 @@ thermograph_topology() { # for the reason that marker was demoted to a fallback in the first place: vps2 runs # prod and beta side by side, and one host-wide file cannot name two backends. TG_SECRETS_BACKEND=sops + # Which AppRole credential file render-secrets-openbao.sh authenticates with. Same + # reasoning as TG_SECRETS_BACKEND above, and the same trap: vps2 renders TWO + # environments, so a single host-wide default cannot serve both. prod and dev each + # get the conventional path (dev is alone on vps1, so there is no collision there); + # beta is the one that must differ, because it shares a filesystem with prod. + # + # Without this, beta's render falls back to prod's credentials and tg-host-prod.hcl + # correctly denies thermograph/data/env/beta — a 403 at deploy time on the box that + # runs prod. + TG_BAO_APPROLE=/etc/thermograph/openbao-approle case "$env_name" in prod) @@ -116,6 +126,10 @@ thermograph_topology() { # therefore prod's blast radius too (see .claude/hooks/prod-guard.sh). TG_SSH_HOST=169.58.46.181 TG_SSH_TARGET=agent@169.58.46.181 + # The one environment that cannot use the default AppRole path: it shares a + # filesystem with prod, so it needs its own credential file to authenticate as + # tg-beta rather than tg-prod. bootstrap-policies.sh installs it here. + TG_BAO_APPROLE=/etc/thermograph/openbao-approle-beta # A SECOND checkout on the same box. Separate from prod's so the two can # sit on different commits of this repo, and so `git reset --hard` in one # deploy can never yank the tree out from under the other's running @@ -228,7 +242,7 @@ thermograph_topology() { export TG_LB_HTTP_PORT TG_LB_FE_PORT TG_DB_NAME TG_DB_USER export TG_TAGS_FILE TG_LOCK_FILE TG_BIND_ADDR TG_SKIP_COMMON export TG_SVC_PREFIX TG_DATA_NETWORK TG_DB_SERVICE TG_POST_DEPLOY - export TG_SECRETS_BACKEND + export TG_SECRETS_BACKEND TG_BAO_APPROLE export TG_SSH_HOST TG_SSH_TARGET } diff --git a/infra/deploy/render-secrets-openbao.sh b/infra/deploy/render-secrets-openbao.sh index 82c449c..aaf606d 100644 --- a/infra/deploy/render-secrets-openbao.sh +++ b/infra/deploy/render-secrets-openbao.sh @@ -27,7 +27,13 @@ thermograph_openbao_source() { local env_name="${1:?env name required}" local addr="${THERMOGRAPH_BAO_ADDR:-https://10.10.0.1:8200}" local mount="${THERMOGRAPH_BAO_MOUNT:-thermograph}" - local approle="${THERMOGRAPH_BAO_APPROLE:-/etc/thermograph/openbao-approle}" + # Explicit override first, then the per-environment value env-topology.sh derives, + # then the conventional path. Same precedence shape as render-secrets.sh:44's + # THERMOGRAPH_SECRETS_BACKEND / TG_SECRETS_BACKEND pair, for the same reason: the + # THERMOGRAPH_-prefixed name is the by-hand escape hatch, the TG_ one is what the + # deploy path sets. Falling straight through to the bare default is what made beta + # authenticate as tg-prod and get denied its own path. + local approle="${THERMOGRAPH_BAO_APPROLE:-${TG_BAO_APPROLE:-/etc/thermograph/openbao-approle}}" local ca="${THERMOGRAPH_BAO_CACERT:-/etc/thermograph/openbao-ca.crt}" command -v bao >/dev/null 2>&1 || { diff --git a/infra/openbao/README.md b/infra/openbao/README.md index 037bfcb..855e4d7 100644 --- a/infra/openbao/README.md +++ b/infra/openbao/README.md @@ -185,15 +185,14 @@ its own host: ```sh # on vps2 infra/openbao/verify-parity.sh --env prod # expect 32 keys -THERMOGRAPH_BAO_APPROLE=/etc/thermograph/openbao-approle-beta \ - infra/openbao/verify-parity.sh --env beta # expect 24 keys +infra/openbao/verify-parity.sh --env beta # expect 24 keys # on vps1 cd /opt/thermograph-dev && infra/openbao/verify-parity.sh --env dev # expect 12 keys ``` -The explicit `THERMOGRAPH_BAO_APPROLE` for beta is a stopgap: `env-topology.sh` does -not yet derive the AppRole path per environment, so the renderer falls back to prod's -credentials and `tg-host-prod.hcl` correctly denies `env/beta`. +Beta needs no special handling: `env-topology.sh` derives `TG_BAO_APPROLE` per +environment, so beta authenticates as `tg-beta` rather than falling back to prod's +credentials and being denied its own path by `tg-host-prod.hcl`. `seed-from-sops.sh` reads every production secret in plaintext. Per `infra/CLAUDE.md` the equivalent `seed-from-live.sh` is explicitly *not for an agent to run*; this diff --git a/infra/openbao/bootstrap.sh b/infra/openbao/bootstrap.sh index f3d0f69..6fa199d 100755 --- a/infra/openbao/bootstrap.sh +++ b/infra/openbao/bootstrap.sh @@ -213,10 +213,9 @@ secrets is not for an agent to run: Then prove parity. NOT \`--all\` from one box: each AppRole is CIDR-bound to the host that legitimately renders it, so an environment can only be verified from its own host. - # here (vps2) -- beta needs its own AppRole, prod uses the default + # here (vps2) infra/openbao/verify-parity.sh --env prod # expect 32 keys - THERMOGRAPH_BAO_APPROLE=/etc/thermograph/openbao-approle-beta \\ - infra/openbao/verify-parity.sh --env beta # expect 24 keys + infra/openbao/verify-parity.sh --env beta # expect 24 keys # on vps1 cd /opt/thermograph-dev && infra/openbao/verify-parity.sh --env dev # expect 12 keys