Vault+K8S 最佳實踐
在之前的文章「防拖庫的最佳實踐」中,我提到了使用 Vault 來儲存祕鑰的方案。本文是我在開發中使用的最佳實踐。
準備工作
首先生成一套部署 Vault 所需要的證書。在這裡我使用了cfssl
這個工具,在 Mac 下可以使用brew install cfssl
來安裝。
-
建立一個
ca-csr.json
配置檔案,如ofollow,noindex" target="_blank">ca/ca-csr.json 。之後執行命令。cfssl gencert -initca ./ca/ca-csr.json | cfssljson -bare ca
生成
ca.pem
和ca-key.pem
。 -
再建立一個
ca-config.json
配置檔案,如ca/ca-config.json ,和vault-csr.json
配置檔案,如ca/vault-csr.json 。之後執行命令:cfssl gencert \ -ca=ca.pem \ -ca-key=ca-key.pem \ -config=./ca/ca-config.json \ -hostname="vault,localhost,127.0.0.1,vault.of.your.custom.domain.name.com" \ -profile=default \ ./vault-csr.json | cfssljson -bare vault
-
生成
vault.pem
後再執行cat vault.pem ca.pem > vault-combined.pem
將 vault 的證書和 ca 證書合併。
這時我們應該有一組ca-key.pem
和vault-combined.pem
。
在 k8s 上部署
假設我們已經有一套 k8s 線上,而且可以使用kubectl
命令控制。
-
先將 ca 證書, vault 的證書和祕鑰寫入 k8s 的 secret 中。
kubectl create secret generic vault-tls \ --from-file=ca.pem \ --from-file=vault.pem=vault-combined.pem \ --from-file=vault-key.pem
-
建立 vault 配置檔案
vault.hcl
。如果是開發環境,不需要高可用(HA),我會推薦使用 mysql 作為儲存服務配置,例如vault-mysql.hcl 。 如果是線上環境,我推薦使用支援 高可用(HA) 的儲存層,例如 Google Cloud Storagevault-gcs.hcl 。-
可以在
gcloud
的console
中使用gsutil mb gs://vault_storage_bucket_name
命令建立 gcs 的 bucket。
-
可以在
-
將 vault 的配置檔案
vault.hcl
寫入 k8s 的 configmap。kubectl create secret generic vault-config \ --from-file=vault.hcl
-
建立一個 k8s 服務配置檔案
vault.yml
,例如開發測試環境配置vault-stage.yml ,或者生產環境vault-prod.yml -
將域名配置寫入 configmap。
kubectl create configmap vault \ --from-literal \ api-addr=https://vault.of.your.custom.domain.name.com:8200
-
如果使用了 gcs ,將谷歌的
service-account-cert.json
,例如vault-service-account-cert-example.json
(這個檔案是 gcp 平臺上生成的)。寫入 secret 。kubectl create secret generic vault-gcs \ --from-file=service-account-cert.json
-
啟動 vault 服務
kubectl apply -f vault.yaml
初始化 vault
vault 在初始化之前是不能正常服務,而且面向 k8s 的健康檢查服務也會顯示不正常。所以要先登入初始化。
-
通過
kubectl get pods
找到 vault 服務的 pod id。如果是按前面開發測試(stage)環境的配置,這個 pod id 應該是vault-0
。如果是生產環境,應該是vault-
後一串隨機的 id。 通過kubectl
登入進入這個pod的命令列:kubectl exec -it vault-0-or-your-vault-pod-id -- /bin/sh
-
初始化 vault。
vault operator init -ca-cert=/etc/vault/tls/ca.pem
初始化 vault 的操作只有第一次建立部署 vault 時。會生成5個 root tokens。一定要儲存好。初始化成功之後,就不需要也不會再執行 init 的操作。
-
unseal vault。這個操作使用初始化時生成的 root tokens 中的任意3個,在每次 vault 叢集啟動後都要執行。
vault operator unseal -ca-cert=/etc/vault/tls/ca.pem
至此完成 vault 服務的部署。
使用 vault
完成 vault 的初始化(init)和 unseal 之後。 k8s 叢集應該就會顯示 vault 服務健康狀態正常,並開始提供服務。這時就可以在任何一個可以訪問到這個 vault 服務的例項上,使用 vault 命令列,或者在程式碼中使用 vault 的庫來存取 vault 資料。
-
使用命令列,先配置環境變數
export VAULT_ADDR=https://vault.of.your.custom.domain.name.com:8200 export VAULT_CACERT=./ca.pem export VAULT_TOKEN=your-vault-token
-
建立一個 policy 配置,例如vault-policy-example.hcl 。並通過 vault 命令列建立這個 policy。
export VAULT_ADDR=https://vault.of.your.custom.domain.name.com:8200 export VAULT_CACERT=./ca.pem export VAULT_TOKEN=your-vault-token
-
在 secret/example 下寫入一個鍵值。例如寫入一個檔案或 key。
vault kv put secret/example/foobar \ [email protected] \ gcloud_apikey="your-gcloud-api-key"
-
建立一個 policy 配置,例如一個 secret/example/* 下的只讀策略,vault-policy-example-readonly.hcl 。並通過 vault 命令列建立這個 policy。
policy write example-readonly vault-policy-example-readonly.hcl
-
為這個 policy 建立一個 token。這個 token 僅有讀取這個 policy 下的鍵值許可權。因此更適合應用中使用。
vault token create -policy=example-readonly
-
之後我們就可以在程式碼中通過這個 readonly 的 token 來讀路徑下的鍵值。假設我們使用 golang。
... import ( ... vault "github.com/hashicorp/vault/api" ) ... // 配置 vault api 的地址 vaultAddr := os.Getenv("VAULT_API_ADDR") config := &vault.Config{ Address: vaultAddr, } // 配置 vault 的 CA Cert if err := config.ConfigureTLS(&vault.TLSConfig{ CACert: os.Getenv("CA_CERT"), }); err != nil { log.Fatalf("failed to configure vault tls: %v", err) } vaultClient, err := vault.NewClient(config) if err != nil { log.Fatalf("failed to init vault client %s: %v", vaultAddr, err) } // 配置 vault 的 token vaultClient.SetToken(os.Getenv("VAULT_TOKEN")) keyName := "secret/example/foobar" secretValues, err := vaultClient.Logical().Read(keyName) if err != nil { log.Fatalf("failed to read vault secret %s: %v", keyName, err) } // 列印配置資訊 fmt.Println(secretValues.Data["gcloud_apikey"].(string)) fmt.Println(secretValues.Data["grpc_tls_cert"].(string))
維護
維護上要注意的是, token 和 證書都是有過期時間。到期前要 renew 和重新配置新證書。可以通過命令列 renew:
vault token renew your-vault-token
如果 token 所屬的 policy 有 /auth/token/renew-self 相應的許可權。那麼也可以亦可以程式碼中自己 renew 自己。那麼就能保證活躍的 token 一直有效。而不活躍的 token ,即使被忘記了,一段時間後也會失效。