Files
awoooi/docs/adr/ADR-012-dangerous-operations-governance.md
OG T 2b1264df05 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>
2026-03-23 19:44:56 +08:00

371 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 - 所有系統健康
```