生产命名空间里的安全配置漂移,很少来自一次故意的大改,更多是被跳过流水线的“顺手修一下”慢慢磨掉的:有人用 kubectl edit 临时给 Pod 加了 hostNetwork: true 排查网络,有人用 kubectl patch 删掉一条 NetworkPolicy 让新服务先通,还有人把 hostPath 卷加回来跑一个一次性任务忘了撤。这些操作在变更窗口里可能没有触发任何流水线审批,却让基线悄悄失效。本文面向需要维护生产集群安全底座的云安全工程师、平台开发者和集群运维人员,给出一条从“定期扫描”推进到“漂移可追踪、修复可审计”的路径:先用只读快照和变更发现把漂移变成证据,再设计一条默认关闭、权限独立的受控修复通道。范围限于 Kubernetes 原生对象和通用策略工具的通用方法,不评价具体商业 KSPM 产品,也不假设所有漂移都能自动修回来。

基线与变更发现:先把“读”管住
漂移检测的第一步不是写修复脚本,而是回答“以什么为准、多久对一次、证据从哪里来”。基线如果只是一次性扫描报告,就无法区分“从未合规”和“曾经合规但后来被改掉”。可追踪的基线需要同时具备两个属性:范围明确、时间上连续。
基线范围与快照频率
基线对象不宜追求全量覆盖,先收敛到安全敏感字段。对 Pod 模板,重点看 securityContext 下的 privileged、allowPrivilegeEscalation、runAsNonRoot、readOnlyRootFilesystem 和 capabilities.drop;对卷,重点看 hostPath、hostNetwork、hostPort、hostPID;对命名空间边界,重点看 NetworkPolicy 的默认拒绝规则、ServiceAccount 的 automount 设置以及 RoleBinding 的 subject 范围。Pod Security Standards 的 restricted 级别可以直接作为一组现成的判定标签,在命名空间上通过 pod-security.kubernetes.io/enforce、audit、warn 三组标签分别控制拦截、审计和告警行为,官方文档对各版本的标签行为有明确说明,落地前应当按集群版本核对。
快照本身用只读导出即可,不需要特殊工具:
kubectl get deploy,sts,ds,pod,networkpolicy,sa,rolebinding \
-n production -o yaml > snapshots/production-$(date +%Y%m%d%H%M).yaml频率按命名空间的变更节奏分层:核心业务命名空间可以每小时一次,低频命名空间每天一次。快照要落到带版本控制的位置,和 Git 中声明的期望状态并存,后面修复 diff 时可以直接对照两者。快照里不需要保存 Secret 内容,导出前用标签过滤掉 kind: Secret 的对象,避免把敏感材料写进二级存储。
审计日志与策略扫描怎么互补
审计日志回答“谁在什么时候改了什么”,策略扫描回答“当前状态离基线有多远”。只做扫描,你知道某个 Pod 有 hostPath,但不知道是哪个操作者、哪条 kubectl 命令带进来的;只做审计,你能看到 patch 动作,但不知道整个命名空间当前的暴露面全貌。两者需要一起看。
审计策略里至少把安全敏感对象的写操作记到 Request 级别,并把请求体保留下来:
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Request
verbs: ["patch", "update", "create"]
resources:
- group: ""
resources: ["pods", "serviceaccounts"]
- group: "apps"
resources: ["deployments", "statefulsets", "daemonsets"]
- group: "networking.k8s.io"
resources: ["networkpolicies"]
- group: "rbac.authorization.k8s.io"
resources: ["rolebindings", "clusterrolebindings"]
- level: Metadata审计策略通过 apiserver 启动参数或托管控制面提供的审计配置入口挂载,具体入口因部署形态不同而不同,落地时应以所用发行版的官方文档为准。策略扫描则可以用 OPA Gatekeeper 或 Kyverno 的 audit 模式,把违规对象列出来作为存量度量,先跑 audit 再考虑 enforce,避免一上来就 deny 把正常发布卡住。
告警降噪:按对象和字段收敛
审计日志和扫描结果直接转发会很快淹没值班同学。降噪的核心不是调阈值,而是先把“关心的变更”定义清楚。一个实用的做法是维护一份字段白名单,只对白名单内的字段变化触发告警:Pod 模板的 securityContext、hostNetwork、hostPath、NetworkPolicy 的 podSelector 和 ingress/egress 规则、RoleBinding 的 roleRef。同一对象在 15 分钟内的多次 patch 合并为一条事件,附上首次和最后一次的 diff。区分“已授权变更”和“漂移”也很关键:来自 CI ServiceAccount 且带有审批标签的变更可以降为记录,来自个人账号且不在发布窗口内的 kubectl edit 才升级为告警。
受控修复通道:默认关闭、权限独立
检测组件一旦拿到写权限,它本身就成了新的攻击面。正确的结构是让检测面只读、修复面单独授权,两条通道在 RBAC 层面物理分开。
权限隔离
检测组件使用的 ServiceAccount 只绑定 get、list、watch:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: drift-detector-readonly
rules:
- apiGroups: [""]
resources: ["pods", "services", "serviceaccounts"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets", "daemonsets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["networking.k8s.io"]
resources: ["networkpolicies"]
verbs: ["get", "list", "watch"]
- apiGroups: ["rbac.authorization.k8s.io"]
resources: ["rolebindings"]
verbs: ["get", "list", "watch"]修复通道使用另一个 ServiceAccount,初始不绑定任何写角色,需要修复时由审批流程临时绑定一个范围受限的 Role,只允许操作目标命名空间的目标资源类型,并给这个绑定设置到期时间。修完后解绑,而不是长期保留。这样即使检测组件被攻破,攻击者能拿到的也只是集群的读视图。

dry-run 与 diff 审批
修复动作在落到集群之前,应该先产出一份可审阅的 diff。客户端 dry-run 只在本地构建对象,服务端 dry-run 会把请求送进完整的准入链但不持久化到 etcd,能暴露校验错误、准入 webhook 拒绝和 RBAC 权限问题,也能显示 mutating webhook 实际改了什么。对于安全字段修复,建议组合使用:
kubectl diff -f remediation.yaml
kubectl apply -f remediation.yaml --dry-run=server -o yaml > preview.yamlkubectl diff 给出当前对象与期望状态之间的人类可读差异,服务端 dry-run 的输出则用来确认准入链没有把修复后的对象又改回去。审批环节关注三件事:diff 是否只触及目标字段、是否有 mutating webhook 注入了额外字段、修复后的对象是否仍然满足命名空间的 Pod Security 标签要求。审批记录和 diff 一起归档,后面复盘时能还原“当时批准的是什么”。
灰度应用与回滚
修复不要按命名空间一把推。先在一个标签选择的子集上应用,观察业务探针和错误率,再逐步扩大范围。回滚路径必须在修复之前准备好,而不是事后补救:修复前把目标对象的当前 YAML 存到带时间戳的位置,回滚就是重新 apply 这份快照。对 Deployment 这类对象,也可以利用 rollout 历史,但要注意 kubectl rollout undo 回滚的是 ReplicaSet 模板,不会自动恢复被外部 patch 改掉的 NetworkPolicy 或 ServiceAccount 绑定,所以对象级快照仍然是更可靠的兜底。
灰度观察窗口建议覆盖至少一个完整的业务周期,并同时看三个信号:工作负载的就绪状态和重启计数、入口错误率和延迟、以及漂移检测组件对目标命名空间的重新扫描结果是否回到基线。三者都正常再把修复脚本推进到下一批。
验证与复盘
修复完成后,验证不能只看对象是否 apply 成功。回到基线快照做一次对照,确认被修复的字段确实回到了期望值,并且没有引入新的敏感字段。对涉及 hostPath 和 hostNetwork 的修复,额外看一遍 Pod 重建后实际运行时的卷挂载和网络命名空间,因为有些字段在 apply 时通过,运行时表现可能受准入 webhook 或节点配置影响。复盘时把这次漂移的来源查清楚:是发布流水线缺少某个字段的校验,还是有人绕过了流水线,或者是某个 Operator 自动改了对象。来源不同,后续加固的位置也不同——流水线缺校验就补准入策略,绕过流水线就收紧个人账号的写权限,Operator 改对象就在策略里为它声明例外并纳入审计。
这套方法的边界也要说清楚:它能把漂移变成可追踪的事件、把修复变成可审计的动作,但不承诺覆盖所有风险类型,比如运行时逃逸或镜像内恶意行为不属于配置漂移的范畴。基线字段的选择、快照频率和告警阈值都需要按团队的实际变更节奏调整,涉及 Kubernetes 版本和准入行为差异的部分,应以所用版本的官方文档和实际集群的 dry-run 结果为准。
- it2021
- it2021.com
- Kubernetes
- 云安全





宁夏 中卫 1F
只读快照这个思路挺实用,先把证据留住
2F
核心业务每小时快照对apiserver压力大吗
3F
审计日志和扫描结果互补,这个我踩过坑
4F
15分钟合并同一对象patch很关键,不然告警看不过来
5F
CI账号带审批标签降为记录,这个设计不错
6F
修复账号临时绑Role还设到期时间,细节到位