問題
AWS 帳號週一還符合 CIS benchmark,週二可能就因為有人開了一條 security group 而不合規。定期掃描只會告訴你某個控制項現在失敗,卻不會說明改了什麼、誰改的,也分不清這是新問題,還是團隊已接受的雜訊。再把 LLM 放進流程,又多一層風險:如果由模型決定什麼算告警,它就可能被資源裡的一段文字說服,而放掉該發的告警。
CloudSheriff 是為 Daytona HackSprint 做的限時專案(time-boxed build),設了兩條硬性限制:對帳號只有唯讀權限,以及模型永遠不參與判斷。
做法
三種證據來源,各司其職:
- Prowler:執行 CIS AWS benchmark,回答哪些控制項通過、哪些失敗。
- 正規化的資源快照:精確回答兩次掃描之間改了什麼。
- CloudTrail Event history:回答誰、何時、透過哪個 API 改的。它只用來補充告警,不是「變更前狀態」的依據。
告警由純程式碼根據結構化欄位決定。LLM 拿到的是已經決定好的告警,只負責替值班工程師寫一段簡短說明。
測試環境是用 OpenTofu 在專用沙盒帳號建立、刻意設錯的實驗環境:一組合規的 security group 作為漂移目標;一組從第一天就對網際網路開放 RDP 的舊 group(已知雜訊,必須保持靜默,還帶著想說服模型降級的文字);以及絕不該告警的負向對照組。
運作方式
- 最小權限。 工具使用 external ID 與短期憑證,取得(assume)一個稽核角色:AWS 的 SecurityAudit 與 ViewOnlyAccess,外加明確拒絕讀取物件內容、機密值與解密。
- 用完即丟的掃描室。 固定版本的 Prowler 在全新的 Daytona 沙盒中執行;憑證只在該次掃描期間存在,掃完沙盒即刪除。
- 穩定的證據。 只正規化 JSON-OCSF 的結構化欄位,不對狀態文字做雜湊,因為掃描器改了用語會製造假漂移。
- 三軸比對。 每筆 finding 與已存狀態比較三個維度:評估結果(通過、失敗、未評估)、生命週期(作用中、已解決、資源消失)、變化(新增、退步、變更、不變)。針對性的重掃不會把沒掃到的控制項標成已修復。
- 確定性的告警閘門。 必須同時滿足:評估失敗、屬於新增、退步或變更,且嚴重度為 medium 以上。基準線(baseline)掃描永遠不告警。
- 溯源。 針對 security group 告警,工具查詢相符的 CloudTrail 事件;事件還沒送達就回報「溯源待定」,而不是「查無此人」。
- 說明。 模型只拿到已決定的事實,資源標籤放在標示為不可信資料的區塊,而且沒有任何 AWS 憑證。若 API 失敗或拒答,改由範本產生說明。整個呼叫以 Arize 追蹤。
成果
2026-09-30 完整流程實機跑通:建立基準線、對 demo group 開放 SSH 給網際網路、收到恰好一則帶有 CloudTrail 溯源與 LLM 說明的退步告警,關閉規則後狀態回到已解決。該次 CloudTrail 約兩分鐘送達事件。
離線測試方面,70 個單元測試通過(1 個跳過),6 個 OpenTofu 測試以 mock provider 通過。另有一支腳本模擬稽核角色的政策,檢查 14 個寫入或讀取資料內容的動作會被拒絕、6 個讀取動作會被允許,這是「唯讀」主張的佐證。
專案已下線:實驗環境於 2026-10-06 銷毀,掃描映像檔也已刪除。
學到的事
- 一個資料來源只回答一個問題。把掃描狀態、設定歷史與稽核紀錄混成一個故事,很容易得到信心十足卻錯誤的歸因。
- 讓模型離開決策路徑。以結構化欄位做閘門,讓注入誘餌從設計上就無害,不必靠 prompt 的措辭。
- 銷毀資源後,要查雲端帳號本身,而不只看 state。OpenTofu 回報 29 個資源銷毀了 28 個,有一個 bucket 脫離 state 卻仍留在 AWS,直到手動刪除。
- 這是概念驗證:快照與溯源只涵蓋 security group。