Kubernetes 的 secret 並不是真正的 secret

rife發表於2023-03-26

引言

Kubernetes 已經成為現代軟體基礎設施中不可或缺的一部分。因此,管理 Kubernetes 上的敏感資料也是現代軟體工程的一個重要方面,這樣您就可以將安全性重新置於 DevSecOps 中。Kubernetes 提供了一種使用 Secret 物件儲存敏感資料的方法。雖然總比沒有好,但它並不是真正的加密,因為它只是 base64 編碼的字串,任何有權訪問叢集或程式碼的人都可以對其進行解碼。

注意: 預設情況下,Kubernetes Secrets 未加密儲存在 API 伺服器的底層資料儲存 (etcd) 中。具有 API 訪問許可權的任何人都可以檢索或修改 Secret,任何具有 etcd 訪問許可權的人也可以。此外,任何有權在名稱空間中建立 Pod 的人都可以使用該訪問許可權來讀取該名稱空間中的任何 Secret;這包括間接訪問,例如建立 Deployment 的能力。— Kubernetes 文件

使用正確的 RBAC 配置和保護 API 伺服器可以解決從叢集讀取 secret 的問題,瞭解有關 RBAC 和叢集 API 安全性的更多資訊請檢視如何使用最佳實踐保護您的 Kubernetes 叢集。保護原始碼中的的 secret 是更大的問題。每個有權訪問包含這些 secret 的儲存庫的人也可以解碼它們。這使得在 Git 中管理 Kubernetes secret 變得非常棘手。

讓我們看看如何使用更安全的方式設定 secret :

  • Sealed Secrets
  • External Secrets Operator
  • Secrets Store CSI driver

您需要一個 Kubernetes 叢集來執行示例。我使用 k3d 建立了一個本地叢集。您也可以使用 kind 或 minikube 。


Sealed Secrets

Sealed Secrets 是一個開源的 Kubernetes 控制器和來自 Bitnami 的客戶端 CLI 工具,旨在使用非對稱密碼加密解決“在 Git 中儲存 secret ”問題的一部分。具有 RBAC 配置的 Sealed Secrets 防止非管理員讀取 secret 是解決整個問題的絕佳解決方案。

它的工作原理如下:

  1. 使用公鑰和 kubeseal CLI 在開發人員機器上加密 secret 。這會將加密的 secret 編碼為 Kubernetes 自定義資源定義 (CRD)。
  2. 將 CRD 部署到目標叢集。
  3. Sealed Secret 控制器使用目標叢集上的私鑰對機密進行解密,以生成標準的 Kubernetes secret。

私鑰僅供叢集上的 Sealed Secrets 控制器使用,公鑰可供開發人員使用。這樣,只有叢集才能解密機密,而開發人員只能對其進行加密。

優點

  • 支援模板定義,以便可以將後設資料新增到未加密的 secret 中。例如,您可以使用模板定義為未加密的 secret 新增標籤和註釋。
  • 未加密的 secret 將由加密的 secret CRD 擁有,並在加密的 secret 更新時更新。
  • 預設情況下,證書每 30 天輪換一次,並且可以自定義。
  • secret 使用每個叢集、名稱空間和 secret 組合(私鑰+名稱空間名稱+ secret 名稱)的唯一金鑰進行加密,防止解密中出現任何漏洞。在加密過程中,可以使用 strict, namespace-wide, cluster-wide 來配置範圍。
  • 可用於管理叢集中的現有 secret。
  • 具有 VSCode 擴充套件,使其更易於使用。

缺點

  • 由於它將加密的 secret 解密為常規 secret ,如果您有權訪問叢集和名稱空間,您仍然可以解碼它們。
  • 需要為每個叢集環境重新加密,因為金鑰對對於每個叢集都是唯一的。

安裝

在叢集上安裝 controller,在本地機器上安裝 CLI。

  1. release 頁面下載 controller.yaml
  2. 執行 kubectl apply -f controller.yaml 將 controller 部署到叢集中。控制器將安裝到 kube-system 名稱空間下。
  3. 安裝 CLI,透過 brew install kubeseal 安裝,或者從 release 頁面下載。

使用

讓我們建立一個 sealed secret 。

  1. 建立一個 secret,透過命令 kubectl create secret 或者編寫 yaml 檔案,如下所示:
echo -n secretvalue | kubectl create secret generic mysecret \
  --dry-run=client \
  --from-file=foo=/dev/stdin -o yaml > my-secret.yaml

這將產生一個如下所示的 secret 定義;

# my-secret.yaml

apiVersion: v1
data:
  foo: c2VjcmV0dmFsdWU=
