The brain behind your secrets.
Unified secrets control plane for cloud-native teams.
secrets-bridge.io · all repos
Outbound-only execution agent for Secrets Bridge. Runs inside a target boundary (Kubernetes cluster, private VPC, customer account) and communicates ONLY outbound to the Control Plane API + the local secrets provider. No inbound listener on a public network interface.
| Issue | Step | Status |
|---|---|---|
| #1 | Outbound client: identity + heartbeat | ✅ merged |
| #2 | Job claim/complete loop | ✅ merged |
| #5 | PatchExecutor + router (Piece 4b) |
✅ merged |
| #6 | Vault resolver + ResolverByType (Piece 4c) |
✅ merged |
| #7 | AWS Secrets Manager resolver (Piece 4d) | ✅ merged |
| #8 | DiscoverExecutor + native tag preservation (Piece 6b) |
✅ merged |
| #9 | ReadExecutor — selective fetch + per-key wrap (Piece 5b) |
✅ merged |
| #10 | Transit security hardening (Piece 7 — TLS validation + CA pinning) | ✅ merged |
| #11 | Wire-envelope encryption (Piece 8b — X25519 + KMS DEK) | ✅ merged |
The agent is now feature-complete for the write + read + discover flows against both Vault and AWS Secrets Manager, with TLS validation + CA pinning + bi-directional wire-envelope crypto on top of TLS. See skills/PROGRESS.md for the slice-by-slice log.
| Invariant | How enforced |
|---|---|
| No Postgres / Redis / any DB driver | CI job no-db-or-redis greps go.sum for forbidden module paths and fails the build if any appear |
| No inbound public listener | Local probe + metrics server binds to 127.0.0.1 only (configurable but loopback by default) |
| One credential — no on-disk identity state | Pod restart re-reads the same K8s Secret; no PVC needed |
admin → CP: POST /api/v1/agents → { id, agent_secret }
admin → K8s: creates Secret with both fields
Pod start: reads Secret (env vars OR file) → heartbeats forever
Pod restart: same — reads same Secret, no state to persist
The agent reads its credential pair from either env vars or a mounted file. Env vars take precedence when set; this lets a chart use the cleaner env-var path for dev installations and the safer file mount for production.
Priority: if SB_AGENT_ID AND SB_AGENT_SECRET are both set, the env-var path wins. Otherwise the file at SB_IDENTITY_FILE is consulted.
env:
- name: SB_AGENT_ID
valueFrom:
secretKeyRef: { name: my-agent-secret, key: agent_id }
- name: SB_AGENT_SECRET
valueFrom:
secretKeyRef: { name: my-agent-secret, key: agent_secret }volumes:
- name: identity
secret:
secretName: my-agent-secret
items:
- { key: identity.json, path: identity.json }
defaultMode: 0400
volumeMounts:
- { name: identity, mountPath: /etc/secrets-bridge, readOnly: true }Both modes use the same K8s Secret. No PVC is needed in either case.
cmd/agent/ main + config (the binary)
internal/
client/ typed CP HTTP client (Heartbeat)
identity/ env-var-or-file credential loader
local/ loopback /healthz /readyz /metrics
observability/ slog JSON logger
The agent imports only the stdlib + github.com/prometheus/client_golang. Once Step 8 (job loop) lands it will also import github.com/secrets-bridge/core/providers to execute provider operations.
go build ./...
go vet ./...
go test -race -count=1 ./...End-to-end against a running CP:
# 1. Bring up the CP (separate repo)
( cd ../api && docker compose up -d )
# 2. Mint an agent via the CP
MINT=$(curl -s -X POST -H 'Content-Type: application/json' \
-d '{"name":"demo-agent","scope":{"cluster":"prod-eu"}}' \
http://localhost:8080/api/v1/agents)
AGENT_ID=$(echo "$MINT" | jq -r '.id')
SECRET=$(echo "$MINT" | jq -r '.agent_secret')
# 3a. Run with env-var bootstrap
SB_CP_ENDPOINT=http://localhost:8080 \
SB_AGENT_ID=$AGENT_ID \
SB_AGENT_SECRET=$SECRET \
SB_HEARTBEAT_INTERVAL=2s \
go run ./cmd/agent
# 3b. OR with file bootstrap
echo "{\"agent_id\":\"$AGENT_ID\",\"agent_secret\":\"$SECRET\"}" \
> /tmp/identity.json
SB_CP_ENDPOINT=http://localhost:8080 \
SB_IDENTITY_FILE=/tmp/identity.json \
SB_HEARTBEAT_INTERVAL=2s \
go run ./cmd/agent
# 4. Watch the agent's last_seen_at tick on the CP side
watch -n 1 'curl -s http://localhost:8080/api/v1/agents | jq'docker build -t secrets-bridge-agent:dev .Multi-stage build on golang:1.25-alpine → distroless/static:nonroot. No shell, no package manager.
