使用 KMS 提供程序进行数据加密
此页面介绍如何配置密钥管理服务 (KMS) 提供程序和插件以启用 Secret 数据加密。在 Kubernetes 1.32 中,静态 KMS 加密有两个版本。如果可行,您应该使用 KMS v2,因为 KMS v1 已弃用(自 Kubernetes v1.28 起)并且默认禁用(自 Kubernetes v1.29 起)。KMS v2 提供了比 KMS v1 更好的性能特性。
警告
本文档适用于 KMS v2 的正式发布版本(以及已弃用的版本 1 实现)。如果您使用的是早于 Kubernetes v1.29 的任何控制平面组件,请查看您集群正在运行的 Kubernetes 版本文档中的对应页面。早期版本的 Kubernetes 具有不同的行为,这可能与信息安全相关。准备工作
您需要拥有一个 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 [稳定]
- 您的集群必须使用 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
为了响应过程调用版本,兼容的 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 插件应在其StatusResponse.version
中返回其 KMS 兼容性版本。该状态响应还应包含 "ok" 作为StatusResponse.healthz
以及key_id
(远程 KMS KEK ID)作为StatusResponse.key_id
。Kubernetes 项目建议您使您的插件与稳定的v2
KMS API 兼容。Kubernetes 1.32 也支持 KMS 的v2beta1
API;未来的 Kubernetes 版本可能会继续支持该测试版。当一切正常时,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
过程调用以填充其 watch 缓存。因此,插件实现必须尽可能快地执行这些调用,并且应力求将每个请求的延迟保持在 10 毫秒以下。理解
key_id
和密钥轮换key_id
是当前正在使用的远程 KMS KEK 的公共非秘密名称。它可能在 API 服务器的常规操作期间被记录,因此不得包含任何私有数据。鼓励插件实现使用哈希来避免泄露任何数据。KMS v2 指标在通过/metrics
端点公开此值之前会对其进行哈希处理。API 服务器将从
Status
过程调用返回的key_id
视为权威性的。因此,此值的更改向 API 服务器发出信号,表明远程 KEK 已更改,并且使用旧 KEK 加密的数据应在执行无操作写入时标记为过时(如下所述)。如果EncryptRequest
过程调用返回的key_id
与Status
不同,则响应将被丢弃,并且该插件被视为不正常。因此,实现必须保证从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 服务器。警告
由于您无法控制使用 DEK 执行的写入次数,因此 Kubernetes 项目建议至少每 90 天轮换一次 KEK。协议: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 提供程序加密您的数据
要加密数据
使用
kms
提供程序的适当属性创建一个新的EncryptionConfiguration
文件,以加密 Secrets 和 ConfigMaps 等资源。如果要加密在 CustomResourceDefinition 中定义的扩展 API,则您的集群必须运行 Kubernetes v1.26 或更新版本。在 kube-apiserver 上设置
--encryption-provider-config
标志,使其指向配置文件的位置。--encryption-provider-config-automatic-reload
布尔参数确定如果磁盘内容发生更改,是否应自动重新加载由--encryption-provider-config
设置的文件。重新启动 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 加密配置的顺序。
在执行确保所有 Secret 都已加密中定义的步骤之前,providers
列表应以 identity: {}
提供程序结尾,以允许读取未加密的数据。所有资源都加密后,应删除 identity
提供程序,以防止 API 服务器处理未加密的数据。
有关 EncryptionConfiguration
格式的详细信息,请查看API 服务器加密 API 参考。
验证数据是否已加密
正确配置静态加密后,资源将在写入时加密。重新启动 kube-apiserver
后,在存储时,任何新创建或更新的 Secret 或在 EncryptionConfiguration
中配置的其他资源类型都应加密。要进行验证,您可以使用 etcdctl
命令行程序检索您的秘密数据的内容。
在
default
命名空间中创建一个名为secret1
的新 Secretkubectl create secret generic secret1 -n default --from-literal=mykey=mydata
使用
etcdctl
命令行,从 etcd 中读取该 SecretETCDCTL_API=3 etcdctl get /kubernetes.io/secrets/default/secret1 [...] | hexdump -C
其中
[...]
包含连接到 etcd 服务器的其他参数。验证存储的 Secret 是否以 KMS v1 的
k8s:enc:kms:v1:
或 KMS v2 的k8s:enc:kms:v2:
为前缀,这表明kms
提供程序已加密结果数据。验证通过 API 检索时 Secret 是否已正确解密
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
将
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>
重新启动所有
kube-apiserver
进程。运行以下命令强制使用
kms
提供程序重新加密所有 Secret。kubectl get secrets --all-namespaces -o json | kubectl replace -f -