docs: 完整治理架構 ADR-010/011/012 + CLAUDE.md 鐵律更新

2026-03-23 重大事故修復與治理:

1. ADR-010: Secrets 集中管理 (Bitwarden + Sealed Secrets)
2. ADR-011: NetworkPolicy 變更治理 (偵測 + 告警 + 人工決策)
3. ADR-012: 危險操作治理 (Tier 分級 + CI/CD 攔截 + 審計)
4. UX-001: 告警疲勞解決方案 (時間衰減 + 智慧分組)

CLAUDE.md 更新:
- 新增最高優先級鐵律 (禁止 ClawBot、OpenClaw 核心、禁止危險 API)
- 新增任務開始前必讀 Memory 對照表

事故教訓:
- Telegram Token 連續三次被 logOut 失效
- AWOOOI API 程式碼呼叫 logOut 導致災難
- 已停用 AWOOOI API Telegram,OpenClaw 為唯一 Gateway

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
OG T
2026-03-23 19:44:56 +08:00
parent e23493741a
commit 2b1264df05
6 changed files with 1424 additions and 7 deletions

View File

@@ -0,0 +1,393 @@
# ADR-010: 集中式 Secrets 管理架構
**狀態**: 已批准
**日期**: 2026-03-23
**決策者**: 統帥
## 背景
### 當前痛點
```
問題:密碼散落各處,變更一處 → 全線崩潰
Harbor 密碼存在位置:
├── GitHub Secrets (wooo-aiops)
├── GitHub Secrets (awoooi) ← 今天才補上
├── K8s Secrets (awoooi-prod)
├── Self-hosted Runner 環境變數
├── 開發者本地 ~/.docker/config.json
└── 文檔/記憶體 MD 檔案
結果:
- 變更密碼需同步 5+ 個地方
- 經常遺漏 → CI/CD 崩潰
- 無審計日誌
- 無法追蹤誰何時改了什麼
```
### 需要管理的 Secrets 清單
| 類別 | Secret 名稱 | 使用位置 |
|------|------------|----------|
| **Registry** | HARBOR_USER / HARBOR_PASSWORD | CI/CD, K8s |
| **Database** | DATABASE_URL (PostgreSQL) | API, Worker |
| **Cache** | REDIS_URL | API, Worker, OpenClaw |
| **AI** | ANTHROPIC_API_KEY | API (Agent Teams) |
| **AI** | GEMINI_API_KEY | API (Fallback) |
| **AI** | OLLAMA_URL | API (Local LLM) |
| **Notification** | OPENCLAW_TG_BOT_TOKEN | OpenClaw |
| **Notification** | OPENCLAW_TG_CHAT_ID | OpenClaw |
| **K8s** | KUBECONFIG | CI/CD |
| **Webhook** | WEBHOOK_HMAC_SECRET | API |
---
## 決策
採用 **混合式 Secrets 管理架構**
1. **Bitwarden (Self-hosted)** - Single Source of Truth
2. **Sealed Secrets** - K8s 內部密碼加密
3. **GitHub Secrets** - CI/CD 專用 (從 Bitwarden 同步)
---
## 架構設計
```
┌─────────────────────────────────────────────────────────────────────┐
│ AWOOOI Secrets 管理架構 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ Bitwarden (Self-hosted on 192.168.0.188) │ │
│ │ ══════════════════════════════════════ │ │
│ │ 🔐 Single Source of Truth │ │
│ │ │ │
│ │ Organizations: │ │
│ │ └── AWOOOI/ │ │
│ │ ├── Infrastructure/ │ │
│ │ │ ├── Harbor (admin / Wooo_Harbor_2026!) │ │
│ │ │ ├── PostgreSQL (awoooi / ******) │ │
│ │ │ └── Redis (password / ******) │ │
│ │ ├── AI-Services/ │ │
│ │ │ ├── Anthropic API Key │ │
│ │ │ ├── Gemini API Key │ │
│ │ │ └── Ollama URL │ │
│ │ └── Notifications/ │ │
│ │ ├── Telegram Bot Token │ │
│ │ └── Telegram Chat ID │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ GitHub Secrets │ │ Sealed │ │ Local Dev │ │
│ │ (CI/CD) │ │ Secrets │ │ (.env.local) │ │
│ │ │ │ (K8s) │ │ │ │
│ │ HARBOR_USER │ │ │ │ bw get password │ │
│ │ HARBOR_PASSWORD │ │ 加密存 Git │ │ "Harbor" │ │
│ │ OP_TOKEN (sync) │ │ 自動解密 │ │ │ │
│ └────────┬────────┘ └──────┬──────┘ └─────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ K3s Cluster (192.168.0.120/121) │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ awoooi-prod namespace │ │ │
│ │ │ ├── awoooi-secrets (from Sealed Secrets) │ │ │
│ │ │ ├── awoooi-config (ConfigMap, non-sensitive) │ │ │
│ │ │ └── Pods (consume secrets as env vars) │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
---
## 實施計畫
### Phase 1: Bitwarden Self-hosted (Day 1-2)
```bash
# 在 192.168.0.188 部署 Vaultwarden (輕量版 Bitwarden)
docker run -d --name vaultwarden \
-v /data/vaultwarden:/data \
-e ADMIN_TOKEN='your-admin-token' \
-e DOMAIN='https://vault.wooo.work' \
-p 8088:80 \
vaultwarden/server:latest
# Nginx 設定
server {
listen 443 ssl;
server_name vault.wooo.work;
location / {
proxy_pass http://localhost:8088;
}
}
```
**初始化步驟:**
1. 訪問 https://vault.wooo.work
2. 建立 Organization: AWOOOI
3. 建立 Collections: Infrastructure, AI-Services, Notifications
4. 匯入現有密碼
### Phase 2: Sealed Secrets (Day 2-3)
```bash
# Step 1: 安裝 Sealed Secrets Controller
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.26.0/controller.yaml
# Step 2: 取得 Public Key
kubeseal --fetch-cert \
--controller-name=sealed-secrets-controller \
--controller-namespace=kube-system \
> /Users/ogt/awoooi/k8s/sealed-secrets-cert.pem
# Step 3: 加密現有 Secrets
cat <<EOF > /tmp/secrets.yaml
apiVersion: v1
kind: Secret
metadata:
name: awoooi-secrets
namespace: awoooi-prod
type: Opaque
stringData:
DATABASE_URL: "postgresql+asyncpg://awoooi:xxx@192.168.0.188:5432/awoooi_prod"
REDIS_URL: "redis://:xxx@192.168.0.188:6380/10"
HARBOR_USER: "admin"
HARBOR_PASSWORD: "Wooo_Harbor_2026!"
ANTHROPIC_API_KEY: "sk-ant-xxx"
EOF
kubeseal --cert=sealed-secrets-cert.pem \
--format=yaml \
< /tmp/secrets.yaml \
> k8s/awoooi-prod/03-sealed-secrets.yaml
# 清理明文
rm /tmp/secrets.yaml
# Step 4: 提交加密版本
git add k8s/awoooi-prod/03-sealed-secrets.yaml
git commit -m "feat(security): migrate to Sealed Secrets"
```
### Phase 3: GitHub Actions 整合 (Day 3-4)
```yaml
# .github/workflows/sync-secrets.yml
name: Sync Secrets from Bitwarden
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * 0' # 每週日同步
jobs:
sync:
runs-on: [self-hosted, harbor, k8s]
steps:
- name: Install Bitwarden CLI
run: |
npm install -g @bitwarden/cli
bw config server https://vault.wooo.work
- name: Login & Sync
env:
BW_PASSWORD: ${{ secrets.BW_MASTER_PASSWORD }}
run: |
export BW_SESSION=$(bw login --raw)
# 取得 Harbor 認證
HARBOR_USER=$(bw get username "Harbor")
HARBOR_PASSWORD=$(bw get password "Harbor")
# 更新 GitHub Secrets
gh secret set HARBOR_USER --body "$HARBOR_USER"
gh secret set HARBOR_PASSWORD --body "$HARBOR_PASSWORD"
echo "✅ Secrets synced from Bitwarden"
```
### Phase 4: 開發者本地環境 (Day 4)
```bash
# 安裝 Bitwarden CLI
brew install bitwarden-cli
# 登入
bw config server https://vault.wooo.work
bw login
# 產生 .env.local
cat > generate-env.sh << 'EOF'
#!/bin/bash
export BW_SESSION=$(bw unlock --raw)
echo "DATABASE_URL=$(bw get password 'PostgreSQL')" > .env.local
echo "REDIS_URL=$(bw get password 'Redis')" >> .env.local
echo "ANTHROPIC_API_KEY=$(bw get password 'Anthropic')" >> .env.local
echo "✅ .env.local generated from Bitwarden"
EOF
chmod +x generate-env.sh
```
---
## 密碼 Rotation SOP
### 變更密碼流程
```
┌─────────────────────────────────────────────────────────────┐
│ 密碼變更 SOP (Single Source of Truth) │
├─────────────────────────────────────────────────────────────┤
│ │
│ Step 1: 在 Bitwarden 更新密碼 │
│ └── vault.wooo.work → Edit → Save │
│ │
│ Step 2: 觸發同步 Workflow │
│ └── gh workflow run sync-secrets.yml │
│ │
│ Step 3: 重新 Seal K8s Secrets │
│ └── ./scripts/reseal-secrets.sh │
│ │
│ Step 4: 部署更新 │
│ └── kubectl apply -f k8s/awoooi-prod/ │
│ │
│ Step 5: 驗證 │
│ └── curl https://awoooi.wooo.work/api/v1/health │
│ │
└─────────────────────────────────────────────────────────────┘
```
### 自動化 Rotation Script
```bash
#!/bin/bash
# scripts/rotate-secret.sh
SECRET_NAME=$1
NEW_VALUE=$2
if [ -z "$SECRET_NAME" ] || [ -z "$NEW_VALUE" ]; then
echo "Usage: ./rotate-secret.sh <secret-name> <new-value>"
exit 1
fi
echo "🔄 Rotating secret: $SECRET_NAME"
# Step 1: Update Bitwarden
export BW_SESSION=$(bw unlock --raw)
bw edit item "$SECRET_NAME" --value "$NEW_VALUE"
# Step 2: Sync to GitHub
gh workflow run sync-secrets.yml
# Step 3: Re-seal K8s secrets
./scripts/reseal-secrets.sh
# Step 4: Apply to cluster
kubectl apply -f k8s/awoooi-prod/03-sealed-secrets.yaml
# Step 5: Rolling restart
kubectl rollout restart deployment -n awoooi-prod
echo "✅ Secret rotated successfully"
```
---
## 緊急撤銷流程
```bash
# 如果密碼洩漏,立即執行:
# 1. 在 Bitwarden 產生新密碼
bw generate -ulns --length 32
# 2. 更新所有相關服務
./scripts/rotate-secret.sh "Harbor" "$(bw generate -ulns --length 32)"
# 3. 撤銷舊 Token (如適用)
# Harbor: Admin UI → Users → Reset Password
# GitHub: Settings → Secrets → Delete old, Add new
# 4. 審計日誌檢查
bw list items --search "Harbor" --pretty
```
---
## 監控與告警
```yaml
# 新增到 Prometheus 告警規則
groups:
- name: secrets-monitoring
rules:
- alert: SecretRotationOverdue
expr: time() - secret_last_rotation_timestamp > 86400 * 90
for: 1d
labels:
severity: warning
annotations:
summary: "Secret {{ $labels.secret_name }} 超過 90 天未輪換"
- alert: SecretAccessAnomaly
expr: rate(secret_access_total[1h]) > 100
for: 5m
labels:
severity: critical
annotations:
summary: "異常的 Secret 存取頻率"
```
---
## 成本分析
| 項目 | 方案 | 成本 |
|------|------|------|
| Secrets 儲存 | Vaultwarden (Self-hosted) | $0 |
| K8s 加密 | Sealed Secrets | $0 |
| 同步工具 | Bitwarden CLI | $0 |
| **總計** | | **$0/年** |
---
## 驗收標準
- [ ] Vaultwarden 部署在 192.168.0.188
- [ ] 所有 Secrets 已匯入 Bitwarden
- [ ] Sealed Secrets Controller 運行中
- [ ] K8s Secrets 已加密存入 Git
- [ ] GitHub Actions 可從 Bitwarden 同步
- [ ] 開發者可用 CLI 產生 .env.local
- [ ] Rotation SOP 文件完成
- [ ] 監控告警已設定
---
## 附錄Secrets 清單 (機密)
> ⚠️ 此清單僅供內部參考,實際密碼存於 Bitwarden
| 名稱 | 類型 | 最後更新 | 下次 Rotation |
|------|------|----------|---------------|
| Harbor | Registry | 2026-03-23 | 2026-06-23 |
| PostgreSQL | Database | TBD | TBD |
| Redis | Cache | TBD | TBD |
| Anthropic | API Key | TBD | TBD |
| Gemini | API Key | TBD | TBD |
| Telegram Bot | Token | 2026-03-22 | 2026-06-22 |

View File

@@ -0,0 +1,288 @@
# ADR-011: NetworkPolicy 變更治理架構
**狀態**: 提案
**日期**: 2026-03-23
**決策者**: 統帥
**觸發**: 多次 NetworkPolicy 變更導致生產事故
## 問題陳述
```
事故時間線:
├── 2026-03-20: Worker 無法連 Redis → 發現 Egress 被阻擋
├── 2026-03-22: OTEL 上報失敗 → 發現 Port 24317/24318 未開
├── 2026-03-23: Y 按鈕執行超時 → K8s API 192.168.0.120:6443 未開
└── 每次都是「事後診斷」才發現 NetworkPolicy 問題
```
**根本原因**:
1. 任何有 kubectl 權限的人都可以直接修改 NetworkPolicy
2. 修改後沒有任何告警或審計
3. Git 版本與叢集版本經常不同步
4. 沒有 Dry-Run / Diff 機制防止錯誤
---
## 決策:三層防護架構
```
┌──────────────────────────────────────────────────────────────────────┐
│ NetworkPolicy 變更治理架構 │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ Layer 1: GitOps (Single Source of Truth) │
│ ══════════════════════════════════════════ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 開發者 │ ──▶ │ PR 審核 │ ──▶ │ ArgoCD │ │
│ │ 修改 YAML │ │ 至少 2 人 │ │ 自動同步 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Git │ │ GitHub │ │ K8s │ │
│ │ Commit │ │ Actions │ │ Cluster │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ Layer 2: Policy Validation (防錯) │
│ ═══════════════════════════════════ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Kyverno / OPA Gatekeeper │ │
│ │ ──────────────────────────────── │ │
│ │ Rule 1: NetworkPolicy 必須有 system 標籤 │ │
│ │ Rule 2: 禁止 podSelector: {} (空選擇器) 覆蓋既有規則 │ │
│ │ Rule 3: Egress 必須明確指定 Port (禁止開放全部) │ │
│ │ Rule 4: 生產環境 NetworkPolicy 必須有註解說明 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ Layer 3: 變更告警 (監控) │
│ ════════════════════════════ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Falco / kube-state-metrics + Alertmanager │ │
│ │ ──────────────────────────────────────────── │ │
│ │ Alert: NetworkPolicy 被創建/修改/刪除 │ │
│ │ Alert: 直接 kubectl apply (繞過 GitOps) │ │
│ │ Alert: 非白名單用戶修改關鍵資源 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────┘
```
---
## 實施計畫
### Phase 1: 立即措施 (Day 1) ✅
```yaml
# 1. 為所有 NetworkPolicy 加上變更元數據
metadata:
annotations:
awoooi.io/last-modified: "2026-03-23T15:30:00Z"
awoooi.io/modified-by: "ogt"
awoooi.io/change-reason: "修復 K8s API 連線 - Y 按鈕執行超時"
awoooi.io/ticket: "AWOOOI-123"
```
```bash
# 2. 設定 kubectl 審計 (立即啟用)
# 在 K3s server 加入:
--audit-policy-file=/etc/rancher/k3s/audit-policy.yaml
--audit-log-path=/var/log/k3s/audit.log
```
### Phase 2: GitOps (Week 1)
```yaml
# ArgoCD Application (輕量版)
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: awoooi-networkpolicy
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/your-org/awoooi.git
path: k8s/awoooi-prod
targetRevision: main
destination:
server: https://kubernetes.default.svc
namespace: awoooi-prod
syncPolicy:
automated:
prune: false # 禁止自動刪除
selfHeal: true # 有人手動改會自動復原
syncOptions:
- CreateNamespace=false
```
### Phase 3: Policy Validation (Week 2)
```yaml
# Kyverno Policy: 強制 NetworkPolicy 必須有註解
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-networkpolicy-annotations
spec:
validationFailureAction: Enforce
rules:
- name: require-change-reason
match:
resources:
kinds:
- NetworkPolicy
namespaces:
- awoooi-prod
validate:
message: "NetworkPolicy 必須有 awoooi.io/change-reason 註解"
pattern:
metadata:
annotations:
awoooi.io/change-reason: "?*"
awoooi.io/modified-by: "?*"
```
### Phase 4: 變更告警 (Week 2)
```yaml
# Falco Rule: NetworkPolicy 變更告警
- rule: NetworkPolicy Modified
desc: 偵測 NetworkPolicy 被創建、修改或刪除
condition: >
k8s_audit and
ka.target.resource = "networkpolicies" and
ka.verb in (create, update, patch, delete)
output: >
🚨 NetworkPolicy 變更告警
[%ka.verb] %ka.target.namespace/%ka.target.name
by %ka.user.name from %ka.sourceips
priority: WARNING
tags: [network, security, k8s]
```
```yaml
# Prometheus Alert: 直接 kubectl 修改 (繞過 GitOps)
- alert: DirectKubectlNetworkPolicyChange
expr: |
increase(apiserver_audit_event_total{
verb=~"create|update|patch|delete",
resource="networkpolicies",
user_agent!~"argocd.*"
}[5m]) > 0
for: 0m
labels:
severity: critical
annotations:
summary: "有人直接 kubectl 修改 NetworkPolicy (繞過 GitOps)"
description: "{{ $labels.user }} 在 {{ $labels.namespace }} 修改了 NetworkPolicy"
```
---
## PR 審核流程 (CODEOWNERS)
```
# .github/CODEOWNERS
# NetworkPolicy 變更需要 CIO + SRE 雙重審核
k8s/*/02-network-policy.yaml @awoooi/cio @awoooi/sre-team
k8s/*/networkpolicy*.yaml @awoooi/cio @awoooi/sre-team
```
---
## 回滾機制 (統帥要求補充)
### 基線管理
```bash
k8s/awoooi-prod/
├── 02-network-policy.yaml # 當前版本
├── .baselines/
│ ├── LATEST_KNOWN_GOOD.yaml # 最後驗證通過版本
│ └── 02-network-policy.{date}.yaml # 歷史版本
```
### 一鍵回滾
```bash
# 緊急回滾 (< 30 秒)
./scripts/rollback-networkpolicy.sh awoooi-prod
# 回滾到特定日期
./scripts/rollback-networkpolicy.sh awoooi-prod 2026-03-22
```
### Drift 偵測 + 人工決策 (禁止盲目自動回滾)
- 每 5 分鐘比對叢集狀態 vs Git 基線
- 偵測到差異 → 立即告警 + 顯示差異
- **人工判斷**: 合法變更 → 同步到 Git錯誤變更 → 執行回滾
- 唯一自動回滾條件: 變更導致健康檢查失敗 + 變更在 10 分鐘內 + 基線曾健康
### 回滾 vs 修復 決策樹
```
偵測到問題
├─ 影響生產?
│ ├─ 是 → 立即回滾 (< 30 秒)
│ └─ 否 → 評估後決定
└─ 回滾後
├─ 記錄 RCA
├─ 修復根因
└─ 更新基線
```
---
## 驗收標準
| 項目 | 狀態 |
|------|------|
| NetworkPolicy 只能透過 PR 修改 | ⬜ |
| PR 需要 2 人審核 | ⬜ |
| 直接 kubectl apply 會觸發告警 | ⬜ |
| 告警發送到 Telegram + OpenClaw | ⬜ |
| 審計日誌保留 90 天 | ⬜ |
| Kyverno 強制註解規則 | ⬜ |
| **基線快照每日保存** | ⬜ |
| **一鍵回滾腳本可用** | ⬜ |
| **Drift 偵測每 5 分鐘執行** | ⬜ |
| **回滾耗時 < 30 秒** | ⬜ |
---
## 成本
| 元件 | 方案 | 成本 |
|------|------|------|
| GitOps | ArgoCD (已有) | $0 |
| Policy | Kyverno (開源) | $0 |
| 告警 | Falco + Alertmanager (開源) | $0 |
| **總計** | | **$0** |
---
## 附錄: 今日事故根因分析
```
2026-03-23 Y 按鈕執行超時
根因:
NetworkPolicy allow-required-egress 遺漏 K8s API 實際端點
問題鏈:
1. ClusterIP 10.43.0.1:443 已允許 ✓
2. 但實際流量路由到 192.168.0.120:6443 ✗
3. 192.168.0.120 在 192.168.0.0/16 排除範圍內 → 被阻擋
修復:
新增 192.168.0.120:6443 到 allow-required-egress
教訓:
1. K8s Service ClusterIP ≠ 實際 Endpoint
2. NetworkPolicy 需要允許完整路由路徑
3. 變更前應該用 dry-run 驗證
```

View File

@@ -0,0 +1,370 @@
# ADR-012: 危險操作治理架構
**狀態**: 提案
**日期**: 2026-03-23
**決策者**: 統帥
**觸發事件**: Telegram Token 連續三次被 logOut 災難
---
## 背景
### 事故摘要
2026-03-23AWOOOI API 程式碼中的 `logOut` API 呼叫導致 Telegram Bot Token 連續三次被永久失效:
```
時間線:
11:09 - 第一個 Token 被 logOut (舊版 AWOOOI API)
19:31 - 第二個 Token 被 logOut (舊 Pod 未完全終止)
19:39 - 第三個 Token 成功 (AWOOOI API 已停用 Telegram)
```
### 問題根因
| 問題 | 說明 |
|------|------|
| 危險程式碼進入生產 | `logOut` 永久失效 Token無人攔截 |
| 無 Code Review | 涉及外部服務的危險操作未被審查 |
| 無執行時告警 | 危險操作執行時沒有任何通知 |
| 無審計日誌 | 無法追溯誰/何時執行了什麼 |
| 變更順序錯誤 | 給新 Token 時舊服務還在運行 |
---
## 決策
建立**三層危險操作治理架構**
1. **預防層** - CI/CD 攔截危險模式
2. **執行層** - 危險操作需人工確認
3. **審計層** - 所有操作記錄可追溯
---
## 架構設計
```
┌─────────────────────────────────────────────────────────────────────┐
│ 危險操作治理架構 (Dangerous Operations Governance) │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Layer 1: 預防層 (Prevention) │ │
│ │ ════════════════════════════ │ │
│ │ │ │
│ │ CI/CD Pipeline │ │
│ │ ├── 危險模式掃描 (grep logOut, deleteWebhook, etc.) │ │
│ │ ├── 外部服務 API 白名單檢查 │ │
│ │ └── PR 標記 (涉及危險操作需額外審核) │ │
│ │ │ │
│ │ Code Review │ │
│ │ ├── CODEOWNERS: 危險檔案需 CTO/CISO 審核 │ │
│ │ └── PR Template: 勾選「是否涉及外部服務」 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Layer 2: 執行層 (Execution Control) │ │
│ │ ═══════════════════════════════════ │ │
│ │ │ │
│ │ 危險操作分級 │ │
│ │ ├── Tier 0 (禁止): logOut, revokeToken │ │
│ │ ├── Tier 1 (需審批): deleteWebhook, setWebhook │ │
│ │ ├── Tier 2 (需告警): sendMessage (批量) │ │
│ │ └── Tier 3 (自動): getMe, getUpdates │ │
│ │ │ │
│ │ 執行前檢查 │ │
│ │ ├── 產生簽核卡片 (Tier 1 操作) │ │
│ │ ├── 發送 Telegram 告警 (Tier 1-2 操作) │ │
│ │ └── 記錄到活躍事件 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Layer 3: 審計層 (Audit Trail) │ │
│ │ ═════════════════════════════ │ │
│ │ │ │
│ │ 記錄內容 │ │
│ │ ├── 操作類型、時間、執行者 │ │
│ │ ├── 影響範圍 (哪些服務/Token) │ │
│ │ ├── 執行結果 (成功/失敗) │ │
│ │ └── 關聯的 Incident ID │ │
│ │ │ │
│ │ 儲存位置 │ │
│ │ ├── PostgreSQL: audit_logs 表 │ │
│ │ ├── SignOz: Trace 追蹤 │ │
│ │ └── 活躍事件: 顯示在 Dashboard │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
---
## 危險 API 清單
### Telegram API
| API | 分級 | 說明 | 處理方式 |
|-----|------|------|----------|
| `logOut` | Tier 0 (禁止) | 永久失效 Token | CI/CD 攔截,禁止進入生產 |
| `close` | Tier 0 (禁止) | 關閉所有 session | CI/CD 攔截 |
| `deleteWebhook` | Tier 1 (審批) | 可能影響其他服務 | 需簽核卡片 |
| `setWebhook` | Tier 1 (審批) | 可能覆蓋其他設定 | 需簽核卡片 |
| `sendMessage` (批量) | Tier 2 (告警) | 可能 spam 用戶 | 發送告警 |
| `getUpdates` | Tier 3 (自動) | 只能單一實例 | 限定 OpenClaw |
| `getMe` | Tier 3 (自動) | 無副作用 | 允許 |
### K8s API
| 操作 | 分級 | 說明 | 處理方式 |
|------|------|------|----------|
| `kubectl delete namespace` | Tier 0 (禁止) | 刪除整個 namespace | 絕對禁止 |
| `kubectl delete pvc` | Tier 1 (審批) | 刪除持久化資料 | 需簽核 |
| `kubectl rollout restart` | Tier 2 (告警) | 重啟服務 | 發送告警 |
| `kubectl scale` | Tier 2 (告警) | 調整副本數 | 發送告警 |
| `kubectl get` | Tier 3 (自動) | 只讀操作 | 允許 |
### Database
| 操作 | 分級 | 說明 | 處理方式 |
|------|------|------|----------|
| `DROP DATABASE` | Tier 0 (禁止) | 刪除資料庫 | 絕對禁止 |
| `TRUNCATE TABLE` | Tier 1 (審批) | 清空表格 | 需簽核 |
| `DELETE FROM` (無 WHERE) | Tier 1 (審批) | 刪除所有資料 | 需簽核 |
| `ALTER TYPE` | Tier 2 (告警) | 修改 schema | 發送告警 |
---
## 實施計畫
### Phase 1: CI/CD 攔截 (Week 1)
```yaml
# .github/workflows/ci.yml
- name: Scan for Dangerous Operations
run: |
echo "🔍 掃描危險操作..."
# Tier 0: 禁止的操作
BANNED_PATTERNS="logOut|\.close\(\)|revokeToken|DROP DATABASE|TRUNCATE TABLE"
if grep -rn --include="*.py" -E "$BANNED_PATTERNS" apps/; then
echo "❌ 發現 Tier 0 禁止操作!"
echo "請移除以下危險程式碼後重新提交"
exit 1
fi
# Tier 1: 需要審批的操作
REVIEW_PATTERNS="deleteWebhook|setWebhook|DELETE FROM"
if grep -rn --include="*.py" -E "$REVIEW_PATTERNS" apps/; then
echo "⚠️ 發現 Tier 1 操作,需要 CTO/CISO 審核"
echo "::warning::此 PR 包含危險操作,請確認已通過審核"
fi
echo "✅ 危險操作掃描完成"
```
### Phase 2: CODEOWNERS (Week 1)
```
# .github/CODEOWNERS
# 涉及外部服務的檔案需要額外審核
apps/api/src/services/telegram_*.py @awoooi/cto @awoooi/ciso
apps/api/src/services/executor.py @awoooi/cto @awoooi/ciso
k8s/**/02-network-policy.yaml @awoooi/cio @awoooi/sre
# 危險操作相關
apps/api/src/services/*gateway*.py @awoooi/cto
```
### Phase 3: 執行時控制 (Week 2)
```python
# apps/api/src/core/dangerous_ops.py
from enum import Enum
from typing import Callable, Any
import structlog
logger = structlog.get_logger()
class OperationTier(Enum):
FORBIDDEN = 0 # 絕對禁止
REQUIRES_APPROVAL = 1 # 需要簽核
REQUIRES_ALERT = 2 # 需要告警
AUTOMATIC = 3 # 自動允許
DANGEROUS_OPERATIONS = {
"telegram.logOut": OperationTier.FORBIDDEN,
"telegram.close": OperationTier.FORBIDDEN,
"telegram.deleteWebhook": OperationTier.REQUIRES_APPROVAL,
"telegram.setWebhook": OperationTier.REQUIRES_APPROVAL,
"k8s.deleteNamespace": OperationTier.FORBIDDEN,
"k8s.deletePVC": OperationTier.REQUIRES_APPROVAL,
"k8s.rolloutRestart": OperationTier.REQUIRES_ALERT,
}
async def execute_dangerous_operation(
operation_name: str,
operation_fn: Callable,
*args,
**kwargs
) -> Any:
"""執行危險操作的統一入口"""
tier = DANGEROUS_OPERATIONS.get(operation_name, OperationTier.AUTOMATIC)
if tier == OperationTier.FORBIDDEN:
logger.error(
"forbidden_operation_blocked",
operation=operation_name,
)
raise PermissionError(f"操作 {operation_name} 被禁止執行")
if tier == OperationTier.REQUIRES_APPROVAL:
# 產生簽核卡片,等待 Y/n
approval = await create_approval_request(
title=f"危險操作: {operation_name}",
description=f"即將執行 {operation_name},需要人工確認",
tier="Tier 1",
)
if not approval.approved:
raise PermissionError("操作被拒絕")
if tier in [OperationTier.REQUIRES_APPROVAL, OperationTier.REQUIRES_ALERT]:
# 發送告警
await send_telegram_alert(
f"⚠️ 危險操作執行中: {operation_name}"
)
# 記錄審計日誌
await log_audit_trail(
operation=operation_name,
tier=tier.name,
args=str(args),
kwargs=str(kwargs),
)
# 執行操作
result = await operation_fn(*args, **kwargs)
# 記錄結果
await log_audit_trail(
operation=operation_name,
result="success",
)
return result
```
### Phase 4: 審計日誌 (Week 2)
```sql
-- 新增審計日誌表
CREATE TABLE IF NOT EXISTS audit_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
operation VARCHAR(255) NOT NULL,
tier VARCHAR(50) NOT NULL,
actor VARCHAR(255), -- 執行者 (系統/用戶)
target VARCHAR(255), -- 影響目標
args JSONB,
result VARCHAR(50), -- success/failure/blocked
error_message TEXT,
incident_id VARCHAR(50), -- 關聯的 Incident
trace_id VARCHAR(50), -- SignOz Trace ID
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_audit_logs_operation ON audit_logs(operation);
CREATE INDEX idx_audit_logs_timestamp ON audit_logs(timestamp);
CREATE INDEX idx_audit_logs_tier ON audit_logs(tier);
```
---
## 變更管理流程
### Token/Secret 更新 SOP
```
┌─────────────────────────────────────────────────────────────────┐
│ Token/Secret 更新 SOP │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Step 1: 停止所有使用該 Token 的服務 │
│ ──────────────────────────────────── │
│ - kubectl scale deployment --replicas=0 │
│ - docker stop <container> │
│ - 等待所有實例完全終止 │
│ │
│ Step 2: 驗證沒有殘留實例 │
│ ──────────────────────────── │
│ - kubectl get pods (確認沒有 Running/Terminating) │
│ - docker ps (確認沒有相關容器) │
│ │
│ Step 3: 取得新 Token │
│ ──────────────────────── │
│ - @BotFather → Revoke current token │
│ - 複製新 Token │
│ │
│ Step 4: 更新到唯一的服務 │
│ ──────────────────────── │
│ - 更新 .env 或 K8s Secret │
│ - 記錄到 memory/reference_telegram_token.md │
│ │
│ Step 5: 啟動服務並驗證 │
│ ──────────────────────── │
│ - 啟動服務 │
│ - 檢查日誌確認無錯誤 │
│ - 測試 Telegram 連線 │
│ │
│ Step 6: 更新文檔 │
│ ──────────────── │
│ - 更新 memory 相關 MD │
│ - 發送 Telegram 確認訊息 │
│ │
└─────────────────────────────────────────────────────────────────┘
```
---
## 驗收標準
| 項目 | 狀態 |
|------|------|
| CI/CD 危險模式掃描 | ⬜ |
| CODEOWNERS 危險檔案審核 | ⬜ |
| Tier 0 操作絕對禁止 | ⬜ |
| Tier 1 操作需簽核卡片 | ⬜ |
| Tier 2 操作發送告警 | ⬜ |
| 審計日誌表建立 | ⬜ |
| Token 更新 SOP 文檔 | ✅ |
| Memory 文檔更新 | ✅ |
---
## 附錄: 2026-03-23 事故完整時間線
```
11:09:47 - AWOOOI API 啟動,呼叫 logOut (第一個 Token 失效)
11:09:52 - OpenClaw 嘗試啟動失敗 "Logged out"
...
19:31:16 - 給第二個 Token
19:31:27 - 舊 AWOOOI Pod 呼叫 logOut (第二個 Token 失效)
19:31:31 - OpenClaw 啟動失敗 "Logged out"
19:33:00 - 新 AWOOOI Pod 部署完成 (但 Token 已死)
...
19:35:00 - 清空 AWOOOI API Telegram Token
19:35:30 - 重啟 AWOOOI API (不再碰 Telegram)
19:39:01 - 給第三個 Token
19:39:17 - OpenClaw 啟動成功
19:39:36 - 所有系統健康
```