gabriel / musehub public
secrets.sh bash
178 lines 7.2 KB
Raw
sha256:e5530c6b8996b96fc4030d6d30fe4f688926bdda2875fb01d303092b11513bd2 fix(9A-4 F7): env alias + staging secrets for overseer prov… Human minor ⚠ breaking 5 days ago
1 #!/usr/bin/env bash
2 # MuseHub secrets bootstrap — fetch from AWS SSM Parameter Store, write .env
3 #
4 # Runs on the EC2 instance BEFORE deploy.sh. Pulls every secret from SSM
5 # Parameter Store (SecureString, AES-256 at rest via KMS) and writes a fresh
6 # /opt/musehub/.env. The .env on disk is the runtime injection point for
7 # all Docker containers (--env-file).
8 #
9 # Why SSM instead of a static .env:
10 # - Secrets never travel through source control or build artifacts.
11 # - Access is audited via CloudTrail (who fetched what, when).
12 # - Rotation updates SSM; next deploy.sh run picks up the new value.
13 # - IAM role on the EC2 instance grants read access — no AWS keys on disk.
14 #
15 # SSM parameter layout (all SecureString, KMS-encrypted):
16 # /musehub/<env>/DB_PASSWORD
17 # /musehub/<env>/WEBHOOK_SECRET_KEY
18 # /musehub/<env>/RUNNER_TOKEN
19 # /musehub/<env>/BLOB_STORAGE_ACCESS_KEY_ID
20 # /musehub/<env>/BLOB_STORAGE_SECRET_ACCESS_KEY
21 # /musehub/<env>/WORKER_INTERNAL_KEY (shared secret for Cloudflare Worker → MuseHub callbacks)
22 # /musehub/<env>/MPACK_WORKER_URL (public URL of the CF mpack-receiver Worker; optional)
23 #
24 # Prerequisites:
25 # - AWS CLI v2 installed on the EC2 instance
26 # - EC2 instance profile with IAM policy:
27 # ssm:GetParameter, ssm:GetParametersByPath
28 # on arn:aws:ssm:<region>:<account>:parameter/musehub/<env>/*
29 # - KMS decrypt on the CMK used for the SecureString parameters
30 #
31 # Usage:
32 # MUSEHUB_ENV=production bash deploy/secrets.sh
33 # MUSEHUB_ENV=staging bash deploy/secrets.sh
34 #
35 # After this script writes .env, run deploy.sh as usual.
36 #
37 # Fallback (no SSM / local dev):
38 # If AWS CLI is not available or SSM fetch fails, the script exits non-zero
39 # so deploy.sh does not start with stale/missing secrets. For local dev,
40 # manage .env manually — never run this script on a dev laptop.
41
42 set -euo pipefail
43
44 MUSEHUB_ENV="${MUSEHUB_ENV:-production}"
45 APP_DIR="${APP_DIR:-/opt/musehub}"
46 ENV_FILE="$APP_DIR/.env"
47 REGION="${AWS_REGION:-us-east-1}"
48 SSM_PREFIX="/musehub/${MUSEHUB_ENV}"
49
50 log() { echo "[secrets] $*"; }
51 die() { echo "[secrets] ERROR: $*" >&2; exit 1; }
52
53 # ── Preflight ─────────────────────────────────────────────────────────────────
54
55 command -v aws > /dev/null 2>&1 || die "AWS CLI not installed. Install: sudo apt-get install -y awscli"
56
57 # Verify we can reach SSM (IAM role check) — use GetParameter on DB_PASSWORD
58 # (always required) rather than GetParametersByPath (requires broader permission).
59 aws ssm get-parameter \
60 --name "$SSM_PREFIX/DB_PASSWORD" \
61 --region "$REGION" \
62 --with-decryption \
63 --query 'Parameter.Value' \
64 --output text > /dev/null 2>&1 \
65 || die "Cannot read $SSM_PREFIX/DB_PASSWORD from SSM — check the EC2 instance IAM role."
66
67 log "Fetching secrets from SSM: $SSM_PREFIX (region=$REGION)"
68
69 # ── Fetch each parameter ──────────────────────────────────────────────────────
70
71 _get() {
72 local name="$1"
73 local required="${2:-true}"
74 local value
75 value=$(aws ssm get-parameter \
76 --name "$SSM_PREFIX/$name" \
77 --region "$REGION" \
78 --with-decryption \
79 --query 'Parameter.Value' \
80 --output text 2>/dev/null) || {
81 if [ "$required" = "true" ]; then
82 die "Required parameter $SSM_PREFIX/$name not found in SSM"
83 fi
84 echo ""
85 return
86 }
87 echo "$value"
88 }
89
90 DB_PASSWORD=$(_get "DB_PASSWORD")
91 WEBHOOK_SECRET_KEY=$(_get "WEBHOOK_SECRET_KEY")
92 RUNNER_TOKEN=$(_get "RUNNER_TOKEN" false)
93 BLOB_STORAGE_ACCESS_KEY_ID=$(_get "BLOB_STORAGE_ACCESS_KEY_ID" false)
94 BLOB_STORAGE_SECRET_ACCESS_KEY=$(_get "BLOB_STORAGE_SECRET_ACCESS_KEY" false)
95 WORKER_INTERNAL_KEY=$(_get "WORKER_INTERNAL_KEY" false)
96 PACK_WORKER_URL=$(_get "PACK_WORKER_URL" false)
97
98 # ── Resolve per-environment non-secret config ─────────────────────────────────
99
100 if [ "$MUSEHUB_ENV" = "staging" ]; then
101 PUBLIC_URL="https://staging.musehub.ai"
102 CORS_ORIGINS='["https://staging.musehub.ai"]'
103 BLOB_STORAGE_BUCKET="musehub-staging"
104 BLOB_STORAGE_ENDPOINT="https://bed873d46de5273abf843468a7833f09.r2.cloudflarestorage.com"
105 BLOB_STORAGE_REGION="auto"
106 # Phase 9A-4 F7 — overseer provenance enrichment read wire (non-secret posture gate).
107 OVERSEER_PROVENANCE_ENRICHMENT="enabled"
108 elif [ "$MUSEHUB_ENV" = "production" ]; then
109 PUBLIC_URL="https://musehub.ai"
110 CORS_ORIGINS='["https://musehub.ai", "https://www.musehub.ai"]'
111 BLOB_STORAGE_BUCKET="musehub-prod"
112 BLOB_STORAGE_ENDPOINT="https://bed873d46de5273abf843468a7833f09.r2.cloudflarestorage.com"
113 BLOB_STORAGE_REGION="auto"
114 OVERSEER_PROVENANCE_ENRICHMENT="disabled"
115 else
116 die "Unknown MUSEHUB_ENV='$MUSEHUB_ENV'. Must be 'staging' or 'production'."
117 fi
118
119 # ── Write .env ────────────────────────────────────────────────────────────────
120
121 log "Writing $ENV_FILE (env=$MUSEHUB_ENV, public_url=$PUBLIC_URL)"
122
123 # Back up the existing .env if present
124 if [ -f "$ENV_FILE" ]; then
125 cp "$ENV_FILE" "${ENV_FILE}.bak.$(date +%Y%m%d_%H%M%S)"
126 log "Previous .env backed up"
127 fi
128
129 # Write new .env — mode 600, owner musehub
130 umask 177
131 cat > "$ENV_FILE" << EOF
132 # Generated by deploy/secrets.sh at $(date -u +%Y-%m-%dT%H:%M:%SZ)
133 # Secrets sourced from AWS SSM Parameter Store: $SSM_PREFIX
134 # DO NOT edit manually — re-run secrets.sh to refresh from SSM.
135
136 MUSE_ENV=${MUSEHUB_ENV}
137 DEBUG=false
138 PUBLIC_URL=${PUBLIC_URL}
139 CORS_ORIGINS=${CORS_ORIGINS}
140 DB_PASSWORD=${DB_PASSWORD}
141 BLOB_STORAGE_BUCKET=${BLOB_STORAGE_BUCKET}
142 BLOB_STORAGE_ENDPOINT=${BLOB_STORAGE_ENDPOINT}
143 BLOB_STORAGE_REGION=${BLOB_STORAGE_REGION}
144 WEBHOOK_SECRET_KEY=${WEBHOOK_SECRET_KEY}
145 MUSEHUB_OVERSEER_PROVENANCE_ENRICHMENT=${OVERSEER_PROVENANCE_ENRICHMENT:-disabled}
146 EOF
147 if [ -n "$RUNNER_TOKEN" ]; then
148 echo "RUNNER_TOKEN=${RUNNER_TOKEN}" >> "$ENV_FILE"
149 fi
150 if [ -n "$BLOB_STORAGE_ACCESS_KEY_ID" ]; then
151 echo "BLOB_STORAGE_ACCESS_KEY_ID=${BLOB_STORAGE_ACCESS_KEY_ID}" >> "$ENV_FILE"
152 echo "BLOB_STORAGE_SECRET_ACCESS_KEY=${BLOB_STORAGE_SECRET_ACCESS_KEY}" >> "$ENV_FILE"
153 fi
154 if [ -n "$WORKER_INTERNAL_KEY" ]; then
155 echo "WORKER_INTERNAL_KEY=${WORKER_INTERNAL_KEY}" >> "$ENV_FILE"
156 fi
157 if [ -n "$PACK_WORKER_URL" ]; then
158 echo "PACK_WORKER_URL=${PACK_WORKER_URL}" >> "$ENV_FILE"
159 fi
160
161 chown musehub:musehub "$ENV_FILE" 2>/dev/null || true
162 log ".env written ($(wc -l < "$ENV_FILE") lines, mode 600)"
163
164 # ── Sanity check — no weak values leaked into env ────────────────────────────
165
166 WEAK_PASSWORDS=("musehub" "changeme123" "password" "postgres" "secret" "")
167 for WEAK in "${WEAK_PASSWORDS[@]}"; do
168 if [ "$DB_PASSWORD" = "$WEAK" ]; then
169 die "DB_PASSWORD from SSM is a known weak value ($WEAK). Rotate it immediately."
170 fi
171 done
172
173 if [ ${#DB_PASSWORD} -lt 16 ]; then
174 die "DB_PASSWORD from SSM is too short (${#DB_PASSWORD} chars). Minimum is 16."
175 fi
176
177 log "Secrets sanity check passed."
178 log "Run 'bash deploy/deploy.sh' to deploy."
File History 2 commits
sha256:e5530c6b8996b96fc4030d6d30fe4f688926bdda2875fb01d303092b11513bd2 fix(9A-4 F7): env alias + staging secrets for overseer prov… Human minor 5 days ago
sha256:42bc547653458bb15e283222247af0237872d9cb5c2a72ad2ca1c111ed2c0ca4 feat(9A-4 F7): overseer-run provenance enrichment API for S… Human minor 7 days ago