- Daily Report
- eBPF
- Networking
- Security
- Kubernetes
多租户网络策略应该怎样在 eBPF 数据面里组合?
多租户集群会叠加管理员、命名空间和 Cilium 网络策略。本文分析不同策略层的组合语义,并提出可解释的 eBPF policy composition 与 verdict witness。
平台管理员禁止租户之间直接通信,某个 namespace 的维护者又通过 Kubernetes NetworkPolicy 放行自己的服务,安全团队同时为同一批 workload 加上一条 Cilium L7 规则。这三条策略都可能是合法的,最后也都可能影响 eBPF 数据面里一次 packet lookup 的结果。
真正困难的已经不是 eBPF 能不能执行 allow 或 deny,而是:多套策略被压成高效的 datapath state 以后,系统还能不能说明谁有权做这个决定、哪条规则真正决定了结果,以及另一条看起来相关的规则为什么没有生效。
Kubernetes 原生 NetworkPolicy 的组合规则相对简单。按照当前 Kubernetes NetworkPolicy 文档,如果多个 policy 同时选中一个 Pod,它们允许的 ingress 或 egress 集合按加法合并。规则之间没有先后顺序,只要某个适用的 policy 允许该 flow,最终 allow 集合就包含它。
新的 Network Policy API 引入了另一套语义。当前发布的 ClusterNetworkPolicy v0.2.0 支持 Admin 与 Baseline tier、priority,以及 Accept、Deny、Pass。管理员可以定义普通 workload policy 无法覆盖的高优先级决定,也可以通过 Pass 明确把某类流量交给下一层策略处理。项目的 getting-started 文档 当前把 v0.2.0 列为最新发布版本。
Cilium 又把这个问题变成了一个很具体的 eBPF 工程问题。Cilium 1.20.1 的 Network Policy 文档说明,它可以同时运行 Kubernetes NetworkPolicy、ClusterNetworkPolicy、CiliumNetworkPolicy 和 CiliumClusterwideNetworkPolicy。同一份文档也明确提醒:同时使用多种 policy format 时,完整的 allowed traffic 很容易变得难以理解,如果没有仔细检查,甚至可能出现意外放行。
这就是本文关注的缺口。控制面保留 policy object、owner、tier、selector 和 rule label;真正执行时,eBPF 数据面希望得到尽可能紧凑的查表结果。Cilium 的 eBPF map 文档把每个 endpoint 的 Policy map 描述为 identity、port、protocol 组合,默认上限为 16K。这样的表示非常适合快速 enforcement,却没有直接回答管理员最关心的问题:这个 entry 为什么存在,是哪一层 authority 允许或拒绝了它,如果 verdict 错了应该改哪一份 policy?
本文的判断是,多租户 eBPF policy 需要一个位于 policy intent 与 datapath state 之间的组合契约。它不应该发明一套新的网络策略语言,而是把不同策略语言已有的 authority、priority、delegation 和 additive 语义保留下来,在安装 datapath state 之前识别真正有意义的冲突,并让最终 verdict 带有一个足够紧凑的来源 witness。
这个问题和此前的 eBPF hook composition 不一样。那篇文章讨论的是多个 BPF program 共享同一 hook 时,mutation、shared state 和 competing outcome 怎样组合。它也不同于 有状态 eBPF 原子升级,后者关注 programs、maps、links 和 userspace controller 怎样安全切换 generation。本文假设 BPF 程序本身可以完全不变,真正需要组合的是不同安全策略拥有者和不同 policy language 的语义。
三层网络策略已经在使用不同的组合规则
设计组合机制之前,首先要承认已有 API 并不是同一种逻辑。把所有 policy 都压成“deny 优先”反而会破坏原来的语义。
Kubernetes NetworkPolicy 本来就是 additive
原生 Kubernetes API 主要描述 allow。一旦 Pod 在某个方向进入 isolation,最终允许哪些连接取决于所有适用 policy 的 allow union。这个模型的好处是 namespace owner 可以独立增加 policy,而不需要知道某条对象会先还是后执行。
因此,这里使用“冲突”一词需要很谨慎。两个普通 NetworkPolicy 的 selector 重叠,不代表 API 语义上存在冲突。如果一条宽规则和一条窄规则都适用,最终 allow set 仍然是二者的并集。
但是,人可以产生意图冲突。例如一个团队以为窄规则会限制宽规则,而 Kubernetes 实际上继续允许宽规则覆盖的流量。一个好的分析器不应该因为 selector overlap 就报警,而应该明确告诉用户:某条 policy 扩大了 effective allow set、某条规则没有实际影响、某个约束被另一层 authority 覆盖,或者某个决定被显式委托给了下一层。
ClusterNetworkPolicy 把 authority 和 delegation 放进 API
Network Policy API 的目标之一,就是补足 namespace 级 NetworkPolicy 无法表达的 cluster administrator 控制。当前 API reference包含 priority 语义,而 ClusterNetworkPolicy 模型区分管理员策略和 baseline guardrail。
Cilium 当前实现把实际顺序写得更直接:Admin tier、NetworkPolicy tier、Baseline tier。Admin 的决定不能被普通 NetworkPolicy 覆盖,Baseline 则是更低层的 guardrail。Pass 更值得注意,因为它把“把决定交给下一层”明确写成 policy action,而不是靠 rule miss 隐式 fall through。
所以一个 DENIED 已经不够解释结果。管理员可能需要知道,是 Admin rule 在 workload policy 参与之前就做出了决定,还是 Admin 先 Pass,之后 namespace policy 才真正允许了连接。
Cilium 把多种 policy format 编译进 eBPF 数据面
Cilium 是这个问题最直接的生产例子。它从 Kubernetes 和 Cilium CRD 接收 policy,计算 endpoint policy,维护 security identity,最后把 enforcement state 编程进 BPF map。它的 policy troubleshooting 文档说明,operator 可以把 endpoint 信息、rule label 和 cilium-dbg policy get 组合起来,恢复某个 endpoint 当前受到哪些 source policy 影响。
这是一条可用的调试路径,但本质上仍然是控制面重建。Hubble 的 policy-verdict 可以直接告诉我们 source/destination identity、方向、port 和 allow/drop,却没有把“哪一层 policy owner 真正决定了这个 verdict”天然变成 datapath verdict 的稳定属性。
所以缺的并不是 Cilium 没有 observability,而是source-policy provenance 还不是编译后 verdict 的一个可移植契约。
多租户让 provenance 从调试便利变成正确性问题
如果一个集群只有一个团队,遇到异常时把所有 policy object 全部打开通常还能查清楚。多租户改变的是 authority model。
设想一个共享集群:
- 平台团队负责 Admin tier 的 tenant isolation 和 emergency block;
- 各 tenant 团队负责 namespace 内的 service connectivity;
- 安全团队通过 Cilium policy 负责跨 namespace 或 L7 控制。
tenant blue 希望 blue/frontend 可以访问 blue/api:443。平台策略首先判断 source 和 destination 是否属于同一个 tenant;tenant 自己的 policy 允许 service pair;安全团队又限制该连接上的 HTTP method。
如果请求失败,真正该修改哪个对象取决于哪个 layer 做出了最后决定。namespace owner 改自己的 NetworkPolicy 无法绕过 Admin deny;为了修一个 L7 deny 去放宽 Admin rule,又会扩大不该扩大的 authority。只有最终 DROP 而没有来源 witness 的系统,会迫使 operator 手工恢复整条 hierarchy。
Network Policy API 自己的当前示例也说明 tenancy 语义仍在演进。它的 tenant isolation 示例把需要表达“same tenant”的 selector 标成 currently not implementable,并指向 NPEP-122。该 proposal 进一步讨论 strict isolation、可被 namespace owner override 的 isolation,以及 tenant identity 本身应该怎样定义。
这并不表示 Kubernetes 今天无法做多租户隔离。它说明的是:tenant identity 和 delegation 是需要明确建模的语义,generic policy compiler 不能只看 label overlap 就自行猜测。
effective policy 在变成 BPF map entry 之前需要一个表示层
一种实际可做的设计,是在 policy language 与 datapath layout 之间加入中间表示。每条 normalized rule 可以保留:
rule = {
source_object,
source_generation,
owner,
authority_tier,
priority,
action,
subject_selector,
peer_selector,
direction,
protocol,
port,
l7_constraints
}这些字段不需要在每个 packet 上全部携带,但在计算 effective decision 时必须存在。
对一个 candidate flow,compiler 先按照来源 API 自己的语义求值。普通 Kubernetes NetworkPolicy 进入 additive allow set;ClusterNetworkPolicy 保留 tier、priority 与 Accept、Deny、Pass;Cilium 的 L7 constraint 保留自己的 enforcement stage。之后输出两类产物:
- 面向 fast path 的 datapath state,继续优化 identity、port、protocol 和 L7 enforcement;
- 面向解释的 decision provenance,保存这个 datapath state 为什么存在。
provenance 不需要把完整 YAML 放进 BPF map。datapath 只需要一个 generation、normalized rule ID、action 和 authority tier。userspace 保留 rule ID 到 source object 与 owner 的 reverse mapping。
允许一个 L3/L4 tuple 时,witness 可以类似:
policy_gen=1842
verdict=ALLOW
owner=tenant-blue
rule_id=0x31a8
layer=NetworkPolicy
admin_path=PASS:0x9f20如果 Admin tier 已经决定 deny,则可以是:
policy_gen=1842
verdict=DENY
owner=platform-security
rule_id=0x0d44
layer=Admin这样 packet verdict 提供的是一个稳定 join key,而不是要求事后做一次取证工程。
现有研究还缺什么
1. 多种 policy format 已经能同时执行,但 effective intent 仍然难以检查
Kubernetes NetworkPolicy 使用 additive 语义;ClusterNetworkPolicy 加入 tier、priority 与 delegation;Cilium policy 又增加自己的 L3-L7 表达。Cilium 能同时运行这些格式,并且官方文档已经提醒完整 allow set 容易变得难以理解。
缺少的是一个共同的 effective-policy artifact,明确表示来自多个 format 的 source policy 怎样共同形成每个 decision region。它要区分正式 precedence 与普通 overlap,还要识别 broadening rule、完全没有 effect 的 rule、delegated decision,以及只在另一层策略存在时才会生效的 rule。
最直接的验证方式,是生成包含所有支持 format 的 policy set,再枚举或符号化采样 source/destination identity 与 port,把 analyzer 预测出来的 effective policy 和真实 datapath verdict 对比。如果一个 compact normalized model 必须塞入大量 Cilium-specific 特例才能匹配实现,这个 abstraction 就太泛化了。
2. tenant identity 经常由 label 暗示,而不是一个有 authority 的对象
当前 tenancy proposal 已经展示了问题:某个 label 可以把 namespace 分组,但系统还需要知道哪一个 label 真正定义 tenant、谁有权修改它,以及该 isolation 是 strict 还是允许 namespace owner override。
缺少的是一个与 authority 绑定的 tenant identity。否则两个 policy author 可以对同一组 label 有完全不同的组织语义,analysis tool 也只能猜这个 label 是安全边界还是普通 workload metadata。
测试时应加入 tenant label mutation 和恶意 namespace 创建。如果一个不可信 namespace 仅靠修改 label 就能进入另一个 tenant 的 policy group,而 composition model 没有独立 authority check,它的安全模型就是不完整的。
3. datapath verdict 并不天然携带 source-policy witness
BPF policy map 必须优先服务快速 packet decision。Cilium 当前文档中的 policy map 以 identity、port、protocol pair 为核心,而 source policy owner 与 rule identity 主要保留在控制面。现有 troubleshooting 可以从 endpoint label 和 agent state 恢复 policy,但它与发生 verdict 的那一刻并不是同一个证据对象。
缺少的属性是一个稳定的 policy generation 和 deciding rule witness。它可以放在 companion map、policy-verdict event,或者由 immutable generation manifest 解析,但关系必须保存足够久,才能解释旧 incident。
可以在持续 policy churn 下记录 verdict,再删除旧 policy object。之后要求 debugger 解释旧 generation 的 verdict。如果它默默使用当前 policy 来解释过去的 packet,provenance 就不够强。
4. L3/L4 和 L7 可能在不同位置改变 deny 语义
Cilium 的 policy enforcement 文档特别指出,即使 EnableDefaultDeny 被关闭,L7 rule 如果没有对应 allow-all 规则,仍然可能造成 drop。这个例子说明,一个 default deny Boolean 无法概括完整 policy stack。
缺少的是能说明最终决定在哪个 enforcement stage 发生的组合模型。operator 应该直接区分“Admin tier 在 workload policy 前 deny”“L3/L4 没有 admit identity”“连接允许建立,但 L7 policy 拒绝了当前 request”,而不是自己翻译每一层内部状态。
验证可以固定 source/destination,只改变 L3、L4、L7 policy,检查 explanation 是否总能指向正确的 deciding layer 和 source rule。
兼具学术价值与生产价值的方向
1. 把多种 policy language 编译成带 authority 的 composition IR
缺口。 当前 API 已经存在不同的组合语义,生产 CNI 也能同时支持多种 format,但 operator 缺少一个可检查的 effective result。
机制。 为 Kubernetes NetworkPolicy、ClusterNetworkPolicy、CiliumNetworkPolicy 和 CiliumClusterwideNetworkPolicy 做 compiler frontend。selector 和 action 可以 normalize,但 source language 的语义不能被抹平。authority tier、owner、priority、显式 delegation 与 enforcement layer 都是 first-class field。compiler 再把 identity/port 空间切成 decision region,同时产生 datapath entry 与 provenance manifest。
这个 compiler 不能把所有 format 偷换成“deny wins”。Kubernetes NetworkPolicy 继续 additive;ClusterNetworkPolicy 的 tier 和 Pass 明确保留;Cilium L7 仍然有自己的 stage。只有当 organizational invariant 或 ownership rule 被违反,或者规则出现意外 broadening、shadowing、delegation 时,才报告 conflict。
与现有工作的差异。 CNI controller 今天当然已经会编译 policy。新的性质不是“再做一个 compiler”,而是把编译结果变成带 authority provenance、可检查且尽量可移植的 composition IR,不再只剩 implementation-specific policy tree 和 BPF state。
产物。 一个开放 schema、四种 policy format importer、Cilium backend,以及可以从某个固定 policy generation 回答“为什么这个 flow 被允许或拒绝”的命令行工具。
评测。 生成包含 selector overlap、Admin/Baseline、Pass、namespace policy、Cilium L7 rule 与 label mutation 的 policy suite。对比 IR、Cilium 实际 verdict 与 API 定义的预期语义。指标包括 verdict agreement、conflict report precision、compile latency、BPF map entry 数量和 incremental update cost。去掉 authority field 做 ablation,观察哪些 case 变成 ambiguity 或错误 flattening。
学术价值。 核心问题是:不同作者独立定义的 network policy,是否可以在保留原 policy language 语义的同时,通过显式 authority algebra 正确组合。
生产价值。 平台团队可以用同一个 artifact 做 pre-deployment review、policy diff、incident explanation 和 policy API migration。
失败条件。 如果真实 policy 大部分都需要 implementation-specific exception 才能进入 IR,或者现有 policy tracing 已经稳定地暴露了同样的 machine-readable effective semantics,就没有必要再维护一个中间层。
2. 在 eBPF datapath 中保留紧凑 verdict witness
缺口。 fast policy map 负责回答能不能过包,source-policy ownership 和 rule identity 仍主要在控制面。
机制。 每个 compiled decision region 得到一个 generation-scoped witness ID。正常 BPF lookup 继续使用 compact identity/port/protocol key;companion value 或 companion map 把 entry 关联到 witness ID。deny 和可配置比例的 allow verdict 把该 ID 发到 policy-verdict telemetry。userspace 再从 immutable manifest 解析 owner、tier、source object、rule 与 delegation path。
hot path 不应该承受完整 provenance。实现可以只对 first-seen flow、deny、sampled allow 或 operator 打开的 diagnostic mode 输出 witness。generation 必须成为 identity 的一部分,避免旧 verdict 被新的 policy manifest 重新解释。
与现有工作的差异。 Hubble 已经有 policy-verdict,Cilium 也已经给 source policy rule 加 label。新的性质是建立从真实 datapath decision 到 deciding source-policy witness 的 generation-stable join。
产物。 一个 Cilium prototype,扩展 policy-map metadata 或增加 companion witness map,同时加入 Hubble decoder 与 retained generation manifest。
评测。 在连续 policy update 下跑 connection matrix,并保留多个 generation 的 verdict event。删除旧 policy 后继续做解释。测 explanation accuracy、额外 BPF map memory、packet-path overhead、event bandwidth 和 manifest retention cost。比较 full per-packet provenance、sampled witness 与纯控制面 reconstruction。
学术价值。 可以回答 enforcement datapath 至少要保留多少 provenance,才能在控制面不断变化时继续给出正确 explanation。
生产价值。 安全和 SRE 团队可以从一次 unexpected allow 或 deny 直接定位 responsible policy owner,而不是 incident 之后再拼多个 live controller view。
失败条件。 如果控制面 reconstruction 在 policy churn 后仍然完全准确且定位成本很低,或者 compact witness 已经明显降低 datapath scale,就应该把 witness 留在 sampled telemetry 或 userspace manifest,而不是 packet path。
3. 做一个 counterexample-driven 的多租户 policy benchmark
缺口。 conformance test 能检查单个 resource 是否按 API 实现,却不一定发现多个合法 owner 把 policy 组合在一起以后产生的 operator mistake。
机制。 benchmark generator 明确定义 tenant、authority role 与 connectivity invariant,然后跨 Kubernetes 与 Cilium format 生成 policy set。每次只加入一种 controlled mutation,例如过宽 namespace selector、错误 tier、意外 Pass、tenant label 变化、L7 restriction、stale policy generation 或 policy-map capacity pressure。
每个 mutation 同时给出两个 ground truth:packet 应该 allow 还是 deny,以及最小 explanation 是什么,也就是哪一个 authority 和 rule 应该决定结果、哪一个候选 rule 应该被忽略、delegated 或按照 additive 语义处理。
与现有工作的差异。 API conformance 主要验证 resource semantics。这里测的是跨 owner、跨 format 的 composition mistake 与 explanation quality,而且每个单独 policy object 都可以是合法的。
产物。 可重复运行的 Kubernetes test suite、policy corpus、expected flow matrix、policy-generation history,先支持 Cilium,再随着其他 Network Policy API implementation 成熟增加 adapter。
评测。 统计 datapath verdict correctness、explanation correctness、conflict detection precision/recall、定位 responsible owner 所需时间和资源 overhead。human/operator study 可以作为次要指标,主要 oracle 使用 generator 给出的 ground-truth decision graph。baseline 包括普通 kubectl 检查、Cilium 当前 policy/debug 工具、composition IR 与 datapath witness。
学术价值。 它把 policy compositionality 和 explainability 变成可测系统性质,而不是只依赖配置事故案例。
生产价值。 CNI maintainer 与 platform team 可以针对真实的 multi-owner interaction 做 upgrade、API migration 和 organization guardrail regression test。
失败条件。 如果真实生产 policy 很少混用 authority 或 format,生成出来的冲突也和实际 incident 不相似,这个 benchmark 就夸大了 corner case,应该缩小到 operator 真正部署的组合。
不需要先替换 Kubernetes policy API
这个 composition contract 可以渐进实现。
第一版完全可以只运行在 Cilium control plane。它读取现有 policy object,在当前 realized policy 旁边生成 composition manifest,再用已有 policy tracing 比较预测与实际结果,不改 packet path。
第二步只把 witness ID 加进 policy-verdict telemetry。如果这样已经足够解释 incident,就没有理由为每个 policy-map lookup 增加 metadata。
只有当 policy churn 或 post-incident reconstruction 证明 telemetry-only witness 仍然不够时,才值得让 datapath 直接携带 generation-scoped witness。
这种顺序很重要。eBPF 在这里提供 enforcement 和 observability,但真正难的是 policy semantic。一个在控制面已经被错误 flatten 的 authority model,不会因为 packet lookup 更快就自动变正确。
哪些结果会改变这个判断?
如果实际 benchmark 证明,当前 ClusterNetworkPolicy 语义加上 Cilium 已有 policy tracing,已经能对 operator 真正使用的多种 policy 组合给出稳定、机器可读、跨 generation 正确的 explanation,那么更好的选择是完善现有 tooling,而不是引入新的 composition IR。
verdict witness 也不是无条件值得加入 datapath。如果它明显提高 policy-map pressure、降低 endpoint scale,或者引入纯控制面重建可以避免的 packet-path cost,就应该只保留在 sampled policy-verdict telemetry 或 userspace generation manifest。
最后,Network Policy API 未来可能会把 tenancy model 定义得更直接。如果 tenant identity、ownership、delegation 与 precedence 都变成实现必须保留的明确语义,额外 composition layer 可以继续缩小。目标并不是创造另一种 policy language,而是让多个合法 policy owner 最终汇聚到一个 eBPF verdict 的过程正确、可检查,而且在 policy generation 变化后仍然解释得通。
参考资料
- Kubernetes Network Policies
- Kubernetes NetworkPolicy API reference
- Network Policy API
- Network Policy API v0.2.0 getting started
- Network Policy API specification
- Network Policy API examples
- NPEP-122: Tenancy API
- Network Policy API implementations
- Cilium 1.20.1 Network Policy
- Cilium eBPF Maps
- Cilium policy troubleshooting
- Cilium policy enforcement modes
- Cilium repository
继续阅读
这个页面有帮助吗?