静态加密机密数据
Kubernetes 中允许允许用户编辑的持久 API 资源数据的所有 API 都支持静态加密。 例如,你可以启用静态加密 Secret。 此静态加密是对 etcd 集群或运行 kube-apiserver 的主机上的文件系统的任何系统级加密的补充。
本页展示如何启用和配置静态 API 数据加密。
此任务涵盖使用 Kubernetes API 存储的资源数据的加密。 例如,你可以加密 Secret 对象,包括它们包含的键值数据。
如果要加密安装到容器中的文件系统中的数据,则需要:
- 使用提供加密 volumes 的存储集成
- 在你自己的应用程序中加密数据
准备开始
你必须拥有一个 Kubernetes 的集群,同时你必须配置 kubectl 命令行工具与你的集群通信。 建议在至少有两个不作为控制平面主机的节点的集群上运行本教程。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:
此任务假设你将 Kubernetes API 服务器组件以静态 Pod 方式运行在每个控制平面节点上。
集群的控制平面必须使用 etcd v3.x(主版本 3,任何次要版本)。
要加密自定义资源,你的集群必须运行 Kubernetes v1.26 或更高版本。
在 Kubernetes v1.27 或更高版本中可以使用通配符匹配资源。
kubectl version
.配置并确定是否已启用静态数据加密
kube-apiserver
的参数 --encryption-provider-config
控制 API 数据在 etcd 中的加密方式。
该配置作为一个名为 EncryptionConfiguration
的 API 提供。下面提供了一个示例配置。
重要: 对于高可用配置(有两个或多个控制平面节点),加密配置文件必须相同!
否则,kube-apiserver
组件无法解密存储在 etcd 中的数据。
理解静态数据加密
---
#
# 注意:这是一个示例配置。请勿将其用于你自己的集群!
#
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
- configmaps
- pandas.awesome.bears.example # 自定义资源 API
providers:
# 此配置不提供数据机密性。
# 第一个配置的 provider 正在指定将资源存储为纯文本的 "identity" 机制。
- identity: {} # 纯文本,换言之未加密
- aesgcm:
keys:
- name: key1
secret: c2VjcmV0IGlzIHNlY3VyZQ==
- name: key2
secret: dGhpcyBpcyBwYXNzd29yZA==
- aescbc:
keys:
- name: key1
secret: c2VjcmV0IGlzIHNlY3VyZQ==
- name: key2
secret: dGhpcyBpcyBwYXNzd29yZA==
- secretbox:
keys:
- name: key1
secret: YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=
- resources:
- events
providers:
- identity: {} # 即使如下指定 *.* 也不会加密 events
- resources:
- '*.apps' # 通配符匹配需要 Kubernetes 1.27 或更高版本
providers:
- aescbc:
keys:
- name: key2
secret: c2VjcmV0IGlzIHNlY3VyZSwgb3IgaXMgaXQ/Cg==
- resources:
- '*.*' # 通配符匹配需要 Kubernetes 1.27 或更高版本
providers:
- aescbc:
keys:
- name: key3
secret: c2VjcmV0IGlzIHNlY3VyZSwgSSB0aGluaw==
每个 resources
数组项目是一个单独的完整的配置。
resources.resources
字段是应加密的 Kubernetes 资源(例如 Secret、ConfigMap 或其他资源)名称
(resource
或 resource.group
)的数组。
如果自定义资源被添加到 EncryptionConfiguration
并且集群版本为 1.26 或更高版本,
则 EncryptionConfiguration
中提到的任何新创建的自定义资源都将被加密。
在该版本之前存在于 etcd 中的任何自定义资源和配置不会被加密,直到它们被下一次写入到存储为止。
这与内置资源的行为相同。请参阅确保所有 Secret 都已加密一节。
providers
数组是可能的加密 provider 的有序列表,用于你所列出的 API。
每个条目只能指定一个 provider 类型(可以是 identity
或 aescbc
,但不能在同一个项目中同时指定二者)。
列表中的第一个 provider 用于加密写入存储的资源。
当从存储器读取资源时,与存储的数据匹配的所有 provider 将按顺序尝试解密数据。
如果由于格式或密钥不匹配而导致没有 provider 能够读取存储的数据,则会返回一个错误,以防止客户端访问该资源。
EncryptionConfiguration
支持使用通配符指定应加密的资源。
使用 “*.<group>
” 加密 group 内的所有资源(例如以上例子中的 “*.apps
”)或使用
“*.*
” 加密所有资源。“*.
” 可用于加密核心组中的所有资源。“*.*
”
将加密所有资源,甚至包括 API 服务器启动之后添加的自定义资源。
不允许在同一资源列表或跨多个条目中使用相互重疊的通配符,因为这样一来配置的一部分将无法生效。
resources
列表的处理顺序和优先级由配置中列出的顺序决定。
如果启用了通配符,但想要针对特定资源退出加密,则可以通过添加带有资源名称的新 resources
数组项,
后跟附带 identity
提供商的 providers
数组项。例如,如果启用了 “*.*
”,
但想要排除对 events
资源的加密,则应向 resources
数组添加一个新项(以 events
为资源名称),
后跟包含 identity
的 providers 数组。新项应如下所示:
- resources:
- events
providers:
- identity: {}
确保新项列在资源数组中的通配符 “*.*
” 项之前,使新项优先。
有关 EncryptionConfiguration
结构体的更多详细信息,
请参阅加密配置 API。
如果通过加密配置无法读取资源(因为密钥已更改),唯一的方法是直接从底层 etcd 中删除该密钥。 任何尝试读取资源的调用将会失败,直到它被删除或提供有效的解密密钥。
Provider
下表描述了每个可用的 Provider:
名称 | 加密类型 | 强度 | 速度 | 密钥长度 |
---|---|---|---|---|
identity | 无 | N/A | N/A | N/A |
不加密写入的资源。当设置为第一个 provider 时,已加密的资源将在新值写入时被解密。 | ||||
aescbc | 带有 PKCS#7 填充的 AES-CBC | 弱 | 快 | 32 字节 |
由于 CBC 容易受到密文填塞攻击(Padding Oracle Attack),不推荐使用。密钥材料可从控制面主机访问。 | ||||
aesgcm | 带有随机数的 AES-GCM | 每写入 200k 次后必须轮换 | 最快 | 16、24 或者 32 字节 |
不建议使用,除非实施了自动密钥轮换方案。密钥材料可从控制面主机访问。 | ||||
kms v1 (自 Kubernetes 1.28 起弃用) | 针对每个资源使用不同的 DEK 来完成信封加密。 | 最强 | 慢(与 kms V2 相比) | 32 字节 |
通过数据加密密钥(DEK)使用 AES-GCM 加密数据;
DEK 根据 Key Management Service(KMS)中的配置通过密钥加密密钥(Key Encryption Keys,KEK)加密。
密钥轮换方式简单,每次加密都会生成一个新的 DEK,KEK 的轮换由用户控制。 阅读如何配置 KMS V1 Provider | ||||
kms v2 (beta) | 针对每个 API 服务器使用不同的 DEK 来完成信封加密。 | 最强 | 快 | 32 字节 |
通过数据加密密钥(DEK)使用 AES-GCM 加密数据;
DEK 根据 Key Management Service(KMS)中的配置通过密钥加密密钥(Key Encryption Keys,KEK)加密。
Kubernetes 默认在 API 服务器启动时生成一个新的 DEK,
然后重复使用该密钥进行资源加密。然而,如果你使用 KMS v2 并且启用了 `KMSv2KDF`
[特性门控](/zh-cn/docs/reference/command-line-tools-reference/feature-gates/),
则 Kubernetes 将转为基于秘密的种子数为每次加密生成一个新的 DEK。
无论你配置哪种方法,每当 KEK 轮换时,DEK 或种子也会轮换。
如果使用第三方工具进行密钥管理,会是一个不错的选择。
从 `v1.27` 开始,该功能处于 Beta 阶段。 阅读如何配置 KMS V2 Provider。 | ||||
secretbox | XSalsa20 和 Poly1305 | 强 | 更快 | 32 字节 |
使用相对较新的加密技术,在需要高度评审的环境中可能不被接受。密钥材料可从控制面主机访问。 |
每个 Provider 都支持多个密钥 - 在解密时会按顺序使用密钥,如果是第一个 Provider,则第一个密钥用于加密。
在 EncryptionConfig 中保存原始的加密密钥与不加密相比只会略微地提升安全级别。
请使用 kms
驱动以获得更强的安全性。
默认情况下,identity
驱动被用来对 etcd 中的 Secret 数据提供保护,而这个驱动不提供加密能力。
EncryptionConfiguration
的引入是为了能够使用本地管理的密钥来在本地加密 Secret 数据。
使用本地管理的密钥来加密 Secret 数据能够保护数据免受 etcd 破坏的影响,不过无法针对主机被侵入提供防护。 这是因为加密的密钥保存在主机上的 EncryptionConfig YAML 文件中, 有经验的入侵者仍能访问该文件并从中提取出加密密钥。
封套加密(Envelope Encryption)引入了对独立密钥的依赖,而这个密钥并不保存在 Kubernetes 中。 在这种情况下,入侵者需要攻破 etcd、kube-apiserver 和第三方的 KMS 驱动才能获得明文数据,因而这种方案提供了比本地保存加密密钥更高的安全级别。
加密你的数据
创建一个新的加密配置文件:
---
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
- configmaps
- pandas.awesome.bears.example
providers:
- aescbc:
keys:
- name: key1
# 参见以下文本了解有关 Secret 值的详情
secret: <BASE 64 ENCODED SECRET>
- identity: {} # 这个回退允许读取未加密的 Secret;
# 例如,在初始迁移期间
遵循如下步骤来创建一个新的 Secret:
生成一个 32 字节的随机密钥并进行 base64 编码。如果你在 Linux 或 macOS 上,请运行以下命令:
head -c 32 /dev/urandom | base64
将这个值放入到
EncryptionConfiguration
结构体的secret
字段中。设置
kube-apiserver
的--encryption-provider-config
参数,将其指向配置文件所在位置。你将需要把新的加密配置文件挂载到
kube-apiserver
静态 Pod。以下是这个操作的示例:- 将新的加密配置文件保存到控制平面节点上的
/etc/kubernetes/enc/enc.yaml
。 - 编辑
kube-apiserver
静态 Pod 的清单:/etc/kubernetes/manifests/kube-apiserver.yaml
, 代码范例如下:
--- # # 这是一个静态 Pod 的清单片段。 # 检查是否适用于你的集群和 API 服务器。 # apiVersion: v1 kind: Pod metadata: annotations: kubeadm.kubernetes.io/kube-apiserver.advertise-address.endpoint: 10.20.30.40:443 creationTimestamp: null labels: app.kubernetes.io/component: kube-apiserver tier: control-plane name: kube-apiserver namespace: kube-system spec: containers: - command: - kube-apiserver ... - --encryption-provider-config=/etc/kubernetes/enc/enc.yaml # 增加这一行 volumeMounts: ... - name: enc # 增加这一行 mountPath: /etc/kubernetes/enc # 增加这一行 readOnly: true # 增加这一行 ... volumes: ... - name: enc # 增加这一行 hostPath: # 增加这一行 path: /etc/kubernetes/enc # 增加这一行 type: DirectoryOrCreate # 增加这一行 ...
- 将新的加密配置文件保存到控制平面节点上的
- 重启你的 API 服务器。
你的配置文件包含可以解密 etcd 内容的密钥,因此你必须正确限制控制平面节点的访问权限,
以便只有能运行 kube-apiserver
的用户才能读取它。
重新配置其他控制平面主机
如果你的集群中有多个 API 服务器,应轮流将更改部署到每个 API 服务器。
确保在每个控制平面主机上使用相同的加密配置。
验证数据已被加密
数据在写入 etcd 时会被加密。重新启动你的 kube-apiserver
后,任何新创建或更新的 Secret
或在 EncryptionConfiguration
中配置的其他资源类别都应在存储时被加密。
如果想要检查,你可以使用 etcdctl
命令行程序来检索你的 Secret 数据内容。
以下示例演示了如何对加密 Secret API 进行检查。
创建一个新的 Secret,名称为
secret1
,命名空间为default
:kubectl create secret generic secret1 -n default --from-literal=mykey=mydata
使用
etcdctl
命令行工具,从 etcd 中读取 Secret:ETCDCTL_API=3 etcdctl get /registry/secrets/default/secret1 [...] | hexdump -C
这里的
[...]
是用来连接 etcd 服务的额外参数。例如:
ETCDCTL_API=3 etcdctl \ --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/server.crt \ --key=/etc/kubernetes/pki/etcd/server.key \ get /registry/secrets/default/secret1 | hexdump -C
输出类似于(有删减):
00000000 2f 72 65 67 69 73 74 72 79 2f 73 65 63 72 65 74 |/registry/secret| 00000010 73 2f 64 65 66 61 75 6c 74 2f 73 65 63 72 65 74 |s/default/secret| 00000020 31 0a 6b 38 73 3a 65 6e 63 3a 61 65 73 63 62 63 |1.k8s:enc:aescbc| 00000030 3a 76 31 3a 6b 65 79 31 3a c7 6c e7 d3 09 bc 06 |:v1:key1:.l.....| 00000040 25 51 91 e4 e0 6c e5 b1 4d 7a 8b 3d b9 c2 7c 6e |%Q...l..Mz.=..|n| 00000050 b4 79 df 05 28 ae 0d 8e 5f 35 13 2c c0 18 99 3e |.y..(..._5.,...>| [...] 00000110 23 3a 0d fc 28 ca 48 2d 6b 2d 46 cc 72 0b 70 4c |#:..(.H-k-F.r.pL| 00000120 a5 fc 35 43 12 4e 60 ef bf 6f fe cf df 0b ad 1f |..5C.N`..o......| 00000130 82 c4 88 53 02 da 3e 66 ff 0a |...S..>f..| 0000013a
验证存储的密钥前缀是否为
k8s:enc:aescbc:v1:
,这表明aescbc
provider 已加密结果数据。 确认etcd
中显示的密钥名称和上述EncryptionConfiguration
中指定的密钥名称一致。 在此例中,你可以看到在etcd
和EncryptionConfiguration
中使用了名为key1
的加密密钥。通过 API 检索,验证 Secret 是否被正确解密:
kubectl get secret secret1 -n default -o yaml
其输出应该包含
mykey: bXlkYXRh
,其中mydata
的内容使用 base64 进行加密, 请参阅解密 Secret 了解如何完全解码 Secret 内容。
确保所有相关数据都被加密
仅仅确保新对象被加密通常是不够的:你还希望对已经存储的对象进行加密。
例如,你已经配置了集群,使得 Secret 在写入时进行加密。 为每个 Secret 执行替换操作将加密那些对象保持不变的静态内容。
你可以在集群中的所有 Secret 上进行此项变更:
# 以能够读写所有 Secret 的管理员身份运行此命令
kubectl get secrets --all-namespaces -o json | kubectl replace -f -
上面的命令读取所有 Secret,然后使用相同的数据更新这些 Secret,以便应用服务端加密。
如果由于冲突写入而发生错误,请重试该命令。 多次运行此命令是安全的。
对于较大的集群,你可能希望通过命名空间或更新脚本来对 Secret 进行划分。
轮换解密密钥
在不发生停机的情况下更改 Secret 需要多步操作,特别是在有多个 kube-apiserver
进程正在运行的高可用环境中。
- 生成一个新密钥并将其添加为所有服务器上当前 provider 的第二个密钥条目
- 重新启动所有
kube-apiserver
进程以确保每台服务器都可以使用新密钥进行解密 - 将新密钥设置为
keys
数组中的第一个条目,以便在配置中使用其进行加密 - 重新启动所有
kube-apiserver
进程以确保每个服务器现在都使用新密钥进行加密 - 运行
kubectl get secrets --all-namespaces -o json | kubectl replace -f -
以用新密钥加密所有现有的 Secret - 在使用新密钥备份 etcd 后,从配置中删除旧的解密密钥并更新所有密钥
当只运行一个 kube-apiserver
实例时,第 2 步可以忽略。
解密所有数据
要禁用静态加密,请将 identity
provider
作为配置中的第一个条目并重新启动所有 kube-apiserver
进程。
---
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- identity: {}
- aescbc:
keys:
- name: key1
secret: <BASE 64 ENCODED SECRET>
然后运行以下命令以强制解密所有 Secret:
kubectl get secrets --all-namespaces -o json | kubectl replace -f -
配置自动重新加载
你可以配置加密提供程序配置的自动重新加载。
该设置决定了 API 服务器
是仅在启动时加载一次为 --encryption-provider-config
指定的文件,
还是在每次你更改该文件时都自动加载。
启用此选项可允许你在不重启 API 服务器的情况下更改静态加密所需的密钥。
要允许自动重新加载,
可使用 --encryption-provider-config-automatic-reload=true
运行 API 服务器。