kind: Secret
metadata:
  creationTimestamp: null
  name: mysecret
  1. 使用 kubeseal CLI 加密 secret。這將使用從伺服器獲取的公鑰加密 secret 並生成加密的 secret 定義。現在可以丟棄 my-secret.yaml 檔案。您也可以下載公鑰並在本地離線使用。
kubeseal --format yaml < my-secret.yaml > my-sealed-secret.yaml

這將產生一個加密的 secret 定義,my-sealed-secret.yaml,如下所示;

# my-sealed-secret.yaml

apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  creationTimestamp: null
  name: mysecret
  namespace: default
spec:
  encryptedData:
    foo: AgA6a4AGzd7qzR8mTPqTPFNor8tTtT5...==
  template:
    metadata:
      creationTimestamp: null
      name: mysecret
      namespace: default

此檔案可以安全地提交到 Git 或與其他開發人員共享。

  1. 最後,您可以將其部署到要解封的叢集中。
kubectl apply -f my-sealed-secret.yaml
  1. 現在,您可以在叢集中看到未加密的 secret 。
kubectl describe secret mysecret

您可以像使用任何其他 Kubernetes 金鑰一樣在部署中使用此金鑰。


External Secrets Operator

Sealed Secrets 是保護 secret 的方式之一,但除此之外還有更好的方法。使用 External Secrets Operator (ESO) 和外部 secret 管理系統,如 HashiCorp VaultAWS Secrets ManagerGoogle Secrets ManagerAzure Key Vault。雖然設定起來有點複雜,但如果您使用雲提供商來託管您的 Kubernetes 叢集,這是一種更好的方法。ESO 支援許多這樣的 secret 管理器並監視外部 secret 儲存的變化,並使 Kubernetes secret 保持同步。

ESO 提供了四個 CRD 來管理 secret。ExternalSecretClusterExternalSecret CRD 定義需要獲取哪些資料以及如何轉換這些資料。SecretStoreClusterSecretStore CRD 定義了與外部 secret 儲存的連線細節。Cluster 字首的 CRD 表示作用範圍是叢集。

它的工作原理如下;

  1. 建立 SecretStoreCRD 以定義與外部機密儲存的連線詳細資訊。
  2. 在外部 secret 儲存中建立 secret 。
  3. 建立一個 ExternalSecretCRD 來定義需要從外部 secret 儲存中獲取的資料。
  4. 將 CRD 部署到目標叢集。
  5. ESO 控制器將從外部 secret 儲存中獲取資料並建立 Kubernetes secret 。

優點

  • secret 儲存在安全的外部 secret 管理器中,而不是程式碼儲存庫中。
  • 使 secret 與外部 secret 管理器保持同步。
  • 與許多外部 secret 管理者合作。
  • 可以在同一個叢集中使用多個 secret 儲存。
  • 提供用於監控的 Prometheus 指標。

缺點

  • 需要精心設定才能使用。
  • 建立一個 Kubernetes secret 物件,如果您有權訪問叢集和名稱空間,則可以對其進行解碼。
  • 依靠外部 secret 管理器及其訪問策略來確保安全。

安裝

可以使用以下命令透過 Helm 安裝 ESO :

helm repo add external-secrets https://charts.external-secrets.io

helm install external-secrets \
  external-secrets/external-secrets \
  --namespace external-secrets \
  --create-namespace

如果您想在 Helm release 中包含 ESO,請將 --set installCRDs=true 標誌新增到上述命令中。

讓我們看看如何將 ESO 與不同的 secret 管理器一起使用。

使用 HashiCorp Vault

HashiCorp Vault 是一個流行的 secret 管理器,提供不同的 secret 引擎。ESO 只能與 Vault 提供的 KV Secrets Engine 一起使用。Vault 在 HashiCorp 雲平臺 (HCP) 上提供了一個您可以自行管理的免費開源版本和一個帶有免費等級的託管版本。

確保您在本地 Vault 例項或 HCP cloud 中設定了鍵值 secret 儲存。您還可以使用 Vault Helm chart 將 Vault 部署到 Kubernetes 叢集

  1. 建立一個新的 SecretStore CRD,vault-backend.yaml,以定義與 Vault 的連線詳細資訊。
# vault-backend.yaml

apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: vault-backend
spec:
  provider:
    vault:
      server: 'YOUR_VAULT_ADDRESS'
      path: 'secret'
      version: 'v2'
      namespace: 'admin' # required for HCP Vault
      auth:
        # points to a secret that contains a vault token
        # https://www.vaultproject.io/docs/auth/token
        tokenSecretRef:
          name: 'vault-token'
          key: 'token'
  1. 建立一個 secret 資源來儲存 Vault token。使用具有對 Vault KV 儲存中的 secret/ 路徑具有讀取許可權的策略的令牌。
