在 Kubernetes 中创建Secret时,其值会以 Base64 形式显示。因此,初次接触的人很容易误以为 Secret 已经加密。
然而,Base64 是一种编码,而非加密。任何人都可以通过以下命令查看原始值:
echo 'bXlkYXRh' | base64 --decode
更重要的问题是,默认配置的kube-apiserver在将 Secret 存储到 etcd 时,不会应用单独的静态加密(encryption at rest)。如果 etcd 备份文件、数据目录或磁盘快照泄露,敏感数据可能会在未经 RBAC 授权的情况下暴露。
本文将以通过 kubeadm 配置的 Kubernetes 集群为例,亲自验证以下过程:
- 在应用加密前检查 etcd 中存储的 Secret
- 生成 32 字节加密密钥
- 编写
EncryptionConfiguration - 将配置连接到
kube-apiserver静态 Pod - 在 etcd 中验证新 Secret 是否已加密
- 重写现有 Secret 以全部加密
- 密钥轮换和生产环境中的注意事项
本教程是基于 Kubernetes 官方文档的Encrypting Confidential Data at Rest章节编写的。

API 服务器加密并将 Secret 存储到 etcd 的流程
首先需要理解:加密什么?
Kubernetes 静态加密并非用于加密挂载到 Pod 的文件或应用程序数据库的功能。
加密对象是通过 Kubernetes API 创建并持久存储在 etcd 中的 API 资源。典型地,可以指定以下资源:
secretsconfigmaps- 用户自定义资源 CRD 对象
- 其他存储在 etcd 中的 Kubernetes API 资源
数据流如下:
kubectl 또는 애플리케이션
↓
kube-apiserver
↓ EncryptionConfiguration에 따라 암호화
etcd
当客户端通过 Kubernetes API 查询 Secret 时,kube-apiserver会解密 etcd 中的密文并返回正常的 API 响应。因此,用户可以像以前一样使用kubectl get secret。
静态加密是防范 etcd 磁盘或备份泄露的防御层。它不能替代以下安全控制:
| 安全控制 | 保护区域 |
|---|---|
| Kubernetes RBAC | 控制谁可以通过 API 读取 Secret |
| TLS | 保护 API 服务器和 etcd 之间的传输路径 |
| etcd 静态加密 | 保护存储在 etcd 数据和备份中的值 |
| 磁盘加密 | 保护整个节点磁盘或卷 |
| KMS/HSM | 在集群外部保护加密密钥并管理其生命周期 |
仅应用其中一项并不能消除对其他层的需求。
实践环境和注意事项
本文假设以下环境:
- 通过 kubeadm 配置的 Kubernetes 集群
- etcd 3.x
- 每个控制平面上
kube-apiserver作为静态 Pod 运行 - 可使用
sudo访问控制平面节点 - 可使用
kubectl和etcdctl
通过以下命令检查集群版本:
kubectl version
此操作会更改控制平面的核心设置。在生产环境中,务必备份 etcd 快照和当前静态 Pod 清单,并确认恢复过程后再进行。
sudo cp -a /etc/kubernetes/manifests/kube-apiserver.yaml
/root/kube-apiserver.yaml.before-encryption
如果是高可用集群,所有控制平面节点必须具有相同的加密设置和密钥。如果节点之间的设置不同,可能会出现某些 API 服务器写入的数据无法被其他 API 服务器解密的情况。
第 1 步:检查是否已应用加密
首先,检查kube-apiserver是否包含--encryption-provider-config参数。
sudo grep -- '--encryption-provider-config'
/etc/kubernetes/manifests/kube-apiserver.yaml
如果没有输出结果,则表示 API 服务器级别的静态加密未启用。
即使存在参数,也不要掉以轻心。如果EncryptionConfiguration的第一个 provider 是identity,则新数据将以明文形式存储。这是因为 provider 的顺序决定了读写操作。
- 写入时:使用列表中的第一个 provider
- 读取时:从上到下尝试,使用第一个可解密的 provider
在迁移过程中,应将加密 provider 放在首位,并将用于读取旧明文数据的identity放在其后。
第 2 步:在应用前创建 Secret 并检查 etcd 值
为了进行比较,创建一个测试 Secret。
kubectl create secret generic secret-before
--namespace default
--from-literal=mykey=mydata
通过 Kubernetes API 查询时,值显示为 Base64。
kubectl get secret secret-before -n default -o yaml
预期数据如下:
data:
mykey: bXlkYXRh
现在,在控制平面节点上检查 etcd 的原始值。
sudo 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/secret-before | hexdump -C
如果未应用加密,您应该能够在输出中识别 Secret 的结构和对应于mydata的数据。这就是为什么仅凭 Base64 显示无法保护 etcd 存储数据的原因。
etcdctl连接证书和端点可能因集群配置而异。如果使用外部 etcd,则必须指定实际的端点和客户端证书。
第 3 步:生成 32 字节加密密钥
官方文档建议在使用本地加密 provider 时,生成一个 32 字节的随机密钥并进行 Base64 编码。
KEY=$(head -c 32 /dev/urandom | base64)
不建议将密钥打印到屏幕、复制到 shell 历史记录、即时通讯工具或普通文档中。拥有此密钥的人可以解密 etcd 中的加密数据。
创建用于存储加密配置的目录。
sudo install -d -m 700 /etc/kubernetes/enc
第 4 步:编写 EncryptionConfiguration
本次实践中,为了理解 Kubernetes 官方教程的存储格式和验证流程,我们将使用aescbc provider 加密 Secret 和 ConfigMap。
注意:当前的 Kubernetes 官方文档将
aescbc归类为弱加密方式,因为它可能受到 CBC 填充预言攻击,不建议在生产环境中使用。以下配置仅用于实践,以学习静态加密的工作原理和迁移过程。对于新的生产环境,应优先考虑 KMS v2。
sudo tee /etc/kubernetes/enc/enc.yaml >/dev/null <<EOF
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
- configmaps
providers:
- aescbc:
keys:
- name: key1
secret: ${KEY}
- identity: {}
EOF
unset KEY
sudo chmod 600 /etc/kubernetes/enc/enc.yaml
sudo chown root:root /etc/kubernetes/enc/enc.yaml
provider 的顺序很重要。
providers:
- aescbc: ...
- identity: {}
在此配置中,新创建或更新的数据将通过aescbc加密。由于末尾的identity provider,现有明文存储的数据仍可读取。
反之,如果将identity放在首位,API 服务器将继续以明文形式存储新数据。
aescbc、aesgcm、secretbox、KMS 中如何选择?
Kubernetes 支持多种加密 provider,但其操作特性各不相同。
| Provider | 特点 | 操作注意事项 |
|---|---|---|
aescbc |
配置简单的 AES-CBC 本地对称密钥方式 | 官方文档将其归类为弱方式,不建议在生产环境中使用 |
aesgcm |
提供认证加密 | 需要密钥轮换,以避免使用同一密钥写入超过约 20 万条记录 |
secretbox |
基于 XSalsa20 和 Poly1305 | 密钥存在于本地配置文件中 |
kms v2 |
外部 KMS 管理密钥加密密钥的信封加密 | 提供强大的密钥隔离,需要单独的插件和可用性设计 |
将本地密钥直接存储在EncryptionConfiguration中的方式优于不使用加密,但如果控制平面文件和 etcd 数据同时被窃取,防御能力将受到限制。对于重要的生产环境,应考虑与云 KMS 或 HSM 集成。
第 5 步:将加密设置连接到 kube-apiserver
编辑静态 Pod 清单。
sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml
在kube-apiserver的command中添加以下参数:
- --encryption-provider-config=/etc/kubernetes/enc/enc.yaml
在同一容器的volumeMounts中添加只读挂载。
volumeMounts:
- name: enc
mountPath: /etc/kubernetes/enc
readOnly: true
在 Pod spec.volumes中添加 hostPath 卷。
volumes:
- name: enc
hostPath:
path: /etc/kubernetes/enc
type: DirectoryOrCreate
保存文件后,kubelet 将检测到静态 Pod 更改并重新创建kube-apiserver。
kubectl -n kube-system get pod
-l component=kube-apiserver -w
检查 API 服务器是否恢复正常状态。
kubectl get --raw='/readyz?verbose'
如果发生错误,请检查 kubelet 日志和容器运行时日志,并使用预先备份的清单进行恢复。
sudo journalctl -u kubelet -n 200 --no-pager
第 6 步:验证新 Secret 是否已加密存储
应用加密设置后,创建一个新的 Secret。
kubectl create secret generic secret-after
--namespace default
--from-literal=mykey=mydata
直接从 etcd 读取值。
sudo 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/secret-after | hexdump -C
输出中应显示以下前缀:
k8s:enc:aescbc:v1:key1:
各项目的含义如下:
| 值 | 含义 |
|---|---|
k8s:enc |
Kubernetes 静态加密数据 |
aescbc |
使用的 provider |
v1 |
存储格式版本 |
key1 |
用于加密的密钥名称 |
现在,验证通过 API 进行的解密是否也正常。
kubectl get secret secret-after
--namespace default
-o jsonpath='{.data.mykey}' | base64 --decode
echo
如果输出以下值,则表示正常:
mydata
这意味着 etcd 中存储的是密文,但拥有权限的用户仍然可以通过 Kubernetes API 正常使用 Secret。
第 7 步:重写现有 Secret 以全部加密
这里有一个最容易被忽视的部分。
即使启用了加密设置,etcd 中已存储的数据也不会自动重新加密。只有在设置应用后新创建或更新的资源才会加密。
如果将所有现有 Secret 以相同内容重新保存,API 服务器将应用加密 provider 并将其记录到 etcd 中。
kubectl get secrets --all-namespaces -o json
| kubectl replace -f -
如果发生写入冲突,再次执行也是安全的。在大型集群中,最好按命名空间分批执行 Secret 更新,而不是一次性更新所有 Secret。
如果 ConfigMap 也被指定为加密对象,则需要单独重新保存。
kubectl get configmaps --all-namespaces -o json
| kubectl replace -f -
迁移后,检查最初创建的secret-before是否也具有加密前缀。
sudo 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/secret-before | hexdump -C
第 8 步:是否移除明文回退?
如果已充分验证所有目标数据均已加密,则可以移除identity provider,以防止 API 服务器读取明文存储的数据。
providers:
- aescbc:
keys:
- name: key1
secret: <BASE64_ENCODED_32_BYTE_KEY>
然而,这一步需要谨慎。如果仍有任何未加密的资源,API 服务器将无法读取该对象。
因此,必须在满足以下所有条件后才能进行:
- 所有目标资源是否已重新保存?
- 所有控制平面是否使用相同的配置?
- 是否已在 etcd 中对加密前缀进行抽样或全面检查?
- 加密密钥是否已安全备份?
- 恢复过程是否已实际验证?
在运营初期,更安全的做法是保留identity作为读取回退,并在迁移验证完成后,通过单独的更改操作将其移除。
第 9 步:不中断地轮换加密密钥的原理
密钥轮换并非立即删除现有密钥的操作。
Kubernetes 官方文档指导的顺序如下:
- 将新密钥作为现有 provider 的第二个密钥添加到所有控制平面。
- 应用设置,使所有 API 服务器都能读取新密钥。
- 安全备份新密钥。
- 将新密钥提升到密钥列表的首位。
- 应用设置,使所有 API 服务器在新写入时使用新密钥。
- 使用
kubectl replace重新保存现有 Secret。 - 验证所有数据都已使用新密钥加密后,移除旧密钥。
例如,中间阶段的配置将如下所示:
providers:
- aescbc:
keys:
- name: key2
secret: <NEW_BASE64_KEY>
- name: key1
secret: <OLD_BASE64_KEY>
- identity: {}
第一个密钥用于加密新数据,后面的密钥用于解密旧数据。如果在所有数据都用新密钥重新保存之前删除旧密钥,则无法解密现有 Secret。
Kubernetes 还支持通过--encryption-provider-config-automatic-reload=true选项自动重新加载配置文件。启用后,API 服务器会定期检查文件更改,因此无需在每次密钥轮换时重启 API 服务器。但是,实际应用时间需要通过相关指标进行确认。
高可用控制平面中必须遵循的顺序
如果存在多个控制平面,请不要同时更改所有节点,而应逐个顺序应用。
| 步骤 | 检查事项 |
|---|---|
| 1 | 将相同的enc.yaml安全地部署到所有节点 |
| 2 | 更改第一个 API 服务器设置并确认正常恢复 |
| 3 | 逐个更改其余 API 服务器 |
| 4 | 通过负载均衡器检查 API 请求是否正常 |
| 5 | 检查新数据的加密前缀 |
| 6 | 重新保存现有数据 |
在滚动更改期间,所有 API 服务器都必须能够读取当前的 etcd 数据。在将新密钥部署到所有节点之前,不应将其提升为第一个写入密钥。
生产环境中推荐 KMS 的原因
到目前为止使用的aescbc方式对于按照官方流程实践静态加密操作很有用。然而,目前的官方文档将此方式归类为弱加密,并且加密密钥也位于/etc/kubernetes/enc/enc.yaml中。
如果攻击者同时获取 etcd 数据和控制平面配置文件,他们就可以同时获取密文和解密密钥。
使用 KMS provider 可以将实际数据加密密钥保护在集群外部的 KMS 或 HSM 中。此外,还可以更容易地利用以下操作功能:
- 密钥访问权限分离
- 集中式密钥轮换
- 密钥使用审计日志
- 利用 HSM 或云管理型 KMS
- Kubernetes 管理员和密钥管理员的角色分离
因此,本文的本地aescbc配置应被视为理解静态加密工作原理的起点,对于需要高安全级别的生产集群,建议考虑使用 KMS provider 进行数据加密。
常见错误
1. 认为 Secret 的 Base64 是加密
Base64 是一种任何人都可以恢复的表示方式。它与 etcd 静态加密无关。
2. 将 identity 作为第一个 provider
可以读取,但新数据将以明文形式存储。写入 provider 是列表中的第一个项目。
3. 仅应用设置而不重新保存现有 Secret
现有数据不会自动转换。需要像kubectl replace这样的重写过程。
4. 为每个节点部署不同的密钥
在 HA 环境中,API 服务器的解密能力可能因节点而异,导致间歇性故障。
5. 未备份密钥就删除旧密钥
用该密钥加密的数据将无法恢复。如果只有 etcd 备份而没有密钥备份,则无法恢复加密的 Secret。
6. 对加密配置文件设置宽松权限
本地 provider 的密钥实际上就是解密权限。应限制为只有 root 和 API 服务器的执行主体才能读取。
实践总结
本次实践中确认的核心要点如下:
- Kubernetes Secret 的 Base64 不是加密。
- 默认设置下,API 资源可能在 etcd 中以未加密方式存储。
EncryptionConfiguration的第一个 provider 会加密新数据。identity是迁移过程中用于读取现有明文数据的回退机制。- 仅应用设置并不能加密现有数据。
- 必须在 etcd 中检查
k8s:enc:aescbc:v1:key1:前缀。 - 密钥和 etcd 备份应一起管理,但访问权限应分离。
- 在生产环境中,外部 KMS 集成提供更强的密钥保护。
静态加密并不能阻止攻击者获取 Kubernetes API 权限。相反,它降低了 etcd 磁盘、快照和备份文件泄露时敏感数据立即暴露的风险。
Kubernetes 安全性在 RBAC、网络保护、审计日志、备份安全和静态加密协同工作时才能完善。
发表回复