Bootstrap & first sign-in¶
After the api starts for the first time, the platform has to be
bootstrapped with a usable admin identity. Otherwise nothing
can be done — every write endpoint is gated by
auth.Require(perm) and there are no role assignments yet.
What "bootstrapped" means¶
Two things happen on first boot when local_users is empty and
the bootstrap env vars are set:
- A local admin user is created — bcrypt-hashed password
stored in
local_users(idempotent: a second boot with the same env vars is a no-op once any user exists). - The new user is bound to the seed
adminrole with global scope, via the existing user_roles flow.
The api logs both actions at INFO so you can see them in
docker logs secrets-bridge-api-1:
{"msg":"bootstrap local admin created","user_id":"<uuid>","email":"admin@example.com"}
{"msg":"bootstrap admin assignment created","user_id":"<uuid>","role":"admin","scope":"global"}
Configuration¶
| Env var | Required | Default | Notes |
|---|---|---|---|
SB_BOOTSTRAP_ADMIN_EMAIL |
recommended on first boot | — | Becomes the local-admin email |
SB_BOOTSTRAP_ADMIN_PASSWORD |
recommended on first boot | — | Rotate immediately after first sign-in |
SB_BOOTSTRAP_ADMIN_USER_ID |
optional | — | Pre-OIDC escape hatch: assigns the admin role to a specific opaque user_id (matches the future OIDC sub claim). Use this alongside _EMAIL/_PASSWORD if you want both the local-admin login and a pre-arranged OIDC identity to hold admin. |
First sign-in¶
- Open the UI (
http://localhost:18080/for docker-compose). - You'll be redirected to
/login. - Enter the email + password from the env vars.
- You land on the Dashboard with the
Agents OnlineKPI showing0 / 0.
The token is stored in React state only — closing the tab signs you out by design (BRD §15).
Rotating the admin password¶
There's no UI surface for this in the current slice. For now, use
psql directly against the api's Postgres:
# Generate a bcrypt hash for the new password
NEW_HASH=$(htpasswd -bnBC 12 "" "your-new-password" | tr -d ':\n' | sed 's/^.\$2y/$2y/')
# Update the row
docker exec -i secrets-bridge-postgres-1 \
psql -U secrets_bridge -d secrets_bridge \
-c "UPDATE local_users SET password_hash = decode('$(echo "$NEW_HASH" | xxd -p)', 'hex') WHERE email = 'admin@example.com';"
A real password-change endpoint + UI page lands as a follow-up.
Disabling the local admin (after OIDC ships)¶
Once you've added a real OIDC identity with the admin role, you can disable the local-admin row so brute-force attempts against its bcrypt hash are pointless:
A disabled user's login attempts return the same generic
401 invalid credentials; the audit log records
error_kind=user_disabled.
Minting your first agent¶
From the Dashboard:
- Sidebar → Agents
- + Mint agent → drawer opens
- Fill Name (e.g.
prod-eu-vault) + Scope cluster (e.g.prod-eu) - Mint → the drawer swaps to the reveal-once panel
- Copy the agent secret immediately. The bash snippet at the bottom is ready to drop into your agent container's env.
- Click I've copied it → the panel closes and the secret is gone from React state forever.
If you lose the secret before copying it, revoke the agent and mint a new one. There's no recovery path — that's the whole point of reveal-once.
Submitting your first read request¶
Once you have an agent running and provider creds
configured for it:
- Sidebar → Requests → + New request
- Read type
- Provider: Vault (or AWS Secrets Manager)
- Provider config: leave blank for Vault (defaults to
kvMount=secret) - Secret reference: e.g.
prod/db/password - Keys: e.g.
DB_PASSWORD(or leave one empty row for "all keys in the bundle") - Justification: required — typically an incident or ticket number
- Submit → you're navigated to the request detail page
Then either:
- Sign in as a second user (with
secret.approvepermission) and approve from the Requests page's Approver queue, or - Enable
allow_self_approvalon the workflow used by the request and approve from the detail page's Approver actions card.
Once approved → agent claims → fetches → posts wrap → request →
executed. Refresh the detail page; the Wraps card now shows
DB_PASSWORD with the Reveal (one-time) button. Click it,
confirm, see the value, copy it, close.
That's the whole read flow.