- devops docs: 8 files updated for Phase 6 state; field-trial.md added (946-line runbook) - developer docs: api-reference (50+ endpoints), quick-start, 5 existing guides updated, 5 new guides added - engineering docs: all 12 files updated (services, architecture, SDK guide, testing, overview) - OpenSpec archives: phase-7-devops-field-trial, developer-docs-phase6-update, engineering-docs-phase6-update - VALIDATOR.md + scripts/start-validator.sh: V&V Architect tooling added - .gitignore: exclude session artifacts, build artifacts, and agent workspaces Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5.8 KiB
HashiCorp Vault Setup
Phase 2 of AgentIdP optionally stores credential secrets in HashiCorp Vault KV v2 instead of bcrypt hashes in PostgreSQL. This guide covers:
- Dev mode quickstart
- Production Vault configuration
- Migration from bcrypt to Vault
Vault is entirely optional. If VAULT_ADDR is not set, AgentIdP operates in bcrypt mode (identical to Phase 1 behaviour).
How Vault integration works
When enabled:
POST /api/v1/agents/{agentId}/credentials— the plain-text secret is written to Vault at{mount}/data/agentidp/agents/{agentId}/credentials/{credentialId}. Only the Vault path is stored in PostgreSQL (credentials.vault_path). No bcrypt hash is written.POST /api/v1/token— the submittedclient_secretis compared against the value read from Vault (constant-time comparison). No bcrypt is involved.POST /api/v1/agents/{agentId}/credentials/{credentialId}/rotate— a new Vault version is written (KV v2 versioning). The path is unchanged; the old version is retained in Vault history.DELETE /api/v1/agents/{agentId}/credentials/{credentialId}— all versions of the secret are permanently deleted from Vault.
Coexistence: Credentials created before Vault was enabled keep their bcrypt hash and continue to work. New credentials use Vault. Both paths coexist until all pre-Vault credentials are rotated.
Dev mode quickstart
The fastest way to get Vault running locally:
# Pull and start Vault in dev mode (in-memory, auto-unsealed)
docker run --rm -d \
--name vault-dev \
-p 8200:8200 \
-e VAULT_DEV_ROOT_TOKEN_ID=dev-root-token \
hashicorp/vault:1.15 server -dev
# Verify it is running
curl http://127.0.0.1:8200/v1/sys/health | jq .
Add to your .env:
VAULT_ADDR=http://127.0.0.1:8200
VAULT_TOKEN=dev-root-token
VAULT_MOUNT=secret
Note: The
.env.examplefile usesVAULT_KV_MOUNTas the variable name. The application reads bothVAULT_KV_MOUNTandVAULT_MOUNT— preferVAULT_KV_MOUNTin new configurations for consistency with the current.env.example.
The KV v2 secrets engine is automatically enabled at secret/ in dev mode. No further configuration is needed.
Warning
: Dev mode stores everything in memory. Data is lost when the container stops. Do not use dev mode in production.
Production Vault configuration
1. Enable KV v2 secrets engine
vault secrets enable -path=secret kv-v2
Or use a custom mount path:
vault secrets enable -path=agentidp kv-v2
# Set VAULT_MOUNT=agentidp in your .env
2. Create a policy for AgentIdP
# agentidp-policy.hcl
path "secret/data/agentidp/*" {
capabilities = ["create", "read", "update", "delete"]
}
path "secret/metadata/agentidp/*" {
capabilities = ["delete"]
}
Apply the policy:
vault policy write agentidp agentidp-policy.hcl
3. Create a service token
vault token create \
-policy=agentidp \
-ttl=8760h \
-renewable=true \
-display-name="agentidp-service"
Copy the token field from the output and set it as VAULT_TOKEN in your environment.
4. Token renewal
Service tokens expire unless renewed. Set up a scheduled renewal before the TTL expires:
# Renew with a new 720-hour (30-day) lease
vault token renew -increment=720h <token>
In Kubernetes, use Vault Agent Injector or the Vault Secrets Operator to handle renewal automatically.
Running migration 005
After configuring Vault, run the migration to add the vault_path column:
npm run db:migrate
Verify the migration:
SELECT column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_name = 'credentials'
ORDER BY ordinal_position;
You should see a vault_path column with data_type = text and is_nullable = YES.
Migrating existing credentials to Vault
Existing credentials (with vault_path IS NULL) continue to work via bcrypt until they are rotated. To migrate a credential:
# Rotate the credential — this writes the new secret to Vault
curl -s -X POST http://localhost:3000/api/v1/agents/$AGENT_ID/credentials/$CRED_ID/rotate \
-H "Authorization: Bearer $TOKEN" | jq .
The response includes the new clientSecret (store it immediately). After rotation, vault_path is set and the bcrypt hash is cleared.
To migrate all credentials for an agent in bulk, rotate them one by one using the API.
Verifying Vault secrets
After generating a credential with Vault enabled, verify the secret was written:
vault kv get secret/agentidp/agents/$AGENT_ID/credentials/$CRED_ID
Expected output:
====== Secret Path ======
secret/data/agentidp/agents/<agentId>/credentials/<credentialId>
======= Metadata =======
Key Value
--- -----
created_time 2026-03-28T...
version 1
====== Data ======
Key Value
--- -----
clientSecret <the secret>
Troubleshooting
VAULT_WRITE_ERROR on credential generation
- Verify Vault is running:
curl $VAULT_ADDR/v1/sys/health - Verify the token has write access:
vault token capabilities $VAULT_TOKEN secret/data/agentidp/test - Check Vault audit logs:
vault audit list
VAULT_READ_ERROR on token issuance
- Verify the
vault_pathstored in PostgreSQL matches the actual Vault path - Check the token has read access to
secret/data/agentidp/*
Vault is down — what happens?
If Vault is unreachable, credential generation and token issuance for Vault-backed credentials will fail with a 500 error. Credentials created before Vault was enabled (bcrypt mode) continue to work.
For high availability, run Vault in HA mode with an integrated Raft storage backend. See Vault HA documentation.