iT邦幫忙

2026 iThome 鐵人賽

DAY 22
0
Claude AI

AI 負責答,我負責讓答案可信——公部門工程師的 30 天 Claude 協作紀律系列 第 22 篇

Day 22:從一張不合格的稽核表,到自建集中式 log server

  • 分享至 

  • xImage
  •  

Day 22:從一張不合格的稽核表,到自建集中式 log server

會走到「自建一套集中式 log server」這一步,其實是兩條線在差不多的時間點交會的。

一條線接續前一天完整性監控的結論。為了防止網站被置換,我本來想做檔案雜湊比對的偵測機制,但 AI 提了一個更根本的方向:與其事後偵測,不如把容器的程式目錄改成唯讀掛載,直接擋掉寫入。唯讀之後帶出一個連鎖反應:程式再也不能把 log 寫進自己的目錄,只能改成往標準輸出(stdout)丟。於是問題就變成——這些只存在於 stdout 的 log,要收到哪裡去、怎麼長期保存供稽核查詢?

另一條線,是這篇的主角:一張系統操作紀錄表,被發現根本應付不了資安稽核。

兩條需求指向同一個答案——一套集中式的 log server。先看第二條線是怎麼開始的。

起點:一張看起來還算合理的表,其實六個地方不合格

我丟了一份系統文件給 AI,請它評估裡面一張系統操作紀錄表是不是能應付資安稽核。這張表記錄使用者登入登出,欄位有帳號、程式代碼、IP、時間戳、事件代碼,乍看之下該有的都有。

AI 讀完文件後,抓出六個問題,兩個標成嚴重:沒有記錄登入方式(帳密、憑證、健保卡、SSO——稽核發生爭議時無法判斷是不是正常管道登入)、沒有 session ID(沒辦法把一筆稽核紀錄跟某次瀏覽器 session 對起來,事後追蹤斷在半路)。中度的幾項也踩到真實痛點:IP 是直接從 req.ip 拿的,如果部署在反向代理後面沒設對 trust proxy,抓到的其實是代理伺服器的 IP,稽核紀錄本身就失真了;時間戳存的是一串沒有時區資訊的字串,未來要是系統跨時區部署,這串數字連自己是幾點都說不清楚;而且這張表沒有任何防竄改機制,只要有資料庫寫入權限,誰都能直接刪改稽核紀錄——稽核紀錄本身失去了公信力。

存廢決策:把三件事拆開來看

我問:如果乾脆廢掉這張表,有辦法補齊功能嗎?AI 先把這張表目前實際在做的三件不同性質的事拆開來看:一是「使用者是否超過 60 天沒登入」這種業務判斷,二是認證事件的稽核紀錄,三是(後來延伸出來的)業務操作稽核。

拆開之後答案就清楚了:業務判斷用不著整張稽核表,換成一張極輕量的「最後登入時間表」就夠,60 天檢查不用再全表掃描;真正的稽核責任,交給外部的 log 收集機制,應用程式本身不再持有稽核資料,也就不存在「誰有寫入權限誰就能刪改」這個問題。核心原則講得很直白:業務邏輯判斷用資料庫小表,稽核紀錄用 log,兩件事分開,各自職責清楚——這樣以後稽核要求變了,也不用去動業務邏輯。

搭起來:Fluent Bit + OpenSearch,還有 session_id 把兩份 log 串起來

架構定案是 Node.js 容器輸出 JSON 格式的 log 到 stdout,Fluent Bit 以 sidecar 的方式接管這條管道、送進 OpenSearch,由 OpenSearch Dashboards 給稽核人員查詢介面。

中間有一個實際補上的細節:原本的 access log 只用 IP 當「誰」,這樣沒辦法真正回答稽核最常問的那句「誰、什麼時候、做了什麼事」。補上 session_id 之後,稽核人員可以從一筆認證事件(比如登入成功)往前追同一個 session_id 底下所有的操作紀錄,一條完整的行為軌跡才真正串得起來。

DB 端的稽核 hook 設計也换了一個思考方向:一開始的做法是白名單——想稽核哪張表就手動加進清單,但這樣新表一多,很容易漏掉沒登記的表。後來反過來想:預設全部都稽核,只有明確不需要的才排除,維護負擔從「每次新增表要記得加」變成「只有例外才要處理」,漏網的機率小很多,而且這份排除清單本身就是一份文件,稽核時能直接說明為什麼這些表不在範圍內。

DB 稽核 hook 的一個坑:想記「誰刪的」,卻連資料一起刪掉了

順著這套 hook 往下設計,我提了一個具體構想:在資料被刪除前,先用一個 hook 把「操作人」欄位更新一次,這樣就能留下「這筆是誰刪的」。AI 沒有直接照做,反而先點出一個邏輯矛盾:這筆資料馬上就要被刪了,剛更新進去的操作人欄位,會跟著這筆資料一起消失——稽核 log 裡雖然會留下一筆更新、一筆刪除,但資料庫本身早就沒有這筆資料可以對照了。它反問:你真正想要的到底是什麼?

