使用 KMS 提供程式進行資料加密

本頁面展示瞭如何配置金鑰管理服務(KMS)提供程式和外掛以啟用 Secret 資料加密。在 Kubernetes 1.34 中,存在兩個版本的 KMS 靜態加密。如果可行,應使用 KMS v2,因為 KMS v1 已棄用(自 Kubernetes v1.28 起)並在預設情況下停用(自 Kubernetes v1.29 起)。KMS v2 提供了比 KMS v1 顯著更好的效能特性。

準備工作

你需要有一個 Kubernetes 叢集,並且 kubectl 命令列工具已配置為與你的叢集通訊。建議在至少有兩個不作為控制平面主機的節點叢集上執行本教程。如果你還沒有叢集,可以使用 minikube 建立一個,或者使用這些 Kubernetes 演練場之一

所需的 Kubernetes 版本取決於你選擇的 KMS API 版本。Kubernetes 建議使用 KMS v2。

  • 如果你選擇 KMS API v1 以支援 v1.27 之前的叢集,或者你有一個僅支援 KMS v1 的舊版 KMS 外掛,任何受支援的 Kubernetes 版本都將適用。此 API 自 Kubernetes v1.28 起已棄用。Kubernetes 不建議使用此 API。

要檢查版本,請輸入 kubectl version

KMS v1

特性狀態: Kubernetes v1.28 [已棄用]
  • 需要 Kubernetes 1.10.0 或更高版本

  • 對於 1.29 及更高版本,KMS 的 v1 實現預設停用。要啟用此功能,請設定 --feature-gates=KMSv1=true 來配置 KMS v1 提供程式。

  • 你的叢集必須使用 etcd v3 或更高版本。

KMS v2

特性狀態: Kubernetes v1.29 [stable]
  • 你的叢集必須使用 etcd v3 或更高版本。

KMS 加密和每個物件的加密金鑰

KMS 加密提供程式使用信封加密方案來加密 etcd 中的資料。資料使用資料加密金鑰 (DEK) 進行加密。DEK 使用儲存在遠端 KMS 中的金鑰加密金鑰 (KEK) 進行加密和管理。

如果你使用(已棄用的)KMS v1 實現,每次加密都會生成一個新的 DEK。

使用 KMS v2,**每次加密**都會生成一個新的 DEK:API 伺服器使用**金鑰派生函式**從秘密種子和一些隨機資料生成一次性資料加密金鑰。當 KEK 輪換時,種子也會輪換(詳見下面的“理解 key_id 和金鑰輪換”部分)。

KMS 提供程式使用 gRPC 透過 UNIX 域套接字與特定的 KMS 外掛進行通訊。KMS 外掛作為一個 gRPC 伺服器實現,並部署在與 Kubernetes 控制平面相同的宿主機上,負責與遠端 KMS 的所有通訊。

配置 KMS 提供程式

要在 API 伺服器上配置 KMS 提供程式,請在加密配置檔案中的 providers 陣列中包含一個 kms 型別的提供程式,並設定以下屬性:

KMS v1

  • apiVersion: KMS 提供程式的 API 版本。將此值留空或將其設定為 v1
  • name:KMS 外掛的顯示名稱。一旦設定,無法更改。
  • endpoint:gRPC 伺服器(KMS 外掛)的監聽地址。該端點是一個 UNIX 域套接字。
  • cachesize:要以明文形式快取的資料加密金鑰 (DEK) 的數量。快取後,無需再次呼叫 KMS 即可使用 DEK;而未快取的 DEK 則需要呼叫 KMS 才能解密。
  • timeout:在返回錯誤之前,kube-apiserver 應等待 kms-plugin 響應多長時間(預設為 3 秒)。

KMS v2

  • apiVersion: KMS 提供程式的 API 版本。將其設定為 v2
  • name:KMS 外掛的顯示名稱。一旦設定,無法更改。
  • endpoint:gRPC 伺服器(KMS 外掛)的監聽地址。該端點是一個 UNIX 域套接字。
  • timeout:在返回錯誤之前,kube-apiserver 應等待 kms-plugin 響應多長時間(預設為 3 秒)。

KMS v2 不支援 cachesize 屬性。所有資料加密金鑰 (DEK) 在伺服器透過呼叫 KMS 解密後都將以明文形式快取。一旦快取,DEK 就可以無限期地用於解密而無需呼叫 KMS。

請參閱 瞭解靜態加密配置

實現 KMS 外掛

