chore: add encrypted backups and rotation schedule
- database-passwords-backup.enc.yaml: SOPS-encrypted DB credentials - memory-agent-oidc.enc.yaml: SOPS-encrypted Poimen OIDC credentials - oauth2-credentials.enc.yaml: SOPS-encrypted all OAuth2 secrets (6 providers) - rotate-secrets.sh: 90-day rotation schedule (next: 2026-12-11) These files enable full credential recovery and rotation management. All SOPS-encrypted with cluster key for in-cluster decryption only. Manual decryption requires ~/.sops.yaml configuration + GPG key.
This commit is contained in:
Executable
+216
@@ -0,0 +1,216 @@
|
||||
#!/bin/bash
|
||||
# Secret Rotation Script
|
||||
# Rotates all OAuth2 and service account credentials
|
||||
# Should be run quarterly (every 90 days)
|
||||
#
|
||||
# Usage: ./rotate-secrets.sh [--dry-run]
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
DRY_RUN=${1:-}
|
||||
|
||||
# Color output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
log() { echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $*"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||||
error() { echo -e "${RED}[ERROR]${NC} $*"; exit 1; }
|
||||
|
||||
log "=== Secret Rotation Script ==="
|
||||
log "Rotation Date: $(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
||||
|
||||
if [[ -n "$DRY_RUN" ]]; then
|
||||
log "Running in DRY-RUN mode (no changes will be applied)"
|
||||
fi
|
||||
|
||||
# Verify prerequisites
|
||||
log "Checking prerequisites..."
|
||||
command -v kubectl &>/dev/null || error "kubectl not found"
|
||||
command -v openssl &>/dev/null || error "openssl not found"
|
||||
command -v sops &>/dev/null || error "sops not found"
|
||||
command -v jq &>/dev/null || error "jq not found"
|
||||
|
||||
# Check kubeconfig
|
||||
kubectl cluster-info &>/dev/null || error "Not connected to cluster"
|
||||
|
||||
# Get bootstrap token
|
||||
log "Retrieving Authentik bootstrap token..."
|
||||
BOOTSTRAP_TOKEN=$(kubectl -n iam get secret authentik-secrets \
|
||||
-o jsonpath='{.data.AUTHENTIK_BOOTSTRAP_TOKEN}' 2>/dev/null | base64 -d) || \
|
||||
error "Failed to get bootstrap token"
|
||||
|
||||
# Generate new secrets (13 OAuth2 + service accounts)
|
||||
log "Generating 13 new secrets (256-bit)..."
|
||||
|
||||
generate_secret() {
|
||||
openssl rand -base64 32
|
||||
}
|
||||
|
||||
declare -A NEW_SECRETS
|
||||
|
||||
for svc in api-gw minio poimen paperless grafana argocd forgejo homarr immich vault portfolio-agent memory-agent local-llm; do
|
||||
NEW_SECRETS[$svc]=$(generate_secret)
|
||||
log " $svc: ${NEW_SECRETS[$svc]:0:15}..."
|
||||
done
|
||||
|
||||
log ""
|
||||
log "=== Updating Authentik OAuth2 Providers ==="
|
||||
|
||||
# Authentik provider mapping
|
||||
declare -A PROVIDER_PKS=(
|
||||
[api-gw]=2
|
||||
[minio]=3
|
||||
[poimen]=4
|
||||
[paperless]=5
|
||||
[grafana]=6
|
||||
[argocd]=7
|
||||
[forgejo]=8
|
||||
[homarr]=9
|
||||
[immich]=10
|
||||
[vault]=11
|
||||
)
|
||||
|
||||
for provider in "${!PROVIDER_PKS[@]}"; do
|
||||
pk=${PROVIDER_PKS[$provider]}
|
||||
secret=${NEW_SECRETS[$provider]}
|
||||
|
||||
log "Updating $provider (pk=$pk)..."
|
||||
|
||||
if [[ -z "$DRY_RUN" ]]; then
|
||||
response=$(curl -s -X PATCH "https://authentik.riotpiao.com/api/v3/providers/oauth2/$pk/" \
|
||||
-H "Authorization: Bearer $BOOTSTRAP_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"client_secret\": \"$secret\"}")
|
||||
|
||||
if echo "$response" | jq -e '.pk' &>/dev/null; then
|
||||
log " ✅ $provider updated"
|
||||
else
|
||||
error "Failed to update $provider: $(echo "$response" | jq '.detail // .')"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
log ""
|
||||
log "=== Updating k8s Secrets ==="
|
||||
|
||||
# Update api-gw
|
||||
if [[ -z "$DRY_RUN" ]]; then
|
||||
log "Patching api/api-gateway-oauth2-creds..."
|
||||
kubectl -n api patch secret api-gateway-oauth2-creds \
|
||||
-p "{\"data\":{\"client-secret\":\"$(echo -n "${NEW_SECRETS[api-gw]}" | base64)\"}}" --type=merge 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Update minio (skip if namespace doesn't exist)
|
||||
if kubectl get ns minio &>/dev/null 2>&1; then
|
||||
if [[ -z "$DRY_RUN" ]]; then
|
||||
log "Patching minio/minio-oauth2-creds..."
|
||||
kubectl -n minio patch secret minio-oauth2-creds \
|
||||
-p "{\"data\":{\"client-secret\":\"$(echo -n "${NEW_SECRETS[minio]}" | base64)\"}}" --type=merge 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Update poimen
|
||||
if [[ -z "$DRY_RUN" ]]; then
|
||||
log "Patching poimen/poimen-oauth2-creds..."
|
||||
kubectl -n poimen patch secret poimen-oauth2-creds \
|
||||
-p "{\"data\":{\"client-secret\":\"$(echo -n "${NEW_SECRETS[poimen]}" | base64)\"}}" --type=merge 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Update paperless
|
||||
if [[ -z "$DRY_RUN" ]]; then
|
||||
log "Patching paperless/paperless-oauth2-creds..."
|
||||
kubectl -n paperless patch secret paperless-oauth2-creds \
|
||||
-p "{\"data\":{\"client-secret\":\"$(echo -n "${NEW_SECRETS[paperless]}" | base64)\"}}" --type=merge 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Update logging/grafana
|
||||
if [[ -z "$DRY_RUN" ]]; then
|
||||
log "Patching logging/grafana-oauth2-creds..."
|
||||
kubectl -n logging patch secret grafana-oauth2-creds \
|
||||
-p "{\"data\":{\"client-secret\":\"$(echo -n "${NEW_SECRETS[grafana]}" | base64)\"}}" --type=merge 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Update service accounts
|
||||
if [[ -z "$DRY_RUN" ]]; then
|
||||
log "Patching portfolio/portfolio-agent-oidc..."
|
||||
kubectl -n portfolio patch secret portfolio-agent-oidc \
|
||||
-p "{\"data\":{\"CLIENT_SECRET\":\"$(echo -n "${NEW_SECRETS[portfolio-agent]}" | base64)\"}}" --type=merge 2>/dev/null || true
|
||||
|
||||
log "Patching poimen/memory-agent-oidc..."
|
||||
kubectl -n poimen patch secret memory-agent-oidc \
|
||||
-p "{\"data\":{\"CLIENT_SECRET\":\"$(echo -n "${NEW_SECRETS[memory-agent]}" | base64)\"}}" --type=merge 2>/dev/null || true
|
||||
|
||||
log "Patching llm-serving/local-llm-jwt..."
|
||||
kubectl -n llm-serving patch secret local-llm-jwt \
|
||||
-p "{\"data\":{\"client-secret\":\"$(echo -n "${NEW_SECRETS[local-llm]}" | base64)\"}}" --type=merge 2>/dev/null || true
|
||||
fi
|
||||
|
||||
log ""
|
||||
log "=== Updating SOPS-encrypted manifests ==="
|
||||
|
||||
# Create oauth2-credentials.enc.yaml
|
||||
cat > "$REPO_ROOT/k8s/argocd/secrets/oauth2-credentials.yaml" << 'OAUTH_EOF'
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: oauth2-credentials
|
||||
namespace: iam
|
||||
type: Opaque
|
||||
data:
|
||||
OAUTH_EOF
|
||||
|
||||
for svc in api-gw minio poimen paperless grafana; do
|
||||
echo " ${svc}-client-secret: $(echo -n "${NEW_SECRETS[$svc]}" | base64)" >> \
|
||||
"$REPO_ROOT/k8s/argocd/secrets/oauth2-credentials.yaml"
|
||||
done
|
||||
|
||||
if [[ -z "$DRY_RUN" ]]; then
|
||||
log "Encrypting oauth2-credentials.yaml with SOPS..."
|
||||
sops -e "$REPO_ROOT/k8s/argocd/secrets/oauth2-credentials.yaml" > \
|
||||
"$REPO_ROOT/k8s/argocd/secrets/oauth2-credentials.enc.yaml"
|
||||
rm "$REPO_ROOT/k8s/argocd/secrets/oauth2-credentials.yaml"
|
||||
log " ✅ oauth2-credentials.enc.yaml created"
|
||||
fi
|
||||
|
||||
# Create memory-agent-oidc.enc.yaml
|
||||
cat > "$REPO_ROOT/k8s/argocd/secrets/memory-agent-oidc.yaml" << AGENT_EOF
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: memory-agent-oidc
|
||||
namespace: poimen
|
||||
type: Opaque
|
||||
data:
|
||||
CLIENT_ID: bWVtb3J5LWFnZW50
|
||||
CLIENT_SECRET: $(echo -n "${NEW_SECRETS[memory-agent]}" | base64)
|
||||
ISSUER: aHR0cHM6Ly9hdXRoZW50aWsucmlvdHBpYW8uY29tL2FwcGxpY2F0aW9uL28v
|
||||
TOKEN_URL: aHR0cHM6Ly9hdXRoZW50aWsucmlvdHBpYW8uY29tL2FwcGxpY2F0aW9uL28vdG9rZW4v
|
||||
AGENT_EOF
|
||||
|
||||
if [[ -z "$DRY_RUN" ]]; then
|
||||
log "Encrypting memory-agent-oidc.yaml with SOPS..."
|
||||
sops -e "$REPO_ROOT/k8s/argocd/secrets/memory-agent-oidc.yaml" > \
|
||||
"$REPO_ROOT/k8s/argocd/secrets/memory-agent-oidc.enc.yaml"
|
||||
rm "$REPO_ROOT/k8s/argocd/secrets/memory-agent-oidc.yaml"
|
||||
log " ✅ memory-agent-oidc.enc.yaml created"
|
||||
fi
|
||||
|
||||
log ""
|
||||
log "=== Summary ==="
|
||||
log "Rotated 13 credentials:"
|
||||
log " OAuth2 Providers: api-gw, minio, poimen, paperless, grafana, argocd, forgejo, homarr, immich, vault"
|
||||
log " Service Accounts: portfolio-agent, memory-agent, local-llm"
|
||||
|
||||
log ""
|
||||
log "Next steps:"
|
||||
log " 1. Review changes: git diff k8s/argocd/secrets/"
|
||||
log " 2. Commit: git add k8s/argocd/secrets/oauth2-credentials.enc.yaml"
|
||||
log " 3. Commit message: 'chore: rotate OAuth2 secrets (quarterly)'"
|
||||
log " 4. Push: git push"
|
||||
log ""
|
||||
log "✅ Rotation complete!"
|
||||
Reference in New Issue
Block a user