答案藏在一個 AI 不可能知道的既有架構事實裡,而這個事實得由我補上:資料庫本身早就有一支「刪除前」的觸發器,會在刪除的同時自動把整筆資料複製到一張歷史表。這句話一補上,原本被判定「邏輯矛盾」的方案立刻成立——在刪除前更新操作人欄位,目的不是要讓應用層留下什麼,而是讓那支既有的觸發器在複製快照的同一刻,順手把操作人也一併帶進歷史表;應用層原本打算另外做的「刪除前快照」也就不必了。這又是一次同樣的分工:AI 把 hook 的觸發時機、單筆與批次刪除的差異這些框架細節講得清清楚楚,但「我們的資料庫早就有一支觸發器在做這件事」——這個只有我知道,也只能由我補。

在地合規現實:自簽憑證這一關

架構搭得差不多之後,我問了一句:內部服務之間用自簽憑證,稽核應該也能過吧?

AI 一開始給的答案是通用的資安知識:自簽憑證在內部服務之間是被接受的標準做法,大型系統的內部通訊幾乎都是自簽或內部 CA,真正該小心的是客戶端有沒有正確驗證,而不是憑證本身。

我把在地的現實糾正回去:自簽憑證在弱點掃描上是不會通過的,屬於中高風險,台灣公部門的弱掃只要出現中高風險就過不了,稽核照樣會被開缺失。AI 認了這個落差——弱掃工具看到自簽憑證就是報中風險以上,不管是內網還是外網、不管客戶端有沒有正確驗證,工具不會理解「這是內部服務之間的信任關係」,掃出來就是缺失,跟資安設計本身合不合理是兩回事。最後採用的方向是乾脆不開 TLS,讓那個 port 只綁 loopback、只在內網走,OpenSearch 開啟帳密認證,靠「來源限制 + 帳密」滿足存取控管的要求——這件事本來就不需要靠 TLS 達成,TLS 解決的是傳輸過程被竊聽,是不同的要求項目,兩者不必綁在一起做。

一個容易被忽略的坑:down 掉會讓還沒讀完的 log 直接消失

架構上線前,我確認了一件事:log 在 rotate 之前,會一直留在容器所在的宿主機上。

接著我問:有沒有辦法先手動觸發一次 rotate,再把容器 down 掉?想法是先把 log 切一份出來保住,再安全地重啟。

AI 說這個想法對這個問題完全沒有幫助,甚至可能更糟——因為 docker-compose down 刪除容器時,刪掉的是整個目錄,rotate 產生出來的 .1、.2 備份檔一樣會被砍掉。先 rotate 再 down,結果跟直接 down 完全一樣。真正的保障是確保 Fluent Bit 在 rotate 發生前就已經讀完資料並確認送出,這也是為什麼設定裡要加一段 rotate 後的等待時間,讓 Fluent Bit 有機會讀完舊檔再切換,即使短暫中斷,重啟後也能從上次記錄的位置續讀,不會遺漏也不會重複。

這一天的分工

AI 在這篇裡做的事,是把「稽核紀錄要滿足什麼要求」這種抽象需求,一路轉成具體的資料表欄位清單、分層架構、排除清單設計邏輯,這些屬於它擅長的系統性拆解。但貫穿整篇最關鍵的兩次補位,都是人補上的現場事實:一次是那支「刪除前自動把整筆複製到歷史表」的既有觸發器——AI 把 hook 的觸發時機、單筆與批次刪除的差異都講得清清楚楚,卻不可能知道這套資料庫早就內建了這個機制,是我補上這句,原本被它判成「邏輯矛盾」的方案才立刻成立;另一次是台灣公部門弱掃對自簽憑證零容忍這個在地規則,AI 給的是放諸四海皆準的資安常識,人給的是「這裡的稽核就是不會過」這個無法從通用知識推導出來的現實。兩次都是同一種形狀:AI 補得了框架,補不了「這個系統實際上長什麼樣、這裡的稽核到底認不認」。

還有一件事值得補一句:這一整套 log server 的規範——哪些欄位必填、少了辨識欄位的輸出會被丟掉、session_id 怎麼把不同類型的日誌串起來、系統識別碼為什麼不准自動轉小寫——後來我沒讓它只活在這次對話裡,而是一條條寫進了這個專案的規範文件,每條旁邊都附上它的原因。這樣之後每一個新的工作階段要新增日誌,都會自動照這套規範走,不必我再從頭講一遍。至於怎麼把這種判斷沉澱成一份規範、規範本身又該怎麼寫,是後面方法論篇的主題。

log server 架構搭起來,接下來要問的是:這些稽核責任要怎麼真正焊進整條部署管線裡,讓每一次上線都自動符合這些要求,而不是靠人記得。


上一篇
Day 21:一套完整性監控,最容易死在「合法的變動」手上
系列文
AI 負責答,我負責讓答案可信——公部門工程師的 30 天 Claude 協作紀律 共 22 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言