跳轉到

Admin Control Plane Hardening:T11-T14 Phase 拆解與關鍵技術發現

文檔資訊

  • 分類: architecture
  • 難度: advanced
  • 預估閱讀時間: 12 分鐘
  • 標籤: admin-hardening, golang, kubernetes, security, audit, phase-decomposition, trusted-proxy, infra-wiring, EKS

摘要

MatchRPG Server 的 Admin Control Plane Hardening 重構(Phase 2),從一份混合 bug fix 與 capability expansion 的設計稿,拆解為四個獨立 Phase(2A hardening → 2B policy → 2C surface → 2D rollout),完成 T11/T12 實作後,在 T13 infra wiring 階段發現多個未閉環的關鍵問題,包括 ADMIN_ENVIRONMENT Kubernetes precedence bug、trusted proxy assumption-based 設計缺陷、IP whitelist no-op 退化等。

關鍵學習

  • 先拆 Phase 再實作:原設計把 bug fix 和 capability expansion 綁在一起,正確做法是先切出 hardening-only Phase 2A,讓修 bug 不被新功能卡住。

  • Kubernetes env precedence 陷阱:Deployment 的 explicit env 欄位會覆蓋 envFrom(ConfigMap),導致 ADMIN_ENVIRONMENT 實際值是 namespace 名稱(matchrpg-dev)而非設計的枚舉值(development),直接影響 test commands 註冊與 rate limiter 行為。

  • trusted proxy hardening 需區分 assumption-based vs closed-loop:只靠 trustedProxyCount 選 XFF header index,不驗證直連來源是否為受信 proxy,這是 assumption-based 不是真正 hardened。

  • audit async tracking 的 WaitGroup 邊界writeCommandAudit() 用 goroutine 呼叫但沒進 WaitGroup,shutdown 時會丟 log;RPC audit path 才有正確追蹤。

  • IP whitelist 空值退化:server 端在 whitelist 為空時退化成 no-op,base ConfigMap 空字串 + overlay 沒補 = 實際上無保護,文檔宣稱 done 被 repo 現況打臉。

  • T12 ListCommands 未達 capability-aware:只看 role/env/test_only,dependency 缺失時仍正常註冊命令,執行時才報錯;availability story 未做實。

  • operator_key_id 需 uniqueness 約束:ParseAPIKeys() 只解析格式,沒拒絕 duplicate KeyID / label,未來多 key 共用 KeyID 時 audit attribution 會碰撞。

  • 文檔必須與 code 同步:T13 文檔宣稱 Admin UI auth done,但 SPA 仍只有 IP whitelist,無 API key auth,phase 文檔過時或 T13 未完成,二擇一必須明確。

技術細節

Phase 拆解決策

原設計把 schema migration、backend 實作、測試、上線流程綁成一個大 workstream。重構後拆為:

  • Phase 2A(T11):hardening-only fixes — audit WaitGroup、ReloadConfig audit 語義、test.* env 過濾、rate limiter 接線、IP whitelist/trusted proxy、set_resource TOCTOU + version/changelog/cache、reset_progress player-scoped lock
  • Phase 2B(T12):policy metadata expansion — CommandHandler 正交屬性(RequiresReason/RequiresTicket/TestOnly)、op_id UUIDv7 correlation、audit dual-write、changelog correlation、key_id enrichment、capability-aware ListCommands
  • Phase 2C(T13):infra wiring — ADMIN_ENVIRONMENT ConfigMap 單一來源、staging/prod IP whitelist、Prometheus bearer token mount、NetworkPolicy
  • Phase 2D(T14):rollout/sign-off — staging fire drill、migration apply/rollback 驗證、break-glass runbook

關鍵 Bug:ADMIN_ENVIRONMENT Precedence

# deployment.yaml:51 — 錯誤:explicit env 覆蓋 envFrom
env:
  - name: ADMIN_ENVIRONMENT
    valueFrom:
      fieldRef:
        fieldPath: metadata.namespace  # 實際值:matchrpg-dev

正確做法:移除 Deployment 的 explicit ADMIN_ENVIRONMENT,改為完全由 ConfigMap + overlay 控制。

set_resource 語義澄清

現有 player.set_resource 已是「目標值語義」(非 delta),真正問題是:

  1. TOCTOU race:GetAmount → 算 delta → Add/Deduct 的非原子操作
  2. data_version / changelog / cache invalidation

ReloadConfig Audit Contract

現況:業務失敗時回 Success:false 但 nil error
問題:audit_hooks.go 只看 Twirp error,nil error = audit 不記失敗
修法:業務失敗時改回 twirp.FailedPrecondition

Prometheus Token Mount 問題

不能直接用 app namespace 的 admin-secrets 給 monitoring namespace 的 Prometheus。需要: 1. 在 monitoring namespace 另建 ExternalSecret 2. 在 kube-prometheus-stack Helm values 正規 mount 3. prometheus-config.yaml 的 bearer_token_file 才有實際對應路徑

