Implements all 22 WS6 tasks completing Phase 3 Enterprise. Column-level encryption (AES-256-CBC, Vault-backed key) via EncryptionService applied to credentials.secret_hash, credentials.vault_path, webhook_subscriptions.vault_secret_path, and agent_did_keys.vault_key_path. Backward-compatible: isEncrypted() guard skips decryption for existing plaintext rows until next read-write cycle. Audit chain integrity (CC7.2): AuditRepository computes SHA-256 Merkle hash on every INSERT (hash = SHA-256(eventId+timestamp+action+outcome+agentId+orgId+prevHash)). AuditVerificationService walks the full chain verifying hash continuity. AuditChainVerificationJob runs hourly; sets agentidp_audit_chain_integrity Prometheus gauge to 1 (pass) or 0 (fail). TLS enforcement (CC6.7): TLSEnforcementMiddleware registered as first middleware in Express stack; 301 redirect on non-https X-Forwarded-Proto in production. SecretsRotationJob (CC9.2): hourly scan for credentials expiring within 7 days; increments agentidp_credentials_expiring_soon_total. ComplianceController + routes: GET /audit/verify (auth+audit:read scope, 30/min rate-limit); GET /compliance/controls (public, Cache-Control 60s). ComplianceStatusStore: module-level map updated by jobs, consumed by controller. Prometheus: 2 new metrics (agentidp_credentials_expiring_soon_total, agentidp_audit_chain_integrity); 6 alerting rules in alerts.yml. Compliance docs: soc2-controls-matrix.md, encryption-runbook.md, audit-log-runbook.md, incident-response.md, secrets-rotation.md. Tests: 557 unit tests passing (35 suites); 26 new tests (EncryptionService, AuditVerificationService); 19 compliance integration tests. TypeScript clean. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4.8 KiB
Secrets Rotation Runbook — SentryAgent.ai AgentIdP
Control: SOC 2 CC9.2 — Secrets Rotation Last updated: 2026-03-31
Overview
AgentIdP manages three categories of secrets that require periodic rotation:
- Agent client secrets — Per-credential client secrets used for OAuth 2.0 token issuance
- OIDC signing keys — RSA/EC keys used to sign ID tokens
- AES-256-CBC encryption key — Column-level database encryption key (see
encryption-runbook.md)
1. Agent Credential (Client Secret) Rotation
API endpoint
POST /api/v1/agents/:agentId/credentials/:credentialId/rotate
Requires Bearer token with agents:write scope.
Procedure
# 1. List active credentials for the agent
curl -s -H "Authorization: Bearer <token>" \
"https://api.sentryagent.ai/v1/agents/<agentId>/credentials?status=active"
# 2. Rotate the credential (generate new secret)
curl -s -X POST \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"expiresAt": "2027-03-31T00:00:00.000Z"}' \
"https://api.sentryagent.ai/v1/agents/<agentId>/credentials/<credentialId>/rotate"
# Response includes the new clientSecret — store it immediately; it is never shown again
Key points
- The new
clientSecretis returned once only — store it securely before the response is discarded - The agent's previous secret is immediately invalidated (Vault KV v2 version overwritten)
- An audit event
credential.rotatedis logged to the immutable audit chain - A
credential.rotatedwebhook event is dispatched to all active subscriptions
Recommended rotation schedule
| Credential type | Recommended rotation interval |
|---|---|
| Production agent credentials | 90 days |
| Staging / development credentials | 180 days |
| Service account credentials | 365 days (annual) |
| Credentials involved in a security incident | Immediately |
Automated expiry detection
SecretsRotationJob runs hourly and queries credentials expiring within 7 days.
Prometheus alert CredentialExpiryApproaching fires immediately when any are detected.
Respond to this alert by rotating the flagged credential(s) before the expiry date.
2. OIDC Signing Key Rotation
Overview
OIDC signing keys are managed by OIDCKeyService (src/services/OIDCKeyService.ts).
Keys are stored in the oidc_keys PostgreSQL table. The current active key is used to
sign all new ID tokens; public keys are exposed via GET /.well-known/jwks.json.
When to rotate
- Key compromise or suspected exposure
- Scheduled rotation (recommended every 90 days for production)
- Algorithm upgrade (e.g. RS256 → ES256)
Rotation procedure
OIDC key rotation is handled automatically by OIDCKeyService.ensureCurrentKey():
# Force generation of a new signing key by calling the internal rotate endpoint
# (or trigger by redeploying with OIDC_FORCE_KEY_ROTATION=true)
# 1. Mark current key as inactive (if manual rotation is required)
psql "$DATABASE_URL" -c "
UPDATE oidc_keys
SET active = false
WHERE active = true;"
# 2. Restart the application — ensureCurrentKey() will generate a new key on startup
kubectl rollout restart deployment/agentidp
JWKS update behavior
- Old public keys remain in
GET /.well-known/jwks.jsonfor 24 hours after rotation (grace period for in-flight tokens) - After the grace period, old keys are removed from the JWKS endpoint
- Redis JWKS cache TTL is configured by
JWKS_CACHE_TTL_SECONDS(default: 3600)
Impact on existing tokens
Existing valid tokens signed with the old key continue to work until they expire, as long as the old public key remains in JWKS. After the grace period, old tokens will fail verification.
3. Encryption Key Rotation
See docs/compliance/encryption-runbook.md for the full AES-256-CBC encryption key rotation procedure.
Summary: Generate new 32-byte hex key → write to Vault at ENCRYPTION_KEY_VAULT_PATH → restart app → existing rows re-encrypted lazily on next read-write cycle.
Schedule Recommendations
| Secret Type | Production Interval | Staging Interval | Trigger for Immediate Rotation |
|---|---|---|---|
| Agent client secrets | 90 days | 180 days | Credential suspected compromised |
| OIDC signing keys | 90 days | 180 days | Key file exposed, algorithm upgrade |
| AES-256-CBC encryption key | 365 days (annual) | On demand | Key exposed, Vault breach, compliance audit requirement |
| Webhook HMAC secrets | Per customer policy | N/A | Webhook endpoint compromised |
Compliance Evidence
For SOC 2 CC9.2 evidence collection:
- Prometheus metric history:
agentidp_credentials_expiring_soon_total - Audit log entries with
action: credential.rotated— query viaGET /audit?action=credential.rotated - Key rotation records from Vault audit log
- This runbook + sign-off from Security Engineering