從 Zeabur 事件出發:保管別人的機密要防什麼
保管別人的機密 — 第 1 篇:起點
2026 年 10 月,Zeabur 公布了一份資安事件初步報告。
關於報告的備份版本
Zeabur 已宣布將於 2026 年 12 月 31 日結束營運,原報告頁面之後可能無法存取。因此我們製作了一份備份版本(PDF,2026 年 10 月 22 日存檔),作為文章情境的補充。系列中引用的事件細節,都可以在這份備份裡對照。
每次看到這種重大事件,我大多會回頭檢查自己的知識儲備夠不夠:如果換成是我,事前能不能實作好?營運中怎麼定期稽核?出事之後怎麼補救?
這次的問題是:若有一天,我要提供一個服務,得替使用者保管機敏資料,例如資料庫密碼、API 金鑰這類環境變數,該怎麼把它寫好?
這個系列就是在回答這個問題。Zeabur 在這裡只是對照組,真正的主角是「一個合格的機密保管服務應該長什麼樣子」。五篇共用同一張主圖,每篇換一個視角,讀完至少能對整套設計有個概念。
簡述 Zeabur 報告
事件的完整經過,建議直接讀原文或上面的備份版本,這裡只挑多數人讀到時最驚訝的一件事:平台資料庫裡存著 3,218,965 筆環境變數,名稱和值都是明文。攻擊者用一個內部唯讀帳號就全部讀走了。
會驚訝,是因為環境變數裡裝的正是資料庫密碼、API 金鑰、第三方服務的憑證。多數人把它們交給平台時,會以為平台有加密;實際上,任何能對這張表下 SELECT 的帳號,都能看到所有使用者的所有機密。
報告裡的其他問題,例如沒有期限的叢集管理權杖、收錄所有機密的每日備份,第 4 篇講到對應的設計時再拿出來對照。
以應該保護的 DB 連線資訊為例
為了不讓討論飄在空中,整個系列都用同一筆機密當例子。主角是 Org A,Org ACME 的簡稱,一家什麼都做的虛構公司(A Company that Makes Everything)。後面需要第二個客戶來對照時,就叫它 Org B。
Org A 在平台上部署了一個服務,服務需要連資料庫,所以 Org A 在後台填了一個環境變數
DATABASE_URL = postgres://app:s3cret@db:5432/main。
平台要做的事情聽起來很單純:把它存起來,每次 Org A 的容器啟動時,把這個值放進容器的環境變數。
最直覺的做法,是在資料庫開一張表,一欄放名稱、一欄放值。這就是 Zeabur 的做法,也是大部分人第一次寫會寫出來的樣子。它的問題是:任何能讀這張表的人,就等於能讀所有使用者的所有機密。內部唯讀帳號、備份檔、一次 SQL injection、一個手滑多給權限的工程師帳號,全部都通往同一個結果。
這個系列的每一篇,都在回答同一個問題:DATABASE_URL 這個字串,從 Org A 按下儲存,到它出現在容器裡,中間每一步該怎麼保護?
情境總覽:資料生命週期的旅程
下面這張圖是整個系列的地圖。後面每一篇都會再出現一次,只是亮起來的部分不同。
圖上有九個參與者,從左到右:
- 使用者:Org A 的人,用 CLI 或網頁填機密。
- API:平台對外的入口,也包含內部帳號、請求 log 這些經手資料的地方。
- Secret service:寫入端,負責把收到的機密加密後存起來。
- KMS:Key Management Service,金鑰管理服務。保管最重要的那把鑰匙,鑰匙本身永遠不出來。
- 平台 DB:存加密後的資料。
- 備份:平台 DB 的每日快照。
- 注入元件:執行端,在容器啟動時把機密解開、交給容器。
- 容器:Org A 的服務本身,最後要拿到明文的地方。
- 控制面(control plane):負責排程容器、管理叢集的系統,手上有管理權杖。
流程分成兩段。寫入是第 1 到 8 步:Org A 送出機密,經過加密存進資料庫,再被備份。執行時是第 9 到 15 步:容器要啟動,注入元件替它把機密拿出來、解開、交給它。
步驟編號全系列固定。第 2 篇說「第 12 步」,跟第 3 篇說的「第 12 步」是同一件事。現在看不懂每一步在做什麼沒關係,後面會一段一段拆開講。
攻擊者會從哪裡進來
主圖上的 A 到 D,是四個最常見的突破口。用天真的設計(明文存在資料庫)對照,看看各自會發生什麼:
| 位置 | 攻擊者拿到什麼 | 天真設計下的結果 | 希望的結果 |
|---|---|---|---|
| A 平台 DB | 整個資料庫的內容,例如 SQL injection、資料庫憑證外洩 | 所有機密直接外洩 | 只拿到解不開的密文 |
| B 備份 | 備份檔 | 所有機密直接外洩 | 跟 A 一樣,只有密文 |
| C 內部帳號 | 工程師或內部服務的帳號 | 能查資料庫就能讀全部 | 讀得到密文,但沒有權限解開 |
| D 控制面 | 叢集管理權杖 | 能進任何容器、讀任何設定 | 權杖很快過期、從外面用不了,被用了會馬上被發現 |
注意 D 跟其他三個不一樣。A、B、C 都是「拿到存起來的東西」,加密可以擋住。D 是「拿到能操作系統的權限」,攻擊者可以直接進容器,而容器裡本來就有明文。這一類加密幫不上忙,要靠別的手段,這也是 Zeabur 這次的主要入口。第 4 篇會專門處理它。
三個設計目標
把上面那張表的最後一欄濃縮,就是這個系列要達成的三個目標:
- 拿到資料庫,不等於拿到機密。資料庫、備份、log 裡只有密文,鑰匙放在別的地方。
- 能解開機密的身分越少越好,而且每次解開都留下紀錄。平台上大部分的人和服務,根本不需要看到使用者的機密。
- 每一把鑰匙、每一個權杖都會過期,外洩時換得掉,被濫用時看得到。
第一個目標靠加密,第二個靠權限設計,第三個靠生命週期管理和偵測。這三個目標對應到下面的五層設計清單。
用時間軸來看:事前、營運中、事後
開頭那三個問題,剛好是看待這件事的三個時間點。這個系列也照這個順序走:
| 時間點 | 要回答的問題 | 在哪幾篇 |
|---|---|---|
| 事前 | 系統要怎麼設計、怎麼實作,才不會一被入侵就全部外洩 | 第 2 篇儲存層、第 3 篇存取層、第 4 篇控制面與生命週期 |
| 營運中 | 系統上線之後,怎麼定期確認設計還成立:權限有沒有慢慢變寬、憑證有沒有過期、告警還會不會響 | 第 5 篇前半 |
| 事後 | 真的出事時,怎麼在最短時間內發現、止血、補救 | 第 5 篇後半 |
設計得再好,上線一年後也會走樣:新同事多拿了權限、某把金鑰忘了輪換、某條告警因為太吵被關掉。Zeabur 的管理權杖從 2024 年建立後就沒有輪換過,八月事件後的輪換也只做了一半,問題都出在營運中這一段。所以這個系列不只講怎麼設計,也講上線之後怎麼維持。
五層設計清單(也是這個系列的目錄)
每一層先列出最關鍵的幾條,細節留給各篇:
| 層 | 最關鍵的做法 | 對應的目標 |
|---|---|---|
| 儲存 | 用 DEK 加密資料、KEK 加密 DEK,KEK 只待在 KMS | 1 |
| 存取 | API 只能加密不能解密;Decrypt 權限只給注入元件;明文存了就讀不回來 | 2 |
| 控制面與生命週期 | 管理權杖短期有效、不對公網;備份金鑰分開保管;機密可以輪換、到期、撤銷 | 3 |
| 稽核、偵測與應變 | 定期對帳權限與憑證清單;大量解密、容器內執行指令要告警;外洩時先切斷再輪換 | 3 |
知識的源頭來自於經典的設計
這個系列比較像一份文獻整理,每個環節都來自現成的文件:
- 信封加密是 Google Cloud KMS、AWS Secrets Manager、Kubernetes 加密 etcd 都在用的標準做法。
- 「API 只能加密、主機上的 agent 才能解密、存了讀不回來」是 Fly.io 公開的 secrets 架構。
- 權限、生命週期、稽核、事故應變,OWASP Secrets Management Cheat Sheet 有完整的清單。
這個系列做的事,是把這些散在各處的做法串成一條從頭走到尾的流程,再用 Zeabur 的實際經過檢查每一層擋不擋得住。目標是先完整看過一次、有個概念,真的要實作或稽核時,知道該去翻哪一份文件。完整的參考資料整理在第 5 篇。
名詞
後面幾篇會一直用到這些詞,忘記時回來這裡查:
| 名詞 | 意思 |
|---|---|
| 明文 | 沒加密、直接看得懂的原始內容,例如 postgres://app:s3cret@... |
| 密文 | 加密後的內容,沒有鑰匙看不懂 |
| DEK | Data Encryption Key,資料加密金鑰。拿來加密實際資料的那把 |
| KEK | Key Encryption Key,金鑰加密金鑰。不碰資料,只拿來加密 DEK |
| KMS | Key Management Service,金鑰管理服務。保管 KEK 的地方,例如 AWS KMS、GCP Cloud KMS |
| sealed box | 一種用公鑰加密的格式,GitHub 用它在瀏覽器端先把機密加密再送出 |
| workload identity | 服務自己的身分證明,由平台發給執行中的程式,通常短期有效 |
| 注入元件 | 在容器啟動時解開機密、交給容器的元件,例如主機上的 agent。屬於資料面,依照控制面發給它的身分做事 |
| 控制面 | control plane。負責「決定事情」的那一層:容器要在哪裡跑、設定是什麼、誰有什麼權限。容器編排器(例如 Kubernetes 的 API server、scheduler)就是控制面的一部分 |
| 資料面 | data plane。負責「實際做事」的那一層:真正在跑的容器,以及主機上替它們做事的元件 |
| 稽核紀錄 | 誰在什麼時候做了什麼的紀錄,事後追查用 |
下一篇
主圖上的第一個目標,「拿到資料庫不等於拿到機密」,要怎麼做到?下一篇從一封實體的信開始講信封加密,最後在瀏覽器裡實際加密一次 DATABASE_URL。