Skip to content
Select themeSelect language

Use the OpenBao secret backend

By default SupaCloud reads every secret from environment variables. Setting SECRET_BACKEND=openbao switches to OpenBao as the authoritative source: the server hydrates all application-level secrets from the single KV v2 path supacloud/app before any required-secret validation runs. Environment variables remain a permitted override on top of vault — they always win over the vault-provided value.

  • An OpenBao or HashiCorp Vault-compatible server with KV v2 enabled at the supacloud mount.
  • The SupaCloud server process can reach the vault address (VAULT_ADDR).
  • Either a static token or an AppRole with read access to supacloud/app and the existing per-subsystem paths (supacloud/db, supacloud/messaging, supacloud/management, supacloud/ai, supacloud/registry).
  1. Static token — set a single env var:

    Terminal window
    VAULT_ADDR=https://vault.example.com
    VAULT_TOKEN=<your-token>
  2. AppRole — set the role ID and secret ID instead:

    Terminal window
    VAULT_ADDR=https://vault.example.com
    OPENBAO_ROLE_ID=<role-id>
    OPENBAO_SECRET_ID=<secret-id>

    The server logs in at boot, then a background loop renews the token at roughly half its TTL and re-logs-in automatically if renewal fails (Issue #385). The AppRole’s policy should permit auth/token/renew-self; if not, the re-login fallback covers it.

All application-level secrets live in a single KV v2 map keyed by their env-var name. Write it once; update individual keys when rotating a secret:

Terminal window
vault kv put supacloud/app \
SUPACLOUD_CREDENTIAL_ENCRYPTION_KEY="base64:<32-byte key>" \
SUPACLOUD_JWT_SECRET="<min-32-chars>" \
OIDC_CLIENT_SECRET="<value>" \
SUPACLOUD_MCP_CONFIRMATION_SECRET="<min-32-chars>" \
DISCORD_BOT_TOKEN="<value>" \
STRIPE_SECRET_KEY="<value>" \
STRIPE_WEBHOOK_SECRET="<value>" \
SUPACLOUD_LINEAR_OAUTH_CLIENT_ID="<value>" \
SUPACLOUD_LINEAR_OAUTH_CLIENT_SECRET="<value>" \
SUPACLOUD_NOTION_OAUTH_CLIENT_ID="<value>" \
SUPACLOUD_NOTION_OAUTH_CLIENT_SECRET="<value>"

Only include the keys your deployment uses; unknown keys are ignored.

The credential-encryption key is loaded into an in-process holder (secret_store) and never written to std::env, so it cannot be inherited by agent containers or the code-runner subprocess.

Add to your server environment (or .env):

Terminal window
SECRET_BACKEND=openbao

The server will now:

  1. Hydrate supacloud/app from vault, filling any AppConfig field still empty after the env pass.
  2. Seed the in-process encryption-key holder and the generic secret map for lazy call sites (Stripe, SMTP, FinTS, edition license).
  3. Run required-secret validation — SUPACLOUD_JWT_SECRET and the credential-encryption key are re-checked after hydration (in AppConfig::validate_required), so a vault-sourced value satisfies them. SUPACLOUD_INITIAL_ADMIN_EMAILS is a non-secret prod guard validated in from_env before vault is read and cannot be supplied via the supacloud/app map.
  4. Fatal at boot if a required secret is absent from both vault and env.

Check the server startup log for the Loaded … from OpenBao entries (and Cached vault secret: … per path), for example:

Loaded app secrets from OpenBao
Cached vault secret: supacloud/app
Cached vault secret: supacloud/db

If a required secret is missing from both sources the process exits with a message naming the variable, e.g.:

SUPACLOUD_CREDENTIAL_ENCRYPTION_KEY must be set in prod so database
credential material is encrypted at rest

Set SECRET_BACKEND=env (or remove VAULT_TOKEN/OPENBAO_ROLE_ID so auto-detection falls back to env). The server will not contact OpenBao and will read every secret directly from environment variables.