使用 KMS 提供程序进行数据加密

本页面介绍如何配置 Key Management Service (KMS) Provider 和插件以启用 Secret 数据加密。在 Kubernetes 1.33 中,存在两种版本的 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。

  • 如果你为了支持 v1.27 之前的集群而选择了 KMS API v1,或者你有一个只支持 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 provider。

  • 你的集群必须使用 etcd v3 或更高版本

KMS v2

特性状态: Kubernetes v1.29 [稳定版]
  • 你的集群必须使用 etcd v3 或更高版本

KMS 加密和每对象加密密钥

KMS 加密 provider 使用信封加密方案来加密 etcd 中的数据。数据使用数据加密密钥 (DEK) 加密。DEK 使用存储和管理在远程 KMS 中的密钥加密密钥 (KEK) 进行加密。

如果你使用(已弃用)KMS 的 v1 实现,每次加密都会生成新的 DEK。

对于 KMS v2,每次加密都会生成一个新的 DEK:API 服务器使用密钥派生函数从一个秘密种子结合一些随机数据生成一次性数据加密密钥。每当 KEK 轮换时,种子也会轮换(详见下文的“理解 key_id 和密钥轮换”章节)。

KMS Provider 使用 gRPC 通过 UNIX 域套接字与特定的 KMS Plugin 通信。KMS Plugin 作为 gRPC 服务器实现,部署在与 Kubernetes 控制平面相同的主机上,负责与远程 KMS 的所有通信。

配置 KMS Provider

要在 API 服务器上配置 KMS Provider,请在加密配置文件中的 providers 数组中包含一个类型为 kms 的 Provider,并设置以下属性:

KMS v1

  • apiVersion:KMS provider 的 API 版本。将此值留空或设置为 v1
  • name:KMS plugin 的显示名称。一旦设置,不能更改。
  • endpoint:gRPC 服务器 (KMS plugin) 的监听地址。此端点是一个 UNIX 域套接字。
  • cachesize:以明文形式缓存的数据加密密钥 (DEK) 的数量。缓存后,使用 DEK 无需再次调用 KMS;而未缓存的 DEK 需要调用 KMS 来解包。
  • timeoutkube-apiserver 在返回错误之前应等待 kms-plugin 响应多久(默认为 3 秒)。

KMS v2

  • apiVersion:KMS Provider 的 API 版本。将其设置为 v2
  • name:KMS plugin 的显示名称。一旦设置,不能更改。
  • endpoint:gRPC 服务器 (KMS plugin) 的监听地址。此端点是一个 UNIX 域套接字。
  • timeoutkube-apiserver 在返回错误之前应等待 kms-plugin 响应多久(默认为 3 秒)。

KMS v2 不支持 cachesize 属性。一旦服务器通过调用 KMS 解包数据加密密钥 (DEK),所有 DEK 都将以明文形式缓存。缓存后,DEK 可以无限期地用于解密,而无需调用 KMS。

参阅理解静态数据加密配置

实现 KMS Plugin

要实现 KMS Plugin,你可以开发一个新的 Plugin gRPC 服务器或启用云提供商已提供的 KMS Plugin。然后将该 Plugin 与远程 KMS 集成,并将其部署在 Kubernetes 控制平面上。

启用云提供商支持的 KMS

请查阅你的云提供商关于启用特定于云提供商的 KMS Plugin 的说明。

开发 KMS Plugin gRPC 服务器

你可以使用 Go 语言可用的 Stub 文件开发 KMS Plugin gRPC 服务器。对于其他语言,使用 Proto 文件生成 Stub 文件,然后用它开发 gRPC 服务器代码。

KMS v1

  • 使用 Go 语言:使用 Stub 文件 api.pb.go 中的函数和数据结构来开发 gRPC 服务器代码。

  • 使用 Go 语言以外的语言:使用 protoc 编译器结合 Proto 文件 api.proto 为特定语言生成 Stub 文件。

