Skip to main content

Runbook: Secrets Rotation

Overview

This runbook covers rotating secrets used by Cloud Aegis, including:

  • JWT signing keys
  • Cloud provider credentials (AWS, Azure, GCP)
  • Database connection strings
  • Redis authentication
  • AI provider API keys (Anthropic, OpenAI)
  • Identity provider secrets (Okta, Entra ID)

Prerequisites

  • Access to cloud provider secret stores (AWS Secrets Manager, Azure Key Vault, GCP Secret Manager)
  • kubectl access to the Cloud Aegis cluster
  • Database admin access (for connection string rotation)
  • 1Password vault access for development secrets

Secret Inventory

SecretStorageRotation FrequencyAuto-Rotation
JWT signing keyEnv var / Secrets Manager90 daysNo
PostgreSQL passwordSecrets Manager / Key Vault90 daysRDS: Yes
Redis AUTH tokenSecrets Manager90 daysElastiCache: Yes
Anthropic API keySecrets Manager180 daysNo
OpenAI API keySecrets Manager180 daysNo
Okta API tokenSecrets Manager90 daysNo
Entra ID client secretAzure Key Vault365 daysNo
AWS IAM access keysIAM90 daysNo (use OIDC/WIF)

JWT Signing Key Rotation

Step 1: Generate New Key

# For HS256 (symmetric)
openssl rand -base64 64 > new-jwt-secret.txt

# For RS256 (asymmetric)
openssl genrsa -out new-jwt-private.pem 2048
openssl rsa -in new-jwt-private.pem -pubout -out new-jwt-public.pem

Step 2: Store New Key

# AWS Secrets Manager
aws secretsmanager update-secret \
--secret-id aegis/jwt-signing-key \
--secret-string "$(cat new-jwt-secret.txt)" \
--region us-east-1

# Azure Key Vault
az keyvault secret set \
--vault-name aegis-kv \
--name jwt-signing-key \
--value "$(cat new-jwt-secret.txt)"

Step 3: Deploy with Dual-Key Support

During rotation, both old and new keys must be accepted for a grace period (default: 24h). Update the configmap:

kubectl edit configmap aegis-config -n aegis
# Add: jwt.previous_signing_key = <old-key>
# Update: jwt.signing_key = <new-key>

kubectl rollout restart deployment/aegis-api -n aegis
kubectl rollout status deployment/aegis-api -n aegis

Step 4: Remove Old Key (after grace period)

kubectl edit configmap aegis-config -n aegis
# Remove: jwt.previous_signing_key

kubectl rollout restart deployment/aegis-api -n aegis

Step 5: Clean Up

rm new-jwt-secret.txt new-jwt-private.pem new-jwt-public.pem

Database Password Rotation

AWS RDS (Auto-Rotation)

# Enable auto-rotation (90-day cycle)
aws secretsmanager rotate-secret \
--secret-id aegis/db-password \
--rotation-rules AutomaticallyAfterDays=90

# Manual rotation trigger
aws secretsmanager rotate-secret \
--secret-id aegis/db-password

# Verify new password works
aws secretsmanager get-secret-value \
--secret-id aegis/db-password \
--query 'SecretString' --output text | \
psql "postgresql://aegis:[email protected]/aegis" -c "SELECT 1;"

Manual Rotation

# 1. Generate new password
NEW_PASSWORD=$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c 32)

# 2. Update database password
psql $DATABASE_URL -c "ALTER USER aegis PASSWORD '$NEW_PASSWORD';"

# 3. Update secret store
aws secretsmanager update-secret \
--secret-id aegis/db-password \
--secret-string "$NEW_PASSWORD"

# 4. Restart API pods to pick up new credentials
kubectl rollout restart deployment/aegis-api -n aegis
kubectl rollout status deployment/aegis-api -n aegis

# 5. Verify connectivity
kubectl exec -n aegis deployment/aegis-api -- \
./aegis health | grep database

AI Provider API Key Rotation

Anthropic Claude

# 1. Generate new key in Anthropic console
# https://console.anthropic.com/settings/keys

# 2. Update secret store
aws secretsmanager update-secret \
--secret-id aegis/anthropic-api-key \
--secret-string "$NEW_ANTHROPIC_KEY"

# 3. Restart API pods
kubectl rollout restart deployment/aegis-api -n aegis

# 4. Verify AI provider health
curl -sf https://api.aegis.io/health | jq '.components.ai_provider'

# 5. Revoke old key in Anthropic console

OpenAI (Fallback)

# Same process, different secret ID
aws secretsmanager update-secret \
--secret-id aegis/openai-api-key \
--secret-string "$NEW_OPENAI_KEY"

kubectl rollout restart deployment/aegis-api -n aegis

Identity Provider Secret Rotation

Okta API Token

# 1. Create new token in Okta Admin Console
# Security > API > Tokens > Create Token

# 2. Update secret
aws secretsmanager update-secret \
--secret-id aegis/okta-api-token \
--secret-string "$NEW_OKTA_TOKEN"

# 3. Restart and verify
kubectl rollout restart deployment/aegis-api -n aegis
curl -sf https://api.aegis.io/health | jq '.components.identity_provider'

# 4. Revoke old token in Okta Admin Console

Entra ID Client Secret

# 1. Create new client secret in Azure Portal
# App registrations > Cloud Aegis > Certificates & secrets > New client secret

# 2. Update Key Vault
az keyvault secret set \
--vault-name aegis-kv \
--name entra-client-secret \
--value "$NEW_ENTRA_SECRET"

# 3. Restart and verify
kubectl rollout restart deployment/aegis-api -n aegis

Development Secrets

Development JWT secrets are stored in 1Password (aegis-dev-jwt-secret vault item) and referenced in frontend/.env.development (gitignored).

# Rotate dev JWT secret
# 1. Generate new secret
openssl rand -base64 64

# 2. Update 1Password vault item
# 3. Update frontend/.env.development with new VITE_DEV_TOKEN
# 4. Restart dev server

Verification

After any secret rotation:

# 1. Health check
curl -sf https://api.aegis.io/health | jq .

# 2. Verify no auth errors in logs (wait 5 minutes)
kubectl logs -n aegis -l app=aegis-api --since=5m | \
grep -i "auth.*error\|unauthorized\|forbidden" | head -20

# 3. Run smoke test
curl -sf https://api.aegis.io/api/v1/findings \
-H "Authorization: Bearer $API_TOKEN" | jq '.total'

Escalation

ConditionAction
Rotation causes auth failuresRollback to previous secret, investigate
Secret leaked in logs/gitImmediate rotation + security incident (Runbook 02)
Auto-rotation failsManual rotation, fix Lambda/function
Database password rotation breaks connectionsRestart all pods, verify PgBouncer

Contact Information

  • On-Call: PagerDuty
  • Platform Team: #platform-support (Slack)
  • Security Team: #security-ops (Slack)