使用 KMS 提供商进行数据加密

此页面展示如何配置密钥管理服务 (KMS) 提供程序和插件以启用机密数据加密。在 Kubernetes 1.31 中,有两种版本的静态加密 KMS。如果可行,您应该使用 KMS v2,因为 KMS v1 已被弃用(自 Kubernetes v1.28 起)并且默认情况下已禁用(自 Kubernetes v1.29 起)。与 KMS v1 相比,KMS v2 提供了更好的性能特征。

开始之前

您需要拥有一个 Kubernetes 集群,并且 kubectl 命令行工具必须配置为与您的集群通信。建议您在至少包含两个节点(不充当控制平面主机)的集群上运行本教程。如果您还没有集群,可以使用 minikube 创建一个,或者您可以使用以下 Kubernetes Playground 之一

您需要的 Kubernetes 版本取决于您选择的 KMS API 版本。Kubernetes 建议使用 KMS v2。

  • 如果您选择了 KMS API v2,您应该使用 Kubernetes v1.31(如果您正在运行其他支持 v2 KMS API 的 Kubernetes 版本,请切换到该 Kubernetes 版本的文档)。
  • 如果您选择了 KMS API v1 以支持早于版本 v1.27 的集群,或者您有一个仅支持 KMS v1 的旧版 KMS 插件,任何支持的 Kubernetes 版本都可以正常工作。自 Kubernetes v1.28 起,该 API 已被弃用。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 [稳定]
  • 您的集群必须使用 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) 的数量。缓存后,DEK 可在不再次调用 KMS 的情况下使用;而未缓存的 DEK 需要调用 KMS 来解封。
  • timeoutkube-apiserver 在返回错误之前应等待 kms-plugin 响应多长时间(默认值为 3 秒)。

KMS v2

  • apiVersion:KMS 提供程序的 API 版本。将其设置为 v2
  • name:KMS 插件的显示名称。一旦设置,就不能更改。
  • endpoint:gRPC 服务器(KMS 插件)的侦听地址。该端点是 UNIX 域套接字。
  • timeoutkube-apiserver 在返回错误之前应等待 kms-plugin 响应多长时间(默认值为 3 秒)。

KMS v2 不支持 cachesize 属性。一旦服务器通过调用 KMS 解封了所有数据加密密钥 (DEK),它们就会以明文形式被缓存。缓存后,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

    响应过程调用版本,兼容的 KMS 插件应返回 v1beta1 作为 VersionResponse.version

  • 消息版本:v1beta1

    来自 KMS 提供程序的所有消息都将版本字段设置为 v1beta1

  • 协议:UNIX 域套接字 (unix)

    该插件实现为 gRPC 服务器,它侦听 UNIX 域套接字。插件部署应在文件系统上创建一个文件以运行 gRPC unix 域套接字连接。API 服务器(gRPC 客户端)配置有 KMS 提供程序(gRPC 服务器)unix 域套接字端点,以便与其通信。抽象 Linux 套接字可以通过用 /@ 启动端点来使用,例如 unix:///@foo。使用这种类型的套接字时必须谨慎,因为它们没有 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.31 还支持 KMS 的 v2beta1 API;未来的 Kubernetes 版本可能会继续支持该 beta 版本。

    API 服务器在大致每分钟都会轮询 Status 过程调用,当一切正常时,以及当插件不正常时每 10 秒轮询一次。插件必须注意优化此调用,因为它将处于不断负载之下。

  • 加密

    EncryptRequest 过程调用提供明文和用于记录的 UID。响应必须包含密文、用于 KEK 的 key_id 以及 KMS 插件需要帮助将来的 DecryptRequest 调用(通过 annotations 字段)的任何元数据。该插件必须保证任何不同的明文都会产生不同的响应 (密文, key_id, 注释)

    如果插件返回一个非空的 annotations 映射,则所有映射键必须是完全限定的域名,例如 example.comannotation 的一个示例用例是 {"kms.example.io/remote-kms-auditid":"<远程 KMS 使用的审计 ID>"}

    API 服务器不会以高频率执行 EncryptRequest 过程调用。插件实现仍然应该努力将每个请求的延迟保持在 100 毫秒以下。

  • 解密

    DecryptRequest 过程调用提供来自 EncryptRequest(密文, key_id, 注释) 和用于记录的 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_idEncryptRequest 返回的相同。此外,插件必须确保 key_id 稳定且不会在值之间频繁切换(例如,在远程 KEK 旋转期间)。

    插件不得重复使用 key_id,即使在先前使用的远程 KEK 已恢复的情况下也是如此。例如,如果插件正在使用 key_id=A,切换到 key_id=B,然后返回到 key_id=A - 插件应报告一些派生值,例如 key_id=A_001 或使用新的值,例如 key_id=C,而不是报告 key_id=A

    由于 API 服务器大约每分钟轮询一次 Status,因此 key_id 旋转并非立即生效。此外,API 服务器将保持最后一次有效状态约三分钟。因此,如果用户希望采取被动方式进行存储迁移(例如,通过等待),他们必须安排在远程 KEK 旋转后 3 + N + M 分钟进行迁移(N 是插件观察 key_id 更改所需的时间,M 是允许处理配置更改所需的缓冲时间 - 建议最小 M 为五分钟)。请注意,执行 KEK 旋转不需要重新启动 API 服务器。

  • 协议:UNIX 域套接字 (unix)

    该插件实现为 gRPC 服务器,它侦听 UNIX 域套接字。插件部署应在文件系统上创建一个文件以运行 gRPC unix 域套接字连接。API 服务器(gRPC 客户端)配置有 KMS 提供程序(gRPC 服务器)unix 域套接字端点,以便与其通信。抽象 Linux 套接字可以通过用 /@ 启动端点来使用,例如 unix:///@foo。使用这种类型的套接字时必须谨慎,因为它们没有 ACL 概念(与传统的基于文件套接字不同)。但是,它们受 Linux 网络命名空间的约束,因此只能被同一 Pod 中的容器访问,除非使用主机网络。

