GitOps 和 Kubernetes 中的 secret 管理
GitOps 是使用 Git 作為基礎(chǔ)設(shè)施和應用程序配置的來源,利用 Git 工作流,實現(xiàn) Git 倉庫中描述配置的自動化。我們知道基礎(chǔ)設(shè)施配置和應用程序配置經(jīng)常都需要訪問某種敏感資產(chǎn),也就是我們通常說的 Secrets(比如身份認證 Token、私鑰等),才能正確運行、訪問數(shù)據(jù)或以其他方式與第三方系統(tǒng)以安全的方式進行通信。但是如果直接在 Git 中存儲 Secrets 數(shù)據(jù)顯然是非常不安全的行為,我們也不應該這樣做,即使是有訪問權(quán)限控制的私有 Git 倉庫。
那么我們應該如何才能解決 Secrets 數(shù)據(jù)的存儲問題呢?并為實施 GitOps 的用戶和客戶提供在不損害私密性的情況下為其應用程序提供 Secrets 的機制呢?這 Kubernetes 生態(tài)系統(tǒng)中有一些方法和開源項目可以幫助我們來解決這些問題,我們將在本文介紹幾種比較流行的方案。
在 GitOps 中管理 Secrets 主要有兩種主要的架構(gòu)方法:
將加密后的 secret 數(shù)據(jù)存儲在 Git 倉庫中,通過自動化的一些工具將這些數(shù)據(jù)進行解密變成 Kubernetes 的 Secrets 對象。 存儲在 Git 倉庫中的 secert 數(shù)據(jù)的引用,自動化工具可以根據(jù)這些引用檢索到實際的 secret 數(shù)據(jù),最后將獲取到的數(shù)據(jù)渲染為 Kubernetes Secrets 對象。
Git 中加密的 Secrets
目前有兩個比較流行的開源項目遵循在 Git 中存儲加密數(shù)據(jù)的方法:Bitnami 的 Sealed Secrets[1] 與 Mozilla 的 SOPS[2] 項目,使用這兩個項目需要注意以下事項:
純文本私密數(shù)據(jù)由用戶處理加密后存儲在 Git 倉庫中。 通過查看 Git 中的提交日志,可以輕松追蹤加密數(shù)據(jù)的 Git 提交者的身份,此信息可用于發(fā)起社會工程攻擊。 如果加密密鑰被泄露,則可能很難追蹤使用泄露密鑰加密的所有數(shù)據(jù)并將其撤消,因為這些數(shù)據(jù)可能會散布在大量存儲庫中。
Sealed Secrets
Sealed Secrets 項目使用公鑰加密的方式來對私密數(shù)據(jù)進行加密,使用起來非常方便。Sealed Secrets 由兩個主要部分組成:
一個 Kubernetes 控制器,它了解用于解密和加密數(shù)據(jù)的私鑰和公鑰,并負責對資源對象的調(diào)諧。 一個簡單的 CLI(kubeseal[3])工具提供給開發(fā)人員將數(shù)據(jù)提交到Git 倉庫之前對其進行加密操作。
Sealed Secrets 引入了一個新的自定義資源 SealedSecret,控制器會 watch 這些 CRD 的變更,對 SealedSecret 資源中的加密數(shù)據(jù)進行解密,并將結(jié)果保存在 Kubernetes Secret 對象中,用于解密的私鑰以 Kubernetes Secret 方式存儲在 etcd 中。

KubeSeal CLI 工具允許開發(fā)人員獲取普通的 Kubernetes Secret 資源并將其轉(zhuǎn)換為 SealedSecret 對象,可以從控制器自動獲取執(zhí)行加密所需的公鑰,否則,公鑰必須由用戶提供作為輸入。

