Automated secret rotator
Cron-driven rotation of cookie / cipher keys
Manual secret rotation is error-prone, easy to skip, hard to remember. Automate via cron.
Eligible secrets
These rotate cleanly without service disruption:
- Kratos session cookie secret (overlap supported).
- Hydra system secret (overlap supported).
- Hydra cookie secret (overlap supported).
These need more care:
- Postgres passwords (downtime risk).
- Encryption keys (data re-encryption).
Focus on the easy ones first.
Pattern
Daily 03:00:
- Check last rotation date.
- If > 90 days, rotate.#!/bin/bash
# /usr/local/bin/rotate-kratos-cookie.sh
set -e
DOTENV=/home/deploy/olympus/.env
LAST_ROTATION_FILE=/var/lib/olympus/last-cookie-rotation
NOW=$(date +%s)
# Read last rotation
LAST=$(cat $LAST_ROTATION_FILE 2>/dev/null || echo 0)
AGE_DAYS=$(( (NOW - LAST) / 86400 ))
if [ $AGE_DAYS -lt 90 ]; then
echo "Last rotation was $AGE_DAYS days ago, skipping"
exit 0
fi
echo "Rotating Kratos cookie secret"
# Get current value
CURRENT=$(grep KRATOS_SECRETS_COOKIE $DOTENV | cut -d= -f2)
NEW=$(openssl rand -base64 32)
# Build new list: NEW first (active), CURRENT second (for verification of existing sessions)
NEW_VALUE="${NEW},${CURRENT}"
# Update .env atomically
sed -i.bak "s/^KRATOS_SECRETS_COOKIE=.*/KRATOS_SECRETS_COOKIE=${NEW_VALUE}/" $DOTENV
# Reload Kratos (without downtime: pause+resume)
podman-compose -f /home/deploy/olympus/docker-compose.yml restart ciam-kratos
# Update last-rotation marker
echo "$NOW" > $LAST_ROTATION_FILE
# Send notification
echo "Kratos cookie secret rotated. Next rotation in 90 days." | mail -s "Olympus secret rotation" oncall@your-domain.com# /etc/cron.d/olympus-rotation
0 3 * * * deploy /usr/local/bin/rotate-kratos-cookie.sh >> /var/log/olympus/rotation.log 2>&1Daily check. Rotates if eligible.
Stale value cleanup
After 24h (longest session lifespan), the OLD value can be removed:
# In the daily check:
if [ $AGE_DAYS -ge 1 ] && [ $AGE_DAYS -lt 90 ]; then
# Within last 24h since rotation? If so, leave value.
# If > 1 day since rotation, drop old half.
CURRENT=$(grep KRATOS_SECRETS_COOKIE $DOTENV | cut -d= -f2)
if [[ "$CURRENT" == *,* ]]; then
NEW_ONLY=${CURRENT%%,*}
sed -i "s/^KRATOS_SECRETS_COOKIE=.*/KRATOS_SECRETS_COOKIE=${NEW_ONLY}/" $DOTENV
podman-compose restart ciam-kratos
fi
fiTwo-phase: rotate (add new, keep old). Day-later: drop old.
Audit trail
Log to your audit system:
curl -X POST $AUDIT_API/event \
-d "{ \"event_type\": \"secret_rotated\", \"actor\": \"cron\", \"metadata\": { \"secret\": \"kratos_cookie\", \"age_days\": $AGE_DAYS } }"Trail of automated rotations. Differentiate from manual.
Failure handling
If rotation fails (Kratos doesn't restart, sed fails), DON'T leave .env in broken state:
set -e
trap 'mv ${DOTENV}.bak ${DOTENV}; exit 1' ERRIf anything errors, restore backup .env. Manual investigation needed.
Alert ops:
mail -s "URGENT: Olympus rotation failed" oncall@your-domain.com < /var/log/olympus/rotation.logMonitoring
Cron runs but you don't see output. Add health monitoring:
# At end of script:
curl https://uptime-kuma.your-domain.com/api/push/kratos-rotationUptime monitor expects ping at certain interval. Missing pings → alert.
What this doesn't cover
- Postgres password rotation (needs careful sequencing).
- Encryption key rotation (needs re-encrypt step).
- OAuth client secret rotation (per-client cadence).
These need either:
- Manual scripts with verification.
- More sophisticated automation (Terraform + secret-as-code).
Secret storage during rotation
Best: secrets live in a vault (HashiCorp Vault, AWS Secrets Manager), not .env files.
Rotation pseudo-code:
new_secret = vault.create_secret(name="kratos-cookie-v2", value=random())
vault.update_alias("kratos-cookie-current", new_secret)
service.reload() # picks up via vault SDK
vault.delete_secret("kratos-cookie-v1") # after graceOlympus's .env-based config is simpler but less auditable. For production at scale: vault.
How often
| Secret | Recommended interval |
|---|---|
| Cookie / cipher keys | 90 days |
| Database passwords | 90 days (manual) |
| OAuth client secrets | 180 days |
| Encryption keys | Per ADR 0006 |
| TLS certs | Auto-renewed |
Adjust to your threat model.