允许 TCP 为什么还可能拒绝报价

orders → inventory-v1 的 GET /quote?sku=paper 在四层被放行,不表示同一连接上的每个 HTTP 路径都获准。把 eBPF、相互认证和 mTLS 写成一个开关,会在排障时误把 L7 拒绝认作内核断网。按 Cilium 1.18 文档选择能力边界(2026-10-03 核验,网页实际标识 1.18.14);相互认证页面明确标为 Beta。这是候选文档基线,当前没有部署 Cilium,功能开关与内核/发行版兼容须在安装时再次核对;源码固定 v1.18.0 tag,不将文档补丁版本和源码标签混作同一构建。

同一请求先经内核数据路径再进入 Envoy 七层处理,认证和加密分支独立

图中的数据方向是 orders → 内核策略/转发 →(需要 L7 时)Envoy → inventory。返回值在反方向返回;源 identity、策略命中和代理是否真的收到请求各自需要证据。无 L7 规则时不能假定这个 HTTP 请求一定经过 Envoy。inventory 不调用 external-stub,它的只读报价也不产生合成 POST /reserve 账本条目。

四件事在四个位置

eBPF 程序在内核相关钩子处理连接、转发和基于身份的 L3/L4 策略;遇到需要解析 HTTP 的规则时,流量交给 Cilium 管理的 Envoy L7 代理检查路径、方法等属性。Envoy 执行七层过滤不意味着所有字节都由 eBPF 解析,也不意味着 Cilium 的所有策略都需要每 Pod 一个 Envoy sidecar。相互认证验证对端身份,和 WireGuard/IPsec 的节点间数据加密解决不同问题;一条链路可以只启用其中部分能力,不能从一次 200 推断认证、机密性或完整请求路径。

例子:允许 orders 的身份访问 inventory TCP 端口,但只允许 GET /quote。同源 GET /quote?sku=paper 预期到达 v1;同源 GET /admin 若配置为拒绝则应在 L7 层停止,库存应用没有对应处理记录。另起错误身份却请求 /quote,若 L4 身份策略拒绝,Envoy 未必看到它。核验须用同一个请求 ID 串起发送端、Cilium monitor/policy verdict、Envoy 访问日志、后端响应;单独一份 policy YAML 只是声明。源 identity 变动、L7 不支持的协议、直连 host-network 或转发路径例外都可能改变证据链。

以下是实验输入集合,非当前集群捕获的流量:

1
2
3
orders: GET /quote?sku=paper  → 四层允许、七层允许
orders: GET /admin → 四层允许、七层拒绝
unknown: GET /quote?sku=paper → 四层身份拒绝
能力 决策位置 哪一项证据不能代替它
L3/L4 身份策略 内核数据路径 /quote 返回 200
HTTP 方法/路径策略 经 L7 代理时的 Envoy CNI 已安装
对端认证、传输加密 各自启用的身份/加密链路 eBPF 程序已加载

复跑前要回答的版本问题

在隔离集群冻结 Cilium、内核、CNI、Envoy 镜像 digest 与启用的 feature flags,查 1.18 Envoy 集成 和 相互认证文档 对应版本的支持状态;然后分别执行仅 L4、添加 L7、启用认证、启用透明加密的用例。记录密钥只允许公开元信息;没有覆盖的功能明确不宣称支持。当前无 kubectl 或 Cilium 数据面,上述对照 NOT_RUN。先修:02 Service 地址、19 身份、21 授权 与 20 的 mTLS。

同一份策略逐步增加约束

材料在 examples/service-mesh/electives/E02/,命名空间固定 sm-e02。workloads.yaml 部署 orders、unknown 和 inventory;应用对 /quote 与 /admin 都返回 200,并打印请求 ID。因此只有 L7 阶段才应出现代理产生的 403。这个后端特意不实现业务权限,避免应用自身 403 混入网络策略实验。

仅 L4 的完整 l4.yaml 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: inventory-access
namespace: sm-e02
spec:
endpointSelector:
matchLabels: {app: inventory}
ingress:
- fromEndpoints:
- matchLabels: {app: orders}
toPorts:
- ports:
- {port: "8080", protocol: TCP}

它只允许选中来源到库存 TCP 8080。第二阶段用同名资源替换为 l7.yaml,完整差异是添加 HTTP 方法和路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: inventory-access
namespace: sm-e02
spec:
endpointSelector:
matchLabels: {app: inventory}
ingress:
- fromEndpoints:
- matchLabels: {app: orders}
toPorts:
- ports:
- {port: "8080", protocol: TCP}
rules:
http:
- {method: "GET", path: "/quote"}

两个文件必须替换同一个资源。额外保留一个宽松的允许策略,会因为允许规则组合而改变结论。本实验用不带查询字符串的 /quote,让路径匹配边界保持清楚。扩展到查询参数、URL 编码或路径重写时,必须分别测试代理实际匹配到的路径,不从这个样例推断全部情况。

认证阶段的 auth.yaml 在同一 ingress 项中增加:

1
2
authentication:
mode: required

仓库保存了包含全部 L4/L7 字段的完整 auth.yaml,可以直接 apply。它要求已启用 Cilium/SPIRE 集成;auth-values.yaml 提供对应 Helm values。已有 Cilium 测试集群可以在保留原安装 values 的基础上追加配置;不要覆盖 kubeProxyReplacement、路由模式等与宿主有关的设置。

