Sooua
登录
返回文章列表
云原生安全··8 分钟阅读

NetworkPolicy 不是零信任按钮:Kubernetes 微隔离最容易失效的六个边界

从 CNI 支持、默认拒绝、DNS egress、namespaceSelector、策略叠加和数据面验证六个边界,拆解 Kubernetes NetworkPolicy 如何真正落地。

NetworkPolicy 不是零信任按钮:Kubernetes 微隔离最容易失效的六个边界

主题:云原生安全。本文讨论防御性网络隔离、验证和排障,不涉及攻击利用步骤。

核心观点

Kubernetes NetworkPolicy 不是“创建一个 YAML 就获得零信任”。它只是 Kubernetes API 层的策略对象,真正拦截发生在 CNI 数据面。策略能否生效,取决于 CNI 是否实现、Pod 是否被选中、Ingress/Egress 是否都进入隔离态、DNS 是否被显式放行、跨命名空间选择器是否写对,以及你有没有做数据面验证。

很多生产集群的微隔离失败,不是因为没有写策略,而是因为把 kubectl get netpol 当成了验证结果。正确做法是把 NetworkPolicy 当成一套控制闭环:声明策略、注入数据面、实测连通性、记录阻断原因、再把命名空间创建和标签治理纳入 CI/CD。

1. 第一条边界:CNI 不支持,策略对象就是摆设

Kubernetes 官方文档明确说明,如果使用的网络插件不支持 NetworkPolicy,创建资源本身不会产生隔离效果。换句话说,API Server 接受了 YAML,不代表数据面会执行。

落地前要先回答三个问题:

问题证据不能接受的替代品
当前 CNI 是什么节点 CNI 配置、DaemonSet、云厂商集群网络插件信息“我们集群是 Kubernetes,所以应该支持”
是否支持 NetworkPolicyCNI 官方文档、版本说明、插件配置kubectl api-resources 能看到 NetworkPolicy
策略是否注入数据面Calico/Cilium/云厂商数据面状态、实测阻断结果kubectl apply 成功

最小检查可以这样做:

kubectl -n kube-system get pods -o wide
kubectl get ds -n kube-system
kubectl get networkpolicy -A
kubectl api-resources | grep -i networkpolicy

但这只是控制面检查。真正的验证必须进入连通性测试,后面会展开。

2. 第二条边界:默认拒绝必须按命名空间落地

NetworkPolicy 是 namespace-scoped。不存在一个原生 Kubernetes NetworkPolicy 能“一键全局默认拒绝所有命名空间”。如果只在 prod 放了 default deny,stagingtoolsdev 仍然可能默认全通。

推荐把 default deny 作为 namespace bootstrap 的必选项,而不是事后手工补:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress

注意这里没有 ingressegress 规则,语义就是选中的 Pod 对入站/出站都进入隔离态。生产中不要只写 Ingress default deny 就声称“微隔离完成”,因为出站路径往往才是横向移动和数据外联的关键。

3. 第三条边界:Egress 默认拒绝会打断 DNS

很多团队第一次启用 Egress default deny,业务立刻报错,原因不是服务端口没放行,而是 DNS 被切断。Pod 访问 service.namespace.svc.cluster.local 之前需要先解析 CoreDNS;如果没有放行到 kube-dns/CoreDNS 的 UDP/TCP 53,应用表现通常是 timeout、name resolution failed 或连接池异常。

一个常见的最小 DNS 放行策略如下:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns-egress
  namespace: production
spec:
  podSelector: {}
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53

这个示例的边界也要写清楚:不同发行版、云厂商或 CoreDNS 标签可能不同,不能直接复制。应先用下面命令确认真实标签:

kubectl -n kube-system get pods --show-labels | grep -E 'coredns|kube-dns'
kubectl get ns kube-system --show-labels

还要注意 Service ClusterIP 与 DNAT/SNAT 的实现差异。业务实际访问的通常是 kube-dns / CoreDNS Service IP,而不是 DNS Pod IP;不同 CNI 对 Service 转发前后如何匹配 NetworkPolicy 可能存在差异。因此 DNS egress 不能只靠 YAML 推断,必须做正向解析测试和负向外联测试,确认当前 CNI 的命中行为。

4. 第四条边界:namespaceSelector 和 podSelector 的组合很容易写错

跨命名空间放行时,最稳妥的方式是基于 namespace 标签和 Pod 标签,而不是写 Pod IP。Pod IP 是短生命周期对象,滚动更新后就会变化。

