iT邦幫忙

2026 iThome 鐵人賽

DAY 13
1
Kubernetes

從零到一:使用 K8S + GitOps 打造異構技術棧的資料分析平台系列 第 13

[Day 13] 配置管理:Secrets 與 ConfigMaps 的安全實踐 —— 將敏感資訊與環境變數從代碼中抽離,實現安全管理。

  • 分享至 

  • xImage
  •  

Day 13: 配置管理:Secrets 與 ConfigMaps 的安全實踐

將敏感資訊與環境變數從代碼中抽離,實現安全管理。

1. Configuration as Code 的挑戰

寫程式時經常需要資料庫連線字串、API URL、密碼。如果寫死在程式碼裡,不僅改起來麻煩,還有嚴重的資安外洩風險——只要 repo 一公開(或被離職同事帶走),你的資料庫密碼就跟著公開了。

K8S 給了兩個工具:非敏感的用 ConfigMap,敏感的用 Secret

2. ConfigMap:非敏感資訊的集中地

我們用 ConfigMap 統一管理整個 Wafer BI 的環境變數(在 Helm 裡由 values.yamlappConfig 區塊渲染):

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: k8sdemo
data:
  POSTGRES_HOST: "postgres-service"
  WAFER_BI_HOST: "wafer-backend-svc.k8sdemo.svc.cluster.local"
  API_GATEWAY_PORT: "8080"
  NODE_ENV: "production"

這些值會注入到微服務的環境變數中。後端架構改了(例如換 DB host),只要改這一個地方,不用動任何程式碼、不用重建任何 image。

3. Secret:小心守護你的密碼

密碼類的資訊用 Secret 資源。實際操作一次,順便揭穿一個常見的誤解:

▲ ConfigMap 與 Secret 實測:get secret 看到 base64「亂碼」,一行 base64 -d 直接還原明文

注意看最後兩步:kubectl get secret -o yaml 顯示的值長得像亂碼,很多人以為這樣就安全了。天真。 base64 是「編碼」不是「加密」,一行 base64 -d 就原形畢露。

所以 Secret 真正的保護來自:

  1. RBAC 權限控管:能 get secret 的人本來就該是管理員
  2. 與 ConfigMap 分離:日誌、debug 工具通常只 dump ConfigMap,降低誤曝光
  3. etcd encryption at rest:叢集層級的靜態加密(雲端託管 K8S 多半可開)

而它不能保護的是:把 Secret YAML 直接 commit 進 Git——base64 擋不住任何人。

4. 那 GitOps 怎麼辦?Sealed Secrets

這就尷尬了:Day 17 之後我們要走 GitOps,「一切設定都進 Git」,但 Secret 進 Git 等於裸奔。哭啊。

解法是 Sealed Secrets:用叢集裡的私鑰做非對稱加密,Git 裡放的是加密後的 SealedSecret,只有目標叢集能解開。你可以放心把它推上公開 repo,攻擊者拿到也只是一坨真正的密文。

裝起來只有兩行——controller 進叢集,kubeseal 進本機:

kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.38.4/controller.yaml
# kubeseal 從同一個 release 頁面下載對應平台的執行檔

controller 一啟動就會自己產生一組 RSA 金鑰對,公鑰給你封裝用,私鑰留在叢集裡。實際跑一次:

https://ithelp.ithome.com.tw/upload/images/20260815/20182549INnzNQXwmG.png

▲ Sealed Secrets 完整流程:本機封裝 → 密文進 Git → 叢集自動解密 → 刪掉明文還會自己補回來

流程拆解:

  1. 本機做出明文 Secret 但不 apply--dry-run=client -o yaml)。這份檔案是唯一有明文的地方,用完就刪
  2. kubeseal 用叢集公鑰封裝,吐出 SealedSecret。截圖裡那串 AgAAVYWDD4rD7H84... 就是真正的密文,792 個字元——這份可以安心進公開 repo
  3. kubectl apply 之後,叢集裡同時存在兩個東西:我 apply 的 SealedSecret(密文),以及 controller 自動解出來的一般 Secret(明文,4 個鍵)
  4. 解出來的值跟封裝前逐字元比對,完全一致——這代表整套流程是無損的,應用程式那邊 envFrom: secretRef 完全不用改

最後兩段是我覺得最能說明它性質的部分:

  • 把解出來的明文 Secret 砍掉,8 秒後它自己回來了。 因為 SealedSecret 才是真相來源,明文 Secret 只是它的產物(ownerReferences 指向 SealedSecret/app-secrets)。這跟 Day 17 的 ArgoCD selfHeal 是同一種思維:你手動改的東西不是真相,Git 裡的才是
  • 私鑰只存在 kube-system 的一個 Secret 裡(截圖最後一行的 sealed-secrets-keyv62s8)。這句話有兩個意思:一是攻擊者拿到你的 repo 也解不開,二是這把私鑰掉了,所有封過的東西就都廢了——它才是真正要備份的資產(Day 27 講災難復原時會再遇到)。