KMS v2

  • 使用 Go 语言:提供了一个高级别,可以简化此过程。底层实现可以使用 Stub 文件 api.pb.go 中的函数和数据结构来开发 gRPC 服务器代码。

  • 使用 Go 语言以外的语言:使用 protoc 编译器结合 Proto 文件 api.proto 为特定语言生成 Stub 文件。

然后使用 Stub 文件中的函数和数据结构来开发服务器代码。

注意事项

KMS v1
  • kms Plugin 版本:v1beta1

    在响应 Version 过程调用时,兼容的 KMS Plugin 应返回 v1beta1 作为 VersionResponse.version

  • 消息版本:v1beta1

    来自 KMS Provider 的所有消息的版本字段都设置为 v1beta1

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

    该 Plugin 被实现为一个监听 UNIX 域套接字的 gRPC 服务器。Plugin 部署应在文件系统上创建一个文件来运行 gRPC UNIX 域套接字连接。API 服务器 (gRPC 客户端) 配置了 KMS Provider (gRPC 服务器) 的 UNIX 域套接字端点以便与其通信。可以通过以 /@ 开头(例如 unix:///@foo)来使用抽象的 Linux 套接字。使用这种类型的套接字时必须小心,因为它们没有 ACL 的概念(不像传统的文件套接字)。但是,它们受限于 Linux 网络命名空间,因此除非使用主机网络,否则它们只能被同一 Pod 内的容器访问。

KMS v2
  • KMS Plugin 版本:v2

    作为对 Status 远程过程调用的响应,兼容的 KMS Plugin 应将其 KMS 兼容版本作为 StatusResponse.version 返回。该状态响应还应包括 "ok" 作为 StatusResponse.healthz,以及一个 key_id (远程 KMS KEK ID) 作为 StatusResponse.key_id。Kubernetes 项目推荐你使你的 Plugin 兼容稳定的 v2 KMS API。Kubernetes 1.33 也支持 KMS 的 v2beta1 API;未来的 Kubernetes 版本很可能会继续支持该 Beta 版本。

    API 服务器在大约每分钟健康时轮询 Status 过程调用,在 Plugin 不健康时每 10 秒轮询一次。Plugin 必须注意优化此调用,因为它将持续负载。

  • 加密

    EncryptRequest 过程调用提供明文和用于日志记录的 UID。响应必须包含密文、使用的 KEK 的 key_id,以及可选地,KMS Plugin 在未来的 DecryptRequest 调用中可能需要的任何元数据(通过 annotations 字段)。Plugin 必须保证任何不同的明文都产生不同的响应 (ciphertext, key_id, annotations)

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

    API 服务器不会以高速率执行 EncryptRequest 过程调用。Plugin 实现仍应努力将每个请求的延迟控制在 100 毫秒以下。

  • 解密

    DecryptRequest 过程调用提供从 EncryptRequest 返回的 (ciphertext, key_id, annotations) 以及用于日志记录的 UID。正如预期,它是 EncryptRequest 调用的逆操作。Plugin 必须验证 key_id 是它们能够理解的 - 除非它们确定数据是之前由它们加密的,否则绝不能尝试解密。

    API 服务器在启动时可能执行数千次 DecryptRequest 过程调用来填充其 Watch 缓存。因此,Plugin 实现必须尽可能快地执行这些调用,并应努力将每个请求的延迟控制在 10 毫秒以下。

  • 理解 key_id 和密钥轮换

    key_id 是远程 KMS KEK 当前使用的公共、非秘密名称。它可能在 API 服务器的常规操作期间被记录日志,因此不得包含任何私有数据。鼓励 Plugin 实现使用哈希来避免数据泄露。KMS v2 指标会确保在通过 /metrics 端点暴露此值之前对其进行哈希处理。

    API 服务器认为从 Status 过程调用返回的 key_id 是权威的。因此,此值的更改向 API 服务器表明远程 KEK 已更改,并且使用旧 KEK 加密的数据在执行无操作写入(如下所述)时应标记为过时。如果 EncryptRequest 过程调用返回的 key_idStatus 不同,则响应将被丢弃,Plugin 被视为不健康。因此,实现必须保证从 Status 返回的 key_id 将与 EncryptRequest 返回的 key_id 相同。此外,Plugin 必须确保 key_id 稳定,并且不会在值之间来回切换(即在远程 KEK 轮换期间)。

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

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

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

    该 Plugin 被实现为一个监听 UNIX 域套接字的 gRPC 服务器。Plugin 部署应在文件系统上创建一个文件来运行 gRPC UNIX 域套接字连接。API 服务器 (gRPC 客户端) 配置了 KMS Provider (gRPC 服务器) 的 UNIX 域套接字端点以便与其通信。可以通过以 /@ 开头(例如 unix:///@foo)来使用抽象的 Linux 套接字。使用这种类型的套接字时必须小心,因为它们没有 ACL 的概念(不像传统的文件套接字)。但是,它们受限于 Linux 网络命名空间,因此除非使用主机网络,否则它们只能被同一 Pod 内的容器访问。

将 KMS Plugin 与远程 KMS 集成

KMS Plugin 可以使用远程 KMS 支持的任何协议与远程 KMS 通信。所有配置数据,包括 KMS Plugin 用于与远程 KMS 通信的身份验证凭证,都由 KMS Plugin 独立存储和管理。KMS Plugin 可以用附加元数据对密文进行编码,然后将其发送到 KMS 进行解密(KMS v2 通过提供专门的 annotations 字段简化了此过程)。

部署 KMS Plugin

确保 KMS Plugin 运行在与 Kubernetes API 服务器相同的主机上。

使用 KMS Provider 加密你的数据

要加密数据

  1. 创建一个新的 EncryptionConfiguration 文件,使用 kms Provider 的适当属性来加密 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 Provider 且加密配置未自动重新加载时,才提供单独的健康检查。

下表总结了每种 KMS 版本的健康检查端点:

KMS 配置不使用自动重载使用自动重载
仅 KMS v1单独健康检查单一健康检查
仅 KMS v2单一健康检查单一健康检查
KMS v1 和 v2 都使用单独健康检查单一健康检查
没有 KMS单一健康检查

Single Healthcheck 意味着唯一的健康检查端点是 /healthz/kms-providers

Individual Healthchecks 意味着每个 KMS Plugin 都有一个与其在加密配置中的位置关联的健康检查端点:/healthz/kms-provider-0/healthz/kms-provider-1 等。

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

在执行 确保所有 Secret 都已加密 中定义的步骤之前,providers 列表应以 identity: {} Provider 结尾,以便允许读取未加密的数据。所有资源加密完成后,应移除 identity Provider,以阻止 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 对于 KMS v1 以 k8s:enc:kms:v1: 开头,对于 KMS v2 以 k8s:enc:kms:v2: 开头,这表明 kms Provider 已加密了结果数据。

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

    kubectl describe secret secret1 -n default
    

    Secret 应包含 mykey: mydata

确保所有 Secret 都已加密

正确配置静态数据加密后,资源将在写入时加密。因此,我们可以执行原地无操作更新以确保数据被加密。

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

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

从本地加密 Provider 切换到 KMS Provider

要从本地加密 Provider 切换到 kms Provider 并重新加密所有 Secret

  1. kms Provider 添加为配置文件的第一个条目,如以下示例所示。

    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 Provider 重新加密。

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

接下来

如果你不再希望对存储在 Kubernetes API 中的数据使用加密,请阅读 解密已存储的静态数据

上次修改时间:2024年9月13日太平洋标准时间上午9:33:修复 Markdown 文件中的一些超链接 (e6855623c7)