要實現 KMS 外掛,你可以開發一個新的 gRPC 外掛伺服器,或者啟用雲提供商已經提供的 KMS 外掛。然後,你將外掛與遠端 KMS 整合,並將其部署到 Kubernetes 控制平面。

啟用雲提供商支援的 KMS

有關啟用雲提供商特定 KMS 外掛的說明,請參閱你的雲提供商。

開發 KMS 外掛 gRPC 伺服器

你可以使用 Go 提供的存根檔案開發 KMS 外掛 gRPC 伺服器。對於其他語言,你可以使用 proto 檔案建立存根檔案,然後用它來開發 gRPC 伺服器程式碼。

KMS v1

  • 使用 Go:使用存根檔案中的函式和資料結構:api.pb.go 來開發 gRPC 伺服器程式碼。

  • 使用 Go 以外的語言:使用 protoc 編譯器和 proto 檔案:api.proto 為特定語言生成存根檔案。

KMS v2

  • 使用 Go:提供了一個高階來簡化該過程。低階實現可以使用存根檔案 api.pb.go 中的函式和資料結構來開發 gRPC 伺服器程式碼。

  • 使用 Go 以外的語言:使用 protoc 編譯器和 proto 檔案:api.proto 為特定語言生成存根檔案。

然後使用存根檔案中的函式和資料結構來開發伺服器程式碼。

備註

