跳到內容
開源

從外部機密庫同步機密

安裝 External Secrets Operator,連接 OpenBao、Vault、1Password 或 AWS Secrets Manager,將機密同步到叢集。

資料庫密碼放在 OpenBao、1Password 或 AWS Secrets Manager 時,External Secrets Operator 會把它複製成叢集裡的 Secret,服務照一般機密讀取。之後在機密庫換密碼,它會依設定的間隔更新 Secret。

  • 叢集要先裝好 cert-manager,它替 webhook 簽發憑證。

  • 準備外部機密庫和唯讀認證資訊。下方範例使用 shop Namespace。

  • 找出 API server 在叢集網路上的 IPv4 位址。RKE2 就是 server 節點的 IP:

    kubectl get endpointslices -n default -l kubernetes.io/service-name=kubernetes \
    -o jsonpath='{.items[0].endpoints[*].addresses[0]}'

只有一台 server 時,kube-api-cidr 填 <ip>/32,有多台時填涵蓋這些位址的 CIDR。

打開 叢集 → 元件,在 目錄 找到 External Secrets Operator,按 安裝。它要填 kube-api-cidr,所以只能 自己執行:填好值,再執行產生的內容,見檢查並安裝叢集元件。

裝好後不會建立任何 SecretStore,在你加上之前,它不會連到任何機密庫。Catalog 安裝 chart 2.12.0,映像檔 ghcr.io/external-secrets/external-secrets:v2.12.0 以 digest 釘選。

external-secrets Namespace 預設拒絕所有流量,只開放 controller 和 webhook 連 API server(TCP 443、6443)、controller 查 DNS(kube-system 的 kube-dns,53),以及 API server 呼叫 webhook(TCP 10250)。controller 要連到機密庫,得再加一條 NetworkPolicy。

OpenBao 在 openbao Namespace、使用連接埠 8200 時,套用:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: to-openbao
namespace: external-secrets
spec:
podSelector:
matchLabels: {app.kubernetes.io/name: external-secrets}
policyTypes: [Egress]
egress:
- to:
- namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: openbao}}
podSelector: {matchLabels: {app.kubernetes.io/name: openbao}}
ports: [{protocol: TCP, port: 8200}]

以下讓 shop Namespace 的 shop-secrets ServiceAccount 用 Kubernetes 驗證登入 OpenBao,只能讀 secret 底下 shop/ 開頭的機密。先在 OpenBao 執行:

bao auth enable kubernetes
bao write auth/kubernetes/config kubernetes_host=https://kubernetes.default.svc
bao secrets enable -path=secret kv-v2
bao policy write shop-read - <<'EOF2'
path "secret/data/shop/*" { capabilities = ["read"] }
EOF2
bao write auth/kubernetes/role/shop bound_service_account_names=shop-secrets \
bound_service_account_namespaces=shop policies=shop-read ttl=1h

接著在叢集套用 ServiceAccount 和 SecretStore:

apiVersion: v1
kind: ServiceAccount
metadata:
name: shop-secrets
namespace: shop
---
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
name: openbao
namespace: shop
spec:
provider:
openBao:
server: http://openbao.openbao.svc:8200
path: secret
version: v2
auth:
kubernetes:
path: kubernetes
role: shop
serviceAccountRef:
name: shop-secrets

使用 HashiCorp Vault 時,provider 改為 vault、CLI 改用 vault,並把 auth.kubernetes 下的 path 欄位改成 mountPath。機密庫在叢集外時,使用 HTTPS 和 caProvider。

在 1Password 建立只能讀取 shop 保管庫的 service account,把它的權杖存進 Secret。onepasswordSDK 直接用這個權杖,不需要架 Connect server:

kubectl -n shop create secret generic onepassword-token --from-literal=token='<service account token>'
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
name: onepassword
namespace: shop
spec:
provider:
onepasswordSDK:
vault: shop
auth:
serviceAccountSecretRef:
name: onepassword-token
key: token

ExternalSecret 的 remoteRef.key 寫成 <項目>/<欄位>,例如 db/password。已經有 1Password Connect server 時,改用 onepassword provider。

建立一個 IAM 使用者,只對這個 Namespace 要用的機密給 secretsmanager:GetSecretValue 和 secretsmanager:DescribeSecret,再把它的存取金鑰存進 Secret:

kubectl -n shop create secret generic aws-sm --from-literal=access-key-id='<id>' --from-literal=secret-access-key='<secret>'
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
name: aws
namespace: shop
spec:
provider:
aws:
service: SecretsManager
region: ap-northeast-1
auth:
secretRef:
accessKeyIDSecretRef: {name: aws-sm, key: access-key-id}
secretAccessKeySecretRef: {name: aws-sm, key: secret-access-key}

以下範例從 openbao SecretStore 讀取 shop/db 的 password,同步成 shop Namespace 裡的 db Secret:

apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: db
namespace: shop
spec:
refreshInterval: 1h
secretStoreRef: {kind: SecretStore, name: openbao}
target: {name: db}
data:
- secretKey: password
remoteRef: {key: shop/db, property: password}

用其他機密庫時,secretStoreRef.name 換成對應的 SecretStore,remoteRef 依機密庫的格式填,例如 1Password 寫 {key: db/password}。

執行 kubectl -n shop get externalsecret db,確認 READY 是 True、STATUS 是 SecretSynced。之後 db Secret 就會出現,照一般機密給服務使用,見設定環境變數。

在 Zeabur 專案的 物件 頁點開這個 ExternalSecret,狀況 的 Ready 是 True,原因是 SecretSynced,也能確認同步成功。

機密庫裡的變更會在 refreshInterval 內寫進 Secret。服務用環境變數讀機密時,要重新啟動才會拿到新值。

  • controller 能讀寫整個叢集的 Secret,也能替任何 ServiceAccount 建立權杖。external-secrets Namespace 只開放給管理員。
  • Chart 把 SecretStore 和 ExternalSecret 的寫入權限加進 Kubernetes 內建的 edit、admin 角色。Zeabur 的開發者和檢視者角色沒有這些權限,要由管理員建立。
  • 每個 Namespace 用自己的 SecretStore,認證資訊也只能讀這個 Namespace 要用的機密。ClusterSecretStore 開放給所有 Namespace,能建立 ExternalSecret 的人就讀得到它的認證資訊能讀的每個機密。要用時設定 spec.conditions,只列出可以用它的 Namespace。
  • 給機密庫的認證資訊只開讀取權限。PushSecret 會把叢集裡的 Secret 寫回機密庫,覆蓋原本的值。
  • 同步後的值是一般 Kubernetes Secret,適用相同的 etcd 加密、RBAC 和稽核紀錄,見安全性。