217 lines
6.9 KiB
Bash
217 lines
6.9 KiB
Bash
#!/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!"
|