部署在 api 命名空间、允许 web/frontend 访问 api/backend 的 Ingress 示例:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-web-to-backend
  namespace: api
spec:
  podSelector:
    matchLabels:
      app: backend
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              tier: frontend-zone
          podSelector:
            matchLabels:
              app: frontend
      ports:
        - protocol: TCP
          port: 8080

这里的缩进和层级很关键。namespaceSelectorpodSelector 放在同一个 from item 中,表示“选中这些 namespace 里的这些 pod”。如果写成两个并列 item,语义可能变成“这些 namespace 的所有 pod”或“本 namespace 中这些 pod”,导致边界扩大。

5. 第五条边界:策略是叠加并集,不是后写覆盖前写

NetworkPolicy 的规则是 additive 的。多个策略选中同一个 Pod 时,允许流量取并集;它不是防火墙那种按顺序匹配、后写覆盖前写的模型。

这会带来一个常见误区:

误区真实语义风险
新写一个更严格策略会覆盖旧策略多个策略允许项会叠加旧策略残留导致仍可访问
删除某个允许项就一定阻断其他策略可能仍允许排障时误判原因
default deny 可以压过 allowdefault deny 让 Pod 进入隔离态,后续 allow 仍会放行需要清点所有选中该 Pod 的策略

排查时不要只看某一条 YAML,要列出所有选中目标 Pod 的策略:

kubectl -n production get pod backend-xxx --show-labels
kubectl -n production get networkpolicy -o yaml

成熟团队会把策略关系纳入 review:每个 Pod 的最终允许流量应该能从 GitOps 产物中推导出来,而不是靠出问题时人工猜。

6. 第六条边界:验证要测“应该通”和“应该不通”

只验证业务能通是不够的。微隔离上线必须同时验证正向和反向:白名单路径应该通,非白名单路径应该失败。

可以把验证写成 CI 作业或发布前检查:

# 应该通
kubectl -n web exec deploy/frontend -- curl -sS --max-time 3 http://backend.api.svc.cluster.local:8080/health
kubectl -n api exec deploy/backend -- nc -vz -w 3 postgres.data.svc.cluster.local 5432
 
# 应该不通
kubectl -n web exec deploy/frontend -- nc -vz -w 3 postgres.data.svc.cluster.local 5432 && exit 1 || true
kubectl -n tools exec deploy/debug -- curl -sS --max-time 3 http://backend.api.svc.cluster.local:8080/health && exit 1 || true

这段脚本的目标不是攻击,而是证明边界真实存在。安全控制如果没有负向测试,基本等于没有验收。

7. 排障路径:不要第一反应就是删策略

NetworkPolicy 出问题时,业务团队经常要求“先关掉策略”。平台团队应该提供一条更短的排障路径:

症状优先检查常见修复
服务名无法解析DNS egress、CoreDNS 标签放行 UDP/TCP 53 到真实 DNS Pod
只有跨 namespace 失败namespace 标签、selector 层级修正 namespaceSelector + podSelector 组合
目标端口 timeout目标 Pod Ingress 与源 Pod Egress两端策略都检查
策略创建成功但完全不拦截CNI 能力、数据面同步确认插件支持,检查 CNI 日志
删除策略后仍可访问其他策略仍允许列出所有选中 Pod 的策略
云厂商集群行为异常CNI chain、云安全组、插件版本对照云厂商文档和变更记录

阿里云 ACK 的 CNI Chain 文档也提醒,自定义 CNI Chain 属于高危操作,多个 CNI 插件之间的协作需要充分测试,策略规则可能与网络策略不兼容。这类云厂商网络实现差异,不能靠通用 YAML 想当然覆盖。

结论

NetworkPolicy 不是零信任按钮,它是一个需要标签规范、CNI 兼容性确认、白名单策略设计和连通性测试的防火墙抽象。default-deny-all 只是起点,真正的工程挑战在于如何让策略变更可审计、可回滚、可排障,而不是让开发团队因为『网络不通』而直接删掉策略。

结论

NetworkPolicy 的价值在于把 Kubernetes 集群的东西向流量从“默认互通”改成“按身份和端口显式授权”。但它不是零信任按钮,也不是单个 YAML 能解决的问题。

真正可靠的微隔离,必须同时满足六件事:CNI 支持、命名空间默认拒绝、DNS egress 可控、选择器语义正确、策略叠加关系可解释、正向和负向测试都通过。做到这些,NetworkPolicy 才是安全边界;做不到,它只是控制面里看起来很安全的一条对象记录。

分享

评论

登录 后参与讨论。

加载中…

相关文章