1
2
3
helm upgrade cilium cilium/cilium --version 1.18.0 --namespace kube-system --kube-context "$LAB_CONTEXT" --reuse-values -f examples/service-mesh/electives/E02/auth-values.yaml
kubectl --context "$LAB_CONTEXT" -n cilium-spire get pods
kubectl --context "$LAB_CONTEXT" -n cilium-spire exec spire-server-0 -c spire-server -- /opt/spire/bin/spire-server healthcheck

上述 upgrade 只面向已由 Helm 安装的独立实验集群。官方 v1.18.0 相互认证教程把认证要求放在 ingress/egress 块,并要求检查 agent 的认证日志。认证握手与普通业务连接分开,认证缓存存在时,不应预期每个 HTTP 请求都有一次新握手。

运行三个阶段

1
2
3
LAB_CONTEXT=kind-cilium STAGE=l4 bash examples/service-mesh/scripts/run-E02.sh
LAB_CONTEXT=kind-cilium STAGE=l7 bash examples/service-mesh/scripts/run-E02.sh
LAB_CONTEXT=kind-cilium STAGE=auth bash examples/service-mesh/scripts/run-E02.sh

脚本每次 apply 同名策略并等待工作负载,保存命令、Pod imageID、策略和日志。L4 的判据是 orders 的两个路径都为 200;L7/auth 的判据统一为 /quote=200、/admin=403。unknown 发出的请求应在连接层失败;若获得任何 HTTP 响应,脚本判失败。脚本对 L7 拒绝使用收敛之后的新 ID,避免先前探测流量已经进入应用而干扰“被拒 ID 不应出现”的断言。

通过这三组功能判据还不能宣布认证或加密成立。应将 inventory Pod 节点、Cilium endpoint identity、源 identity 与 agent 认证日志对齐:

1
2
3
kubectl --context "$LAB_CONTEXT" -n sm-e02 get pods -o wide
kubectl --context "$LAB_CONTEXT" -n sm-e02 get ciliumendpoints -o yaml
kubectl --context "$LAB_CONTEXT" -n kube-system logs -l k8s-app=cilium -c cilium-agent --prefix --tail=1000

需要认证细节时在隔离环境按官方教程临时启用 debug,并记录修改前后设置。目标证据包含同一 identity 对的认证要求、证书验证和认证成功记录;只有 HTTP 200 或 SPIRE Pod Ready 不够。测试跨节点路径时,应显式把源和目标安排到不同节点,节点相同的结果不能替代跨节点验证。

[PATTERN] 一次只增加一个执行层的约束。L4→L7 改变请求内容判决,L7→auth 改变来源可信前提。按阶段保存证据,才能找到行为第一次改变的位置。

失败与恢复不共用一个结论

L4 阶段若 /admin 已拒绝,先查其他命中策略或应用响应;这个阶段尚未加入 HTTP 规则。L7 阶段 /quote 也超时时,先查 Envoy 就绪与代理重定向,不把 TCP 失败解释成路径规则拒绝。auth 阶段才失败则检查 SPIRE 健康、身份条目和 agent 日志;现存 auth cache 可能让部分身份继续访问,不能以短时停 SPIRE 后仍成功断言认证未执行。

恢复基线可以重新 apply l7.yaml,撤去本例的认证要求,再复跑 quote/admin。要恢复 L4 对照则 apply l4.yaml;这会有意取消路径限制,所以只适合专用实验命名空间。实验完成删除 sm-e02,集群级 SPIRE 或加密配置恢复原安装 values。

传输加密需要另一轮跨节点实验:固定相同来源和目标,启用 WireGuard 或 IPsec,记录加密状态及节点间抓包。应用容器内能看到 HTTP 明文并不能否定节点间加密;节点抓包未找到字符串也不能独自证明加密已启用,因为抓错接口同样没有数据。本轮没有这类抓包,透明加密标为 NOT_RUN。

模式 观测 不能代替的证据
阶段替换同名策略 L4 两路径允许,L7 仅 quote 允许 多份宽松策略并存
内容授权和身份认证分开 403 与认证身份日志分别保存 用 HTTP 200 证明认证
定位加密所在链路 跨节点接口与加密状态 应用容器内是否可见明文

练习与参考解答

  1. 画出 /admin 被拒的最短可证路径。参考解答:发送端 → 内核四层允许 → L7 Envoy 按方法/路径拒绝;请求 ID 不应出现在库存处理日志。若监控只有 L4 拒绝,则不能说 L7 策略生效。
  2. 删除 L7 规则、保留四层允许和加密,能继续证明 /admin 被拒吗?参考解答:不能。传输加密不是 HTTP 授权;应观察代理是否仍在路径中、重新发起同一来源请求,并检查是否有其他授权规则。

资料与系列导航

Cilium 1.18 Envoy 代理、相互认证;源码入口固定为 v1.18.0 proxy、auth,无本地源码实验。研究记录 writing-plans/service-mesh/research/E02.md。

系列导航:E01 Linkerd → E02 Cilium(本文) → E03 Proxyless gRPC。