Group Mongo bootstrap secrets under infrastructure ownership

This commit is contained in:
Stephan D committed 2026-09-12 23:36:47 +02:00
1 parent 9dbf0bd80c
commit 9d57a65010
7 files changed
+22 -13

No files matched your search

+1 -1
View File
@@ -176,7 +176,7 @@ Keep infrastructure Dockerfiles under `infra/images/` and local Compose, service
configuration and shell tooling under `infra/local/`. Keep product source under configuration and shell tooling under `infra/local/`. Keep product source under
`backend/` and `frontend/`. Organize Vault KV paths by owning service under `backend/` and `frontend/`. Organize Vault KV paths by owning service under
`services/<service>/<purpose>`, infrastructure under `infrastructure/<component>/<purpose>`, `services/<service>/<purpose>`, infrastructure under `infrastructure/<component>/<purpose>`,
and bootstrap administration under `bootstrap/<component>`. Caller credentials and bootstrap administration under `infrastructure/<component>/bootstrap`. Caller credentials
belong to the receiving service at `services/<service>/callers/<caller>`. belong to the receiving service at `services/<service>/callers/<caller>`.
Use Caddy for local browser HTTPS and certificate management. Treat the private Use Caddy for local browser HTTPS and certificate management. Treat the private
local Docker network as trusted; do not add internal gRPC TLS or Vault-managed local Docker network as trusted; do not add internal gRPC TLS or Vault-managed
+8
View File
@@ -743,3 +743,11 @@ and 1,083 expected HTTP responses, zero failures. Role TTLs were restored and
temporary BFF/frontend containers removed. `make up` intentionally stops at the temporary BFF/frontend containers removed. `make up` intentionally stops at the
missing Resend credential check; no real email, optional security scan or merge missing Resend credential check; no real email, optional security scan or merge
was performed. Earlier supplementary legacy test limitations remain recorded. was performed. Earlier supplementary legacy test limitations remain recorded.
Mongo bootstrap credentials now live at `kv/infrastructure/mongo/bootstrap`,
consistent with component ownership. The live `kv/bootstrap/mongo` value was
copied, compared and archived before removing the old path. Bootstrap and admin
checks read the new path; application identities are denied both paths. Shell
checks and live infrastructure validation (including Mongo transactions, lease
renewal/revocation and obsolete-path absence) passed. `make up` still stops at the
expected missing-Resend check. No backend code or product behavior changed.
+5 -5
View File
@@ -41,10 +41,9 @@ kv/
│ └── notifications/ │ └── notifications/
│ ├── callers/bff # token; read by BFF and notifications │ ├── callers/bff # token; read by BFF and notifications
│ └── email/resend # driver, api_key, sender_address, sender_name │ └── email/resend # driver, api_key, sender_address, sender_name
├── infrastructure/ └── infrastructure/
│ └── nats/auth # token; read by NATS and notifications ├── nats/auth # token; read by NATS and notifications
└── bootstrap/ └── mongo/bootstrap # local administrator and replica-set bootstrap material
└── mongo # local administrator and replica-set bootstrap material
``` ```
Every secret has an owning service or infrastructure component. Service policies Every secret has an owning service or infrastructure component. Service policies
@@ -130,7 +129,8 @@ still interrupt work. Repository consumers use `source.Source` operation scopes;
## Migration and persistence ## Migration and persistence
Bootstrap migrates the old flat `bff`, `caller`, `email` and `nats` paths into the Bootstrap migrates the old flat `bff`, `caller`, `email` and `nats` paths into the
hierarchy above. It copies with CAS protection, compares values, and refuses to hierarchy above, and moves `bootstrap/mongo` to `infrastructure/mongo/bootstrap`.
It copies with CAS protection, compares values, and refuses to
overwrite conflicting destinations. After consumers use the new paths, it removes overwrite conflicting destinations. After consumers use the new paths, it removes
old KV metadata and obsolete `tls`, `frontend-tls`, `notifications-tls` entries. old KV metadata and obsolete `tls`, `frontend-tls`, `notifications-tls` entries.
Readable old version history and metadata are archived privately in Readable old version history and metadata are archived privately in
+3 -3
View File
@@ -28,11 +28,11 @@ mounts() {
fi fi
} }
provision_mongo() { provision_mongo() {
if [ "$(kv bootstrap/mongo)" = null ]; then if [ "$(kv infrastructure/mongo/bootstrap)" = null ]; then
random > "$WORK/password"; random 384 > "$WORK/keyfile"; random > "$WORK/manager" random > "$WORK/password"; random 384 > "$WORK/keyfile"; random > "$WORK/manager"
jq -n --rawfile password "$WORK/password" --rawfile keyfile "$WORK/keyfile" --rawfile manager "$WORK/manager" '{password:($password|rtrimstr("\n")),keyfile:($keyfile|rtrimstr("\n")),manager_password:($manager|rtrimstr("\n"))}' | put bootstrap/mongo jq -n --rawfile password "$WORK/password" --rawfile keyfile "$WORK/keyfile" --rawfile manager "$WORK/manager" '{password:($password|rtrimstr("\n")),keyfile:($keyfile|rtrimstr("\n")),manager_password:($manager|rtrimstr("\n"))}' | put infrastructure/mongo/bootstrap
fi fi
kv bootstrap/mongo > "$WORK/mongo-secret" kv infrastructure/mongo/bootstrap > "$WORK/mongo-secret"
jq -r '.password' "$WORK/mongo-secret" | write "$STATE/mongo/password" jq -r '.password' "$WORK/mongo-secret" | write "$STATE/mongo/password"
jq -r '.keyfile' "$WORK/mongo-secret" | write "$STATE/mongo/keyfile" jq -r '.keyfile' "$WORK/mongo-secret" | write "$STATE/mongo/keyfile"
compose up -d mongo compose up -d mongo
+1 -1
View File
@@ -56,7 +56,7 @@ ensure() (
) )
# Never forward mongosh errors: authentication/command errors may include secrets. # Never forward mongosh errors: authentication/command errors may include secrets.
mongo() { compose exec -T mongo mongosh --quiet --file /dev/stdin > "$WORK/mongo-result" 2> "$WORK/mongo-error" || return 1; cat "$WORK/mongo-result"; } mongo() { compose exec -T mongo mongosh --quiet --file /dev/stdin > "$WORK/mongo-result" 2> "$WORK/mongo-error" || return 1; cat "$WORK/mongo-result"; }
admin_auth() { kv bootstrap/mongo | jq -r '"const admin=db.getSiblingDB(\"admin\"); admin.auth(\"local-admin\","+(.password|tojson)+");"'; } admin_auth() { kv infrastructure/mongo/bootstrap | jq -r '"const admin=db.getSiblingDB(\"admin\"); admin.auth(\"local-admin\","+(.password|tojson)+");"'; }
email_valid() { jq -e '(.api_key|type=="string") and (.api_key|test("\\S")) and (.sender_address|type=="string") and (.sender_address|test("\\S")) and .sender_address!="notifications@localhost.test"' >/dev/null; } email_valid() { jq -e '(.api_key|type=="string") and (.api_key|test("\\S")) and (.sender_address|type=="string") and (.sender_address|test("\\S")) and .sender_address!="notifications@localhost.test"' >/dev/null; }
export_ca() { export_ca() {
compose exec -T frontend cat /data/caddy/pki/authorities/local/root.crt | write "$STATE/ca.pem" 644 compose exec -T frontend cat /data/caddy/pki/authorities/local/root.crt | write "$STATE/ca.pem" 644
+1
View File
@@ -5,6 +5,7 @@ bff services/bff/jwt
caller services/notifications/callers/bff caller services/notifications/callers/bff
email services/notifications/email/resend email services/notifications/email/resend
nats infrastructure/nats/auth nats infrastructure/nats/auth
bootstrap/mongo infrastructure/mongo/bootstrap
PATHS PATHS
} }
# Archive readable version history privately before removing an obsolete KV path. # Archive readable version history privately before removing an obsolete KV path.
+3 -3
View File
@@ -6,7 +6,7 @@ policies() (
directory="$STATE/agents/$service" directory="$STATE/agents/$service"
login=$(jq -n --rawfile role "$directory/role-id" --rawfile secret "$directory/secret-id" '{role_id:($role|rtrimstr("\n")),secret_id:($secret|rtrimstr("\n"))}' | vault auth/approle/login POST) login=$(jq -n --rawfile role "$directory/role-id" --rawfile secret "$directory/secret-id" '{role_id:($role|rtrimstr("\n")),secret_id:($secret|rtrimstr("\n"))}' | vault auth/approle/login POST)
identity=$(printf '%s' "$login" | jq -er '.auth.client_token') identity=$(printf '%s' "$login" | jq -er '.auth.client_token')
printf '%s' "$login" | jq '{token:.auth.client_token,paths:["kv/data/bootstrap/mongo","sys/init","kv/data/bff","kv/data/caller","kv/data/email","kv/data/nats","kv/data/tls"]}' | vault sys/capabilities POST | jq -e '.data | all(.[]; .==["deny"])' >/dev/null || fail 'Service can access bootstrap credentials' printf '%s' "$login" | jq '{token:.auth.client_token,paths:["kv/data/bootstrap/mongo","kv/data/infrastructure/mongo/bootstrap","sys/init","kv/data/bff","kv/data/caller","kv/data/email","kv/data/nats","kv/data/tls"]}' | vault sys/capabilities POST | jq -e '.data | all(.[]; .==["deny"])' >/dev/null || fail 'Service can access bootstrap credentials'
VAULT_TOKEN=$(printf '%s' "$login" | jq -er '.auth.client_token') VAULT_TOKEN=$(printf '%s' "$login" | jq -er '.auth.client_token')
printf '{}' | vault auth/token/revoke-self POST >/dev/null printf '{}' | vault auth/token/revoke-self POST >/dev/null
VAULT_TOKEN=$root_token VAULT_TOKEN=$root_token
@@ -50,10 +50,10 @@ product() {
echo 'PASS: six healthy services, frontend and proxied BFF use verified TLS; no email sent' echo 'PASS: six healthy services, frontend and proxied BFF use verified TLS; no email sent'
} }
layout() { layout() {
for old in bff caller email nats tls frontend-tls notifications-tls; do for old in bootstrap/mongo bff caller email nats tls frontend-tls notifications-tls; do
[ "$(vault "kv/metadata/$old" GET yes)" = null ] || fail "Obsolete KV entry remains: $old" [ "$(vault "kv/metadata/$old" GET yes)" = null ] || fail "Obsolete KV entry remains: $old"
done done
for path in services/bff/jwt services/notifications/callers/bff services/notifications/email/resend infrastructure/nats/auth bootstrap/mongo; do for path in services/bff/jwt services/notifications/callers/bff services/notifications/email/resend infrastructure/nats/auth infrastructure/mongo/bootstrap; do
vault "kv/metadata/$path" | jq -e '.data.current_version>0' >/dev/null || fail "Missing canonical KV entry: $path" vault "kv/metadata/$path" | jq -e '.data.current_version>0' >/dev/null || fail "Missing canonical KV entry: $path"
done done
echo 'PASS: canonical Vault hierarchy present; obsolete flat and TLS entries absent' echo 'PASS: canonical Vault hierarchy present; obsolete flat and TLS entries absent'