使用 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)