本文展示如何启用和配置静态 Secret 数据的加密。
需要 etcd v3.0 或者更高版本
要加密自定义资源,你的集群必须运行 Kubernetes v1.26 或更高版本。
在 Kubernetes v1.27 或更高版本中可以使用通配符配置资源加密。
kube-apiserver
的参数 --encryption-provider-config
控制 API 数据在 etcd 中的加密方式。
该配置作为一个名为 [EncryptionConfiguration
] 的 API 提供。--encryption-provider-config-automatic-reload
布尔参数决定了磁盘内容发生变化时是否应自动重新加载--encryption-provider-config
设置的文件。这样可以在不重启 API 服务器的情况下进行密钥轮换。
下面提供了一个示例配置。
重要: 对于高可用配置(有两个或多个控制平面节点),加密配置文件必须相同!
否则,kube-apiserver
组件无法解密存储在 etcd 中的数据。
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
- configmaps
- pandas.awesome.bears.example
providers:
- 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'
providers:
- aescbc:
keys:
- name: key2
secret: c2VjcmV0IGlzIHNlY3VyZSwgb3IgaXMgaXQ/Cg==
- resources:
- '*.*'
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 中删除该密钥。
任何尝试读取资源的调用将会失败,直到它被删除或提供有效的解密密钥。
名称 | 加密类型 | 强度 | 速度 | 密钥长度 | 其它事项 |
---|---|---|---|---|---|
identity |
无 | N/A | N/A | N/A | 不加密写入的资源。当设置为第一个 provider 时,资源将在新值写入时被解密。 |
secretbox |
XSalsa20 和 Poly1305 | 强 | 更快 | 32 字节 | 较新的标准,在需要高度评审的环境中可能不被接受。 |
aesgcm |
带有随机数的 AES-GCM | 必须每 200k 写入一次 | 最快 | 16、24 或者 32字节 | 建议不要使用,除非实施了自动密钥循环方案。 |
aescbc |
填充 [PKCS#7] 的 AES-CBC | 弱 | 快 | 32 字节 | 由于 CBC 容易受到密文填塞攻击(Padding Oracle Attack),不推荐使用。 |
kms v1 |
使用信封加密方案:数据使用带有 [PKCS#7] 填充的 AES-CBC(v1.25 之前),从 v1.25 开始使用 AES-GCM 通过数据加密密钥(DEK)加密,DEK 根据 Key Management Service(KMS)中的配置通过密钥加密密钥(Key Encryption Keys,KEK)加密 | 最强 | 快 | 32 字节 | 建议使用第三方工具进行密钥管理。为每个加密生成新的 DEK,并由用户控制 KEK 轮换来简化密钥轮换。从 v1.27 开始,该功能处于 Beta 阶段。系统在启动时生成一个新的 DEK 并重复使用它进行加密。当 KEK 被轮转时,DEK 也会被轮转。[配置 KMS V2 provider]。 |
每个 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: <BASE 64 ENCODED SECRET>
- identity: {}
遵循如下步骤来创建一个新的 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
,
apiVersion: v1
kind: Pod
metadata:
annotations:
kubeadm.kubernetes.io/kube-apiserver.advertise-address.endpoint: 10.10.30.4:6443
creationTimestamp: null
labels:
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 # <-- 增加这一行
...
你的配置文件包含可以解密 etcd 内容的密钥,因此你必须正确限制控制平面节点的访问权限,
以便只有能运行 kube-apiserver
的用户才能读取它。
数据在写入 etcd 时会被加密。重新启动你的 kube-apiserver
后,任何新创建或更新的 Secret
或在 EncryptionConfiguration
中配置的其他资源类型都应在存储时被加密。
如果想要检查,你可以使用 etcdctl
命令行程序来检索你的 Secret 数据内容。
创建一个新的 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
的内容是被加密过的,
请参阅[解密 Secret]
了解如何完全解码 Secret 内容。
由于 Secret 是在写入时被加密,因此对 Secret 执行更新也会加密该内容。
kubectl get secrets --all-namespaces -o json | kubectl replace -f -
上面的命令读取所有 Secret,然后使用服务端加密来更新其内容。
如果由于冲突写入而发生错误,请重试该命令。
对于较大的集群,你可能希望通过命名空间或更新脚本来对 Secret 进行划分。
在不发生停机的情况下更改 Secret 需要多步操作,特别是在有多个 kube-apiserver
进程正在运行的高可用环境中。
kube-apiserver
进程以确保每台服务器都可以使用新密钥进行解密keys
数组中的第一个条目,以便在配置中使用其进行加密kube-apiserver
进程以确保每个服务器现在都使用新密钥进行加密kubectl get secrets --all-namespaces -o json | kubectl replace -f -
当只运行一个 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 -
进一步学习 [EncryptionConfiguration 配置 API ]。