Olympus Docs
CookbookSecrets & encryption

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>&1

Daily 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
fi

Two-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' ERR

If anything errors, restore backup .env. Manual investigation needed.

Alert ops:

mail -s "URGENT: Olympus rotation failed" oncall@your-domain.com < /var/log/olympus/rotation.log

Monitoring

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-rotation

Uptime 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 grace

Olympus's .env-based config is simpler but less auditable. For production at scale: vault.

How often

SecretRecommended interval
Cookie / cipher keys90 days
Database passwords90 days (manual)
OAuth client secrets180 days
Encryption keysPer ADR 0006
TLS certsAuto-renewed

Adjust to your threat model.

On this page