Sealed Secrets 控制器支持私鑰的自動密鑰輪轉(zhuǎn),也支持以前使用過的密鑰過期,以執(zhí)行數(shù)據(jù)的重新加密。此外,在加密數(shù)據(jù)時,會使用一個隨機的 nonce 與加密數(shù)據(jù)一起進行加密,這使得通過暴力破解非常難以實現(xiàn)。
這種方法雖然看起來非常方便,但是仍然會帶來初始 Secret 資源被意外放進 Git 倉庫的風險,因為原始的 Secret 資源需要供開發(fā)者使用,不過也可以通過一些方式來降低這種風險,比如在 Git 倉庫中添加一個 pre-commit 的 hooks,拒絕提交 Kubernetes Secrets 數(shù)據(jù)。
此外,如果在集群中的私鑰丟失(由于意外刪除或在災難情況下),并且沒有備份,則必須使用新私鑰的公鑰重新加密所有加密數(shù)據(jù),然后提交到所有 Git 存儲庫。
控制器支持命名空間級別的多租戶,因為它需要明確指定目標命名空間,為命名空間 A 加密的 SealedSecret 在放置在命名空間 B 中時是無法解密的,反之亦然。但是這個約束是由控制器強制執(zhí)行的,而不是通過加密方式實現(xiàn)的。
隨著集群數(shù)量的增加,SealedSecrets 方法也不能很好地擴展。每個集群都會有自己的 SealedSecrets 控制器和私鑰,這意味著如果需要將同一個 Secret 部署到多個集群,則需要創(chuàng)建多個 SealedSecret 對象,增加了維護開銷和 GitOps 配置的復雜性。
最后,私鑰的安全性主要取決于集群的 RBAC 和 etcd 的保護程度,而私鑰的泄露則超出了集群本身的范圍。每一個與私密數(shù)據(jù)相關(guān)的 Git 倉庫也可能會泄露,考慮到 Git 的分布式特性,可能難以追蹤和撤銷。
Mozilla SOPS
SOPS(Secrets OPerationS),簡稱 sops,是 Mozilla 開發(fā)的一種專用的加解密工具,完全不受 Kubernetes Secrets 資源或 Kubernetes 的限制。支持多種輸入格式,包括 YAML、JSON、ENV、INI 和 BINARY 格式。
SOPS 還支持與一些常用的密鑰管理系統(tǒng) (KMS) 集成,例如 AWS KMS、GCP KMS、Azure Key Vault 和 Hashicorp 的 Vault。值得注意的是,這些密鑰管理系統(tǒng)實際上并不是用來存儲 Secrets 本身的,而是提供用于保護數(shù)據(jù)安全的加密密鑰,如果沒有這樣的系統(tǒng),則可以使用 PGP 密鑰對代替。這種方法的好處是給用戶提供了迭代需求的機會,同時讓開發(fā)者能夠快速采用。例如,PGP 可以在開發(fā)環(huán)境中使用,而在更高層次的環(huán)境中使用 KMS,而不需要更換任何底層工具。
但是由于 SOPS 是一個命令行工具,它在?Kubernetes 中的使用是比較有限的,雖然有一些插件,比如 helm-secrets[4] 可以在安裝到集群之前解密與 Helm Chart 一起存儲的 values.yaml 文件,但是在 GitOps 工具(比如 ArgoCD)中并不支持。
對于 Argo CD,用戶必須構(gòu)建包含 SOPS 的自定義容器鏡像,然后使用 Helm 擴展(請參閱此處的方法[5])或 Kustomize 擴展(請查看此處的方法[6])來處理。而 Flux 中對 SOPS 的支持更加成熟,SOPS 可以直接在 Flux 的清單中配置。

