LabHub
学习 学习路径 课程

KCSA — Kubernetes 安全助理

密钥 — 与其藏得住,不如废得快

在 LabHub 中继续学习

一句话总结

衡量秘密管理成熟度,不应看“隐藏得多好”,而应看**“假定已经泄露,废止并替换它需要几分钟”**。这一个视角会改变全部优先级。

概念图: “假定已经泄露,废止并替换它需要几分钟” · 以明文进入源代码仓库。 · 异常处理器把 process.env 整体转储到日志 · 日志收集系统的访问权限通常比应用程序更广。

为什么需要它

标准建议是:“不要硬编码到代码,改用环境变量;把 .env 加入 .gitignore。” 这条建议只阻止一种威胁:以明文进入源代码仓库。

真实泄露途径多得多:进程内存、崩溃报告、CI 日志、镜像层、APM 仪表板、错误暴露的调试端点。仅改用环境变量,无法阻止其中任何一种。

工作原理

环境变量是传递方式,不是存储位置

加载 .env 文件后再删除也没有用。值已经由内核为每个进程保存,在 Linux 中可以像文件一样读取。

tr '\0' '\n' < /proc/2841/environ

同一 UID 或 root 可以直接读取。即使在容器中也没有不同:同一 Pod 的 sidecar、能够访问节点的人,以及通过 kubectl debug --target=app 接入的调试容器,都能看到相同内容。

还应了解环境变量泄露的其他路径。

Kubernetes Secret 的实际位置

选择存储方式的三个标准

“是否加密”不是一个足够好的标准。应改问三个问题。

  1. 有多少条暴露路径
  2. 废止与替换需要几分钟
  3. 能否知道谁在何时读取过

按照这些标准,存储方式的顺序不是按“加密强度”排列,而是按**“事故发生时响应时间越来越短”**排列。

最佳方案是不创建静态密钥

轮换不是运维流程,而是设计属性

最重要的一句话是:如果代码假定只有一把密钥,即使使用最好的秘密管理器,实际轮换也不会发生。

如果 Webhook 验证只查看一个 SECRET,更换密钥的瞬间,所有由旧密钥签名的请求都会被拒绝。发送方与接收方无法原子地同时改变,轮换就会变得事实上不可行,最终没人再执行轮换。

解决方法是分离签名与验证。签名只使用一把当前密钥,而验证要对整个有效密钥集合逐一尝试。环境变量名称从单数变复数(WEBHOOK_SECRETWEBHOOK_SECRETS)就是这种设计的标志。JWT 中的 kid 头 + JWKS 完成同样工作:签发者先在 JWKS 中公布新密钥,验证方取得后再切换签名密钥。

还需要可观测性。以 key_index 等标签记录使用哪把密钥验证成功,就能通过图表而不是猜测确认“使用旧密钥签名的请求是否真的消失”,然后安全删除旧密钥。

响应时间优先于检测

预提交钩子是本地设置,只需一次 git commit --no-verify 就能绕过;新成员还可能在未安装钩子的情况下工作数天。真正的强制力来自服务器端推送保护

在投资检测之前,还应先问:“假定这个秘密已经暴露,废止和替换它需要几分钟?” 如果数字很大,无论检测多严密,事故损失都不会降低。

实际现场中的情况

作者总结的事故响应顺序,是这一观点的集中体现。秘密已经提交时,最常见反应是重写历史,但顺序错了。

推送到公开仓库的凭据会在数秒到数分钟内被自动扫描器收集。即使重写历史,仍会残留在:fork 仓库、开发者已经克隆的本地副本、平台保留的 PR 引用(知道提交哈希时往往仍可访问)、搜索引擎和代码搜索服务缓存、CI 缓存与制品中。此外,清理后只要有人从旧克隆直接推送,被删除的提交又会复活。

因此正确顺序是:废止 → 调查影响 → 清理历史。对于 AWS 密钥,可按“创建新密钥 → 部署 → 把旧密钥设为 Inactive → 删除”的顺序实现无中断轮换,再用 CloudTrail 调查该密钥的使用位置。陌生 IP、平时不用的区域、意外 API 调用是判断入侵的信号。“重写历史不是逆转泄露的措施,而是减少再次发生的卫生工作。”

即使使用 External Secrets Operator,也不能免除集群安全卫生。ExternalSecret 清单不含具体值,可以提交到 Git;但operator 最终仍会把值实体化为集群 Secret,所以 RBAC 和静态存储加密问题依然存在。

下一项实验要做什么

下一项实验将亲自添加 PSA 标签,确认违反 restricted 的 Pod 被拒绝。秘密本身主要在测验中讨论,但请再次确认,前面模块实验编写的 EncryptionConfiguration 正是这里所说“etcd 明文存储”问题的答案。