为什么添加策略反而扩大了访问范围
一句话总结
创建更多策略文件,并不意味着通信会更加安全。必须确认限制的是哪些 Pod、哪个方向,多条 allow 规则如何合并,以及名称解析所需的独立通信是否仍然存在。本模块会通过真实请求和 Cilium 观测记录,区分安全作业后发生的两种故障。
为什么需要它
蓝队负责公司内部订单 API。安全人员要求减少外部连接,于是团队把 client 的 egress 限制到 API 的 8080 端口。策略成功应用,API Pod 也处于 Ready,但应用中的订单处理停止了。直接请求 API Pod IP 能获得响应,使用 service 名称请求却会一直等待到结束。
最初,团队认为 API 故障并重启了 server,但更换 server 后结果仍然相同。这里,control group 十分重要:从同一来源访问同一 server 的同一端口,只改变地址解析路径,结果就出现差异。不要一次修改多个设置,而应先问这种差异暴露了哪项依赖。
另一个团队为了向合作组织开放 API,添加了 allow 策略。他们以为原有策略仍然存在,所以新策略只是附加审核条件。但两条 allow 策略可能变成两个入口,相当于需求写成 AND,实现却做成 OR。安全评审不是统计文件数量,而是确认谁能通过哪扇门。
工作原理
1. 分离 API 表达的意图与执行器
基础 Kubernetes NetworkPolicy 也支持 podSelector 和 namespaceSelector,并非只会列出 IP 地址。策略顶层 podSelector 选择要保护的目标,ingress 的 from 选择入站对端,egress 的 to 选择出站对端。在同一 peer 中放置 namespaceSelector 与 podSelector,表示同时符合两个条件的对端;若拆成不同列表项,则形成不同的 allow 路径。
真正控制 packet 的,是支持相应策略的网络 plugin。本实验中,Cilium 同时处理标准 NetworkPolicy 与 CiliumNetworkPolicy。因此,不能只检查 API object 已经保存,还要确认被选中的 endpoint 和真实请求结果。selector 的详细含义请与 Kubernetes 官方说明对照。
2. 名称相同的 client 也不一定具有相同 identity
Cilium identity 对应一组安全相关 label。即使蓝队 client 与红队 client 都带有 role=client,namespace 相关 label 仍然不同。不要只记数字 ID,也不要只比较一个 app label。应并排查看 CEP 的 identity.id 与 identity.labels,确认它们区分的是哪一组信息。也不能像保存应用永久客户编号那样保存该数字。Cilium 术语说明是理解这一概念的起点。
CNP 的 endpointSelector 选择该策略所在 namespace 中的目标。若要允许来自其他 namespace 的来源,必须明确表达这一边界。本模块使用注册到 Kubernetes 的 namespaced CNP,不能把这里的范围直接泛化到直接写入策略 API 的 object 或 cluster-wide 策略。请同时阅读命名空间边界指南。
3. 阻断 ingress 与阻断 egress 是两件事
本环境的 policyEnforcementMode 为 default。必须区分未被任何策略选中的方向,与被限制规则选中的方向。缩小 server ingress 并不会自动把 client egress 限制到相同范围;反过来,允许访问 API 的出站通信,也不表示已经允许访问名称解析 server 的通信。
本实验会分别保留只允许 API 8080 的 client,以及同时允许 DNS 的 client。前者应继续能够直接请求 IP,但名称请求失败;后者则两种请求都应成功。恢复 DNS 时,需要指定 kube-system 中的 kube-dns endpoint,以及 UDP/TCP 53。通过开放所有 egress 来消除错误,会偏离任务范围。除了 default 之外,还存在 always、never、enableDefaultDeny 与 L7 例外,因此不要把这里看到的现象原样套用到所有设置。请在策略强制执行模式中确认适用条件。
4. 区分 allow 并集与显式 deny
union server 上已有允许蓝队 client 的 CNP。再添加一条允许红队 client 的标准 NetworkPolicy 后,就可能形成两个团队都能通过的状态。不能把新策略理解成进一步收窄原有 allow 的 filter,而应展开查看应用于同一 Pod 的全部 allow 路径。
deny server 保留上述 allow 路径,同时添加显式拒绝红队的 CNP。Cilium 的显式 deny 优先于包括标准 NetworkPolicy 在内的 allow。这与仅因没有 allow 规则而阻断的 default-deny 是不同概念。也不要扩大解释为可使用该功能 deny 特定 URL 或 FQDN。请参考显式拒绝的优先级与限制。
| 目标 | 蓝队 client | 蓝队 stranger | 红队 client | 比较目的 |
|---|---|---|---|---|
| baseline | 允许 | 允许 | 允许 | 确认 server 自身能够响应的 control group |
| native | 允许 | 拒绝 | 拒绝 | 标准 NP 的同 namespace 选择 |
| ingress | 允许 | 拒绝 | 拒绝 | CNP 的来源选择 |
| union | 允许 | 拒绝 | 允许 | 不同 API 的 allow 并集 |
| deny | 允许 | 拒绝 | 拒绝 | 优先于红队 allow 的 deny |
该表是接下来要构建的隔离实验目标状态,并不表示初始状态已经应用了所有正确策略。应阅读实际策略并发出请求,逐步定位与目标不符的单元格。
现场会遇到的情况
HTTP 000 不是 server 发送的 status code。curl 未能获得 HTTP 响应码时也会显示它。不要仅凭 timeout 就断定由网络策略造成;server 未启动、地址错误和连接路径问题也会呈现相同形态。因此,应一起检查 server Ready、IP 直接访问 control group、client 位置、策略 spec 和 Hubble verdict。
只找到一行 Hubble DROPPED 还不够。观测 buffer 中也包含先前实验和其他 Pod 的请求。对齐来源 Pod 与 namespace、IP、目标端口、唯一 source port 及发生时间,就能缩小调查范围;再对照 CEP identity,还能降低混淆同名 endpoint 的风险。必须区分 TCP SYN 已转发,与实际收到 HTTP 200:前者是对传输路径的观察,后者才是应用响应。请使用 Hubble CLI 指南中的 filter 自行缩小范围。
在实际预探测中,22 个请求里有一个停在 DNS 解析阶段,因此没有任何 TCP flow。如果把它判定为 TCP 日志缺失,就是观测方式错误;该请求是在建立 API 连接之前停止的。必须结合 DNS UDP 53 drop 与直接请求 IP 的成功来判断。当时的 DNS 证据只是时间段与来源 Pod 的对照,并不是连 query ID 都关联起来的 packet 级证明。说明证据覆盖范围,也是故障报告的一部分。
下一项实验要做什么
先在准备好的隔离 VM 中确认 identity 与 control group。然后,分别为不同目标配置标准 NP 和 CNP selector、缺少 DNS 的 egress 与修复后的 egress、allow 并集和 deny 优先级。完成某一步后,也要保留之前的比较对象。最后,对照策略与观测结果编写 incident report。
实验中故意保持开放的 union 与 baseline 是用于比较的隔离目标,不能原样复制到生产环境。若要保留作业成果,必须在结束 session 前导出。本模块实验仅验证单个 VM 内的 Cilium 策略行为,并不验证 multi-cluster 连接、BGP 路由或 L7 URL 限制。