kubectl create secret generic vault-token \
  --dry-run=client \
  --from-literal=token=YOUR_VAULT_TOKEN
  1. 在 Vault 中建立一個 secret 。如果您使用的是 Vault CLI,則可以使用以下命令建立一個 secret 。確保您使用適當的策略從 CLI 登入到 vault 例項。
vault kv put secret/mysecret my-value=supersecret
  1. 建立一個 ExternalSecret CRD 來定義需要從 Vault 中獲取的資料。
# vault-secret.yaml

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: vault-example
spec:
  refreshInterval: '15s'
  secretStoreRef:
    name: vault-backend
    kind: SecretStore
  target:
    name: vault-example-sync
  data:
    - secretKey: secret-from-vault
      remoteRef:
        key: secret/mysecret
        property: my-value
  1. 將上述 CRD 應用到叢集,它應該使用從 Vault 獲取的資料建立一個名為 vault-example-sync 的 Kubernetes secret。
kubectl apply -f vault-backend.yaml
kubectl apply -f vault-secret.yaml

您可以使用 kubectl describe 命令檢視叢集中的 secret。

kubectl describe secret vault-example-sync

# output should have the below data
Name:         vault-example-sync
Namespace:    default
Labels:       <none>
Annotations:  reconcile.external-secrets.io/data-hash: ...

Type:  Opaque

Data
====
secret-from-vault:  16 bytes

如果您在建立 secret 時遇到問題,請檢查 ExternalSecret 資源描述輸出的 events 部分。

kubectl describe externalsecret vault-example

如果您看到許可權錯誤,請確保使用具有正確策略的令牌。

其他 secret managers

設定其他 secret 管理器與上述步驟類似。唯一的區別是 SecretStore CRD 和 ExternalSecret CRD 中的 remoteRef 部分。您可以在 ESO 文件中找到針對不同提供商的官方指南。


Secrets Store CSI Driver

Secrets Store CSI Driver 是一個原生的上游 Kubernetes 驅動程式,可用於從工作負載中抽象出 secret 的儲存位置。如果您想使用雲提供商的 secret 管理器而不將 secret 公開為 Kubernetes secret 物件,您可以使用 CSI 驅動程式將 secret 作為卷安裝在您的 pod 中。如果您使用雲提供商來託管您的 Kubernetes 叢集,這是一個很好的選擇。該驅動程式支援許多雲提供商,並且可以與不同的 secret 管理器一起使用。

Secrets Store CSI Driver 是一個 daemonset 守護程式,它與 secret 提供者通訊以檢索 SecretProviderClass 自定義資源中指定的 secret 。

它的工作原理如下;

  1. 建立一個 SecretProviderClassCRD 來定義從 secret 提供者獲取的 secret 的詳細資訊。
  2. 在 pod 的 volume spec 中引用 SecretProviderClass
  3. 驅動程式將從 secret 提供者那裡獲取 secret ,並在 pod 啟動期間將其作為 tmpfs 卷掛載到 pod 中。該卷也將在 pod 刪除後被刪除。

驅動程式還可以同步對 secret 的更改。該驅動程式目前支援 Vault、AWS、Azure 和 GCP 提供商。Secrets Store CSI Driver 也可以將加密資料同步為 Kubernetes secret,只需要在安裝期間明確啟用此行為。

優點

  • secret 儲存在安全的外部 secret 管理器中,而不是程式碼儲存庫中。
  • 使機密與外部機密管理器保持同步。它還支援 secret 的輪換。
  • 與所有主要的外部 secret 管理者合作。
  • 將金鑰作為卷安裝在 pod 中,因此它們不會作為 Kubernetes secret 公開。它也可以配置為建立 Kubernetes secret。

缺點

  • 需要精心設定才能使用,並且比 ESO 更復雜。
  • 使用比 ESO 更多的資源,因為它需要在每個節點上執行。
  • 依賴於外部 secret 儲存及其訪問策略來確保安全。

使用 Google Secret Manager provider

讓我們看看如何配置 driver 以使用 Google Secret Manager (GSM) 作為 secret provider。

確保您使用的是啟用了 Workload Identity 功能的 Google Kubernetes Engine (GKE) 叢集。Workload Identity 允許 GKE 叢集中的工作負載模擬身份和訪問管理 (IAM) 服務帳戶來訪問 Google Cloud 服務。您還需要為專案啟用 Kubernetes Engine API、Secret Manager API 和 Billing。如果未啟用, gcloud CLI 會提示您啟用這些 API。