有一個 sops-secrets-operator[7] 工具嘗試將 SOPS 和 SealedSecrets 的概念結(jié)合起來,它使用 SealedSecrets 工作流程,但沒使用其特有的加密方案,而是使用 SOPS 進行加密。
隨著團隊和個人數(shù)量的增加,SOPS 方法無法很好地擴展,用戶必須執(zhí)行加密并管理 PGP 密鑰或?qū)ζ渌用軅鬏斚到y(tǒng)的身份進行認證,這是該方法的一個弱點。最后當然用戶會成為安全鏈中較弱的環(huán)節(jié),比起直接使用 PGP 密鑰,與密鑰管理系統(tǒng)的整合更為可取,雖然這并沒有消除人為因素和直接與 Secret 交互的需求,但至少它消除了必須管理密鑰的負擔。
存儲在 Git 中的 Secret 引用
在這種方法中,我們會在 Git 中存儲一個清單,該清單表示對位于密鑰管理系統(tǒng)中的 Secret 的引用。然后,GitOps Operator 將該清單部署到 Kubernetes 集群,操作人員獲取數(shù)據(jù)并將其作為 Kubernetes Secret 對象應用到集群。
有兩個主要項目實現(xiàn)了這種方式:ExternalSecrets[8] 和 Kubernetes Secret Store CSI Driver[9]。
這兩種方法都需要有一個密鑰管理系統(tǒng),這通常是一個企業(yè)級的系統(tǒng),用于管理整個企業(yè)的私密數(shù)據(jù),其功能不僅僅是分發(fā)加密密鑰。
ExternalSecrets
ExternalSecrets 項目最初由 GoDaddy 開發(fā),目的是在 Kubernetes 中安全使用外部 secret 管理系統(tǒng),如 HashiCorp 的 Vault、AWS Secrets Manager、Azure Key Vault、阿里巴巴 KMS 和 GCP Secret Manager 等。通過社區(qū)的發(fā)展,項目支持的密鑰管理系統(tǒng)也在不斷增加。
ExternalSecrets 是一個 Kubernetes 控制器,它可以將來自自定義資源 (ExternalSecrets) 的 Secret 應用到集群中,其中包括對外部密鑰管理系統(tǒng)中密鑰的引用。自定義資源指定包含機密數(shù)據(jù)的后端,以及如何通過定義模板將其轉(zhuǎn)換為 Secret,該模板可以包含動態(tài)元素(以 lodash 格式),并可用于向最終的 Secret 資源添加標簽或注解,或者在從后端存儲加載后對某些數(shù)據(jù)進行修改。
ExternalSecrets 資源本身可以安全地存儲在 Git 中,因為它們不包含任何機密信息。ExternalSecrets 本身不執(zhí)行任何加密操作,完全依賴于 Secret Store 后端的安全性。

該 Operator 對多租戶的支持也比較成熟了,可以采用不同的方法來確保不同的租戶彼此隔離,可以使用具有命名空間級別本地憑證的 SecretStore 資源來進行管理,這樣每個租戶將使用不同的憑據(jù)來對密鑰管理系統(tǒng)進行身份驗證。
Kubernetes Secret Store CSI Driver
Secrets Store CSI 驅(qū)動程序也是一個旨在將 Secret 從外部存儲帶入 Kubernetes 集群的項目,支持的后端包括 HashiCorp Vault、Azure Key Vault 和 GCP Secret Manager,其他后端可以從外部開發(fā),并按照插件模式作為"供應商"進行整合。與 ExternalSecrets 項目相反,Secrets Store CSI 驅(qū)動程序不是作為控制器將數(shù)據(jù)協(xié)調(diào)到 Secret 資源中,而是使用一個單獨的卷,該卷被掛載到 Kubernetes pod 中,以包含加密數(shù)據(jù),對于那些不被 pod 直接消費的加密數(shù)據(jù)場景,這種方法會變得有些復雜,甚至無法使用。
盡管 Secrets Store CSI 驅(qū)動程序確實提供了將內(nèi)容同步到 Kubernetes 中的 Secret 資源的可選功能,但由于作為 CSI 實現(xiàn)的性質(zhì),驅(qū)動程序及其創(chuàng)建的密鑰最終將綁定到工作負載。此外,另一個限制因素是,如果刪除了工作負載,則可選創(chuàng)建的 Secret 也將被刪除。
目前,對輪轉(zhuǎn)密鑰的支持有限,該功能仍處于 alpha 狀態(tài),并非所有提供商都支持。
與 ExternalSecrets 類似,Secrets Store CSI 驅(qū)動程序本身不執(zhí)行任何加密操作,而是利用后端系統(tǒng)加密和存儲私密數(shù)據(jù)。

