Quarkus 整合 Vault 與 Secrets Store CSI Driver
Vault 是一個可將機密資訊集中化管理的一個平台,不論是憑證、密碼等。再者要存取這些資訊都要經過一層身份驗證才能獲取儲存在 Vault 服務上的機密資訊。專案整合 Vault 服務能夠降低敏感數據暴露風險、集中管理提高生產效率等。本文章會透過第三方工具整合 Vault 實現無入侵程式碼,其優勢可以將不熟悉的專案進行整合。
此文章會學習到
- Vault 服務整合外部 Kubernetes
- Secrets Store CSI Driver 整合 Vault
- 使用 Secrets Store CSI Driver CRDs 宣告資源
本實驗環境
- OS: Windows 11 WSL
- Docker Engine: 23.0.5
- k3d version: v5.6.0
- kubectl version: v1.27.1
- Helm version: v3.11.3
- Vault chart version: 0.26.1
- Secrets Store CSI chart version: 1.4.0
本實驗環境的建置可應用於標準 Kubernetes 叢集。本實驗所使用的專案連結。
建立環境
Vault 服務
宣告 K3d 設定,設定對應本實驗專案
# config.yaml
apiVersion: k3d.io/v1alpha4
kind: Simple
metadata:
name: vault-cluster
servers: 1
agents: 2
kubeAPI:
host: "vault.cch.com"
hostIP: "127.0.0.1"
hostPort: "6450"
image: rancher/k3s:v1.27.7-k3s1
network: vault-net
ports:
- port: 8050:80
nodeFilters:
- loadbalancer
- port: 8453:443
nodeFilters:
- loadbalancer
options:
k3s:
extraArgs:
- arg: --disable=traefik
nodeFilters:
- server:*
使用上述設定檔建立模擬 Kubernetes 環境
k3d cluster create --servers-memory 3G --agents-memory 3G -c config.yaml
安裝 Nginx Ingress 服務
$ helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
$ helm search repo ingress-nginx
$ helm install ingress-nginx ingress-nginx/ingress-nginx --version 4.8.3 --namespace ingress-nginx --create-namespace
安裝 Vault 服務
$ helm repo add hashicorp https://helm.releases.hashicorp.com
$ helm search repo hashicorp/vault
$ helm install vault hashicorp/vault --version 0.26.1 --namespace vault --create-namespace -f values.yaml
NAME: vault
LAST DEPLOYED: Thu Nov 30 20:51:14 2023
NAMESPACE: vault
STATUS: deployed
REVISION: 1
NOTES:
Thank you for installing HashiCorp Vault!
Now that you have deployed Vault, you should look over the docs on using
Vault with Kubernetes available here:
https://developer.hashicorp.com/vault/docs
Your release is named vault. To learn more about the release, try:
$ helm status vault
$ helm get manifest vault
安裝完之後需要解封,才能存取 Vault 中的資源,這是一個 Vault 加密機制。
$ kubectl --namespace vault exec -it vault-0 -- vault operator init
Unseal Key 1: SOb6GoV2ScLTwDNzSy4igMSLjlMsW3DnpXc47/bm4Yxx
Unseal Key 2: TuI2LjfKBxPgrmMWOvTYmjFcfgjFKrbnmloWWMPXYnov
Unseal Key 3: JMY26yi9G1h14s7vvHB4aryF16Di3SnPj/W6kdseis6d
Unseal Key 4: QKQdXjp2sqPOqluU/DFn3ogvOUD2MwtH4+NwTlL5ufRh
Unseal Key 5: JBfWTtw/JSw+3iHbgNP1SXVQN94YztbXARZL682KCQMp
Initial Root Token: hvs.QyjNEBlD8y5BH4KsYycz5k5K
Vault initialized with 5 key shares and a key threshold of 3. Please securely
distribute the key shares printed above. When the Vault is re-sealed,
restarted, or stopped, you must supply at least 3 of these keys to unseal it
before it can start servicing requests.
Vault does not store the generated root key. Without at least 3 keys to
reconstruct the root key, Vault will remain permanently sealed!
It is possible to generate new unseal keys, provided you have a quorum of
existing unseal keys shares. See "vault operator rekey" for more information.
建置 Quarkus Kubernetes 叢集
設定 Quarkus Kubernetes 叢集,對應本實驗專案。
apiVersion: k3d.io/v1alpha4
kind: Simple
metadata:
name: quarkus-cluster
servers: 1
agents: 1
kubeAPI:
host: "app.cch.com"
hostIP: "127.0.0.1"
hostPort: "6451"
image: rancher/k3s:v1.27.7-k3s1
network: vault-net
ports:
- port: 8051:80
nodeFilters:
- loadbalancer
- port: 8451:443
nodeFilters:
- loadbalancer
options:
k3s:
extraArgs:
- arg: --disable=traefik
nodeFilters:
- server:*
$ k3d cluster create -c config.yaml --servers-memory 2GB --agents-memory 2GB
本實驗專案是透過 Quarkus 框架來整合 Vault。
Kubernetes 叢集整合 Vault 服務
Quarkus 專案會部署至 Quarkus Kubernetes 叢集,但 Vault 服務為另一個叫 vault-cluster 的環境。下面將會示範如何從 Vault 設定基於 Kubernetes 的認證,以讓 quarkus-cluster 中 quarkus 應用程式存取。
在 quarkus-cluster 叢集中手動建置一個給 ServiceAccount 的長期 API 令牌,並透過 kubernetes.io/service-account.name 建立一個新 Secret 物件,內容包含 ca.crt、token 等欄位。有關手動建立長期令牌可參閱官方文件。
apiVersion: v1
kind: ServiceAccount
metadata:
name: vault-auth
labels:
app.kubernetes.io/managed-by: vault
---
apiVersion: v1
kind: Secret
metadata:
name: vault-auth
annotations:
kubernetes.io/service-account.name: vault-auth
type: kubernetes.io/service-account-token
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: role-tokenreview-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:auth-delegator
subjects:
- kind: ServiceAccount
name: vault-auth
namespace: default
透過 kubernetes.io/service-account-token 建立的長期令牌將賦予給 Vault 服務並與 quarkus-cluster 互動。如果使用短期令牌,一旦 Pod 或 ServiceAccount 被刪除 Kubernetes 就會撤銷它,或者如果令牌過期後,Vault 將無法再使用該令牌與 Kubernetes API。但,長期令牌沒有短期令牌的安全性,但兩方式都能整合。
設定給 Vault 驗證 quarkus-cluster 資訊。K8S_HOST 對於本範例來說會有環境上網路路由問題,因此會使用 k3d 建置出來的 serverlb 容器 IP 位置。至於能夠通訊是透過 network: vault-net 設定。下面為實驗步驟:
- 登入 Vault
export VAULT_ADDR=https://vault-demo.cch.com:8453
export VAULT_SKIP_VERIFY=true
vault login -tls-skip-verify hvs.2fVKDYNbdS1kxa186pWZuDWn
- 建置設定給 Vault 驗證的資訊
export SA_JWT_TOKEN=$(kubectl get secret vault-auth -o jsonpath="{ .data.token }" | base64 --decode; echo)
kubectl -n default get secret vault-auth -o jsonpath="{.data['ca\.crt']}" | base64 --decode | tee -a ca.crt
export K8S_HOST=$(kubectl config view --raw --minify --flatten -o jsonpath='{.clusters[].cluster.server}')
- Vault 服務啟用 Kubernetes 認證
$ vault auth enable --path=quarkus-cluster kubernetes
Success! Enabled kubernetes auth method at: quarkus-cluster
- 設定 quarkus-cluster 認證
$ vault write auth/quarkus-cluster/config token_reviewer_jwt=$SA_JWT_TOKEN kubernetes_host=https://172.20.0.6:6443 kubernetes_ca_cert="$(cat ca.crt)"
$ vault read auth/quarkus-cluster/config
Key Value
--- -----
disable_iss_validation true
disable_local_ca_jwt false
issuer n/a
kubernetes_ca_cert -----BEGIN CERTIFICATE-----
MIIBdzCCAR2gAwIBAgIBADAKBggqhkjOPQQDAjAjMSEwHwYDVQQDDBhrM3Mtc2Vy
dmVyLWNhQDE3MDE1OTUyNTQwHhcNMjMxMjAzMDkyMDU0WhcNMzMxMTMwMDkyMDU0
WjAjMSEwHwYDVQQDDBhrM3Mtc2VydmVyLWNhQDE3MDE1OTUyNTQwWTATBgcqhkjO
PQIBBggqhkjOPQMBBwNCAAROecAvJxanXKn5xhGgPAIr4vCSkUON/gTK804MxLAw
AaYiNJIHZcBCsF2HrLjno9Z5WXgMr4auihygOCH+duMEo0IwQDAOBgNVHQ8BAf8E
BAMCAqQwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUIZoFjtr+3eM5JOD6Bj7r
CtjOPjUwCgYIKoZIzj0EAwIDSAAwRQIgPASRbtat2BdrxFfhN2vE4mJqt+G4Q3c6
WPp0RVUPPYACIQDBNsAVzwTTCBYTd49DrVd3KyA6q19nY21bxMtVMHjxtw==
-----END CERTIFICATE-----
kubernetes_host https://172.20.0.6:6443
pem_keys []
- 建置 quarkus-cluster 認證的角色
vault write auth/quarkus-cluster/role/quarkus-vault \
bound_service_account_names=* \
bound_service_account_namespaces=* \
policies=quarkus \
ttl=24h
這邊 * 表示所有的意思,但建議會是明確的定義。policies 則為存取資源像是 KV 引擎、KPI 的權限控管。
- 驗證
本實驗的 Quarkus 專案定義了一個 greeting.message 環境變數,因此下面會定義 KV 引擎。
# 啟用 secrets 引擎,定義版本 2,且路徑 kv
$ vault secrets enable -version=2 kv
Success! Enabled the kv secrets engine at: kv/
推送 greeting.message 的 KV 值至 Vault。
$ vault kv put kv/quarkus/vault-demo greeting.message="vault hello!"
======= Secret Path =======
kv/data/quarkus/vault-demo
======= Metadata =======
Key Value
--- -----
created_time 2023-12-03T04:48:18.833082542Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 1
定義 policy,會對應到第 5 步驟 policies 的值。
# policy.hcl
path "kv/data/quarkus/vault-demo" {
capabilities = ["read"]
}
# 定義個名稱為 quarkus 的 policy 角色
vault policy write quarkus policy.hcl
最後透過部署 Quarkus 資源驗證,部署檔案透過此連結取得。
# 在 quarkus-cluster 部署
kubectl apply -f k8s/deployment.yaml
成功獲取 Vault 上定義的值。
$ curl -k https://app.cch.com:8451/info
{"message":"vault hello!"}
到這邊可以知道如何透過一個 Vault 服務整合外部的 Kubernetes。並透過 Quarkus 框架所提供的 Vault API 進行互動,設定如下。
quarkus.tls.trust-all=true
quarkus.vault.url=https://vault-demo.cch.com:8453 # 定義連線 Vault API 位置
quarkus.vault.kv-secret-engine-mount-path=kv
quarkus.vault.secret-config-kv-path=quarkus/vault-demo
quarkus.vault.kv-secret-engine-version=2
quarkus.vault.authentication.kubernetes.auth-mount-path=auth/quarkus-cluster
quarkus.vault.authentication.kubernetes.role=quarkus-vault
本範例的 Quarkus 應用程式成功連線後,在 Vault 服務可以看到其日誌。Pod 中容器應用程式將透過 Vault API /v1/auth/quarkus-cluster/login 進行登入,並驗證來自 serviceAccount 令牌,成功後再透過 Vault API /v1/kv/data/quarkus/vault-demo 獲取 secrets 資源。
{"@level":"debug","@message":"completed_request","@module":"core","@timestamp":"2023-12-06T11:33:22.628656Z","client_address":"10.42.2.1","client_id":"","duration":"2ms","request_method":"POST","request_path":"/v1/auth/quarkus-cluster/login","start_time":"2023-12-06T11:33:22Z","status_code":200}
{"@level":"debug","@message":"completed_request","@module":"core","@timestamp":"2023-12-06T11:33:22.660627Z","client_address":"10.42.2.1","client_id":"faf515ee-37d8-f117-1afe-d9d4879850ed","duration":"0ms","request_method":"GET","request_path":"/v1/kv/data/quarkus/vault-demo","start_time":"2023-12-06T11:33:22Z","status_code":200}
Vault 與 Secrets Store CSI Driver 整合
安裝 Secrets Store CSI Driver 時 K3d 版本要 v5.6.0,否則會有 CreateContainerError 錯誤訊息,運行 K3d 需多做這一步 K3D_FIX_MOUNTS。
使用 Helm 安裝。
helm repo add secrets-store-csi-driver https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts
helm install csi-secrets-store secrets-store-csi-driver/secrets-store-csi-driver --version 1.4.0 --namespace kube-system --set syncSecret.enabled=true
安裝完後每個節點都會有一個 Pod,該 Pod 是由 DaemonSet 所產生。
$ kubectl get pods -A -l app.kubernetes.io/instance=csi-secrets-store -o wide
NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
kube-system csi-secrets-store-secrets-store-csi-driver-ss22c 3/3 Running 0 3h38m 10.42.1.4 k3d-quarkus-cluster-server-0 <none> <none>
kube-system csi-secrets-store-secrets-store-csi-driver-vg547 3/3 Running 0 3h38m 10.42.0.4 k3d-quarkus-cluster-agent-0 <none> <none>
安裝 vault-csi-provider,以讓 Secrets Store CSI 取得儲存在 Vault 中的資訊,並使用 Secrets Store CSI 介面將它們掛載到 Kubernetes Pod 中。
使用 Helm 安裝 Vault 的 CSI 介面。
helm install vault hashicorp/vault --version 0.26.1 --namespace vault --create-namespace --set "server.enabled=false" --set "injector.enabled=false" --set "csi.enabled=true"