Run local infrastructure and product images with Vault-managed secrets #43
No files matched your search
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
Reference in new issue
Block a user