聖地牙哥的夜色裡,飯店樓下的 bar 傳來輕快的爵士樂。三人各自點了招牌的 Pisco Sour (皮斯可酸酒),原本氣氛該是輕鬆的。
沈如蘭和阿哲剛從 NGO 拜訪回來,腦中還迴盪著 Vicente 提到的資安焦慮;德華則跟著拓銷團認識了幾家潛在合作夥伴,本來準備輪流報告今天的收穫。
就在這時,阿哲的筆電螢幕跳出一連串 Audit Log 異常紀錄——陌生 IP 在短短一小時內對 res.partner
(聯絡人)模型發出高頻請求。
德華臉色發白,酒杯差點掉下來:「不會吧?那不是我前幾天才設定的 Webhook 嗎?要是真的被濫用,公司資料豈不是完蛋?」
沈如蘭放下酒杯,神情一沉:「先別慌,問題要釐清。到底是哪裡出錯?」
「會不會是 Make?」德華小聲嘀咕,「我用它自動寄感謝信,Webhook 的 URL 好像沒什麼防護…」
阿哲眉頭一皺:「我印象中 Make 的 Webhook 預設就是公開 URL。沒加驗證的話,誰拿到就能亂丟資料。」
阿哲立刻操作筆電,快速列出檢查步驟:
他邊查邊說:「這三項查完,我們才能確認是不是『真的外洩』,不然只是嚇自己。」
沈如蘭點頭,壓住現場的緊張:「先確認,再定結論。」
德華仍舊不安:「那是不是要換 n8n?」
阿哲搖搖頭:「沒這麼簡單。Make 上手快,但細節透明度有限;n8n 開源、自架,透明度高,但安全還是取決於設定。換工具≠自動安全。」
沈如蘭冷冷地敲了敲桌面:「所以根本問題不在工具,而在我們的設定。」
阿哲深吸一口氣,邊敲邊念:
1. 在 Odoo 建立最小權限使用者
# 建立 Webhook User,僅限 Portal 權限
user = self.env['res.users'].create({
'name': 'Webhook User',
'login': 'webhook@company.com',
'groups_id': [(6, 0, [self.env.ref('base.group_portal').id])]
})
2. Webhook 驗證 (HMAC)
import hmac
import hashlib
def verify_webhook(request_body, signature, secret):
expected = hmac.new(
secret.encode('utf-8'),
request_body.encode('utf-8'),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(f'sha256={expected}', signature)
3. 在 Odoo 啟用 Audit Log
進入 設定 → 技術 → 記錄日誌 (Audit Log),勾選「啟用」,並選擇要追蹤的模型(例如 res.partner
)。
啟用後,所有敏感操作(新增/修改/刪除)都會留下時間、使用者、IP 紀錄,方便事後調查。
阿哲有些遲疑:「我查 OWASP,還有一些常見建議,要不要列個清單?」
沈如蘭接過筆電,在 Odoo 備忘錄裡親自輸入:
她抬起頭,語氣低沉卻堅定:
「這些不是額外選項,而是我們日常流程的一部分,必須定期檢查,不能再靠運氣。」
原始流程(高風險)
Odoo (Contacts, Records)
│
▼
Make (Webhook) ────→ Gmail (Auto Emails)
│
▼
⚠️ Risk Point: Webhook URL Exposed
(No validation / Token leak)
改良後流程(安全加強)
Odoo (Contacts, Records)
│
▼
n8n (Self-Hosted Webhook + HMAC)
│
├───→ Gmail (Auto Emails)
│
▼
✅ Secure: Minimal Access + Signature Validation + Audit Log
(Rate Limiting + Token Rotation Enabled)
沈如蘭望著兩人,語氣沉穩:
「白天 Vicente 才提過,有另一間 NGO 因為資安事件失去捐款人的信任。現在,我們自己也遇到同樣的影子。」
她望向窗外聖地牙哥的燈火,緩緩說道:
「工具能省事,但信任失去,就回不來。安全,不是最後加的配件,而是從第一天就該放進設計。」
Pisco Sour 的酸味在口中交錯,酸甜苦混雜,就像這場提醒帶來的沉重。
👉三人對望,疲倦卻還得繼續前行。明天,他們將抵達另一個關鍵國家。阿哲心裡,已經浮現一個迫不及待想見的人影。