KMS v1
  • KMS 外掛版本:v1beta1

    在響應 Version 過程呼叫時,相容的 KMS 外掛應返回 v1beta1 作為 VersionResponse.version

  • 訊息版本:v1beta1

    所有來自 KMS 提供程式的訊息的 version 欄位都設定為 v1beta1

  • 協議:UNIX 域套接字(unix

    該外掛實現為監聽 UNIX 域套接字的 gRPC 伺服器。外掛部署應該在檔案系統上建立一個檔案,以執行 gRPC UNIX 域套接字連線。API 伺服器(gRPC 客戶端)配置了 KMS 提供程式(gRPC 伺服器)的 UNIX 域套接字端點,以便與其通訊。可以透過以 /@ 開頭(即 unix:///@foo)來使用抽象的 Linux 套接字。使用這種型別的套接字時必須小心,因為它們沒有 ACL 的概念(與傳統的檔案套接字不同)。但是,它們受 Linux 網路名稱空間的限制,因此除非使用主機網路,否則只能在同一 Pod 中的容器訪問。

KMS v2
  • KMS 外掛版本:v2

    為了響應 Status 遠端過程呼叫,相容的 KMS 外掛應將其 KMS 相容版本作為 StatusResponse.version 返回。該狀態響應還應包括“ok”作為 StatusResponse.healthz,以及一個 key_id(遠端 KMS KEK ID)作為 StatusResponse.key_id。Kubernetes 專案建議您使外掛與穩定的 v2 KMS API 相容。Kubernetes 1.34 也支援 KMS 的 v2beta1 API;未來的 Kubernetes 版本可能會繼續支援該 Beta 版本。

    當一切正常時,API 伺服器大約每分鐘輪詢一次 Status 過程呼叫;當外掛不健康時,則每 10 秒輪詢一次。外掛必須注意最佳化此呼叫,因為它將承受持續的負載。

  • 加密

    EncryptRequest 過程呼叫提供了明文和用於日誌記錄的 UID。響應必須包括密文、所使用的 KEK 的 key_id,以及可選的、KMS 外掛為了將來幫助 DecryptRequest 呼叫所需的任何元資料(透過 annotations 欄位)。外掛必須保證任何不同的明文都會產生不同的響應 (ciphertext, key_id, annotations)

    如果外掛返回非空的 annotations 對映,則所有對映鍵都必須是完全限定的域名,例如 example.com。一個 annotation 的用例示例是 {"kms.example.io/remote-kms-auditid":"<遠端 KMS 使用的審計 ID>"}

    API 伺服器不會以很高的速率執行 EncryptRequest 過程呼叫。外掛實現仍應力求將每個請求的延遲控制在 100 毫秒以內。

  • 解密

    DecryptRequest 過程呼叫提供了 EncryptRequest 中的 (ciphertext, key_id, annotations) 和用於日誌記錄的 UID。正如預期的那樣,它是 EncryptRequest 呼叫的逆操作。外掛必須驗證 key_id 是否是它們理解的——它們不得嘗試解密資料,除非它們確定資料是它們在早些時候加密的。

    API 伺服器在啟動時可能會執行數千個 DecryptRequest 過程呼叫,以填充其監視快取。因此,外掛實現必須儘快執行這些呼叫,並且應力求將每個請求的延遲保持在 10 毫秒以內。

  • 理解 key_id 和金鑰輪換

    key_id 是當前正在使用的遠端 KMS KEK 的公共、非秘密名稱。它可能會在 API 伺服器的正常執行期間被記錄,因此不得包含任何私有資料。建議外掛實現使用雜湊來避免洩漏任何資料。KMS v2 指標在透過 /metrics 端點公開此值之前會對其進行雜湊處理。

    API 伺服器認為 Status 過程呼叫返回的 key_id 是權威的。因此,此值的更改會向 API 伺服器發出訊號,表明遠端 KEK 已更改,並且使用舊 KEK 加密的資料在執行空操作寫入時應標記為陳舊(如下所述)。如果 EncryptRequest 過程呼叫返回的 key_idStatus 不同,則響應將被丟棄,並且外掛被視為不健康。因此,實現必須保證 Status 返回的 key_id 將與 EncryptRequest 返回的 key_id 相同。此外,外掛必須確保 key_id 是穩定的,並且不會在值之間來回切換(即在遠端 KEK 輪換期間)。

    外掛不得重複使用 key_id,即使在重新啟用了以前使用過的遠端 KEK 的情況下也是如此。例如,如果一個外掛使用 key_id=A,然後切換到 key_id=B,再回到 key_id=A,那麼它不應該報告 key_id=A,而是應該報告一些派生值,例如 key_id=A_001,或者使用一個新的值,例如 key_id=C

    由於 API 伺服器大約每分鐘輪詢一次 Status,因此 key_id 輪換並非立即完成。此外,API 伺服器將在上次有效狀態上執行大約三分鐘。因此,如果使用者希望採用被動方式進行儲存遷移(即透過等待),他們必須安排在遠端 KEK 輪換後 3 + N + M 分鐘進行遷移(N 是外掛觀察 key_id 更改所需的時間,M 是允許處理配置更改的所需緩衝區——建議最小 M 為五分鐘)。請注意,執行 KEK 輪換不需要重啟 API 伺服器。

  • 協議:UNIX 域套接字(unix

    該外掛實現為監聽 UNIX 域套接字的 gRPC 伺服器。外掛部署應該在檔案系統上建立一個檔案,以執行 gRPC UNIX 域套接字連線。API 伺服器(gRPC 客戶端)配置了 KMS 提供程式(gRPC 伺服器)的 UNIX 域套接字端點,以便與其通訊。可以透過以 /@ 開頭(即 unix:///@foo)來使用抽象的 Linux 套接字。使用這種型別的套接字時必須小心,因為它們沒有 ACL 的概念(與傳統的檔案套接字不同)。但是,它們受 Linux 網路名稱空間的限制,因此除非使用主機網路,否則只能在同一 Pod 中的容器訪問。

將 KMS 外掛與遠端 KMS 整合

KMS 外掛可以使用 KMS 支援的任何協議與遠端 KMS 通訊。所有配置資料,包括 KMS 外掛用於與遠端 KMS 通訊的身份驗證憑據,都由 KMS 外掛獨立儲存和管理。KMS 外掛可以在將密文傳送到 KMS 進行解密之前,用可能需要的附加元資料對密文進行編碼(KMS v2 透過提供專用的 annotations 欄位使此過程更容易)。

部署 KMS 外掛

確保 KMS 外掛在與 Kubernetes API 伺服器相同的宿主機上執行。

使用 KMS 提供程式加密資料

要加密資料

  1. 建立一個新的 EncryptionConfiguration 檔案,使用 kms 提供程式的相應屬性來加密 Secret 和 ConfigMap 等資源。如果你想加密在 CustomResourceDefinition 中定義的擴充套件 API,你的叢集必須執行 Kubernetes v1.26 或更高版本。

  2. 在 kube-apiserver 上設定 --encryption-provider-config 標誌,指向配置檔案的位置。

  3. 布林引數 --encryption-provider-config-automatic-reload 決定了如果磁碟內容發生變化,--encryption-provider-config 設定的檔案是否應自動重新載入

  4. 重新啟動 API 伺服器。

KMS v1

apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
      - configmaps
      - pandas.awesome.bears.example
    providers:
      - kms:
          name: myKmsPluginFoo
          endpoint: unix:///tmp/socketfile-foo.sock
          cachesize: 100
          timeout: 3s
      - kms:
          name: myKmsPluginBar
          endpoint: unix:///tmp/socketfile-bar.sock
          cachesize: 100
          timeout: 3s

KMS v2

apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
      - configmaps
      - pandas.awesome.bears.example
    providers:
      - kms:
          apiVersion: v2
          name: myKmsPluginFoo
          endpoint: unix:///tmp/socketfile-foo.sock
          timeout: 3s
      - kms:
          apiVersion: v2
          name: myKmsPluginBar
          endpoint: unix:///tmp/socketfile-bar.sock
          timeout: 3s

--encryption-provider-config-automatic-reload 設定為 true 會將所有健康檢查合併到一個健康檢查端點。只有在使用 KMS v1 提供程式且加密配置未自動重新載入時,才可進行單獨的健康檢查。

下表總結了每個 KMS 版本的健康檢查端點

KMS 配置不自動過載自動過載
僅限 KMS v1單獨健康檢查單個健康檢查
僅限 KMS v2單個健康檢查單個健康檢查
KMS v1 和 v2 都支援單獨健康檢查單個健康檢查
無 KMS單個健康檢查

Single Healthcheck 表示唯一的健康檢查端點是 /healthz/kms-providers

Individual Healthchecks 意味著每個 KMS 外掛都有一個相關的健康檢查端點,該端點基於其在加密配置中的位置:/healthz/kms-provider-0/healthz/kms-provider-1 等。

這些健康檢查端點路徑是硬編碼的,並由伺服器生成/控制。單獨健康檢查的索引對應於 KMS 加密配置的處理順序。

在執行 確保所有 Secret 都已加密 中定義的步驟之前,providers 列表應以 identity: {} 提供程式結束,以允許讀取未加密的資料。一旦所有資源都已加密,應刪除 identity 提供程式,以防止 API 伺服器接受未加密的資料。

有關 EncryptionConfiguration 格式的詳細資訊,請查閱 API 伺服器加密 API 參考

驗證資料是否已加密

當靜態加密配置正確時,資源在寫入時被加密。重新啟動 kube-apiserver 後,任何新建立或更新的 Secret 或 EncryptionConfiguration 中配置的其他資源型別都應在儲存時加密。要驗證,您可以使用 etcdctl 命令列程式檢索 Secret 資料的內容。

  1. default 名稱空間中建立一個名為 secret1 的新 Secret。

    kubectl create secret generic secret1 -n default --from-literal=mykey=mydata
    
  2. 使用 etcdctl 命令列工具,從 etcd 中讀取該 Secret。

    ETCDCTL_API=3 etcdctl get /kubernetes.io/secrets/default/secret1 [...] | hexdump -C
    

    其中 [...] 包含連線到 etcd 伺服器的其他引數。

  3. 驗證儲存的 Secret 是否以 k8s:enc:kms:v1: (KMS v1) 或 k8s:enc:kms:v2: (KMS v2) 為字首,這表示 kms 提供程式已加密生成的資料。

  4. 驗證 Secret 在透過 API 檢索時是否已正確解密

    kubectl describe secret secret1 -n default
    

    Secret 應包含 mykey: mydata

確保所有 Secret 都已加密

當靜態加密配置正確時,資源在寫入時被加密。因此,我們可以執行原地空操作更新,以確保資料被加密。

以下命令讀取所有 Secret,然後更新它們以應用伺服器端加密。如果由於衝突寫入而發生錯誤,請重試該命令。對於大型叢集,您可能希望按名稱空間細分 Secret 或編寫更新指令碼。

kubectl get secrets --all-namespaces -o json | kubectl replace -f -

從本地加密提供程式切換到 KMS 提供程式

要從本地加密提供程式切換到 kms 提供程式並重新加密所有 Secret

  1. kms 提供程式新增為配置檔案的第一個條目,如下例所示。

    apiVersion: apiserver.config.k8s.io/v1
    kind: EncryptionConfiguration
    resources:
      - resources:
          - secrets
        providers:
          - kms:
              apiVersion: v2
              name : myKmsPlugin
              endpoint: unix:///tmp/socketfile.sock
          - aescbc:
              keys:
                - name: key1
                  secret: <BASE 64 ENCODED SECRET>
    
  2. 重啟所有 kube-apiserver 程序。

  3. 執行以下命令,強制所有 Secret 使用 kms 提供程式重新加密。

    kubectl get secrets --all-namespaces -o json | kubectl replace -f -
    

下一步

如果您不再希望對 Kubernetes API 中持久化的資料使用加密,請閱讀 解密已靜態儲存的資料

上次修改時間:2025 年 5 月 9 日太平洋標準時間下午 12:28:Sync encryption docs reviewers with kubernetes sig-auth-encryption-at-rest-reviewers (4e42436cf9)