#!/bin/bash # rotate_keys.sh — re-encrypt all AES-256-GCM fields with a new key. # The application must be STOPPED before running. # # Usage: # NEW_ENCRYPTION_KEY=$(python3 -c "import secrets; print(secrets.token_hex(32))") # docker compose stop backend # NEW_ENCRYPTION_KEY="$NEW_ENCRYPTION_KEY" ./scripts/rotate_keys.sh # # On success, update ENCRYPTION_KEY in .env, then: # docker compose up -d backend set -euo pipefail if [ -z "${NEW_ENCRYPTION_KEY:-}" ]; then echo "ERROR: NEW_ENCRYPTION_KEY is not set" echo "Generate with: python3 -c \"import secrets; print(secrets.token_hex(32))\"" exit 1 fi if [ -z "${ENCRYPTION_KEY:-}" ]; then # Try to load from .env in the project root ENV_FILE="$(dirname "$0")/../.env" if [ -f "${ENV_FILE}" ]; then ENCRYPTION_KEY=$(grep -E '^ENCRYPTION_KEY=' "${ENV_FILE}" | cut -d= -f2- | tr -d '"' | tr -d "'") fi fi if [ -z "${ENCRYPTION_KEY:-}" ]; then echo "ERROR: ENCRYPTION_KEY (current key) could not be found in environment or .env" exit 1 fi if [ "${ENCRYPTION_KEY}" = "${NEW_ENCRYPTION_KEY}" ]; then echo "ERROR: NEW_ENCRYPTION_KEY is the same as the current key — nothing to do" exit 1 fi echo "[rotate] This will re-encrypt ALL sensitive fields in the database." echo "[rotate] Ensure the application containers are stopped before continuing." read -r -p "[rotate] Type 'yes' to continue: " CONFIRM if [ "${CONFIRM}" != "yes" ]; then echo "[rotate] Aborted" exit 1 fi echo "[rotate] Running key rotation inside the backend container…" docker compose exec \ -e ENCRYPTION_KEY="${ENCRYPTION_KEY}" \ -e NEW_ENCRYPTION_KEY="${NEW_ENCRYPTION_KEY}" \ backend python -m app.core.key_rotation echo "" echo "[rotate] SUCCESS. Next steps:" echo " 1. Update ENCRYPTION_KEY in .env to: ${NEW_ENCRYPTION_KEY}" echo " 2. Restart the backend: docker compose up -d backend"