feat(secrets): fetch Group-2 SMTP secret from Infisical at runtime (app-native, BUNYIP-525) #523

Merged
nrupard merged 1 commit from feat/BUNYIP-525-infisical-runtime-fetch into main 2026-08-12 16:44:01 +02:00
Owner

App-native runtime fetch of the Group-2 SMTP secret from Infisical (BUNYIP-525). Replaces the reverted host-side render / agent-sidecar attempts (docker #343, #345). Per David: no Infisical CLI or sidecar anywhere; bunyip-api itself fetches its Group-2 integration secrets.

What it does

  • Two-tier secret model. Group-1 startup secrets stay file/SOPS (no Rust knows Infisical, never a boot dependency). Group-2 integration secrets (SMTP first) are fetched by the app from Infisical at runtime via a Universal Auth machine identity: crates/bunyip-domain/src/services/infisical.rs.
  • Raw HTTP on bunyip's existing reqwest (5s timeout), NOT the infisical crate (it is v0.0.3 / unstable); against Infisical's stable REST API.
  • Wiring (main.rs): runs only when INFISICAL_ENABLED is set and the env/file SMTP_PASSWORD is empty; fetches SMTP_PASSWORD and populates the config.email.smtp_password fallback slot BEFORE the DB-over-env email resolution, so the DB email_config row still wins and Infisical is only the fallback. Any failure resolves to None, so the app always starts (email degrades) and Infisical is never a boot dependency.
  • Config: INFISICAL_ENABLED / _ADDRESS / _PROJECT_ID / _ENV / _SECRET_PATH (non-secret, compose-variables); INFISICAL_CLIENT_ID / _CLIENT_SECRET via secret_env (SOPS, honoring {NAME}_FILE). Off by default.
  • CLAUDE.md + docs/secrets-infisical.md updated to the two-tier model.

Validated / not

  • Green here: cargo fmt --check; cargo clippy --workspace --all-targets -- -D warnings (compiles + lints every target, including the new unit tests). Unit tests cover URL building, camelCase login body, and login + secret JSON parsing.
  • NOT run locally: cargo test OOM-kills compiling async-stripe on the 12-16GB dev box (environmental, unrelated to this change). CI runs the tests on proper runners.
  • HOST-VALIDATION: the secret-read uses the long-stable v3 raw endpoint (GET /api/v3/secrets/raw/{name}?workspaceId=...); confirm it against the deployed Infisical version (v4 alternative noted in a comment), and validate a live fetch against infisical.a8n.systems.

Operator steps to enable (per host)

Move INFISICAL_CLIENT_ID / INFISICAL_CLIENT_SECRET under services.app in the docker repo SOPS compose-secrets.yml; set INFISICAL_ENABLED=true + INFISICAL_ADDRESS (a8n.systems staging / psa.systems prod) + INFISICAL_PROJECT_ID (ab5c411a-...) + INFISICAL_ENV + INFISICAL_SECRET_PATH=/bunyip/runtime in compose-variables; seed SMTP_PASSWORD in the Infisical folder; optionally remove SMTP_PASSWORD from SOPS so the Infisical value is used.

App-native runtime fetch of the Group-2 SMTP secret from Infisical (BUNYIP-525). Replaces the reverted host-side render / agent-sidecar attempts (docker #343, #345). Per David: no Infisical CLI or sidecar anywhere; bunyip-api itself fetches its Group-2 integration secrets. ## What it does - Two-tier secret model. Group-1 startup secrets stay file/SOPS (no Rust knows Infisical, never a boot dependency). Group-2 integration secrets (SMTP first) are fetched by the app from Infisical at runtime via a Universal Auth machine identity: `crates/bunyip-domain/src/services/infisical.rs`. - Raw HTTP on bunyip's existing `reqwest` (5s timeout), NOT the `infisical` crate (it is v0.0.3 / unstable); against Infisical's stable REST API. - Wiring (`main.rs`): runs only when `INFISICAL_ENABLED` is set and the env/file `SMTP_PASSWORD` is empty; fetches `SMTP_PASSWORD` and populates the `config.email.smtp_password` fallback slot BEFORE the DB-over-env email resolution, so the DB `email_config` row still wins and Infisical is only the fallback. Any failure resolves to `None`, so the app always starts (email degrades) and Infisical is never a boot dependency. - Config: `INFISICAL_ENABLED` / `_ADDRESS` / `_PROJECT_ID` / `_ENV` / `_SECRET_PATH` (non-secret, compose-variables); `INFISICAL_CLIENT_ID` / `_CLIENT_SECRET` via `secret_env` (SOPS, honoring `{NAME}_FILE`). Off by default. - `CLAUDE.md` + `docs/secrets-infisical.md` updated to the two-tier model. ## Validated / not - Green here: `cargo fmt --check`; `cargo clippy --workspace --all-targets -- -D warnings` (compiles + lints every target, including the new unit tests). Unit tests cover URL building, camelCase login body, and login + secret JSON parsing. - NOT run locally: `cargo test` OOM-kills compiling `async-stripe` on the 12-16GB dev box (environmental, unrelated to this change). CI runs the tests on proper runners. - HOST-VALIDATION: the secret-read uses the long-stable v3 raw endpoint (`GET /api/v3/secrets/raw/{name}?workspaceId=...`); confirm it against the deployed Infisical version (v4 alternative noted in a comment), and validate a live fetch against `infisical.a8n.systems`. ## Operator steps to enable (per host) Move `INFISICAL_CLIENT_ID` / `INFISICAL_CLIENT_SECRET` under `services.app` in the docker repo SOPS `compose-secrets.yml`; set `INFISICAL_ENABLED=true` + `INFISICAL_ADDRESS` (a8n.systems staging / psa.systems prod) + `INFISICAL_PROJECT_ID` (ab5c411a-...) + `INFISICAL_ENV` + `INFISICAL_SECRET_PATH=/bunyip/runtime` in compose-variables; seed `SMTP_PASSWORD` in the Infisical folder; optionally remove `SMTP_PASSWORD` from SOPS so the Infisical value is used.
feat(secrets): fetch Group-2 SMTP secret from Infisical at runtime (app-native)
All checks were successful
E2E / PR gate (pull_request) Successful in 25s
Check / fmt + clippy + build + tests (pull_request) Successful in 4m52s
Create release / Create release from merged PR (pull_request) Has been skipped
7b1b1aed74
Two-tier secret model. Group-1 startup secrets stay file/SOPS (no Rust knows Infisical, never a boot dependency); the Group-2 integration secrets (SMTP first) are fetched by bunyip-api from Infisical at runtime via a Universal Auth machine identity, in crates/bunyip-domain/src/services/infisical.rs. It uses the shared reqwest stack (raw HTTP), not the infisical crate (v0.0.3, unstable), against Infisical's stable REST API. The value feeds the SMTP_PASSWORD fallback slot (config.email.smtp_password) below the DB email_config row, runs only when the env/file SMTP_PASSWORD is empty and INFISICAL_ENABLED is set, and resolves to None on any failure so the app always starts (email degrades, Infisical is never a boot dependency). CLAUDE.md and docs/secrets-infisical.md are updated to the two-tier model. HOST-VALIDATION: the secret-read uses the long-stable v3 raw endpoint, confirm it against the deployed Infisical version and validate a live fetch against infisical.a8n.systems.

#BUNYIP-525

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nrupard deleted branch feat/BUNYIP-525-infisical-runtime-fetch 2026-08-12 16:44:01 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/bunyip!523
No description provided.