
Cilium Security Identities 详解基于标签的安全身份分配、取值范围与作用域【免费下载链接】ciliumeBPF-based Networking, Security, and Observability项目地址: https://gitcode.com/GitHub_Trending/ci/cilium导读Security Identities安全身份是 Cilium 身份模型的核心抽象每个端点Pod、节点、主机等的安全身份由其标签Labels派生而来并作为安全策略NetworkPolicy/CiliumNetworkPolicy判定的最小单位。本文基于 Cilium 官方内部文档与仓库源码系统讲解安全身份的数值编码方式、1 ~ 2^32-1的完整取值范围划分、Cluster-local / ClusterMesh / CIDR / Remote-node 四类作用域的设计动机以及这些取值范围如何被 eBPF 数据路径、VXLAN/Geneve 隧道和 Hubble 观测工具消费。读完本文你将能准确解读 Hubble 输出中的 identity 字段、理解policy-cidr-match-mode与enable-node-selector-labels配置项的作用并掌握 Cilium 身份空间的排布规律。本文主体内容来自仓库文档 Documentation/internals/security-identities.rst底层实现证据引用 pkg/identity/numericidentity.go、pkg/identity/numericidentity/constants.go 与 pkg/option/config.go 等源码文件。什么是 Security Identity在 Cilium 中安全身份Security Identity由一组标签Labels生成。同一个身份对应一组完全相同的标签集合——所有携带相同安全标签的端点会共享同一个安全身份编号从而在策略引擎中一视同仁。从数据结构上看安全身份被存储为uint32类型即 NumericIdentity 本质上是一个 32 位无符号整数// NumericIdentity is the numeric representation of a security identity. // // Bits 24-31 encode the identity scope, as defined by the IdentityScope* // constants. For allocated global identities, // bits 0-23 are divided between the cluster-local identity and cluster ID. type NumericIdentity uint32由此可以推导出两个硬性边界最大值2^32 - 1即MaxNumericIdentity math.MaxUint32最小值1IdentityUnknown 0是非法值。Identity 0 的特殊语义值得特别注意的是Identity 0 不是一个有效身份它在不同上下文中承载着特殊含义在Hubble 输出中如果某个流量的 identity 显示为 0说明该身份未被找到lookup 失败在eBPF 数据路径中Identity 0 扮演任意身份any identity的角色作为策略 map 中的通配符wildcard用于表示匹配所有身份的放行规则。也就是说同一个 0 值在观测平面Hubble表示未知/缺失在转发平面eBPF policy map表示通配符。身份取值范围总览四类作用域安全身份并非在1 ~ 2^32-1之间连续分配而是根据上下文被划分成若干作用域scope。官方文档将其归纳为四类Cluster-local集群本地在单个集群内全局唯一的身份ClusterMesh多集群携带cluster-id信息、跨集群唯一的身份CIDR-basedCIDR 策略由 CIDR 选择器策略生成、节点本地的身份Remote-node远端节点可选针对远端节点标签生成、节点本地的身份。后两类CIDR 与 Remote-node都属于**节点本地node-local**身份这一点是理解整个取值范围划分的关键——它们不需要跨节点保持一致因此不必占用隧道的线缆字段位宽详见下文为什么 CIDR 身份从2^241开始一节。完整的取值范围映射原文档给出了一张完整的区间排布表此处完整保留并补充十六进制与十进制对照0x00000001 - 0x000000FF (1 to 2^8 - 1 ) reserved identities保留身份 0x00000100 - 0x0000FFFF (2^8 to 2^16 - 1 ) cluster-local identities集群本地身份 0x00010000 - 0x00FFFFFF (2^16 to 2^24 - 1 ) identities for remote clustersClusterMesh 远端集群身份 0x01000000 - 0x01FFFFFF (2^24 to 2^25 - 1 ) identities for CIDRs (node-local)CIDR 身份节点本地 0x02000000 - 0x02FFFFFF (2^25 to 2^25 2^24 - 1) identities for remote nodes (local)远端节点身份节点本地 0x01010000 - 0xFFFFFFFF (2^25 2^24 to 2^32 - 1 ) reserved for future use预留可以看到整个 32 位身份空间的高 8 位被用作作用域标记scope tag。在源码 pkg/identity/numericidentity.go 中作用域常量定义如下// IdentityScopeMask is the top 8 bits of the 32 bit identity IdentityScopeMask NumericIdentity(0xFF_00_00_00) // IdentityScopeGlobal is the identity scope used by global and reserved identities. IdentityScopeGlobal NumericIdentity(0) // IdentityScopeLocal is the tag in the numeric identity that identifies // the identity as a local (CIDR) identity. IdentityScopeLocal NumericIdentity(1 numericidentity.Bitlength) // 0x01000000 // IdentityScopeRemoteNode is the tag in the numeric identity that identifies // the identity as a remote node identity. IdentityScopeRemoteNode NumericIdentity(2 numericidentity.Bitlength) // 0x02000000其中numericidentity.Bitlength 24见 pkg/identity/numericidentity/constants.go即线路上on the wire身份编码只使用低 24 位。这与上表完全吻合IdentityScopeLocal1 24对应 CIDR 身份的起点0x01000000IdentityScopeRemoteNode2 24对应远端节点身份的起点0x02000000全局/保留身份的作用域为0其低 24 位承载全部信息。身份作用域是如何被决定的标签集合 → 作用域的决定逻辑位于 pkg/identity/identity.go 的ScopeForLabelsfunc ScopeForLabels(lbls labels.Labels) NumericIdentity { scope : IdentityScopeGlobal // If this is a remote node, return the remote node scope. if lbls.HasRemoteNodeLabel() { return IdentityScopeRemoteNode } // The ingress label is for L7 LB with cilium proxy ... if lbls.IsReserved() lbls.HasIngressLabel() { return IdentityScopeLocal } for _, label : range lbls { switch label.Source { case labels.LabelSourceCIDR, labels.LabelSourceFQDN, labels.LabelSourceReserved, labels.LabelSourceCIDRGroup: scope IdentityScopeLocal default: return IdentityScopeGlobal } } return scope }从源码可以推断出如下规则携带remote-node标签的身份 →IdentityScopeRemoteNode远端节点作用域只由cidr、fqdn、reserved、cidrgroup等来源的标签构成的身份 →IdentityScopeLocalCIDR 本地作用域只要出现其他来源如k8s:标签→ 全局作用域。Cluster-local集群本地身份Cluster-local 身份的范围是1到2^16 - 10x00000001 ~ 0x0000FFFF。其中最低的1 ~ 2550x00000001 ~ 0x000000FF对应**保留身份reserved identity**区间例如host、world、health、init、remote-node等内置身份。在源码中保留身份区间有两条关键边界常量见 pkg/identity/numericidentity.go// UserReservedNumericIdentity represents the minimal numeric identity that // can be used by users for reserved purposes. UserReservedNumericIdentity NumericIdentity(128) // MinimalNumericIdentity represents the minimal numeric identity not // used for reserved purposes. MinimalNumericIdentity NumericIdentity(numericidentity.MinimalIdentity) // 256其中numericidentity.MinimalIdentity 256见 pkg/identity/numericidentity/constants.go其注释明确指出cluster ID 为 0 时的全局身份分配从该值开始。同时pkg/identity/numericidentity.go 中的IsUserReservedIdentity定义了用户保留身份区间128 ~ 255并可通过AddUserDefinedNumericIdentity注册自定义保留身份func IsUserReservedIdentity(id NumericIdentity) bool { return id.Uint32() UserReservedNumericIdentity.Uint32() id.Uint32() MinimalNumericIdentity.Uint32() }保留身份reserved identities清单保留身份由 pkg/identity/numericidentity.go 中的reservedIdentities/reservedIdentityNames/reservedIdentityLabels三张映射表维护并与 pkg/datapath 侧Identity*ID常量一一对应。常见保留身份包括名称语义对应常量pkg/datapathunknown未知身份0IdentityUnknownIDhost本地主机IdentityHostIDworld集群外任意端点IdentityWorldIDworld-ipv4/world-ipv6双栈模式下按 IP 版本区分的集群外端点IdentityWorldIPv4ID/IdentityWorldIPv6IDunmanaged未托管的端点IdentityUnmanagedIDhealth本地 cilium-health 端点IdentityHealthIDinit尚未获得标签的初始化端点IdentityInitIDremote-node本地及远端集群中除本节点外的所有节点IdentityRemoteNodeIDkube-apiserver运行 kube-apiserver 后端的远端节点IdentityKubeAPIServerNodeIDingressIngress 代理连接使用的源地址IdentityIngressID此外还有一组聚合保留身份aggregate reserved identitiescluster、clustermesh、world、remote-node的聚合版本ReservedIdentityAggregateCluster、ReservedIdentityAggregateClusterMesh、ReservedIdentityAggregateWorld、ReservedIdentityAggregateRemoteNode它们不直接作用于流量而是用于policy map 聚合详见 pkg/policy/aggregate.go 相关注释。除了保留身份之外还有一类良知名身份well-known identities——针对 kube-dns、CoreDNS、cilium-operator 等固定组件预分配编号避免依赖集群级分配器。初始化逻辑位于InitWellKnownIdentities见 pkg/identity/numericidentity.go这些身份按iota 100起排布100 起并针对 Kubernetes 1.21 的NamespaceDefaultLabelName特性提供了第二组编号后缀2。保留身份注册后会进入reservedIdentityCache缓存见 pkg/identity/reserved.go 的AddReservedIdentity供策略引擎与 Hubble 查询使用。ClusterMesh跨集群身份在ClusterMesh场景下身份需要跨集群唯一因此cluster-id被编码进身份编号中。具体而言使用8 个比特位存放cluster-id位置在第 3 个八位组对应掩码0x00FF0000。对应区间为0x00010000 ~ 0x00FFFFFF2^16 ~ 2^24-1即文档表格中的 identities for remote clusters。同时文档强调第 4 个八位组最高位必须为 0——这一点由作用域常量保证全局作用域IdentityScopeGlobal 0高 8 位不参与编码。CIDR 身份不受此约束见下一节。在源码中cluster-id的解析通过NumericIdentity.ClusterID完成见 pkg/identity/numericidentity.go// ClusterID returns the cluster ID associated with the identity for the given // cluster configuration. func (id NumericIdentity) ClusterID(cinfo cmtypes.ClusterInfo) uint32 { return (uint32(id) cinfo.GetClusterIDShift()) cinfo.MaxConnectedClusters }NumericIdentity 的类型注释进一步说明了全局身份低 24 位的划分边界取决于配置的最大集群数// max-connected-clusters255: bits 0-15 identity, bits 16-23 cluster ID // max-connected-clusters511: bits 0-14 identity, bits 15-23 cluster ID也就是说cluster-id占据的比特位宽度不是固定值当最大连接集群数为 255 时低 16 位属于集群内身份、高 8 位属于 cluster ID当最大连接集群数为 511 时低 15 位属于集群内身份、高 9 位属于 cluster ID。这解释了 ClusterMesh 区间上限为2^24-1的原因——低 24 位必须同时容纳集群内身份号与cluster-id。CIDR-based基于 CIDR 策略的身份节点本地CIDR 身份由 CIDR 选择器策略生成且是节点本地node-local的——这意味着同一个 CIDR 策略在两台节点上生成的 identity 编号可能不同它不需要与集群内其他节点保持一致。取值范围与位移CIDR 身份的分配编号从1到16777215即0xFFFFFFMaxAllocatorLocalIdentity见 pkg/identity/numericidentity.go但由于它们被左移 24 位按位 OR 上IdentityScopeLocal其有效范围为1 | (1 24) 16777217 (0x01000001) 16777215 | (1 24) 33554431 (0x01FFFFFF)即16777217 ~ 33554431对应文档表格中的0x01000000 - 0x01FFFFFF。源码中相关常量// MaxAllocatorLocalIdentity represents the maximal numeric identity // that the localIdentityCache allocator can allocate for a local (CIDR) // identity. // // Note that this does not represents the maximal value for a local // identity, as the allocated ID will then be bitwise ORed with // IdentityScopeLocal. MaxAllocatorLocalIdentity 0xFFFFFF // MaxLocalIdentity represents the actual maximal numeric identity value // for a local (CIDR) identity. MaxLocalIdentity MaxAllocatorLocalIdentity | IdentityScopeLocal节点本地身份的分配由 pkg/identity/cache/allocator.go 中的本地身份缓存负责CIDR 身份与远端节点身份分别使用独立的作用域IdentityScopeLocal与IdentityScopeRemoteNode实例化分配器。为什么 CIDR 身份从 2^24 1 开始这是本文档最核心的设计说明之一。原因如下节点本地身份CIDR 或 remote-node永远不会用于 Cilium 托管节点之间的流量因此它们不需要放进 VXLAN 或 Geneve 虚拟网络字段VNID而非 CIDR 身份被限制在 24 位内正是为了保证它们能完整塞进 VXLAN/Geneve 的 24 位 VNI 字段在链路上传输既然 CIDR 身份不会在报文里编码它们就可以从更高的值开始编号于是最小 CIDR 身份被设定为2^24 1。换言之身份编号排布的约束主要来自隧道封装字段的位宽。全局身份必须保持 ≤ 24 位以便跨节点/跨集群在隧道中传递本地身份则没有这个包袱直接利用高 8 位作用域标记腾出的空间。Node-local identity远端节点身份可选远端节点身份remote-node identities与 CIDR 身份类似也是节点本地的在不同节点上可能编号不同。它们只在以下条件满足时被启用配置项policy-cidr-match-mode包含nodes或配置项enable-node-selector-labels设置为true。这两个选项在 pkg/option/config.go 中定义其解析与校验逻辑如下// PolicyCIDRMatchMode defines the entities that CIDR selectors can reach PolicyCIDRMatchMode policy-cidr-match-mode // EnableNodeSelectorLabels enables use of the node label based identity EnableNodeSelectorLabels enable-node-selector-labels配套的访问方法与校验见 pkg/option/config.gofunc (c *DaemonConfig) PolicyCIDRMatchesNodes() bool { return slices.Contains(c.PolicyCIDRMatchMode, nodes) } func (c *DaemonConfig) PolicyCIDRMatchesPods() bool { return slices.Contains(c.PolicyCIDRMatchMode, pods) } // PerNodeLabelsEnabled returns true if per-node labels feature // is enabled func (c *DaemonConfig) PerNodeLabelsEnabled() bool { return c.EnableNodeSelectorLabels } func (c *DaemonConfig) validatePolicyCIDRMatchMode() error { // Currently, the acceptable values are nodes and pods. for _, mode : range c.PolicyCIDRMatchMode { switch mode { case nodes, pods: continue default: return fmt.Errorf(unknown CIDR match mode: %s, mode) } } return nil }从源码可以确认policy-cidr-match-mode当前可接受的取值只有nodes与pods分别表示 CIDR 选择器可以匹配节点与 Pod当包含nodes时CIDR 策略可以将节点纳入匹配范围此时节点会获得一个节点本地作用域的身份IdentityScopeRemoteNodeenable-node-selector-labels开启基于节点标签的身份per-node labels能力同样走IdentityScopeRemoteNode作用域。远端节点身份的区间为0x02000000 ~ 0x02FFFFFF对应IdentityScopeRemoteNode 2 24。节点本地身份无论 CIDR 还是 remote-node与 CIDR 身份一样不参与 VXLAN/Geneve 的 VNI 编码。与 eBPF 数据路径及 Hubble 的联动eBPF 数据路径中的身份消费安全身份最终会写入 BPF map如 policy map、ipcache 等供数据路径做快速查找与策略匹配策略 map 通配符Identity 0 在 eBPF 策略 map 中表示任意身份用于实现基于通配的放行规则对应本文第一节的说明scope 判定pkg/identity/numericidentity.go 提供了一系列作用于身份编号的判定方法如HasLocalScope()、HasRemoteNodeScope()、Scope()、IsWorld()、IsCluster()。其中IsCluster()的注释明确要求与 BPF 侧bpf identity_is_cluster()保持同步NOTE: keep this and bpf identity_is_cluster() in sync!说明身份编号的排布约定是用户态 Go 与内核态 eBPF 共同遵守的协议。Hubble 输出中的身份解读Hubble 观测到的流量会携带源/目的身份编号。结合本文的知识你可以这样解读出现0身份未找到lookup 失败属于异常或未解析状态1 ~ 255保留身份对应host、world、health、init、remote-node、kube-apiserver、ingress等可通过 pkg/identity/numericidentity.go 的reservedIdentityNames映射为可读名称256 ~ 65535集群本地身份是集群内普通端点的常态编号65536 ~ 16777215高 8 位 cluster-id 生效时按位解析ClusterMesh 跨集群身份可借助ClusterID()方法还原所属集群16777217 ~ 335544310x01xxxxxx节点本地 CIDR 身份33554433 ~ 503316470x02xxxxxx节点本地远端节点身份。保留身份的文本渲染由NumericIdentity.String()完成见 pkg/identity/numericidentity.go优先返回reservedIdentityNames中的可读名称否则返回十进制编号。参考实现与延伸阅读身份编号的作用域与边界常量pkg/identity/numericidentity.go线缆位宽常量Bitlength 24pkg/identity/numericidentity/constants.go标签 → 作用域判定ScopeForLabelspkg/identity/identity.go保留身份注册与缓存pkg/identity/reserved.go本地CIDR/Remote-node身份分配器pkg/identity/cache/allocator.go配置项定义与校验pkg/option/config.go、pkg/option/config.go身份分配的单元测试含IdentityScopeLocal作用域验证pkg/identity/cache/allocation_test.go策略 map 聚合的保留身份说明pkg/policy/aggregate.go小结Cilium 的安全身份是一个精心设计的 32 位编号空间高 8 位承载作用域标记全局 / CIDR 本地 / 远端节点本地低 24 位承载实际身份号与 cluster-id。这一设计的核心驱动力是隧道封装VXLAN/Geneve 的 24 位 VNI的位宽约束——全局身份必须能塞进线缆字段节点本地身份CIDR、remote-node则因此得以从2^24之后的高位开始编号。理解这张取值范围表不仅能帮助你在排查 Hubble 输出、解读策略匹配行为时快速定位身份类型也能在规划 ClusterMesh 集群数量max-connected-clusters对身份位宽的折衷时做出正确判断。【免费下载链接】ciliumeBPF-based Networking, Security, and Observability项目地址: https://gitcode.com/GitHub_Trending/ci/cilium创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考