可以使用以下 gcloud CLI 命令建立啟用了 Workload Identity 的新叢集。

export PROJECT_ID=<your gcp project>
gcloud config set project $PROJECT_ID

gcloud container clusters create hello-hipster \
  --workload-pool=$PROJECT_ID.svc.id.goog

安裝 Secrets Store CSI Driver

可以使用 Helm 命令在叢集上安裝 Secrets Store CSI 驅動程式:

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 \
    --namespace kube-system

這將在 kube-system 名稱空間下安裝驅動程式和 CRD 。您還需要將所需的 provider 安裝到叢集中。

安裝 GSM provider

讓我們將 GSM provider 安裝到叢集中:

kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/secrets-store-csi-driver-provider-gcp/main/deploy/provider-gcp-plugin.yaml

建立 secret

首先,您需要設定一個工作負載身份服務帳戶。

# Create a service account for workload identity
gcloud iam service-accounts create gke-workload

# Allow "default/mypod" to act as the new service account
gcloud iam service-accounts add-iam-policy-binding \
    --role roles/iam.workloadIdentityUser \
    --member "serviceAccount:$PROJECT_ID.svc.id.goog[default/mypodserviceaccount]" \
    gke-workload@$PROJECT_ID.iam.gserviceaccount.com

現在讓我們建立一個該服務帳戶可以訪問的金鑰。

# Create a secret with 1 active version
echo "mysupersecret" > secret.data
gcloud secrets create testsecret --replication-policy=automatic --data-file=secret.data
rm secret.data

# grant the new service account permission to access the secret
gcloud secrets add-iam-policy-binding testsecret \
    --member=serviceAccount:gke-workload@$PROJECT_ID.iam.gserviceaccount.com \
    --role=roles/secretmanager.secretAccessor

現在您可以建立一個 SecretProviderClass 資源,用於從 GSM 獲取金鑰。請記住將 $PROJECT_ID 替換為您的 GCP 專案 ID。

# secret-provider-class.yaml

apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: app-secrets
spec:
  provider: gcp
  parameters:
    secrets: |
      - resourceName: "projects/$PROJECT_ID/secrets/testsecret/versions/latest"
        path: "good1.txt"
      - resourceName: "projects/$PROJECT_ID/secrets/testsecret/versions/latest"
        path: "good2.txt"

建立一個 Pod

現在您可以建立一個 pod 去使用該 SecretProviderClass 資源從 GSM 獲取金鑰。請記住將 $PROJECT_ID 替換為您的 GCP 專案 ID。

# my-pod.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: mypodserviceaccount
  namespace: default
  annotations:
    iam.gke.io/gcp-service-account: gke-workload@$PROJECT_ID.iam.gserviceaccount.com
---
apiVersion: v1
kind: Pod
metadata:
  name: mypod
  namespace: default
spec:
  serviceAccountName: mypodserviceaccount
  containers:
    - image: gcr.io/google.com/cloudsdktool/cloud-sdk:slim
      imagePullPolicy: IfNotPresent
      name: mypod
      resources:
        requests:
          cpu: 100m
      stdin: true
      stdinOnce: true
      terminationMessagePath: /dev/termination-log
      terminationMessagePolicy: File
      tty: true
      volumeMounts:
        - mountPath: '/var/secrets'
          name: mysecret
  volumes:
    - name: mysecret
      csi:
        driver: secrets-store.csi.k8s.io
        readOnly: true
        volumeAttributes:
          secretProviderClass: 'app-secrets'

將上述資源應用到叢集中。

kubectl apply -f secret-provider-class.yaml
kubectl apply -f my-pod.yaml

等待 pod 啟動,然後 exec 進入 pod 檢視掛載檔案的內容。

kubectl exec -it mypod /bin/bash
# execute the below command in the pod to see the contents of the mounted secret file
root@mypod:/# cat /var/secrets/good1.txt

其他 secret 管理器

您可以找到服務提供商的類似指南:AWS CSI providerAzure CSI providerVault CSI provider


結論

Sealed Secrets 是小型團隊和專案在 Git 中保護 secret 的絕佳解決方案。對於較大的團隊和專案,External Secrets OperatorSecrets Store CSI Driver 是安全管理金鑰的更好的解決方案。External Secrets Operator 可以與許多 secret 管理系統一起使用,並不限於上述系統。當然,這應該與 RBAC 一起使用,以防止非管理員讀取叢集中的 secret 。Secrets Store CSI Driver 可能比 ESO 涉及更多,但它是一個更原生的解決方案。

相關文章