[Security][Backend] OpenBao Phase 2: SpikerSoft.Secrets host-builder seam — service creds from Bao (epic #543) #546

Open
opened 2026-07-13 21:07:25 +00:00 by spikerj · 3 comments
Owner

Thin SpikerSoft.Secrets provider in EventHandlerHostBuilder.WithConfiguration (+ API host): Secrets:UseBao flag, AppRole login, pull Storage__SecretKey-class values + RabbitMQ/Mongo/Redis creds into IConfiguration at startup with env-var fallback (the #493 dual-run pattern — flag off = today's behavior byte-for-byte). Kills the appsettings dockerUser/dockerPassword defaults. VaultSharp or raw HTTP (mirror the #533 seam sizing discussion).

Thin SpikerSoft.Secrets provider in EventHandlerHostBuilder.WithConfiguration (+ API host): Secrets:UseBao flag, AppRole login, pull Storage__SecretKey-class values + RabbitMQ/Mongo/Redis creds into IConfiguration at startup with env-var fallback (the #493 dual-run pattern — flag off = today's behavior byte-for-byte). Kills the appsettings dockerUser/dockerPassword defaults. VaultSharp or raw HTTP (mirror the #533 seam sizing discussion).
Author
Owner

Phase 2 seam is up for review: spikersoft-backend PR #270. New SpikerSoft.Secrets project (ISecretSource + VaultSecretSource: AppRole login + KV v2 over plain HttpClient, zero new NuGet deps) with an AddBaoSecrets() configuration overlay — Secrets:UseBao=false by default, so it is completely inert and safe to merge while the vault is uninitialized; flag-on with the vault down/sealed logs loudly and falls back to env (Strict opt-in for fail-fast). Piloted on EventHandlers.Scheduler only via the existing ConfigureConfiguration hook to avoid the #256 fleet Dockerfile sweep; 17 new unit tests pass locally plus a clean full-solution build. Follow-ups noted in the PR: provision a svc-scheduler AppRole + populate secret/services/*, then fleet rollout + killing the committed dockerUser/dockerPassword defaults. — macbook-claude-session

Phase 2 seam is up for review: spikersoft-backend PR [#270](https://git.spikersoft.com/spikerj/spikersoft-backend/pulls/270). New `SpikerSoft.Secrets` project (ISecretSource + VaultSecretSource: AppRole login + KV v2 over plain HttpClient, zero new NuGet deps) with an `AddBaoSecrets()` configuration overlay — `Secrets:UseBao=false` by default, so it is completely inert and safe to merge while the vault is uninitialized; flag-on with the vault down/sealed logs loudly and falls back to env (Strict opt-in for fail-fast). Piloted on EventHandlers.Scheduler only via the existing ConfigureConfiguration hook to avoid the #256 fleet Dockerfile sweep; 17 new unit tests pass locally plus a clean full-solution build. Follow-ups noted in the PR: provision a svc-scheduler AppRole + populate secret/services/*, then fleet rollout + killing the committed dockerUser/dockerPassword defaults. — macbook-claude-session
Author
Owner

Phase 2 kicked off — Stage 0 up (spikersoft-backend PR #309). With Phase 0/1 closed the vault is live, so the SpikerSoft.Secrets overlay (built 2026-07-14) can finally be adopted.

Plan (agreed): one shared runtime services AppRole (per-service least-privilege deferred); role_id/secret_id delivered as Docker swarm secrets → /run/secrets/; hide-in-Bao first, rotate the burned values as a separate follow-up (each is a coordinated fleet restart).

Stage 0 (PR #309) — inert host wiring: AddBaoSecrets() now registered centrally in EventHandlerHostBuilder.WithConfiguration() (all ~30 workers) + SpikerSoft.Api/Program.cs, replacing the hand-wired Scheduler pilot. Secrets:UseBao off everywhere → config byte-identical to today (#493), safe no-op merge. slnf builds clean, 17/17 Secrets tests pass.

Remaining stages: 1) infra — services policy+AppRole, docker-secret cred delivery, populate secret/services/shared (RabbitMQ pw + Seq key); 2) flip the Scheduler pilot to the live vault; 3) fleet rollout per service; 4) delete committed appsettings plaintext (+ the notifications docker-stack.yml plaintext) once Bao-served everywhere.

Inventory note: most creds are committed plaintext DEFAULTS in appsettings (not env-injected) incl. real Keycloak/Stripe/Twilio/Encryption/InfluxDB values — the overlay is the first mechanism to hide them. Rotation follow-up will be filed when Stage 4 lands.

**Phase 2 kicked off — Stage 0 up (spikersoft-backend PR #309).** With Phase 0/1 closed the vault is live, so the SpikerSoft.Secrets overlay (built 2026-07-14) can finally be adopted. **Plan (agreed):** one shared runtime `services` AppRole (per-service least-privilege deferred); role_id/secret_id delivered as Docker swarm secrets → `/run/secrets/`; hide-in-Bao first, **rotate the burned values as a separate follow-up** (each is a coordinated fleet restart). **Stage 0 (PR #309) — inert host wiring:** `AddBaoSecrets()` now registered centrally in `EventHandlerHostBuilder.WithConfiguration()` (all ~30 workers) + `SpikerSoft.Api/Program.cs`, replacing the hand-wired Scheduler pilot. `Secrets:UseBao` off everywhere → config byte-identical to today (#493), safe no-op merge. slnf builds clean, 17/17 Secrets tests pass. **Remaining stages:** 1) infra — `services` policy+AppRole, docker-secret cred delivery, populate `secret/services/shared` (RabbitMQ pw + Seq key); 2) flip the Scheduler pilot to the live vault; 3) fleet rollout per service; 4) delete committed appsettings plaintext (+ the notifications docker-stack.yml plaintext) once Bao-served everywhere. Inventory note: most creds are committed plaintext DEFAULTS in appsettings (not env-injected) incl. real Keycloak/Stripe/Twilio/Encryption/InfluxDB values — the overlay is the first mechanism to hide them. Rotation follow-up will be filed when Stage 4 lands.
Author
Owner

Audited against origin/masterthe seam itself is adopted fleet-wide, but 6 stacks were left behind and Stage 4 is unswept. Staying open.

Adopted centrally rather than per-service, which is the right shape:

  • EventHandlers.Infrastructure/Builders/EventHandlerHostBuilder.cs:76_builder.Configuration.AddBaoSecrets();, inherited by all ~30 workers.
  • The three hosts that don't use that builder call it directly: SpikerSoft.Api/Program.cs:53, GpuCoordinator/Program.cs:26, NxCacheServer/Program.cs:27.
  • Infra: 27 stack files set Secrets__UseBao=true and 26 mount the AppRole creds (e.g. spikersoft-backend/docker-stack.yml:44-45Secrets__RoleIdFile=/run/secrets/bao_services_role_id).
  • Provisioning is scripted: openbao/provision-services-approle.sh creates the shared services policy/AppRole and both swarm secrets.

Gap 1 — six stacks deploy .NET services with no Bao env at all. Comparing the 32 stacks running spikerj/spikersoft images against the 27 carrying Secrets__UseBao: spikersoft-bootstrap, spikersoft-gameserver, spikersoft-gameserver-init, spikersoft-image-description, and — the ones that actually worry me — spikersoft-embeddings/docker-stack-gpu.yml and spikersoft-quiz-generation/docker-stack-gpu.yml.

Those last two are GPU variants of services whose main stack does have the seam. Grepping Secrets__ in either returns nothing. So the base stack is migrated and the GPU variant isn't — which means once Stage 4 blanks the committed plaintext, deploying the GPU variant boots the service with empty credentials, and it'll look like a config bug rather than a missed migration. That's a live trap sitting behind whoever does Stage 4.

Gap 2 — Stage 4 (delete committed plaintext) is not confirmed. It's verified only for SpikerSoft.Api/appsettings.json:8-9 (comment records the 12 API values blanked, UseBao: false for dev). The ~30 worker appsettings.json files have not been swept for residual Keycloak/Stripe/Twilio/Encryption/InfluxDB values. My separate audit on #633 confirms four committed credentials still live in backend appsettings, plus five more I just found in mailserver/docker-stack.yml — so plaintext retirement is demonstrably incomplete.

Gap 3 — comment 7241 promised a rotation follow-up ticket "when Stage 4 lands"; I found no such ticket.

Suggested order: close gap 1 first (two-line change per GPU stack, and it's cheap insurance), then sweep gap 2, then rotate.

Audited against `origin/master` — **the seam itself is adopted fleet-wide, but 6 stacks were left behind and Stage 4 is unswept.** Staying open. **Adopted centrally rather than per-service, which is the right shape:** - `EventHandlers.Infrastructure/Builders/EventHandlerHostBuilder.cs:76` — `_builder.Configuration.AddBaoSecrets();`, inherited by all ~30 workers. - The three hosts that don't use that builder call it directly: `SpikerSoft.Api/Program.cs:53`, `GpuCoordinator/Program.cs:26`, `NxCacheServer/Program.cs:27`. - Infra: 27 stack files set `Secrets__UseBao=true` and 26 mount the AppRole creds (e.g. `spikersoft-backend/docker-stack.yml:44-45` → `Secrets__RoleIdFile=/run/secrets/bao_services_role_id`). - Provisioning is scripted: `openbao/provision-services-approle.sh` creates the shared `services` policy/AppRole and both swarm secrets. **Gap 1 — six stacks deploy .NET services with no Bao env at all.** Comparing the 32 stacks running `spikerj/spikersoft` images against the 27 carrying `Secrets__UseBao`: `spikersoft-bootstrap`, `spikersoft-gameserver`, `spikersoft-gameserver-init`, `spikersoft-image-description`, and — the ones that actually worry me — **`spikersoft-embeddings/docker-stack-gpu.yml` and `spikersoft-quiz-generation/docker-stack-gpu.yml`**. Those last two are **GPU variants of services whose main stack does have the seam**. Grepping `Secrets__` in either returns nothing. So the base stack is migrated and the GPU variant isn't — which means once Stage 4 blanks the committed plaintext, deploying the GPU variant boots the service with **empty credentials**, and it'll look like a config bug rather than a missed migration. That's a live trap sitting behind whoever does Stage 4. **Gap 2 — Stage 4 (delete committed plaintext) is not confirmed.** It's verified only for `SpikerSoft.Api/appsettings.json:8-9` (comment records the 12 API values blanked, `UseBao: false` for dev). The ~30 worker `appsettings.json` files have not been swept for residual Keycloak/Stripe/Twilio/Encryption/InfluxDB values. My separate audit on #633 confirms four committed credentials still live in backend appsettings, plus five more I just found in `mailserver/docker-stack.yml` — so plaintext retirement is demonstrably incomplete. **Gap 3** — comment 7241 promised a rotation follow-up ticket "when Stage 4 lands"; I found no such ticket. Suggested order: close gap 1 first (two-line change per GPU stack, and it's cheap insurance), then sweep gap 2, then rotate.
Sign in to join this conversation.