准入控制器在云原生安全中的定位与局限
很多人对集群安全的想象是:装上一个准入控制器,门口就有人站岗了。但站岗的保安只查进门的人,进了门之后谁翻抽屉、谁拿东西,他管不着。准入控制器在云原生安全里的位置,大致就是这么个角色——在请求通过认证和鉴权之后、对象真正写入之前拦一刀,能改内容,也能直接拒绝。这一刀切得确实关键,因为它是少数几个还能看到“完整请求”的地方。
它确实堵住了几条具体的路
拿两个插件举例更直观。NodeRestriction 管的是 kubelet 这个身份的边界。kubelet 用 system:nodes 组的凭证跟 API 服务器打交道,如果不加约束,一个被攻陷的 kubelet 理论上可以改动任意 Node 对象、删节点、改污点去干扰调度。这个插件把范围收紧到:只能动自己的 Node、只能动绑在自己节点上的 Pod,污点不许改,带 kubernetes.io/ 和 k8s.io/ 前缀的标签也受限。
DenyServiceExternalIPs 更简单粗暴,直接拒绝给 Service 的 externalIPs 字段新增内容。这个字段当年是给非云集群模拟负载均衡用的,前提假设是“集群里每个人都可信”。多租户环境下这个假设站不住,谁能建 Service 谁就能把任意 IP 挂上去,劫持原本流向那个地址的流量——CVE-2020-8554 说的就是这件事。官方从 1.21 起建议禁用,1.36 正式弃用。
局限也很清楚:它只守入口
真正的坑在于别把它当终点。NodeRestriction 约束的是 kubelet 对 Node 和 Pod 的修改,但它不管 kubelet 读 Secret、读 ConfigMap,那部分归节点鉴权器的读取规则管;它也不替代 RBAC 对普通用户的权限划分。DenyServiceExternalIPs 只拦新增,存量字段的读取和删除照样放行,而且用户还能绕到 LoadBalancer 类型 Service 的 loadBalancerIP 字段去做类似的事。
换句话说,准入控制器的价值是把安全判断往前移到 API 入口,而不是依赖运行时被动发现。它便宜、见效快、回滚也容易——把插件名从启动参数里摘掉,等 API 服务器重启就行。但它的视野到请求落库那一刻为止。
所以比较务实的做法是:把它当第一道闸,而不是唯一一道。启用之后该做的排查一样不少,比如先扫一遍集群里有没有存量 Service 在用 externalIPs,逐项确认业务必要性,再规划替代方案;确需保留的场景用更细的策略做白名单,而不是干脆把闸打开。NodeRestriction 那边则要留意,有没有自定义控制器或运维脚本一直借 kubelet 身份跨节点干活,这类用法得换成独立的 ServiceAccount 加 RBAC。
门卫再尽责,也管不了屋里的灯是不是亮着。

参与讨论
门卫管不了屋里翻抽屉,太形象了