What Changed

T11/T12 主體實作(已完成)

8 項 hardening fixes 全部落在 code 上:audit WaitGroup 邊界修正、ReloadConfig twirp.FailedPrecondition 合約、test.* 非 dev 環境過濾、rate limiter 接進 admin middleware chain、reset_progress player-scoped lock、set_resource transaction + version + changelog + cache、IP whitelist 修正、trusted proxy count 參數化。

T12 完整閉環:schema 042 expand-only migration、proto 新欄位、CommandHandler 正交屬性、reason/ticket runtime gate、op_id UUIDv7 生成與 context 傳遞、audit dual-write、player_data_changelog correlation、key_id audit enrichment、capability-aware ListCommands(部分)、mutation result normalization、GetAuditLogs read-path。

T13 infra wiring(部分完成)

已完成:ADMIN_ENVIRONMENT 改回 ConfigMap 單一來源、staging/prod ADMIN_IP_WHITELIST 填值、Prometheus ExternalSecret + Helm mount + 獨立 scrape job。

發現的未閉環問題

  • trusted proxy 仍是 assumption-based(缺 ingress header sanitation 設定確認)
  • Admin UI auth 與 phase 文檔不一致(SPA 目前只有 IP whitelist)
  • NetworkPolicy 的 from 限制未加
  • T14 有多個實質 runtime 驗證項未完成,不只是 paperwork

So What

這個案例展示了「設計 → 拆解 → 實作 → 驗收」四階段的典型陷阱:

文檔宣稱「done」和實際 repo 現況之間的落差,是最常見的交付風險。這次發現的 ADMIN_ENVIRONMENT Kubernetes precedence bug,在 dev 環境已實際導致行為錯誤(test commands 可能被錯誤過濾或保留),卻因為文檔和 code review 不同步而被忽略了一段時間。

infra wiring 往往是 server-side code 完成後最容易被低估的交付部分。trusted proxy、IP whitelist、Prometheus token mount 這三個問題,每一個都需要 server + infra 雙邊對齊才能閉環,任一方單獨宣稱完成都是錯的。

Trade-offs

  • Phase 2A 先做 hardening-only vs 一次做完所有改動:前者交付速度快、風險小,但需要維護 feature flag 或雙路徑到 Phase 2B 完成;後者整合測試更完整但拖延真正的 bug fix
  • DangerLevel 保留 vs 替換為 policy engine:保留現有分級可以向下相容,但需要清楚說明它「不再是唯一 policy 維度」,文案必須精確
  • trusted proxy assumption-based vs app 層補 trusted CIDR 驗證:前者依賴 infra 正確設置,設置正確時無額外複雜度;後者 defense-in-depth 更好,但需要維護 CIDR 清單
  • approval workflow 降級到 reason/ticket 驗證:降低了操作摩擦,但真正的 two-person approval 留到後續 Phase,short-term 仍只有 reason 作為 accountability

Try It Fast

# 驗證 T11/T12 server-side 測試全過
go test ./internal/modules/admin/... ./internal/modules/player/... ./pkg/middleware/... ./cmd/admin/... ./test/...

# 確認 ADMIN_ENVIRONMENT 不再由 Deployment explicit env 設定
# 應該只在 ConfigMap 裡看到,Deployment 不應有這個 env
grep -r 'ADMIN_ENVIRONMENT' k8s/base/admin/

# 確認 rate limiter 已接進 admin middleware chain
# cmd/admin/main.go 應有 adminRateLimiter 在 handler chain 中
grep -n 'adminRateLimiter\|RateLimiter' cmd/admin/main.go

# 確認 writeCommandAudit 有進 WaitGroup
# admin_twirp.go 的 go writeCommandAudit() 呼叫應改為 wg.Add(1) + defer wg.Done()
grep -n 'WaitGroup\|wg\.' internal/modules/admin/admin_twirp.go

Recommendation

  1. 立即:確認 ADMIN_ENVIRONMENT 的 Kubernetes 修法已在 dev cluster 驗證,不只是 manifest 改對了,還要 kubectl exec 進 pod 確認 env 值
  2. T13 收口前:明確決策 Admin UI SPA auth 策略(只靠 IP whitelist?還是要補 API key check?),並同步更新 phase-2 設計文檔,不能讓文檔和 code 不一致
  3. T14 開始前:infra handoff checklist 的每個未勾選項都是實質工作,需要有人明確 own 並排期,不能因為「只剩 paperwork」而低估
  4. 長期:trusted proxy hardening 應朝 app 層補 trusted proxy CIDR 驗證方向演進,不能永遠依賴「假設 ingress 正確」這個脆弱假設
  5. 持續:每個 Phase 完成後必須對照 repo 現況重新驗收,而非只看測試通過,因為 infra manifest、文檔、live cluster 三者都需要對齊

本文檔由 Semi-Brain 自動生成

Session ID: de4cc14f-f3ed-4243-b84e-6062bb47c079

分析信心度: 88%