本文旨在解釋每個 Kubernetes Vault 元件,並在 Kubernetes 中設定 Vault 伺服器的分步指南。在本文的最後,我們還將透過一個簡單的演示來討論應用程式如何利用 vault。
作為初學者,在了解所涉及的步驟的同時逐個建立元件是了解 Kubernetes 和 Vault 的好方法。循序漸進確保你可以專註於理解「為什麽」,同時學習「如何」。
在 Kubernetes 中建立 Vault Server
如你所知,Kubernetes 預設 secret 物件只是 base64 編碼的。你需要一個好的金鑰管理工具和工作流來管理生產使用案例的金鑰儲存和檢索。
Hashicorp Vault 是最好的開源秘密管理工具之一,它與 Kubernetes 具有良好的整合來儲存和檢索秘密。
Vault Kubernetes 清單
本指南中使用的所有 Kubernetes YAML 清單都托管在 Github 上。複制儲存庫以供參考和實施。
git clone https://github.com/scriptcamp/kubernetes-vault.git
Vault RBAC 設定
在開始設定之前,我想先了解一下我們將在此 Vault 設定中使用的一些基本 Kubernetes 物件。
ClusterRoles 中:Kubernetes ClusterRole 是已分配某些特殊許可權的實體。
ServiceAccount:Kubernetes ServiceAccounts 是分配給 Pod 等實體的身份,以便它們能夠使用角色的許可權與 Kubernetes API 進行互動。
ClusterRoleBindings:ClusterRoleBindings 是向帳戶提供角色的實體,即它們向服務帳戶授予許可權。
Vault 伺服器需要某些額外的 Kubernetes 許可權才能執行其操作。因此,需要透過 ClusterRoleBinding 將 ClusterRole(具有適當的許可權)分配給 ServiceAccount。
預設情況下,Kubernetes 有一個使用所需許可權建立的 ClusterRole,即「 system:auth-delegator 」,因此在這種情況下不需要再次建立它。需要建立服務帳戶和角色繫結
讓我們為 Vault 建立所需的 RBAC。
將以下清單另存為rbac.yaml
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: vault
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: vault-server-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:auth-delegator
subjects:
- kind: ServiceAccount
name: vault
namespace: default
建立服務帳戶和 ClusterRolebinding。
kubectl apply -f rbac.yaml
建立 Vault ConfigMap
Kubernetes 中的 ConfigMap 允許我們在容器上掛載檔,而無需更改 Dockerfile 或重新構建容器映像。
在必須透過檔修改或建立配置的情況下,此功能非常有用。
Vault 需要一個具有適當參數的配置檔來啟動其伺服器。
將以下清單另存為configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: vault-config
namespace: default
data:
extraconfig-from-values.hcl: |-
disable_mlock = true
ui = true
listener "tcp" {
tls_disable = 1
address = "[::]:8200"
cluster_address = "[::]:8201"
}
storage "file" {
path = "/vault/data"
}
建立 configmap
kubectl apply -f configmap.yaml
了解 config map 中參數背後的想法很重要。下面將對其中一些進行解釋。有關更詳盡的選項列表: – 請參閱官方vault文件。
disable_mlock:執行 mlock syscall 可防止記憶體交換到
disk: 此選項將禁止伺服器執行 mlock 系統呼叫。
使用者介面: 啟用內建 Web UI。
listener: 配置 Vault 監聽 API 請求的方式。
storage: 配置儲存 Vault 數據的儲存後端。
部署 Vault 服務
Kubernetes 中的服務是 Pod 用來相互通訊的物件。 型別服務通常用於 Pod 間通訊。
有兩種型別的 ClusterIP 服務ClusterIP
headless services
services 普通的 Kubernetes 服務充當負載均衡器,並遵循迴圈邏輯來分配負載。Headless 服務的作用與負載均衡器不同。
此外,普通服務由 Kubernetes 分配 IP,而 Headless 服務則不是。
對於 Vault 伺服器,我們將建立一個供內部使用的 Headless 服務。當我們將檔庫擴充套件到多個副本時,這將非常有用。
將為 UI 建立一個非 Headless 服務,因為我們希望在存取 UI 時對副本的請求進行負載均衡。
Vault 在埠 8200 上公開其 UI。我們將使用 NodePort 型別的非無頭服務,因為我們希望從 Kubernetes 集群外部存取此終端節點。
將以下清單保存為services.yaml 。它具有服務和無頭服務定義。
---
# Service for Vault Server
apiVersion: v1
kind: Service
metadata:
name: vault
namespace: default
labels:
app.kubernetes.io/name: vault
app.kubernetes.io/instance: vault
annotations:
spec:
type: NodePort
publishNotReadyAddresses: true
ports:
- name: http
port: 8200
targetPort: 8200
nodePort: 32000
- name: https-internal
port: 8201
targetPort: 8201
selector:
app.kubernetes.io/name: vault
app.kubernetes.io/instance: vault
component: server
---
# Headless Service
apiVersion: v1
kind: Service
metadata:
name: vault-internal
namespace: default
labels:
app.kubernetes.io/name: vault
app.kubernetes.io/instance: vault
annotations:
spec:
clusterIP: None
publishNotReadyAddresses: true
ports:
- name: "http"
port: 8200
targetPort: 8200
- name: https-internal
port: 8201
targetPort: 8201
selector:
app.kubernetes.io/name: vault
app.kubernetes.io/instance: vault
component: server
建立服務。
kubectl apply -f services.yaml
了解 publishNotReadyAddresses:
它是 Headless 服務清單檔中的配置選項。
預設情況下,僅當 Pod 處於「就緒」狀態時,Kubernetes 才會在服務下包含 Pod。
「 publishNotReadyAddresses 」選項透過包含可能處於或未處於就緒狀態的 Pod 來更改此行為。您可以透過執行「kubectl get pods」檢視處於就緒狀態的 pod 列表。
需要 Vault StatefulSet
StatefulSet 是用於管理有狀態應用程式的 Kubernetes 物件。
對於此用例,它比部署更可取,因為它為這些 Pod 的排序和唯一性提供了保證,即使用有狀態集可以更好地管理卷。
本部份對於更深入地了解檔庫至關重要。
作為初學者,理解為什麽我們想要部署 Statefulset 而不是 Deployment 是很重要的。畢竟,我們的重點是理解 「為什麽 」以及學習 「如何」。
為什麽我們需要 Statefulset?
Vault 是一個有狀態的應用程式,即它將數據(如配置、金鑰、Vault 操作的後設資料)儲存在卷中。如果數據儲存在記憶體中,則 Pod 重新開機後數據將被擦除。
此外,在案件量增加的情況下,Vault 可能必須擴充套件到多個 pod。
所有這些操作都必須以這樣一種方式完成,以便在 Vault Pod 之間保持數據一致性,例如 、 、 。vault-0vault-1vault-2
我們如何在 Kubernetes 中實作這一目標?請思考,然後提前閱讀!
Vault 在其所有 Pod 之間實作數據的連續復制。因此,當數據寫入時,它會被復制到 . 從 復制數據。等等......
這裏要了解的是需要知道在哪裏尋找 . 否則,復制將如何進行?vault-0vault-1vault-2vault-3vault-1vault-0
它如何知道從何處獲取數據以進行復制過程?
vault-1 如何知道在何處尋找 vault-0?
vault-2 如何知道在哪裏尋找 vault-1?
現在讓我們嘗試回答這些問題。
在部署和有狀態集的情況下,Pod 總是被分配一個唯一的名稱,可以用來尋找 Pod。
在部署的情況下,Pod 總是被分配一個唯一的名稱,但這個唯一的名稱在刪除並重新建立 Pod 後會發生變化。因此,辨識任何 Pod 都沒有用。
Case of deployments:
name of pod initially: vault-7c6c5fd47c-fkvpf
name of pod after it gets deleted & recreated: vault-c5f7c6dfk4-7pfcv
Here, pod name got changed.
在有狀態集的情況下 - 每個 Pod 都被分配了一個唯一的名稱,即使 Pod 被刪除並重新建立,這個唯一的名稱也會保留。
Case of statefulsets:
name of pod initially: vault-0
name of pod after it gets deleted & recreated: vault-0
Here, pod name remained the same.
這就是為什麽我們想在這裏使用有狀態的集合,即我們可以毫無差異地存取任何 pod。
超越vault
如果你探索一下,這些狀態集和部署的概念並不是Vault獨有的 - 你會發現許多流行的Kubernetes工具,如Elasticsearch、Postgresql,由於相同的邏輯,使用有狀態集而不是部署。
部署 Vault StatefulSet
首先,讓我們建立 Statefulset。我還添加了對 vault Statefulset 的解釋。
將以下清單另存為statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: vault
namespace: default
labels:
app.kubernetes.io/name: vault
app.kubernetes.io/instance: vault
spec:
serviceName: vault-internal
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: vault
app.kubernetes.io/instance: vault
component: server
template:
metadata:
labels:
app.kubernetes.io/name: vault
app.kubernetes.io/instance: vault
component: server
spec:
serviceAccountName: vault
securityContext:
runAsNonRoot: true
runAsGroup: 1000
runAsUser: 100
fsGroup: 1000
volumes:
- name: config
configMap:
name: vault-config
- name: home
emptyDir: {}
containers:
- name: vault
image: hashicorp/vault:1.8.0
imagePullPolicy: IfNotPresent
command:
- "/bin/sh"
- "-ec"
args:
- |
cp /vault/config/extraconfig-from-values.hcl /tmp/storageconfig.hcl;
[ -n "${HOST_IP}" ] && sed -Ei "s|HOST_IP|${HOST_IP?}|g" /tmp/storageconfig.hcl;
[ -n "${POD_IP}" ] && sed -Ei "s|POD_IP|${POD_IP?}|g" /tmp/storageconfig.hcl;
[ -n "${HOSTNAME}" ] && sed -Ei "s|HOSTNAME|${HOSTNAME?}|g" /tmp/storageconfig.hcl;
[ -n "${API_ADDR}" ] && sed -Ei "s|API_ADDR|${API_ADDR?}|g" /tmp/storageconfig.hcl;
[ -n "${TRANSIT_ADDR}" ] && sed -Ei "s|TRANSIT_ADDR|${TRANSIT_ADDR?}|g" /tmp/storageconfig.hcl;
[ -n "${RAFT_ADDR}" ] && sed -Ei "s|RAFT_ADDR|${RAFT_ADDR?}|g" /tmp/storageconfig.hcl;
/usr/local/bin/docker-entrypoint.sh vault server -config=/tmp/storageconfig.hcl
securityContext:
allowPrivilegeEscalation: false
env:
- name: HOSTNAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: VAULT_ADDR
value: "http://127.0.0.1:8200"
- name: VAULT_API_ADDR
value: "http://$(POD_IP):8200"
- name: SKIP_CHOWN
value: "true"
- name: SKIP_SETCAP
value: "true"
- name: VAULT_CLUSTER_ADDR
value: "https://$(HOSTNAME).vault-internal:8201"
- name: HOME
value: "/home/vault"
volumeMounts:
- name: data
mountPath: /vault/data
- name: config
mountPath: /vault/config
- name: home
mountPath: /home/vault
ports:
- containerPort: 8200
name: http
- containerPort: 8201
name: https-internal
- containerPort: 8202
name: http-rep
readinessProbe:
exec:
command: ["/bin/sh", "-ec", "vault status -tls-skip-verify"]
failureThreshold: 2
initialDelaySeconds: 5
periodSeconds: 5
successThreshold: 1
timeoutSeconds: 3
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
建立 Statefulset。
kubectl apply -f statefulset.yaml
Vault 的 Statefulset YAML 有很多元件,比如 configmap 掛載、安全上下文、探針等。
註意:Vault 目前仍處於 non ready 狀態。它需要解封才能進入就緒狀態。隨意跳到下一部份來解封 vault。對於生產用例,應使用 Auto Unseal 選項。
讓我們更深入地了解每個部份的作用。
配置: Vault 的額外配置檔正在掛載,然後被復制到行程啟動時使用它的位置。/vault/config/extraconfig-from-values.hcl/tmp/storageconfig.hcl
cp /vault/config/extraconfig-from-values.hcl /tmp/storageconfig.hcl;
/usr/local/bin/docker-entrypoint.sh vault server -config=/tmp/storageconfig.hcl
ServiceAccount 的 ServiceAccount 中: 由於我們希望 Vault 伺服器具有所需的 'system:auth-delegator' 許可權。已為容器組分配服務帳戶。
serviceAccountName: vault
SecurityContext 的使用特權行程執行 Pod 會帶來安全風險。這是因為在 Pod 上執行具有 root 的行程與在主機節點上執行具有 root 的行程相同。
理想情況下,Pod 的執行應牢記容器和主機節點之間的隔離。因此,此處已指示 Pod 以非 root 使用者身份執行。
securityContext:
runAsNonRoot: true
runAsGroup: 1000
runAsUser: 100
fsGroup: 1000
探針:探針可確保 Vault 不會因任何錯誤而陷入迴圈,並且可以在出現意外錯誤時自動重新啟動。
readinessProbe:
exec:
command: ["/bin/sh", "-ec", "vault status -tls-skip-verify"]
failureThreshold: 2
initialDelaySeconds: 5
periodSeconds: 5
successThreshold: 1
timeoutSeconds: 3
VolumeClaimTemplates 的 VolumeClaimTemplates 中: 一個樣版,有狀態集可以透過該樣版為副本建立卷。
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
Vault 設定已完成。下一步是解封並初始化檔庫。
解封並初始化vault
初始化是 Vault 的儲存開始準備接收數據的過程。
Vault 生成一個記憶體中的主金鑰,並套用 Shamir 的秘密共享演算法將該主金鑰分解為多個金鑰。這些金鑰稱為 「解封金鑰」。
因此,要初始化 vault,首先,我們需要使用 unseal keys 解封 vault。
步驟1:首先,我們正在建立令牌和金鑰以開始使用vault。
註意:如果你的系統上沒有安裝 jq,請安裝 jq。
kubectl exec vault-0 -- vault operator init -key-shares=1 -key-threshold=1 -format=json > keys.json
VAULT_UNSEAL_KEY=$(cat keys.json | jq -r ".unseal_keys_b64[]")
echo$VAULT_UNSEAL_KEY
VAULT_ROOT_KEY=$(cat keys.json | jq -r ".root_token")
echo$VAULT_ROOT_KEY
步驟2: Unseal 是檔庫可以構造解密儲存在其中的數據所需的金鑰的狀態。
kubectl exec vault-0 -- vault operator unseal $VAULT_UNSEAL_KEY
重要提示:如果 Vault Pod 重新啟動,它會自動被密封。你需要使用上述 unseal 命令再次解封 vault。此外,如果你執行多個 replicaset,則需要登入所有 Pod 並執行 unseal 命令。對於生產使用案例,有可用的自動解封選項。
登入和存取Vault UI
你可以使用 UI 和 CLI 登入到 Vault。
註意:在生產設定中,Vault UI 和登入將使用 LDAP、okta 或其他遵循標準安全準則的安全機制進行保護。
以下命令將遠端執行 vault log 命令,因為我們在環境變量中本地設定了該命令。VAULT_ROOT_KEY
kubectl exec vault-0 -- vault login $VAULT_ROOT_KEY 上述命令將顯示一個令牌。保存令牌,因為你需要它來登入保管庫 UI。
由於我們在 nodeport 上建立了服務,因此我們將能夠使用埠 32000 上的任何節點 IP 存取保管庫 UI。你可以使用保存的 Token 登入 UI。32000
例如
http://33.143.55.228:32000
建立檔庫金鑰
我們可以使用 CLI 和 UI 建立 secret。我們將使用 CLI 建立金鑰。
要使用 Vault CLI,我們需要 exec 進入 Vault Pod。
kubectl exec -it vault-0 -- /bin/sh
建立金鑰
有多個秘密引擎(資料庫、Consul、AWS 等)。請參閱官方文件以了解有關支持的 Secret Engine 的更多資訊。
在這裏,我們使用的是鍵值引擎 v2。它具有保留相同金鑰的多個版本的高級功能。v1 一次只能管理一個版本。
讓我們啟用 Key value engine。
vault secrets enable -version=2 -path="demo-app" kv
以鍵值格式建立 secret 並將其列出。id(鍵)為 ,secret(值) 為 。path 為namedevopscube demo-app/user01
vault kv put demo-app/user01 name=devopscube
vault kv get demo-app/user01
Kubernetes vault create secret 作為鍵值對。
建立策略
預設情況下,金鑰路徑啟用了拒絕策略。我們需要顯式添加一個策略來讀/寫/刪除 secret。
以下策略規定允許實體對儲存在 「」 下的金鑰執行讀取操作。執行它以建立策略demo-app
vault policy write demo-policy - <<EOH
path "demo-app/*" {
capabilities = ["read"]
}
EOH
你可以列出並驗證策略。
vault policy list
啟用 Vault Kubernetes 身份驗證方法
要使 Kubernetes Pod 與 Vault 互動並獲取金鑰,它需要一個 Vault 令牌。使用 Pod 服務帳戶的 kubernetes auth 方法使 Pod 可以輕松地從保管庫中檢索金鑰。
這樣,任何被分配了 「」 作為服務帳戶的 Pod 都將能夠讀取這些 secret,而無需任何 Vault 令牌。vault
讓我們啟用 kubernetes auth 方法。
vault auth enable kubernetes
你也可以從 Vault UI 檢視和啟用身份驗證方法。
我們已將具有 ClusterRole 的服務帳戶附加到檔庫 Statefulset。以下命令配置服務帳戶令牌,使 Vault 伺服器能夠使用令牌、Kubernetes URL 和集群 API CA 證書對 Kubernetes 進行 API 呼叫。 是 Pod 內部返回內部 API 端點的 env 變量。KUBERNETES_PORT_443_TCP_ADDR
vault write auth/kubernetes/config token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443" kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
現在,我們必須建立一個 vault approle,用於繫結 Kubernetes 服務帳戶、名稱空間和檔庫策略。這樣,Vault Server 就知道特定服務帳戶是否有權讀取儲存的 secret。
讓我們建立一個服務帳戶,應用程式 Pod 可以使用該帳戶從檔庫中檢索金鑰。
kubectl create serviceaccount vault-auth
讓我們建立一個名為 vault 的 vault 並繫結一個名稱空間中名為 的服務帳戶。此外,我們還附加了我們建立的具有金鑰讀取存取許可權的 Secret。approlewebappvault-auth defaultdemo-policy
vault write auth/kubernetes/role/webapp \
bound_service_account_names=vault-auth \
bound_service_account_namespaces=default \
policies=demo-policy \
ttl=72h
現在,預設名稱空間中任何具有服務帳戶的 Pod 都可以在 demo-policy 下請求 Secret。vault-auth
使用服務帳戶獲取儲存在 Vault 中的 Secret
目標是 Pod 應該能夠從 Vault 中獲取 Secret,而無需向其提供任何 Vault Token,即它應該使用 service account token 向 Vault 發出請求。
以下是它的工作原理。
來自 Pod 的 JWT 令牌(服務帳戶令牌)將傳遞到 Vault 伺服器。
Vault 伺服器請求 Kubernetes API 伺服器獲取附加到 JWT 令牌的服務帳戶和名稱空間。
Kubernetes API 伺服器返回名稱空間和服務帳戶詳細資訊。
Vault 伺服器驗證服務帳戶是否有權使用附加的策略讀取金鑰。
驗證後,Vault 伺服器將返回 Vault 令牌。
在另一個 API 呼叫中,檔庫令牌使用金鑰路徑傳遞以檢索金鑰。 為了演示這一點,首先,我們將在名稱空間中部署一個名為 service account 的 pod。vault-clientvault-authdefault
將清單另存為pod.yaml
---
apiVersion: v1
kind: Pod
metadata:
name: vault-client
namespace: default
spec:
containers:
- image: nginx:latest
name: nginx
serviceAccountName: vault-auth
部署 Pod。
kubectl apply -f pod.yaml
現在,獲取 Pod 的 exec 會話,以便我們可以發出 API 請求,並檢視它是否能夠向 Vault 伺服器進行身份驗證並檢索所需的金鑰。
kubectl exec -it vault-client /bin/bash
請按照以下步驟操作。
步驟1:使用服務帳戶令牌 (JWT) 向 Vault 伺服器發出 API 請求以獲取客戶端令牌。
將服務帳戶令牌保存到變量jwt_token
jwt_token=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
使用 curl 進行 API 呼叫。替換為你的保管庫 URL。http://35.193.55.248:32000
curl --request POST \
--data '{"jwt": "'$jwt_token'", "role": "webapp"}' \
http://35.193.55.248:32000/v1/auth/kubernetes/login
上述 API 請求將返回一個包含 .使用此令牌,可以讀取 secret。client_token
JSON (美化) 輸出範例:
{
"request_id": "87440e92-cbe9-8357-059d-c4ecdfd58e84",
"lease_id": "",
"renewable": false,
"lease_duration": 0,
"data": null,
"wrap_info": null,
"warnings": null,
"auth": {
"client_token": "s.r2kKKaYnGR48CR9aWvoV8skx",
"accessor": "bqqCCmrxS58TiTyafH9udHm7",
"policies": [
"default",
"demo-policy"
],
"token_policies": [
"default",
"demo-policy"
]
}
}
現在,我們將使用 client_token 進行 API 呼叫並獲取 下儲存的金鑰。將 token 和 vault URL 替換為相關值。demo-app
curl -H "X-Vault-Token: s.bSRl7TNajYxWvA7WiLdTQS7Z" \
-H "X-Vault-Namespace: vault" \
-X GET http://35.193.55.248:32000/v1/demo-app/data/user01?version=1
上述 API 請求將返回一個 JSON,其中包含 'data' 下的金鑰。
樣本輸出(美化)
{
"request_id": "d2a2fc9c-6eca-9a12-a4a8-8799230e9449",
"lease_id": "",
"renewable": false,
"lease_duration": 0,
"data": {
"data": {
"name": "devopscube"
}
}
}
可能的錯誤和故障排除
如果你刪除有狀態集並再次重新部署檔庫,請不要再次執行 init 命令。它可能會引發以下錯誤。
Error initializing: Error making API request.
URL: PUT http://127.0.0.1:8200/v1/sys/init
Code: 400. Errors:
發生這種情況是因為,根據設計,當你刪除 Statefulset 時,附加到 Pod 的卷不會被刪除。這樣你就有機會復制數據。
因此,PVC 將具有初始化的配置。因此,你只需使用現有 key.json並使用 secret 令牌解封檔庫,而無需初始化。或者,你可以完全刪除 PVC 並使用新的 PVC 重新部署檔庫。
結論
到目前為止,你已經學會了在 Kubernetes 中設定、配置和使用 vault。這是一個很好的起點,你可以探索更多概念和工作流程。
在實施 Vault 生產時,需要遵循許多註意事項和標準安全實踐。