将 KMS 插件与远程 KMS 集成

KMS 插件可以使用 KMS 支持的任何协议与远程 KMS 通信。所有配置数据(包括 KMS 插件用于与远程 KMS 通信的身份验证凭据)均由 KMS 插件独立存储和管理。KMS 插件可以在发送到 KMS 进行解密之前使用其他元数据对密文进行编码(KMS v2 通过提供专用的 annotations 字段使此过程更轻松)。

部署 KMS 插件

确保 KMS 插件与 Kubernetes API 服务器运行在同一主机上。

使用 KMS 提供商加密您的数据

要加密数据

  1. 使用 kms 提供商的适当属性创建一个新的 EncryptionConfiguration 文件,以加密诸如 Secrets 和 ConfigMaps 之类的资源。如果要加密在 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单个健康检查

单个健康检查 表示唯一的健康检查端点是 /healthz/kms-providers

单独的健康检查 表示每个 KMS 插件都有一个关联的健康检查端点,该端点基于其在加密配置中的位置:/healthz/kms-provider-0/healthz/kms-provider-1 等。

这些健康检查端点路径是硬编码的,并由服务器生成/控制。单独的健康检查的索引对应于处理 KMS 加密配置的顺序。

在执行 确保所有密钥都已加密 中定义的步骤之前,providers 列表应以 identity: {} 提供商结尾,以允许读取未加密的数据。一旦所有资源都已加密,则应删除 identity 提供商,以防止 API 服务器认可未加密的数据。

有关 EncryptionConfiguration 格式的详细信息,请查看 API 服务器加密 API 参考.

验证数据是否已加密

当正确配置了静态加密时,资源会在写入时加密。重新启动 kube-apiserver 后,任何新创建或更新的 Secret 或 EncryptionConfiguration 中配置的其他资源类型都应在存储时加密。要进行验证,可以使用 etcdctl 命令行程序检索密钥数据的內容。

  1. default 命名空间中创建一个名为 secret1 的新密钥

    kubectl create secret generic secret1 -n default --from-literal=mykey=mydata
    
  2. 使用 etcdctl 命令行从 etcd 中读取该密钥

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

    其中 [...] 包含用于连接到 etcd 服务器的其他参数。

  3. 验证存储的密钥是否以 k8s:enc:kms:v1:(对于 KMS v1)或 k8s:enc:kms:v2:(对于 KMS v2)为前缀,这表明 kms 提供商已加密结果数据。

  4. 验证通过 API 检索时密钥是否已正确解密

    kubectl describe secret secret1 -n default
    

    密钥应包含 mykey: mydata

确保所有密钥都已加密

当正确配置了静态加密时,资源会在写入时加密。因此,我们可以执行就地无操作更新以确保数据已加密。

以下命令将读取所有密钥,然后更新它们以应用服务器端加密。如果由于冲突写入而发生错误,请重试该命令。对于更大的集群,您可能希望按命名空间细分密钥或编写更新脚本。

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

从本地加密提供商切换到 KMS 提供商

要从本地加密提供商切换到 kms 提供商并重新加密所有密钥

  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. 运行以下命令以强制使用 kms 提供商重新加密所有密钥。

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

下一步

如果您不再希望使用加密来持久保存存储在 Kubernetes API 中的数据,请阅读解密已静态存储的数据.

上次修改时间:2024 年 5 月 16 日下午 2:58 PST:修复示例 KMS 配置文档 (81e6c94293)