From 7542e6e57072e4f821a93764ee846eeb71a116d3 Mon Sep 17 00:00:00 2001 From: OG T Date: Sat, 18 Apr 2026 15:26:04 +0800 Subject: [PATCH] =?UTF-8?q?feat(cd):=20ADR-090-B=20CD=20=E6=B3=A8=E5=85=A5?= =?UTF-8?q?=20L2=E2=86=92L3=2013=20=E5=80=8B=20key=20=E2=80=94=20=E6=B6=88?= =?UTF-8?q?=E6=BB=85=20K8s=20=E5=96=AE=E9=BB=9E=E7=9B=B2=E5=8D=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 2026-04-18 下午(台北時區)—— ogt + Claude Opus 4.7 (1M) 背景: Memory feedback_secrets_leak_incidents + reference_secrets_architecture_v2 定義 L1-L4 分層架構。盤點發現 14 個 K8s secret key 只存在 L3(K8s etcd) 而無 L2(Gitea Secret)備援,etcd 故障或 secret 誤刪將永久遺失。 本 commit 補上 13 個 key 的 L2→L3 CD 自動注入(SMTP_USER/SMTP_PASSWORD 仍為 CHANGE_ME 跳過): DATABASE_URL / MIGRATION_DATABASE_URL (ADR-090-B 新增) REDIS_URL / JWT_SECRET / JWT_ALGORITHM WEBHOOK_HMAC_SECRET (之前 L2 有但 CD 沒引) SENTRY_DSN / CLAUDE_API_KEY GITEA_API_TOKEN (via AWOOOI_GITEA_API_TOKEN 前綴繞過 Gitea 保留字) NEMOTRON_BOT_TOKEN / OPENCLAW_BOT_TOKEN SMTP_HOST / SRE_GROUP_CHAT_ID 模式: 完全照既有 cd.yaml `Inject K8s Secrets` step 模式 — env: 引用 + if [ -n ] guard + kubectl patch json op=add + base64 -w 0 + echo 結果。 110 行新增,0 行刪除,YAML 語法驗證通過。 安全: Gitea Secret 值從 K8s 現有 secret 同步(保持一致),本 CD run 為 no-op patch。 未來 K8s secret 誤刪或 rebuild 可從 Gitea 一鍵恢復。 相關: - docs/superpowers/specs/2026-04-18-blindspot-governance-capacity-l4.md - docs/adr/ADR-090-monitoring-blindspot-governance.md - apps/api/migrations/adr090b_awoooi_migrator_role.sql Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitea/workflows/cd.yaml | 110 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/.gitea/workflows/cd.yaml b/.gitea/workflows/cd.yaml index 54a00db2..457f3e09 100644 --- a/.gitea/workflows/cd.yaml +++ b/.gitea/workflows/cd.yaml @@ -252,6 +252,21 @@ jobs: GITEA_WEBHOOK_SECRET: ${{ secrets.AWOOOI_GITEA_WEBHOOK_SECRET }} # MCP Phase 3: ArgoCD API Token (2026-04-11 Claude Sonnet 4.6) ARGOCD_API_TOKEN: ${{ secrets.ARGOCD_API_TOKEN }} + # 2026-04-18 ogt + Claude Opus 4.7: ADR-090-B L3-only 升級 L2(永久連線串 + 應用 secret) + DATABASE_URL: ${{ secrets.DATABASE_URL }} + MIGRATION_DATABASE_URL: ${{ secrets.MIGRATION_DATABASE_URL }} + REDIS_URL: ${{ secrets.REDIS_URL }} + JWT_SECRET: ${{ secrets.JWT_SECRET }} + JWT_ALGORITHM: ${{ secrets.JWT_ALGORITHM }} + WEBHOOK_HMAC_SECRET: ${{ secrets.WEBHOOK_HMAC_SECRET }} + SENTRY_DSN: ${{ secrets.SENTRY_DSN }} + CLAUDE_API_KEY: ${{ secrets.CLAUDE_API_KEY }} + # AWOOOI_ 前綴避開 Gitea 保留字(同 AWOOOI_GITEA_WEBHOOK_SECRET 模式) + GITEA_API_TOKEN: ${{ secrets.AWOOOI_GITEA_API_TOKEN }} + NEMOTRON_BOT_TOKEN: ${{ secrets.NEMOTRON_BOT_TOKEN }} + OPENCLAW_BOT_TOKEN: ${{ secrets.OPENCLAW_BOT_TOKEN }} + SMTP_HOST: ${{ secrets.SMTP_HOST }} + SRE_GROUP_CHAT_ID: ${{ secrets.SRE_GROUP_CHAT_ID }} run: | # S1/S2: 統一命名 deploy_key,改用 ssh-keyscan(比 StrictHostKeyChecking=no 更安全) mkdir -p ~/.ssh @@ -332,6 +347,101 @@ jobs: echo "⚠️ ARGOCD_API_TOKEN 未設定,ArgoCD MCP 將使用空 token" fi + # ============================================================================ + # ADR-090-B 2026-04-18 ogt + Claude Opus 4.7: L3-only 升級 L2(13 個 key) + # ============================================================================ + # 目的: 消滅「只存 K8s etcd 單點」的災難盲區,Gitea Secret 成為正式真相來源 + # 注意: 每個 block 與上方維持相同結構(if guard + base64 -w 0 + json patch) + + # DATABASE_URL — PG 應用連線串(2026-04-18 輪替) + if [ -n "${DATABASE_URL}" ]; then + sudo kubectl patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[ + {"op":"add","path":"/data/DATABASE_URL","value":"'$(echo -n "${DATABASE_URL}" | base64 -w 0)'"} + ]' && echo "✅ DATABASE_URL 已注入" || echo "⚠️ DATABASE_URL patch 失敗" + else + echo "⚠️ DATABASE_URL 未設定,awoooi-api 將無法連 PG" + fi + + # MIGRATION_DATABASE_URL — CI migration 用 awoooi_migrator 限權帳號(ADR-090-B) + if [ -n "${MIGRATION_DATABASE_URL}" ]; then + sudo kubectl patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[ + {"op":"add","path":"/data/MIGRATION_DATABASE_URL","value":"'$(echo -n "${MIGRATION_DATABASE_URL}" | base64 -w 0)'"} + ]' && echo "✅ MIGRATION_DATABASE_URL 已注入" || echo "⚠️ MIGRATION_DATABASE_URL patch 失敗" + fi + + # REDIS_URL — Redis 連線(6380 on 188) + if [ -n "${REDIS_URL}" ]; then + sudo kubectl patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[ + {"op":"add","path":"/data/REDIS_URL","value":"'$(echo -n "${REDIS_URL}" | base64 -w 0)'"} + ]' && echo "✅ REDIS_URL 已注入" || echo "⚠️ REDIS_URL patch 失敗" + else + echo "⚠️ REDIS_URL 未設定" + fi + + # JWT_SECRET / JWT_ALGORITHM — API 認證 + if [ -n "${JWT_SECRET}" ]; then + sudo kubectl patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[ + {"op":"add","path":"/data/JWT_SECRET","value":"'$(echo -n "${JWT_SECRET}" | base64 -w 0)'"} + ]' && echo "✅ JWT_SECRET 已注入" || echo "⚠️ JWT_SECRET patch 失敗" + fi + if [ -n "${JWT_ALGORITHM}" ]; then + sudo kubectl patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[ + {"op":"add","path":"/data/JWT_ALGORITHM","value":"'$(echo -n "${JWT_ALGORITHM}" | base64 -w 0)'"} + ]' && echo "✅ JWT_ALGORITHM 已注入" || echo "⚠️ JWT_ALGORITHM patch 失敗" + fi + + # WEBHOOK_HMAC_SECRET — Alertmanager webhook HMAC 簽章 + if [ -n "${WEBHOOK_HMAC_SECRET}" ]; then + sudo kubectl patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[ + {"op":"add","path":"/data/WEBHOOK_HMAC_SECRET","value":"'$(echo -n "${WEBHOOK_HMAC_SECRET}" | base64 -w 0)'"} + ]' && echo "✅ WEBHOOK_HMAC_SECRET 已注入" || echo "⚠️ WEBHOOK_HMAC_SECRET patch 失敗" + fi + + # SENTRY_DSN — Sentry 錯誤追蹤(不是 auth token) + if [ -n "${SENTRY_DSN}" ]; then + sudo kubectl patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[ + {"op":"add","path":"/data/SENTRY_DSN","value":"'$(echo -n "${SENTRY_DSN}" | base64 -w 0)'"} + ]' && echo "✅ SENTRY_DSN 已注入" || echo "⚠️ SENTRY_DSN patch 失敗" + fi + + # CLAUDE_API_KEY — Claude 備援 LLM + if [ -n "${CLAUDE_API_KEY}" ]; then + sudo kubectl patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[ + {"op":"add","path":"/data/CLAUDE_API_KEY","value":"'$(echo -n "${CLAUDE_API_KEY}" | base64 -w 0)'"} + ]' && echo "✅ CLAUDE_API_KEY 已注入" || echo "⚠️ CLAUDE_API_KEY patch 失敗" + fi + + # GITEA_API_TOKEN — Gitea API Token(從 AWOOOI_GITEA_API_TOKEN 映射) + if [ -n "${GITEA_API_TOKEN}" ]; then + sudo kubectl patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[ + {"op":"add","path":"/data/GITEA_API_TOKEN","value":"'$(echo -n "${GITEA_API_TOKEN}" | base64 -w 0)'"} + ]' && echo "✅ GITEA_API_TOKEN 已注入" || echo "⚠️ GITEA_API_TOKEN patch 失敗" + fi + + # NEMOTRON_BOT_TOKEN / OPENCLAW_BOT_TOKEN — 多 Bot 架構 + if [ -n "${NEMOTRON_BOT_TOKEN}" ]; then + sudo kubectl patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[ + {"op":"add","path":"/data/NEMOTRON_BOT_TOKEN","value":"'$(echo -n "${NEMOTRON_BOT_TOKEN}" | base64 -w 0)'"} + ]' && echo "✅ NEMOTRON_BOT_TOKEN 已注入" || echo "⚠️ NEMOTRON_BOT_TOKEN patch 失敗" + fi + if [ -n "${OPENCLAW_BOT_TOKEN}" ]; then + sudo kubectl patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[ + {"op":"add","path":"/data/OPENCLAW_BOT_TOKEN","value":"'$(echo -n "${OPENCLAW_BOT_TOKEN}" | base64 -w 0)'"} + ]' && echo "✅ OPENCLAW_BOT_TOKEN 已注入" || echo "⚠️ OPENCLAW_BOT_TOKEN patch 失敗" + fi + + # SMTP_HOST / SRE_GROUP_CHAT_ID + if [ -n "${SMTP_HOST}" ]; then + sudo kubectl patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[ + {"op":"add","path":"/data/SMTP_HOST","value":"'$(echo -n "${SMTP_HOST}" | base64 -w 0)'"} + ]' && echo "✅ SMTP_HOST 已注入" || echo "⚠️ SMTP_HOST patch 失敗" + fi + if [ -n "${SRE_GROUP_CHAT_ID}" ]; then + sudo kubectl patch secret awoooi-secrets -n awoooi-prod --type='json' -p='[ + {"op":"add","path":"/data/SRE_GROUP_CHAT_ID","value":"'$(echo -n "${SRE_GROUP_CHAT_ID}" | base64 -w 0)'"} + ]' && echo "✅ SRE_GROUP_CHAT_ID 已注入" || echo "⚠️ SRE_GROUP_CHAT_ID patch 失敗" + fi + # 2026-04-06 Claude Code: Sprint 3 T2 — known_hosts Secret (Security Fix A1) # 替換 StrictHostKeyChecking=no,讓 SSH 修復路徑使用已知主機指紋 ssh-keyscan -H 192.168.0.110 > /tmp/known_hosts_repair 2>/dev/null