用于連接后端系統(tǒng)的提供程序由外部第三方開發(fā)和維護,雖然這種方法有利于新提供商的開發(fā),但它也對代碼的安全性、成熟度和兼容性提出了問題。但是,Secrets Store CSI 驅(qū)動程序提供了一些在開發(fā)此類提供程序時要遵循的規(guī)則和模式,風險似乎可以忽略不計。在采用提供商時,了解如何管理多租戶非常重要,一些提供商使用一個共享憑證來訪問所有租戶的私密數(shù)據(jù)。
總體而言,Secret Store CSI 驅(qū)動程序項目的目標并不完全清楚。一方面,它似乎贊同我們不應使用 Kubernetes Secrets 的想法,而只是將臨時的內(nèi)存卷掛載到包含從密鑰管理系統(tǒng)獲取的 secret 的 pod 上。但是,與此同時,包括使用 Kubernetes Secrets 的能力,最終通過 etcd 暴露私密信息。
總結(jié)
我們已經(jīng)看到了兩種使用 GitOps 管理 secret 的架構(gòu)方法:存儲在 Git 中的加密 secret 和在 Git 中存儲對 secret 的引用。后一種方法似乎更有希望,因為它可以跨團隊/人員和集群的維度進行擴展,同時不影響安全性。前一種方法也許適合在沒有企業(yè)密鑰管理系統(tǒng)的情況下,或者是在 GitOps 初期的時候。這種方法的挑戰(zhàn)在于它可能無法適當?shù)財U展以適應企業(yè)的規(guī)模。
如果你的目的是有一個可靠的方法來創(chuàng)建 Kubernetes Secrets,那么 External Secret 項目是一個很好的選擇。如果你的目的是將 secret 信息提供給 pod,則最好使用 Secret Store CSI Driver,因為它不需要在集群中創(chuàng)建 Kubernetes Secret 信息。在后一種情況下,還可以選擇使用 sidecar 從密鑰管理系統(tǒng)獲取和刷新secret 信息。
避免創(chuàng)建 Kubernetes Secret 可以減輕一些保護 etcd 的需求,但是在 Kubernetes 中幾乎不可避免,因為太多的核心和附加功能依賴于 Kubernetes Secret。因此我們必須在可以反映為 Kubernetes Secrets 的 Secrets 和那些被 Pod 直接使用的 Secrets 之間取得平衡。
此外,如果我們的目標是使用 GitOps 方式管理我們所有的 IT 配置,那么這也應該適用于密鑰管理系統(tǒng)。Operators,例如 External Secrets,并沒有解決與管理 Secrets 相關(guān)的問題,而是將它們提升一個級別,從 Kubernetes 中的 Secret 創(chuàng)建,到 Key Management System 中的 Secret 創(chuàng)建。
為了打破這種循環(huán),密鑰管理系統(tǒng)必須能夠與需要驗證憑據(jù)的端點協(xié)調(diào),動態(tài)生成密鑰,從而消除將外部生成的密鑰輸入密鑰管理系統(tǒng)的要求,這些密鑰通常是由用戶來處理的。例如,密鑰管理系統(tǒng)可以與數(shù)據(jù)庫協(xié)調(diào),動態(tài)創(chuàng)建范圍更小、壽命較短的數(shù)據(jù)庫憑證。該功能的實現(xiàn)可以在帶有 secret 引擎的 Hashicorp Vault[10] 或帶有動態(tài) secret 的 Akeyless[11] 中找到。
總之,要實現(xiàn)基于 GitOps 的端到端 secret 管理方法,我們需要一個支持動態(tài) secret 的密鑰管理系統(tǒng),并能夠通過 GitOps 進行配置。
原文鏈接:https://cloud.redhat.com/blog/a-guide-to-secrets-management-with-gitops-and-kubernetes
參考資料
Sealed Secrets: https://github.com/bitnami-labs/sealed-secrets
[2]SOPS: https://github.com/mozilla/sops
[3]kubeseal: https://github.com/bitnami-labs/sealed-secrets
[4]helm-secrets: https://github.com/jkroepke/helm-secrets
[5]請參閱此處的方法: https://github.com/jkroepke/helm-secrets/wiki/ArgoCD-Integration
[6]請查看此處的方法: https://github.com/viaduct-ai/kustomize-sops
[7]sops-secrets-operator: https://github.com/isindir/sops-secrets-operator
[8]ExternalSecrets: https://github.com/external-secrets/external-secrets
[9]Kubernetes Secret Store CSI Driver: https://secrets-store-csi-driver.sigs.k8s.io/
[10]Hashicorp Vault: https://www.vaultproject.io/docs/secrets
[11]Akeyless: https://www.akeyless.io/
