4. Secret的静态加密
Secret 默认以 Base64 编码形式存放在 etcd 中,Base64 只是编码而非加密,任何能读取 etcd 数据文件或备份的人都能直接还原出明文凭据。静态加密(Encryption at Rest)解决的就是这一层风险:在 API Server 写入 etcd 之前对 Secret 等资源加密,读取时再解密。它保护的是"落盘数据",不改变传输层 TLS 和 RBAC 的职责边界。
一、EncryptionConfiguration 的结构与语义
静态加密由 API Server 的启动参数 --encryption-provider-config 指向一个 YAML 配置文件来启用。该文件的核心是 resources 列表,每一项声明"哪些资源"用"哪些 provider"加密,而 provider 列表的顺序决定了行为:第一个 provider 用于写入加密,所有 provider 依次尝试用于读取解密。这意味着轮换密钥时,把新密钥放在列表首位、旧密钥保留在后面,即可实现"新写用新钥、旧数据仍可读"的平滑过渡。
常见的 provider 有三类:identity(不加密,明文写入,仅用于读取历史明文数据)、aescbc/aesgcm(本地密钥,密钥直接写在配置里)、kms(调用外部 KMS 服务,本地只保存密文形式的 DEK)。下面是一个同时覆盖三类 provider 的示例配置:
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
- configmaps
providers:
- kms:
name: myKMS
endpoint: unix:///var/run/kmsplugin/socket.sock
cachesize: 1000
timeout: 3s
- aescbc:
keys:
- name: key2
secret: c2VjcmV0IGtleTIgYmFzZTY0IGVuY29kZWQ=
- name: key1
secret: c2VjcmV0IGtleTEgYmFzZTY0IGVuY29kZWQ=
- identity: {}
注意 aescbc 的 keys 也是有序的:第一个 key 用于加密新数据,其余 key 用于解密旧数据。密钥必须是 32 字节的随机值再做 Base64 编码,可用 head -c 32 /dev/urandom | base64 生成。示例中的 secret 值仅为占位,实际部署必须替换为真实随机密钥,且不得提交到代码仓库。
二、KMS Provider 集成:信封加密
本地 aescbc 的弱点是密钥与密文同处一台机器,一旦节点被攻破,加密形同虚设。KMS Provider 采用信封加密(Envelope Encryption):API Server 为每个资源生成一个数据加密密钥 DEK,用 DEK 加密资源本身,再把 DEK 发送给外部 KMS 加密成 KEK 密文,最终 etcd 中保存的是"资源密文 + 被 KMS 加密的 DEK"。主密钥始终留在 KMS 内部,API Server 本地不落盘任何明文密钥。
集成方式有两种:v1 通过 Unix Domain Socket 与 kubelet 上的 KMS 插件通信(即上面的 endpoint: unix://...),v2 则直接以 gRPC 连接 KMS,支持密钥版本与健康状态上报。生产环境建议使用 v2,因为它能感知 KMS 不可用并触发降级告警,而 v1 在 KMS 故障时可能静默失败。
三、etcd 加密实践与验证方法
配置生效后,最直接的验证是绕过 API Server 直接读 etcd。使用 etcdctl 读取原始键值:
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/my-secret | hexdump -C | head
若加密生效,输出开头会出现 k8s:enc:kms:v2:myKMS: 或 k8s:enc:aescbc:v1:key2: 这样的前缀,其后是二进制密文;若仍是可读的 YAML 文本,说明该资源未被加密,需检查 resources 是否包含 secrets、API Server 是否真正重启加载了新配置。注意:启用加密不会自动加密已有数据,历史 Secret 仍是明文,必须通过重写操作触发重新加密。
重写所有 Secret 的常用做法是读取后原样替换:
kubectl get secrets --all-namespaces -o json \
| kubectl replace -f -
该命令会逐个更新 Secret,每次更新都走一次写入路径,从而用当前首选 provider 重新加密。执行后应再次用 etcdctl 抽查若干命名空间,确认前缀已变为新 provider 标识。对于大规模集群,建议分批按命名空间执行,避免 API Server 瞬时压力过高。
四、密钥轮换策略
轮换的目标是限制单个密钥的生命周期与影响面。推荐策略如下:
- 轮换周期:KMS 主密钥建议 90 天轮换一次,本地 aescbc 密钥建议不超过 30 天,具体以合规要求为准。
- 两阶段执行:先"加新留旧"(新密钥写入首位,旧密钥保留用于解密),完成全量重写后,再"删旧"(移除旧密钥并重启)。任何一步跳过都可能导致旧数据无法解密。
- 变更窗口:重启 API Server 会短暂中断写入,应在低峰期滚动重启各控制平面节点,并确认所有节点加载了同一份配置。
- 备份与回滚:轮换前备份 etcd 快照,并保留旧密钥的安全副本;一旦新配置异常,可快速回退到旧配置恢复服务。
五、常见排障与判断依据
实践中几类高频问题值得留意:其一,配置写入了但未重启 API Server,加密未生效,etcd 中仍是明文;其二,resources 只写了 secrets 却遗漏了 configmaps 等同样含敏感信息的资源;其三,KMS 服务不可达时,若配置中 identity 排在前面,会静默降级为明文写入,因此务必把 identity 放在最后一位,仅用于兼容读取。判断加密是否真正生效,唯一可靠的依据是直接观察 etcd 中的原始字节,而不是依赖配置文件的"看起来正确"。
k8s:enc: 前缀;② 确认所有控制平面节点使用同一份 EncryptionConfiguration;③ 确认轮换后旧密钥仍保留且可解密;④ 确认 KMS 健康检查与告警已接入监控。四项全部通过,静态加密才算真正落地。