4.3 秘鑰管理:從環境變數到 Secret Manager#
一把鑰匙洩漏的連鎖反應#
資料庫密碼、JWT secret、AWS key、API token —— 現代應用跑在幾十把秘鑰上。任何一把的洩漏都是一次事故,而秘鑰的最大洩漏源就是開發者自己。
洩漏途徑盤點#
mindmap
root((秘鑰洩漏途徑))
版控
硬編碼進 git
歷史 commit
日誌與錯誤
堆疊追蹤印出 env
debug 輸出
前端
打包進 bundle
網路請求可見
CI/CD
過寬的 token 權限
供應鏈竊取(4.2)
傳輸
URL query 帶 key
Referer 洩漏(2.4)真實代價:git 歷史是永久的#
git log -p | grep -i "api_key" # 攻擊者自動化掃描公開 repo
# GitHub 上每年數百萬把金鑰被自動發現
git rm + commit 不會移除歷史。金鑰進過 git 的正確處置:
- 立即輪換(唯一真正有效的事)
- 清歷史(filter-repo)或乾脆棄庫
- 加入 pre-commit 掃描(gitleaks、truffleHog)防止再犯
輪換 > 刪除 —— 秘鑰的價值在有效,不在秘密的位置。
第一層:環境變數與 .env#
# .env(gitignore!)
DATABASE_URL=postgres://...
JWT_SECRET=...
require('dotenv').config(); // 十二要素應用 III:配置與代碼分離
十二要素應用(2011)的洞見:配置屬於部署環境而非代碼 —— 同一份代碼,staging/prod 各自注入配置。環境變數是最低成本的實現。
但 .env 有天花板:
- 仍然以明文存在開發機與 CI
- 沒有輪換機制
- 沒有存取審計(誰在何時讀了 prod 金鑰?)
- 容易被 debug 頁面/日誌意外印出
第二層:Secret Manager(專用金鑰保管)#
flowchart LR
APP[應用] -->|短時憑證/身份| SM[Secret Manager\nVault / AWS SM / GCP SM]
SM -->|加密傳輸| APP
KMS[KMS/HSM\n主金鑰] -.加密保護.-> SM
AUD[審計日誌\n誰讀了什麼] -.-> SM| 能力 | .env | Secret Manager |
|---|---|---|
| 靜態加密 | ❌ | ✅(KMS 主鑰) |
| 存取審計 | ❌ | ✅ |
| 輪換 | 手動 | 自動/版本化 |
| TTL/短期憑證 | ❌ | ✅ |
| 最小權限授予 | ❌ | ✅(IAM 綁定) |
第三層:身份即憑證(Workload Identity)#
最好的秘鑰是沒有秘鑰。雲端的 IAM 角色綁定讓服務免持憑證:
# GitHub Actions 用 OIDC 取代長期 AWS key(4.2 供應鏈的解)
permissions:
id-token: write
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123:role/deploy # 短期、可審計、無 static key
timeline
title 秘鑰管理演化
2000s : 硬編碼 → git 洩漏
2010s : .env + 十二要素 → 仍明文
2015+ : Vault/Secret Manager → 加密+審計
2020s : OIDC workload identity → 無靜態秘鑰\[ \text{演化主軸}: \text{秘密的存在時間越來越短}(\text{長期} \to \text{短期} \to \text{免持}) \]
洩漏窗口最小化 —— 與 1.4 短憑證、JWT 短效期(3.2)同一哲學。
分層實務清單#
- 前端 bundle 中永無機密(只有 public key 類)
- 機密不進 URL(2.4 Referer 教訓)
- 日誌/錯誤輸出過濾機密欄位(5.3)
- 開發/生產金鑰完全分離(開發金鑰洩漏≠無事,但爆炸半徑小)
- CI 權限最小化 + OIDC
- pre-commit 秘密掃描(gitleaks)
- 輪換演練:金鑰能被換掉嗎?(換不了=被綁架)
練習:金鑰輪換的架構前提#
sequenceDiagram
participant S as 服務
Note over S: 雙鑰並存期:新舊 secret 都能驗
S->>S: 簽發用新鑰,驗證容舊鑰
Note over S: 全部舊 token 過期後
S->>S: 移除舊鑰雙鑰過渡是零停機輪換的通用手法(JWT 的 kid 標頭、DB 密碼雙生效窗口)—— 能輪換的系統才算把秘鑰管起來。
習題#
- 掃描你的 git 歷史(
gitleaks detect),處理發現的每一把金鑰。 - 把一個服務的 AWS key 改造成 OIDC 職責角色,寫出 diff。
- 設計 JWT signing key 的雙鑰輪換方案(含 kid 與驗證邏輯)。