NetworkPolicy 与 Mihomo 代理数据链路

默认互通很方便,也很危险

在没有 NetworkPolicy 时,多数 Kubernetes 网络实现允许 Pod 之间直接通信。对开发环境很省事,但一个 Pod 被攻破后,攻击者也更容易扫描和访问其他服务。

本项目采用“默认拒绝,再按需要放行”的思路:

1
2
3
4
5
先把门全部关上
  → 再开放 CPA/Manager 入站端口
  → 只允许 runtime 出站到 DNS 与 Shared Mihomo
  → 只允许受管 Pool 的 CPA 访问 Mihomo
  → Mihomo 只能访问 DNS 和公共地址

NetworkPolicy 描述允许的流量,不是按顺序执行的防火墙脚本。

前提:CNI 必须真正实现 NetworkPolicy

API Server 会接受合法的 NetworkPolicy 对象,但是否执行取决于集群网络插件。若 CNI 不支持 NetworkPolicy,策略对象可能看起来都存在,流量却没有被拦截。

因此部署前要确认集群的 CNI 和策略能力。不要只用 kubectl get networkpolicy 证明隔离已经生效,还应做有界连通性验证。

default-deny 如何生效

每个号池 Namespace 有一个策略,选择带当前 Pool label 的所有 Pod,并同时声明:

1
2
3
policyTypes:
  - Ingress
  - Egress

但不提供任何 ingress 或 egress allow rule。这会让选中的 Pod 在两个方向上进入隔离状态。

随后另一个 runtime NetworkPolicy 添加允许规则。NetworkPolicy 是可加和的:只要任一适用策略允许某段流量,该段流量就被允许。不同策略不会按文件顺序互相覆盖。

runtime Pod 的入站规则

项目允许 TCP 端口:

1
2
8317  → CPA
18317 → cpa-manager

当前规则只限制端口,没有填写 from,因此从 NetworkPolicy 语义看,任何能够路由到该 Pod 的来源都可以访问这两个端口。

这不等于互联网能直接访问,因为 Pod 仍没有公网入口,Service 也是 ClusterIP;但集群内其他可达工作负载原则上可能连接这两个端口。

如果未来需要让 cpa-manager 只接受特定管理员跳板 Pod,应继续收紧 from,而不是把 ClusterIP 当作完整的身份认证边界。

runtime Pod 的出站规则

只允许两类目的地。

第一类是 kube-system 中带 k8s-app: kube-dns 标签的 DNS Pod,端口为 UDP/TCP 53。

第二类是 Operator Configuration 指定 Namespace 和 pod selector 的 Mihomo Pod,端口为 TCP 18000。

因此 CPA 不能直接访问任意公网地址,正常出站必须走:

1
2
3
4
5
CPA
  → mihomo-gateway.subpool-gateway.svc:18000
  → Shared Mihomo Pod
  → 上游代理
  → 外部目标

这把代理策略集中到共享数据面,避免每个账号池保存代理订阅和节点凭据。

为什么 DNS 必须单独放行

NetworkPolicy 一旦隔离 egress,DNS 查询也会被阻止。应用即使只想连接一个允许的 Service DNS 名称,也必须先解析它。

本项目同时允许 UDP 53 和 TCP 53。普通 DNS 查询常用 UDP,但响应过大、重试或特定场景会使用 TCP,只开 UDP 可能产生难以复现的问题。

策略假设集群 DNS Pod 位于 kube-system,并带 k8s-app: kube-dns 标签。不同发行版若使用不同标签,应同步修改 Operator 逻辑或平台配置,否则所有 Pool 都会出现 DNS 故障。

Shared Mihomo 的入站边界

Mihomo NetworkPolicy 只允许:

  • Namespace 带 subrelay.example.com/managed=true
  • Pod 带 CPA 组件相关 label;
  • 目标端口为 TCP 18000。

namespaceSelectorpodSelector 出现在同一个 peer 中时,是“Namespace 条件 AND Pod 条件”,不是 OR。

这阻止普通 Namespace 的 Pod 随意把 Shared Mihomo 当公共代理使用,也阻止受管 Namespace 中没有正确身份标签的 Pod访问它。

