1 Vault是什麼?
如何在雲上應用中管理和保護使用者的敏感資訊是一個經常令開發者頭疼的問題,使用者的密碼密碼,證書秘鑰等私密資訊時常未經加密被随意的放置在配置檔案,代碼倉庫或是共享存儲裡,而對于普通的開發者來說,設計和實作一套完整的秘鑰管理系統是一個很大的挑戰。且不論令人生畏的加解密算法,很多的雲應用仍然将一些敏感配置資訊僅僅經過base64等一些簡單的hash運算就放置在某個公共的配置中心上,而很多時候這些敏感資訊會從應用的某行異常日志或是某段監控告警中洩露出去;不僅如此,對于一個集中式的秘鑰管理系統,如何面向使用者進行更細粒度的通路鑒權也是一個難題。

Vault的出現給了上述問題一個解決方案,它是HashiCorp公司(旗下還有Vagrant,Terraform,Consul等知名産品)維護的開源軟體,它的設計思想基于雲原生背景下動态基礎設施的特點,在雲上的不同網絡層以及不同的服務之間已經很難找到傳統的信任邊界,服務之間更加強調以身份(identity)為核心的認證和通路控制,而不是像傳統靜态基礎設施中以IP、主機位址作為信任憑證。為此Vault提供了以下幾個功能點:
- Secret存儲形式的多樣性,任意的kv形式敏感資訊(如資料庫密碼,證書,ssh登入秘鑰,openapi身份憑證等);
- 存儲格式的多樣性,支援插件式的存儲引擎擴充,可對接如AWS,Consul,NoSQL,KV,PKI,SSH等多種插件引擎;
- 支援與各類平台的認證對接,可動态生成認證憑據或配置資訊;
- 支援基于Shamir算法的私鑰分割完成Vault後端的加封和解封操作,同時支援高可用的部署形态;
- 支援各類secret的動态生成,續租,撤銷和滾動更新;
- 完備的審計日志;
- 完備的CLI和RESTful API
2 Vault與k8s的內建
Vault松耦合的架構使其支援與多種secret引擎和相應的存儲後端
對接,同時支援與多種認證伺服器的
互動。
本節我們主要介紹Vault與k8s的內建。
2.1 Vault在Kubernetes中的應用場景
Vault作為企業級的secret管理工具,是一些大客戶在業務上雲過程中的安全強需求,尤其是國外市場。在Kubernetes叢集中主要有以下應用場景:
- 作為部署在Kubernetes叢集中的應用對外提供秘鑰管理服務,支援與多家主流雲廠商秘鑰服務以及多種secrets形式的對接,支援多種資料庫服務的存儲對接,同時支援多種認證形式的對接。
- 作為一個公共的加密服務(Encryption as a Service)而不做後端存儲的對接,幫助使用者應用剝離繁瑣的加密加解密邏輯。
- 面向政府、金融等對資料安全規格有很高要求的客戶,Vault支援基于 Two-man原則 利用 私鑰分割算法
對後端服務進行加解封,并結合k8s的高可用部署形态為企業提供更加安全可靠的secret管理能力。
當然這裡隻是列舉了一些Vault原生提供的能力,作為一個在Kubernetes叢集上直接運作的安全應用,任何一個面向k8s的應用工具都可以利用其安全能力。
2.2 安裝Vault
Vault支援helm化安裝,在其
官方文檔中我們可以找到關于啟動參數的詳細配置說明,同時在阿裡雲容器服務的應用目錄apphub中我們也可以通過控制台在ACK叢集中友善的安裝Vault
另外Vault的預設安裝也內建了其控制台的安裝,通過負載均衡服務或ingress路由的方式我們可以在公網通路其UI,在vault pod的日志中我們可以找到登入使用的root token,在控制台中可以友善的設定與存儲引擎和認證方式的對接,同時還可以進行基于政策的通路控制配置。
2.3 認證方式的內建
當使用者希望在k8s pod的業務邏輯中與Vault服務端通訊,擷取需要的secrets時,首先Vault會對這個pod中的請求進行認證,那麼這個pod中的Vault請求認證憑據應該如何擷取呢?如上所述,Vault後端支援多種認證方式的對接,對于Kubernetes,Vault支援基于K8s Service Account Token的認證。
使用上,Vault管理者首先需要在後端enable kubernetes的認證方式,生成一個與Vault互動的指定sa,然後通過CLI或API将sa token和叢集ca,公網位址等資訊寫入到Vault後端中,并配置與vault後端的ACL政策綁定。詳細步驟請參見
當然在pod應用中可以并不局限于一定使用基于sa的kubernetes認證方式。比如[kubernetes-vault](
https://github.com/Boostport/kubernetes-vault)。該項目使用Vault中的[AppRole](
https://www.vaultproject.io/docs/auth/approle.html)認證方式,在該認證模式中,管理者可以為不同的pod建立不同的Vault原生role模型并綁定到對應的policy上,同時可以基于role建立secret_id,secret_id對應的token可作為與Vault進行認證的臨時憑證。kubernetes-vault利用了AppRole的認證互動模式,首先已經完成安裝的kubernetes-vault controller會去Watch叢集中所有pod的建立,當發現建立pod的部署模闆中有指定annotation的init-container存在時,controller會根據模闆中指定的vault role id去Vault請求擷取其對應的secret_id并發送給init-container中kubernetes-vault的用戶端,在應用容器啟動前kubernetes-vault用戶端用controller傳回的secret_id和role_id去Vault請求真正的login token并最終寫入到與應用pod共享的挂載目錄中;同時用戶端會根據token過期時間進行定時的輪轉,保證其可用性。下圖為kubernetes-vault工作流程圖:
在社群也存在不少基于k8s與Vault進行認證對接的其他方案,其設計思路大同小異,基本都采用了通過init-container或sidecar方式引入一個額外的用戶端去Vault請求指定認證模式下的短時憑證并共享給業務容器使用。
在容器服務控制台的應用目錄apphub中,我們同樣可以找到kubernetes-vault,友善開發者使用helm直接在叢集指定命名空間一鍵部署。Vault也計劃在後續自己的官方版本helm chart中增加配置項以支援上述登入認證secret的動态注入。
3 Vault與阿裡雲RAM的內建
當我們在應用中需要通路阿裡雲資源時,需要使用RAM賬号對應的AK或是STS臨時credentials作為通路相應資源接口的憑證。如果使用賬号AK,如何使其能夠被應用邏輯擷取的同時保證AK的安全性一直是一個頭疼的問題;如果使用臨時sts token,由于其時效性,我們也需要在考慮安全性的同時思考如何進行臨時通路憑證的輪轉。相比較兩種方式,使用sts臨時憑證的方式肯定在安全上是更為推薦的方式,同時對這種動态secret的安全管理也正是Vault的優勢所在。本節我們來介紹下Vault與阿裡雲RAM在認證方式和secret管理引擎上的內建。
3.1 認證方式的內建
首先在
認證方式上,Vault服務端的role模型可以與RAM role進行一對一的映射比對,使用者可以使用Vault提供的OpenAPI或是CLI,通過傳入扮演RAM role傳回的臨時憑證調用
GetCallerIdentity
接口,然後Vault服務端會根據請求傳回的角色arn id在其後端存儲中查找是否有對應的權限政策配置,如果存在則認證成功并傳回一個可用于調用Vault其他後端接口的通路token。
3.2 Vault Secret引擎與RAM的內建
當我們需要在業務應用邏輯中使用阿裡雲資源時,通常需要通過角色扮演的方式擷取一個RAM傳回的臨時憑證,然後通過這個臨時憑證完成與RAM的鑒權過程。由于憑證的時效性,我們在保證其安全性的同時還要維護一個對應的秘鑰輪轉機制。Vault的secret引擎實作了與阿裡雲RAM的對接插件,幫助我們安全、動态的管理RAM憑證,其
主要步驟如下:
1.開啟後端引擎
2.在RAM控制台為Vault伺服器建立專屬子賬号并綁定定制化權限政策
3.擷取Vault子賬号對應的AK并通過Vault CLI/API寫入到後端指定路徑下
4.在Vault後端寫入業務中希望擷取的RAM憑證所對應的政策定義或角色,其中政策定義支援inline和remote政策兩種形式,所謂inline模式是指直接在api請求中寫入政策模闆,remote模式指寫入RAM中存在的政策類型和名稱,比如:
$ vault write alicloud/role/policy-based \
remote_policies='name:AliyunOSSReadOnlyAccess,type:System' \
remote_policies='name:AliyunRDSReadOnlyAccess,type:System'
角色模式需要使用者指定希望被扮演的角色arn,另外需要Vault子賬号在該角色的受信實體裡,一個示例如下:
$ vault write alibaba/role/role-based \
role_arn='acs:ram::5138828231865461:role/hastrustedactors'
5.在具體的業務應用中,隻需要通過調用Vault的creds/policy-based或role-based接口即可動态擷取相應的RAM通路憑證,下面是一個角色扮演傳回臨時token的CLI調用示例:
$ vault read alicloud/creds/role-based
Key Value
--- -----
lease_id alicloud/creds/role-based/f3e92392-7d9c-09c8-c921-575d62fe80d9
lease_duration 59m59s
lease_renewable false
access_key STS.L4aBSCSJVMuKg5U1vFDw
secret_key wyLTSmsyPGP1ohvvw8xYgB29dlGI8KMiH2pKCNZ9
security_token CAESrAIIARKAAShQquMnLIlbvEcIxO6wCoqJufs8sWwieUxu45hS9AvKNEte8KRUWiJWJ6Y+YHAPgNwi7yfRecMFydL2uPOgBI7LDio0RkbYLmJfIxHM2nGBPdml7kYEOXmJp2aDhbvvwVYIyt/8iES/R6N208wQh0Pk2bu+/9dvalp6wOHF4gkFGhhTVFMuTDRhQlNDU0pWTXVLZzVVMXZGRHciBTQzMjc0KgVhbGljZTCpnJjwySk6BlJzYU1ENUJuCgExGmkKBUFsbG93Eh8KDEFjdGlvbkVxdWFscxIGQWN0aW9uGgcKBW9zczoqEj8KDlJlc291cmNlRXF1YWxzEghSZXNvdXJjZRojCiFhY3M6b3NzOio6NDMyNzQ6c2FtcGxlYm94L2FsaWNlLyo=
expiration 2018-08-15T21:58:00Z
4 如何在k8s應用中使用Vault Secret
在了解了Vault的基本概念以及與Kubernetes的認證互動流程後,我們進入客戶最為關心的話題。如何在k8s pod應用中友善地擷取Vault服務端管理的secret。社群針對此問題也有激烈的讨論和不少相關解決方案,方案主要集中在兩個方向:
**[定時同步程序](
https://github.com/hashicorp/vault/issues/7364)**:使用一個同步程序定時地從Vault服務端擷取指定範圍的秘鑰更新并同步到k8s叢集中的secret模型,代表的項目有
vaultingkube和[secrets-manager](
https://github.com/tuenti/secrets-manager) 。其主要設計思想也不盡相同,以secrets-manager為例,首先使用者可以通過CRD定義在Vault中關注的secret資料源,然後secrets-manager對應的controller會在Reconcile函數中定時對比指定管理範圍内的k8s secret和vault secret的狀态,如果不一緻則進行一次調協。而使用者在pod應用中可以直接引用原生secret模型中的内容擷取遠端Vault伺服器中的秘鑰。
當然社群中也存在一些對這種秘鑰同步方案的質疑,比如認為該方案在秘鑰同步的傳輸過程和使用者pod使用原生secret的rest互動中會增加攻擊面,但是該方案在部署實施上比較友好,也得到了很多使用者的支援。
CSI插件形式內建:該方案基于
CSI plugin将Vault中的秘鑰通過volume的形式挂載到pod應用中。
secrets-store-csi-driver通過實作一套基于CSI規範的driver機制可以對接
不同廠商的後端存儲,而Vault secret的driver(secrets-store.csi.k8s.com)允許kubelet将各類企業級秘鑰存儲中的secret通過volume挂載,一旦attach動作完成,秘鑰資料即挂載到了容器對應的檔案系統中。在CSI driver的基礎上,不同的秘鑰管理後端可以實作定制化的provider去對接CSI driver架構中的規定接口。provider的功能概括如下:
- 對接後端秘鑰管理系統,提供秘鑰擷取等必須的接口實作
- 适配目前CSI driver的接口定義
- 通過架構中的回調函數無需調用Kubernetes API即可将從後端擷取的秘鑰資料挂載到指定路徑下
HashiCorp官方也基于此架構實作了一套對接Vault的
Provider。這裡我們以此為例具體來看下在一個k8s pod應用中如何通過CSI plugin的方式使用Vault中管理的secret秘鑰。
1 首先我們建立一個開啟了CSI存儲插件的ACK叢集,然後參考
文檔在叢集中部署Vault服務端,為了便于驗證這裡我們使用dev模式省去unseal解封等流程,同時配置provider與Vault互動的認證模式和相應的通路控制政策
然後通過cli向Vault後端寫入測試資料
2 通過官方提供的helm方式安裝Secret Store CSI Driver,指令如下:
helm install . -n csi-secrets-store --namespace dev --set providers.vault.enabled=true
安裝成功後如下圖所示:
3 在叢集中建立
secretproviderclasses
執行個體用于Secret Store CSI Driver與Vault的參數對接,一個示例如下,注意這裡的vault服務端位址可通過kubectl get service vault擷取。
4 最後我們來看下如何在應用pod中對接上述provider執行個體擷取對應的Vault秘鑰。這裡pod對于上述vault provider的使用分為兩種方式:
1)如果pod運作的目标叢集版本在v1.15.0以上,且叢集apiserver和節點kubelet manifest配置均開啟了
CSIInlineVolume=true
的feature-gates,則我們可以在pod中的volume字段内置聲明需要使用的csi provider執行個體。
kind: Pod
apiVersion: v1
metadata:
name: nginx-secrets-store-inline
spec:
containers:
- image: nginx
name: nginx
volumeMounts:
- name: secrets-store-inline
mountPath: "/mnt/secrets-store"
readOnly: true
volumes:
- name: secrets-store-inline
csi:
driver: secrets-store.csi.k8s.com
readOnly: true
volumeAttributes:
secretProviderClass: "vault-foo"
2)如果目标叢集不支援CSI的Inline Volume特性,我們需要首先建立使用csi的pv和對應的pvc執行個體,一個pv模闆示例如下:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-vault
spec:
capacity:
storage: 1Gi
accessModes:
- ReadOnlyMany
persistentVolumeReclaimPolicy: Retain
csi:
driver: secrets-store.csi.k8s.com
readOnly: true
volumeHandle: kv
volumeAttributes:
providerName: "vault"
roleName: "example-role"
vaultAddress: http://172.21.12.21:8200
vaultSkipTLSVerify: "true"
objects: |
array:
- |
objectPath: "/foo"
objectName: "bar"
objectVersion: ""
在pod執行個體模闆中引用指定pvc即可在pod中擷取到vault,這裡我們在ACK叢集以pv/pvc模式為例建立一個nginx應用容器執行個體并在其中挂載上文中我們建立的secretproviderclasses執行個體:
相比于secrets-manager等采用secret定時同步的方式,使用CSI對接指定Vault secret provider執行個體的方式雖然在實施步驟上比較複雜,同時在應用中也無法動态擷取Vault後端secret的變更,但是該方案避免了secret在同步鍊路上頻繁傳輸的安全風險,同時也客服了之前describe po可能造成的秘鑰洩露,在整體安全性上要高出不少。大家可以根據實際應用場景選擇适合自己的方式。