Auto-managed credentials
When a crew declares a sidecar from a known database image (postgres, mysql, mariadb, mongo, rabbitmq,
elasticsearch), Crewship generates and manages the sidecar’s root
password on your behalf. The operator never sees, types, or rotates
the value — crewship apply does it. This page explains how and
why.
TL;DR
crewship apply -f produces:
- One sidecar container (
postgres:16-alpine) on the crew-private bridge network withPOSTGRES_USER=postgresand a generatedPOSTGRES_PASSWORD. - One credential row in the workspace named
POSTGRES_PASSWORD, statusACTIVE, providerAUTO_MANAGED, taggedprovisioned_for_service=backend/postgres. - Every agent in the crew (here:
lead) getsPOSTGRES_PASSWORDadded to its env_refs automatically.
What images are recognised
| Image prefix | Sugar env | Auto-credential |
|---|---|---|
postgres | POSTGRES_USER=postgres | POSTGRES_PASSWORD |
mariadb | — | MARIADB_ROOT_PASSWORD |
mysql | — | MYSQL_ROOT_PASSWORD |
mongo | MONGO_INITDB_ROOT_USERNAME=root | MONGO_INITDB_ROOT_PASSWORD |
rabbitmq | RABBITMQ_DEFAULT_USER=admin | RABBITMQ_DEFAULT_PASS |
elasticsearch | discovery.type=single-node | ELASTIC_PASSWORD |
redis) get no auto-credential at
all — for default Redis the crew-private bridge isolation is enough;
add an explicit auto_credentials: block if your image needs auth.
The catalog lives in
internal/manifest/known_sidecars.go.
New entries are PR additions, not “any image we recognise” magic —
the behaviour stays auditable.
The image-name match strips registry hosts and tags, so all of
these resolve to the same postgres entry:
postgres:16-alpinedocker.io/library/postgres:17harbor.acme.io/library/postgres:16@sha256:...localhost:5000/postgres:latest
Customising the auto-credential
For unknown images or when you want explicit control:name as a sugar default override
the sugar entirely. A crew can also add credentials beyond the
sugar set — both land in the resolved output.
Threat model — why this is safe
The generated value lives in two places in the workspace database:credentials.encrypted_value— AES-256-GCM encrypted withENCRYPTION_KEY. Surfaces the row in the UI for audit, rotate actions, and “Created by” attribution.crews.services_json— plaintext, embedded in the sidecar env literal. The docker provider reads this column at sidecar start time and passes the value viadocker --env.
- Sidecars from
auto_credentialsMUST be crew-private (no host port published). The validator refusesports:that publish to the host on a service with auto-credentials — that combination requires a T2 manual credential and an explicit security review. - The crew-private bridge network is the actual security boundary. An attacker who can read the workspace DB also already controls bridge isolation and the threat model is “host root,” under which a separate encrypted column doesn’t help.
- The duplicated value is bounded to the same DB file the encrypted column lives in. A backup of the workspace state carries both consistently.
Service. EnvRefs resolution at sidecar start). Until then, the duplication
is the trade-off for shipping the friction-zero default today.
What the UI shows
Auto-managed rows appear under Credentials with:- Source column reads “system (backend/postgres)” —
systemis the v98 actor type, and the parenthesised slug is theprovisioned_for_servicetag. (A future PR pins the row to the crew’s lead agent and renders “agent: trapper” instead.) - “Reveal value” and “Edit” actions are hidden.
- “Rotate” is available as a follow-up action (P1).
- Audit timeline shows the create event with the manifest apply as the actor user.
When auto_credentials is the WRONG choice
- The sidecar publishes a port to the host (
docker run -p 5432:5432- style). The bridge is no longer the security boundary; declare the credential manually undercredentials:and accept the operator-input friction. - The credential needs to be readable by something outside the crew (other crews, an external monitor, a backup job). T1 rows are crew-scoped by design — workspace-wide credentials with external consumers belong to T2 / T3.
- You want to bring your own value from a secrets manager (Vault,
AWS Secrets Manager, Doppler). That’s the T3 tier — declare a
credential with a
source:block instead.
Migration: existing manifests with env_refs: [POSTGRES_PASSWORD]
credentials: block, drop the env_refs:, drop the
explicit POSTGRES_USER. Re-apply. Existing user-managed
credential rows are NOT touched — the dispatch refuses to overwrite
a name collision unless provider=AUTO_MANAGED was already there.
Migrate by deleting the manual row first if you want the
auto-managed flavour.
Related
SPEC-4— Auto-managed sidecar credentials (internal design doc). This guide is the canonical user-facing reference.internal/manifest/known_sidecars.go(catalog implementation).- Manifests guide (the broader manifest layer).