Shared Mihomo 的出站边界

Mihomo 可以访问 DNS,也可以访问 0.0.0.0/0 中排除私有、loopback、link-local、组播和保留地址后的公网 IPv4。

这种设计意图是:允许代理连接外部节点和目标,同时降低它访问集群内网、云元数据地址和私网服务的风险。

但它不是完整的出站安全产品:

  • 这里只展示 IPv4 规则,配置也关闭了 IPv6;
  • 公网地址仍是很大的范围;
  • 域名最终解析到什么 IP 会变化;
  • NetworkPolicy 通常是 L3/L4 控制,不理解 HTTP 用户或 URL;
  • CNI 对 ipBlock 与集群 NAT 的具体行为需要实测。

若威胁模型更严格,可在专用 egress gateway、代理认证、域名策略和审计层继续加固。

NetworkPolicy 同时检查两端

一次连接要成功,源 Pod 的 egress 和目标 Pod 的 ingress 都必须允许:

1
2
3
4
5
runtime egress 允许 Mihomo
          AND
Mihomo ingress 允许 runtime
连接才成功

只检查其中一侧,会看到“规则明明写了却还是不通”的现象。

Mihomo 配置在做什么

Shared Mihomo 从 Secret 挂载的 proxies.yaml 读取节点,启用 health check,并建立一个代理组:

1
2
type: load-balance
strategy: round-robin

然后在 18000 端口提供 HTTP proxy listener。CPA 只知道这个统一地址,不知道上游节点凭据。

这种设计有三个好处:

  • 节点和订阅只集中保存在一个共享 Secret;
  • Pool 无需因代理节点变化而修改 CR;
  • Operator 的 status、日志和 Event 不接触代理秘密。

round-robin 为什么不等于每个请求换 IP

Mihomo 的负载选择以新建出站连接为重要粒度。HTTP keep-alive、HTTP/2 多路复用或应用连接池可能让多个请求复用同一条连接,因此连续请求可能使用相同出口。

另外两个 Mihomo Pod 各自维护选择状态,没有跨 Pod 共享一个全局计数器。

所以项目只承诺:新连接会在健康节点集合中按策略选择。它不承诺:

  • 每个 HTTP 请求切换 IP;
  • 所有 Gateway Pod 之间全局严格轮转;
  • 某个账号固定到某个出口;
  • 存在 primary/candidate 主备关系。

Service Ready 也不等于代理链路端到端成功

Operator 通过 EndpointSlice 判断 Mihomo 至少有一个 Ready Pod。这只能证明 Kubernetes 层有可用代理 Endpoint。

以下问题仍可能存在:

  • proxies.yaml 内容错误;
  • 健康检查目标不可达;
  • 上游代理认证失败;
  • 代理可用但目标模型服务不可用;
  • 网络策略或云安全组阻断实际公网路径。

因此 ProxyGatewayReady=True 是基础设施信号,不是业务请求成功率指标。

排查流量时按跳点验证

不要一句“网络不通”就开始改策略。按链路分段:

1
2
3
4
5
6
7
1. runtime Pod 能否解析 Mihomo Service DNS
2. Mihomo Service 是否有 Ready EndpointSlice
3. runtime egress 是否选中正确 Mihomo Pod
4. Mihomo ingress 是否选中正确 Pool Namespace 与 Pod
5. Mihomo Pod 自身是否 Ready
6. Mihomo egress 是否允许目标地址
7. 上游代理与最终业务请求是否成功

常用只读对象检查:

1
2
3
4
5
6
kubectl -n subpool-demo-pool get networkpolicy -o yaml
kubectl -n subpool-gateway get networkpolicy -o yaml
kubectl -n subpool-gateway get endpointslice \
  -l kubernetes.io/service-name=mihomo-gateway -o yaml
kubectl -n subpool-gateway logs deployment/mihomo-gateway \
  --tail=100

不要为了“先通再说”永久删除 default-deny 或放开 CPA 直连公网。那会绕过项目最核心的代理与秘密边界,并把真正故障藏起来。

使用 Hugo 构建
主题 StackJimmy 设计