默认互通很方便,也很危险
在没有 NetworkPolicy 时,多数 Kubernetes 网络实现允许 Pod 之间直接通信。对开发环境很省事,但一个 Pod 被攻破后,攻击者也更容易扫描和访问其他服务。
本项目采用“默认拒绝,再按需要放行”的思路:
| |
NetworkPolicy 描述允许的流量,不是按顺序执行的防火墙脚本。
前提:CNI 必须真正实现 NetworkPolicy
API Server 会接受合法的 NetworkPolicy 对象,但是否执行取决于集群网络插件。若 CNI 不支持 NetworkPolicy,策略对象可能看起来都存在,流量却没有被拦截。
因此部署前要确认集群的 CNI 和策略能力。不要只用 kubectl get networkpolicy 证明隔离已经生效,还应做有界连通性验证。
default-deny 如何生效
每个号池 Namespace 有一个策略,选择带当前 Pool label 的所有 Pod,并同时声明:
| |
但不提供任何 ingress 或 egress allow rule。这会让选中的 Pod 在两个方向上进入隔离状态。
随后另一个 runtime NetworkPolicy 添加允许规则。NetworkPolicy 是可加和的:只要任一适用策略允许某段流量,该段流量就被允许。不同策略不会按文件顺序互相覆盖。
runtime Pod 的入站规则
项目允许 TCP 端口:
| |
当前规则只限制端口,没有填写 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 不能直接访问任意公网地址,正常出站必须走:
| |
这把代理策略集中到共享数据面,避免每个账号池保存代理订阅和节点凭据。
为什么 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。
namespaceSelector 与 podSelector 出现在同一个 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 都必须允许:
| |
只检查其中一侧,会看到“规则明明写了却还是不通”的现象。
Mihomo 配置在做什么
Shared Mihomo 从 Secret 挂载的 proxies.yaml 读取节点,启用 health check,并建立一个代理组:
| |
然后在 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 是基础设施信号,不是业务请求成功率指标。
排查流量时按跳点验证
不要一句“网络不通”就开始改策略。按链路分段:
| |
常用只读对象检查:
| |
不要为了“先通再说”永久删除 default-deny 或放开 CPA 直连公网。那会绕过项目最核心的代理与秘密边界,并把真正故障藏起来。