還有一個常見的坑要先講:密文跟「某一座叢集的金鑰」綁定。你在本機封的 SealedSecret 搬到 OKE 上是解不開的,必須拿新叢集的公鑰重新封一次。這是它的安全性來源,不是 bug。

順帶回收 Day 7 的伏筆:JWT_SECRET 除了要保密,還要夠長(HMAC-SHA 至少 256 bits)。放進 Secret 之前先確認長度,不然部署上去 Java 端直接 WeakKeyException 給你看。

5. 更上一層:不落地的金鑰與 KMS

Sealed Secrets 解決的是「設定怎麼安全地待在 Git 裡」。但有一類東西,連「存在叢集裡」都嫌太危險——例如簽發授權的私鑰。它一旦外流,別人就能無限發放合法的 License,而且你無從追查。

這時候要的不是加密儲存,是根本不要把金鑰交出去。這就是 KMS(Key Management Service)在做的事,我們專案用的是 OpenBao(HashiCorp Vault 的開源分支)的 Transit Secrets Engine

// services/license-service:VaultService.java
transit.createKey(KEY_NAME, VaultTransitKeyCreationRequest.ofKeyType("rsa-4096"));

public String sign(String data) {
    // 把資料送進去,拿簽章出來——私鑰從頭到尾沒有離開 OpenBao
    return transit.sign(KEY_NAME, Plaintext.of(plaintextBase64)).getSignature();
}

關鍵是 transit.sign()運算發生在 KMS 內部,應用程式只拿得到結果,拿不到金鑰本身。就算 license-service 整個被攻破、記憶體被 dump,攻擊者也拿不到那把 rsa-4096——他最多只能在被發現之前多簽幾張 License。

搭配 K8S 的完整鏈路長這樣:

license-service  ──sign()──►  OpenBao Transit(rsa-4096 私鑰不出境)
      │
      └── GET /public-key ──►  user-service 用公鑰驗章

只有公鑰會流出去,而公鑰本來就是公開的。

那什麼時候用哪個?

這是我覺得最值得記住的一張對照:

Sealed Secrets KMS / Transit
保護對象 靜態的設定值 不該落地的金鑰
金鑰在哪 解密後成為叢集裡的明文 Secret 從不離開 KMS
應用程式拿得到什麼 拿得到明文(它需要拿來連 DB) 只拿得到運算結果(簽章、密文)
適合 POSTGRES_PASSWORDJWT_SECRET、API Key 簽章私鑰、加解密主金鑰
代價 幾乎零,只多一個 controller 多一個必須高可用的服務,每次運算都要網路往返

判斷方式很簡單:問「應用程式需不需要看到這個值本身?」

  • 需要(要拿密碼去連資料庫)→ Sealed Secrets 就夠了,反正它終究得是明文
  • 不需要(只是要一個簽章)→ 用 KMS,把金鑰徹底鎖起來

兩者不衝突,而且常常一起用。實務上很典型的組合是:用 Sealed Secrets 保管「連線到 KMS 的 token」,再用 KMS 保管真正的金鑰——我們的 VAULT_TOKEN 走的就是這條路。這樣最敏感的資產只有一份,而且鎖在專門的服務裡。

誠實補充:這篇示範的 OpenBao 是跑在叢集裡的 dev 模式,開機就解封、token 寫死成 root,只夠展示 Transit 的運作方式。正式環境要處理的還有 unseal 流程、token 輪替、以及「OpenBao 掛掉時 License 就簽不出來」的高可用問題——那是另一個 30 天的題目了。

6. 小結

設定與密碼都各就各位了,而且分成三層:ConfigMap 放不敏感的、Sealed Secrets 讓敏感設定能安全地待在 Git、KMS 讓最關鍵的金鑰根本不落地。明天回到「服務健康」這個主題——K8S 怎麼知道你的服務是真的活著,還是已經假死?Liveness 與 Readiness Probes 登場。


上一篇
[Day 12] 持久化存儲:在 K8S 上管理 Delta Lake 數據存儲 (PV/PVC) —— 解決資料庫與數據文件在容器重啟後的丟失問題。
下一篇
[Day 14] 自癒能力:Liveness 與 Readiness Probes 的設計思維 —— 讓 K8S 知道何時該重啟服務,何時該引導流量。
系列文
從零到一:使用 K8S + GitOps 打造異構技術棧的資料分析平台17
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言