进阶 docs.cubesandbox.com 2026-10-08 23:44:25 · 6 阅读

第17章 出网网络策略

出网网络策略 ​Cube Sandbox 的出网控制不是单一开关,而是由 API 校验、模板合并、Cubelet 内置 network runtime 下发、CubeVS eBPF 数据面、CubeEgress L7 代理 共同完成的一条链路。理解这条链路后,配置 allow_out、deny_out 和 rules 时会更容易判断:某个包会被直接转发、被拒绝、被 DNS 学习忽略,还是进入 HTTP/HTTPS 代理。本文重点说明:用户在 SDK / API 中如何配置出网策略。allow_out、deny_out、rules 如何被合并和下发。CubeVS 在 from_cube 方向逐步如何判断、转发、SNAT 或拒绝。DNS 域名 allow-list 如何查询、匹配、学习 A 记录。哪些流量会进入 CubeEgress,哪些不会。常见场景配置示例和排障方法。请求级的 L7 行为,例如完整的 HTTP/HTTPS 规则匹配、审计日志、header 注入、TLS CA 等,请继续参考 安全代理。本文只解释“网络层如何把该进代理的流量送进代理”。总体架构 ​出网流量从沙箱发出后,大致经过下面几层:各组件职责如下:组件主要职责CubeAPI接收 SDK/API 请求,映射网络配置,并转成 CubeMaster 请求。CubeMaster将模板里的网络配置和本次创建请求里的网络配置合并,然后调度到 Cubelet 内置 network runtime。Cubelet 内置 network runtime把 CubeNetworkConfig 转成 CubeVS 可理解的 MVMOptions;从 L7 rules 中抽取网络可达目标;注册或更新 TAP 的 eBPF map。CubeVS运行在宿主机 eBPF 数据面。负责 per-sandbox L3/L4 allow/deny、配置域名的 DNS A 记录学习、session/NAT、TCP RST 拒绝,以及是否把流量送到 L7 代理。CubeEgress透明 HTTP/HTTPS 代理。只处理被 CubeVS 标记为需要 L7 检查的 TCP/80、TCP/443 流量,执行完整 rules。一个关键点是:出站策略的主动判断发生在沙箱流量进入 TAP 后的 from_cube 方向。宿主机网卡上的 from_world 方向主要处理已有会话的回包反向 NAT、端口映射流量,以及 DNS 响应学习;它不是出站 allow/deny 的主判断入口。用户可配置字段 ​创建沙箱时,以下字段共同决定出网行为:字段配置位置生效层含义allow_internet_accessSandbox.create(allow_internet_access=...)CubeVS是否默认允许公网出网。默认 true。为 false 时,CubeVS 会安装 0.0.0.0/0 的 deny-all 规则,然后依靠显式 allow 放行。allow_outnetwork["allow_out"]CubeVS显式允许的 IPv4、CIDR 或 DNS 域名。IP/CIDR 写入 allow_out_v2;域名写入 dns_allow,通过 DNS 查询和响应学习变成临时 IP allow。deny_outnetwork["deny_out"]CubeVS显式拒绝的 IPv4 或 CIDR。这里不接受域名。rulesnetwork["rules"]CubeVS + CubeEgressHTTP/HTTPS L7 规则。CubeVS 只抽取其中的 match.sni / match.host 作为网络可达目标并打上 L7 标记;CubeEgress 才会执行完整 match/action。CubeVS 的基础 IP 策略优先级是:先查 allow_out_v2:命中则允许;如果条目带 L7_REQUIRED 标记,后续 TCP/80、TCP/443 还会被送入 CubeEgress。再查 deny_out:命中则拒绝。TCP 会尽量返回 RST,非 TCP 直接 drop。都不命中时默认允许:除非 allow_internet_access=false 已经通过 deny_out 安装了 0.0.0.0/0。也就是说,策略优先级是:allow > deny > default allow。内置内部网段保护当 allow_internet_access 不是 false 时,CubeVS 会默认把沙箱私有网段和宿主机内部网段加入 deny_out,包括 10.0.0.0/8、127.0.0.0/8、169.254.0.0/16、172.16.0.0/12、192.168.0.0/16。这样可以避免工作负载借公共出网策略访问 Cube 内部基础设施。当 allow_internet_access=false 时,后端安装的是 0.0.0.0/0 的 deny-all;显式 allow_out 和 L7 目标仍然可以优先放行。数量上限 ​在修改沙箱 eBPF map 之前,CubeVS 会统计由 allow_out、deny_out 和从 rules 抽取出的网络目标生成的最终唯一 map key。超限错误会经 Cubelet、CubeMaster 和 CubeAPI 逐层返回给创建沙箱的调用方。最终 map 计数上限network.allow_out_v2:来自 allow_out 和 rules[].match.host/sni 的唯一 IP/CIDR allow key8192 条network.deny_out:最终唯一 deny key;启用内置内部网段时也计入其中8192 条network.dns_allow:来自 allow_out 和 rules[].match.host/sni 的唯一域名 key1024 条DNS inner trie 总容量是 1024 条域名规则。DNS policy mode 单独存放在 ifindex_to_mvmmeta 中,因此 dns_allow 不再为模式 marker 保留条目。等价目标按最终 map key 去重。例如 198.51.100.1 和 198.51.100.1/32 只算一个 IP key;域名 key 会转为小写并忽略尾部的 .。超限错误会包含最终 map 计数器、实际数量和最大值:textnetwork.allow_out_v2 exceeds maximum entries: got 8193, max 8192 network.deny_out exceeds maximum entries: got 8193, max 8192 network.dns_allow exceeds maximum entries: got 1025, max 1024配置语法 ​allow_out ​allow_out 用来表达 L3/L4 网络可达性,支持:IPv4 地址,例如 1.1.1.1,内部按 /32 处理。IPv4 CIDR,例如 203.0.113.0/24。普通 DNS 域名,例如 api.example.com。前缀 *. 通配域名,例如 *.example.com。通配域名只匹配子域,不匹配 apex 域名:配置匹配不匹配*.example.comapi.example.com、a.b.example.comexample.comexample.comexample.comapi.example.com域名会被转换成小写并去掉尾部的 .。只支持合法 DNS 域名;类似 999.999.999.999 这种 dotted-decimal 但不是合法 IPv4 的值会被拒绝或忽略。当 allow_out 包含 DNS 域名时,请求必须同时建立 deny-all 兜底。可以二选一:设置 allow_internet_access=false,CubeVS 会自动把 0.0.0.0/0 写入 deny_out。或者显式在 deny_out 中包含 0.0.0.0/0。这是因为域名 allow_out 需要先通过 DNS A 响应学习成临时 IP allow 条目。如果没有 deny-all 兜底,未学习到的目的 IP 仍会被默认放行,域名 allow-list 就会产生误导。deny_out ​deny_out 只支持 IPv4 或 IPv4 CIDR,例如:pythonnetwork={ "deny_out": [ "169.254.169.254/32", "10.0.0.0/8", ], }deny_out 不支持域名。原因是拒绝域名需要处理 DNS 解析、多 A 记录、TTL、CNAME、缓存污染等问题,而当前拒绝逻辑是纯 IP/CIDR LPM 匹配。在 allow_internet_access=false 的严格模式下,0.0.0.0/0 已经作为兜底拒绝安装到 deny_out。这时额外配置更窄的 deny_out 通常只是表达意图,不会比 deny-all 更严格;真正需要配置的是要放行的 allow_out 或带 host / sni 的 L7 rules。rules ​rules 是 L7 HTTP/HTTPS 规则,完整规则由 CubeEgress 执行。每条规则通常包含:name:规则名,用于审计和模板合并。match.scheme:http 或 https。match.sni:TLS SNI。支持普通域名和 *. 通配域名。match.host:HTTP Host。支持域名、host:port、IPv4、IPv4 CIDR 和 *. 通配域名。match.method:HTTP 方法列表,方法之间是 OR。match.path:默认精确匹配;尾部 * 表示前缀匹配,例如 /v1/*。action.allow:true 放行,false 拒绝。action.audit:审计级别,例如 metadata、full、none。action.inject:可选 header 注入,仅在 allow 且 HTTPS 条件满足时生效。多个字段之间是 AND 关系。例如同时设置 scheme=https、sni=api.example.com、method=["POST"]、path=/v1/*,表示这些条件都满足才匹配。受限出网场景下,L7 规则要写 host 或 sniCubeVS 只会从 rules[].match.sni 和 rules[].match.host 抽取网络可达目标。method、path、scheme 只能在 CubeEgress 里判断,无法告诉 CubeVS 目标 IP 应该被放行或送入代理。因此,当 allow_internet_access=false 时,每条需要真正访问外部 HTTP/HTTPS 服务的 L7 规则都应该包含 host 或 sni。只写 method 和 path 的规则可能永远到不了 CubeEgress,因为网络层不知道该允许哪个目的 IP。控制面合并与下发流程 ​从用户请求到 eBPF map,大致流程如下:1. CubeAPI 映射和转发 ​CubeAPI 接收用户的 SandboxNetworkConfig,正确配置方式如下:pythonnetwork={ "allow_out": ["api.example.com"], "deny_out": ["203.0.113.0/24"], "rules": [...], }CubeAPI 会把请求映射成 CubeMaster 的 CubeNetworkConfig,并转发 allow_out、deny_out 和完整的 rules 列表。2. CubeMaster 与模板合并 ​如果模板里也带了 CubeNetworkConfig,CubeMaster 会把模板配置和用户请求配置合并:allowInternetAccess:如果请求中显式设置,则覆盖模板值。allowOut:把请求列表追加到模板列表后,并按字符串去重。denyOut:把请求列表追加到模板列表后,并按字符串去重。rules:请求规则会排在模板规则之前。若两者包含相同的 name,由于规则列表采用 first-match-wins 机制,CubeEgress 会优先匹配请求规则;同名的模板规则不会被覆盖或删除,而是保留在合并后列表的后续位置。合并后的 CubeNetworkConfig 会发给 Cubelet 内置 network runtime。内置 network runtime 抽取 L7 网络可达目标后,由 CubeVS 校验最终唯一 eBPF map key。3. Cubelet 内置 network runtime 提取 CubeVS 目标 ​Cubelet 内置 network runtime 收到 CubeNetworkConfig 后,会构造 CubeVS 的 MVMOptions:AllowInternetAccess:未设置时默认 true。AllowOut:直接来自用户/模板合并后的 allowOut。DenyOut:直接来自用户/模板合并后的 denyOut。L7AllowOut:从每条 rules[].match.sni 和 rules[].match.host 中抽取。L7 目标抽取规则:来源处理方式match.sni只接受合法域名或 *. 通配域名;转小写,去掉尾部 .;不接受 IP。match.host 为 host:port去掉端口,只保留 host。match.host 为 IPv4保留为 /32 形式的 IP 目标。match.host 为 IPv4 CIDR保留 CIDR。match.host 为域名或 *. 通配域名规范化为小写域名目标。无效 host/SNI不作为 CubeVS 目标下发。例如:pythonrules = [ Rule(match=Match(sni="API.Example.COM."), action=Action(allow=True)), Rule(match=Match(host="gateway.example.com:8443"), action=Action(allow=True)), Rule(match=Match(host="1.2.3.4:443"), action=Action(allow=True)), Rule(match=Match(host="10.1.2.3/8"), action=Action(allow=True)), ]会抽取出类似目标:textapi.example.com gateway.example.com 1.2.3.4 10.0.0.0/8这些目标和普通 allow_out 的区别是:它们会带上 L7_REQUIRED 标记。4. CubeVS map 写入 ​Cubelet 内置 network runtime 最终把不同来源写入不同 map:来源写入位置是否带 L7_REQUIRED作用allow_out IPv4/CIDRallow_out_v2[ifindex]否允许直接 L3/L4 出网。allow_out 域名dns_allow[ifindex]否跟踪匹配的 DNS A 查询,并把响应中的 A 记录临时学习到 allow_out_v2。要求同时设置 allow_internet_access=false 或显式 deny_out=["0.0.0.0/0"],避免未学习 IP 被默认放行。rules[].match.host/sni IPv4/CIDRallow_out_v2[ifindex]是允许访问该 IP/CIDR,并让 TCP/80、TCP/443 进入 CubeEgress。rules[].match.host/sni 域名dns_allow[ifindex]是跟踪匹配的 DNS A 查询并学习带 L7 标记的 IP;它本身不会影响无关 DNS 名称。deny_outdeny_out[ifindex]不适用拒绝匹配目的 IP/CIDR 的流量,除非先命中 allow。内置内部网段deny_out[ifindex]不适用防止访问 Cube 内部基础设施。allow_internet_access=falsedeny_out[ifindex]不适用写入 0.0.0.0/0 deny-all。如果同一个 IP/CIDR 既来自普通 allow_out,又来自 L7 规则,CubeVS 会保留 L7_REQUIRED 标记。静态 allow_out 条目不过期;DNS 学习出的条目带 expires_at_ns,会按 TTL 过期。更新运行中沙箱的策略 ​PUT /sandboxes/{sandboxID}/network 用于替换正在运行的沙箱的出站策略。请求体就是创建时那个 network 对象,且语义是整体替换而不是增量打补丁:没传的字段会被清空。bashcurl -X PUT "$CUBE_API/sandboxes/$SANDBOX_ID/network" \ -H 'Content-Type: application/json' \ -d '{"allowInternetAccess": false, "allowOut": ["api.example.com"], "denyOut": ["0.0.0.0/0"]}'省略 allowInternetAccess 会恢复公网访问「没传就清空」这条规则作用在这个开关上时比其他字段更容易踩到:清空意味着回到「未显式设置」,而它的默认值是允许出网,于是 0.0.0.0/0 的 deny-all 会被撤掉。也就是说,一个创建时带 allow_internet_access=false 的沙箱,只要有一次更新没有重新写上这个字段,就会重新能访问公网 —— 即使这次更新本来只想改 allowOut。(内置的内部网段保护仍然生效,恢复的只是公网出网。)这里没有更省事的写法:denyOut 同样是没传就清空,所以改用显式的 "denyOut": ["0.0.0.0/0"] 也一样得每次都带上。要让沙箱保持隔离,每次更新都必须发送完整的期望策略,其中包括 "allowInternetAccess": false。注意这里策略字段在顶层,而不是像创建沙箱那样嵌在 network 里。这是为了和 E2B 的更新接口一致,让按任一 SDK 写的客户端都能命中同一个线上格式;E2B 自己的创建接口和我们一样是嵌套的,所以这处不一致来自上游而非我们。allowPublicTraffic 和 maskRequestHost 是 CubeSandbox 扩展,E2B 的更新 schema 里没有。rules 两种形状都接受:CubeEgress 的规则数组,或 E2B 的 {host: [{transform: {headers: {...}}}]} 映射。数组是规范形式,因为 E2B 的映射表达不了拒绝判决、审计级别,以及除 host 之外的任何匹配条件 —— 把我们的规则塞成那个形状会静默丢掉它们。E2B 的映射会被转换成等价的放行加注入规则,命名为 e2b-transform-。各 SDK 分别暴露为 sandbox.update_network(...)(Python)、sandbox.updateNetwork(...)(Node)和 sandbox.UpdateNetwork(...)(Go),三者都把整份策略作为一个对象传入、allow_internet_access 包含在其中 —— 和 E2B 的 update_network 形状一致。已经建立的连接会怎样 ​和单纯重写 map 不同,更新也会作用到已有流量。每个沙箱带一个策略代际(mvm_meta.policy_version),在 CubeEgress 和 CubeVS map 都写入新策略之后才递增。每条 session 会缓存自己被放行时的代际,因此每次更新后,存量流的下一个包会被重新判定一次,且只判定一次:仍然放行且判决不变 —— session 被重新盖上新代际后照常继续。这是常见情况,每条流每次更新只多付一次策略查表。不再放行,或判决发生变化(例如某个 host 在普通 SNAT 和 L7 代理之间切换)—— session 立即作废:出入两个方向的连接跟踪记录都被删掉,因此回包不再投递,该 4 元组也立刻空出来可供重连。TCP 会收到 RST,与这里对其他所有不可达 TCP 报文的处理方式一致,让 guest 立即失败而不是卡在重传上;UDP 和 ICMP 没有可 reset 的东西,直接丢弃。判决发生变化时选择作废而不是迁移这条流,是因为 SNAT 路径和 L7 路径对回包元组、以及由谁来终结这条 TCP 连接这两件事的理解并不一致。随后的重连会像任何新流一样按新策略判定。重判是由流量驱动的,不是主动推送的:一条空闲的存量连接要等沙箱下一次在它上面发包时才会被判定。因此一条打开但一直不说话的连接会留在 session 表里,直到它再次发包或按正常超时被回收。有两点需要提前规划:已经学到的 DNS IP 会比产生它的域名规则活得更久。 从 allow_out 里删掉一个域名后,新的 IP 会立刻停止被学习,但此前已为它学到的 IP 会一直放行到 DNS TTL 过期。要想及时收回对某个域名的访问,需要配合 allow_internet_access=false 的策略和较短的解析 TTL。更新在多个平面之间不是事务性的。 先更新 CubeEgress,再更新 CubeVS,最后才写持久化状态,因此中途失败时沙箱所处的状态不会比「新旧策略的并集」更宽松。重放同一个请求即可收敛;Cubelet 重启后会重新应用最后一次成功持久化的策略。from_cube 如何判断和转发 ​from_cube 是挂在沙箱 TAP ingress 上的 TC eBPF 程序。每个沙箱发出的包都会先进入这里。处理顺序可以理解为下面几个阶段。1. 非 IPv4 和 ARP ​CubeVS 主要处理 IPv4 出网。ARP 请求会由 eBPF 直接构造 ARP reply,使沙箱认为网关存在。其他非 IPv4 包会被丢弃。2. 访问沙箱网关 / cube-dev ​如果目的地址是沙箱网关 169.254.68.5,这是内部到 cube-dev 的路径,不按普通公网出站策略处理。当前逻辑只允许:ICMP。TCP 非 SYN 包。TCP SYN 和其他协议会被丢弃。允许的内部包会被 DNAT 到 cubegw0_ip,再重定向到 cube-dev。3. 本地端口映射优化 ​对 TCP 流量,from_cube 会检查 local_port_mapping。如果沙箱源端口对应某个宿主机端口映射,CubeVS 会执行端口 SNAT 并直接重定向到宿主机网卡。这是端口映射场景的优化路径。4. 出站网络策略 ​普通外部流量进入 check_net_policy:用目的 IP 在 allow_out_v2[ifindex] 中做 LPM 查找。 命中且未过期:允许。如果 value 中有 NET_POLICY_FLAG_L7_REQUIRED,后续 TCP/80、TCP/443 会进代理。未命中 allow 时,用目的 IP 在 deny_out[ifindex] 中做 LPM 查找。 命中:拒绝。allow/deny 都没命中:默认允许。对于被拒绝的包:TCP:CubeVS 会尽量原地构造一个 TCP RST 回给沙箱,所以用户态通常看到的是 Connection refused 或连接立即失败,而不是长时间 timeout。UDP/ICMP:直接丢弃。TCP 非 SYN 无 session 的处理对 TCP 出站 NAT 来说,只有 SYN && !ACK && !FIN && !RST 的首包才能创建新 session。后续 TCP 包必须能在 egress_sessions 中查到已有 session。若沙箱发出一个非 SYN 的 TCP 包但没有 session,CubeVS 会返回 TCP RST,而不是为它新建连接。这个逻辑发生在 from_cube 方向。from_world 方向只对已有 ingress_sessions 做回包反向 NAT,没有命中的包通常放行给内核继续处理,而不是执行出站策略拒绝。5. L7 代理路径 ​当一个 TCP 包满足下面条件时,CubeVS 不会做普通 SNAT 出网,而是把原始包重定向到 cube-dev ingress:命中了 allow_out_v2。命中的 value 带 L7_REQUIRED 标记。目的端口是 TCP 80 或 443。随后宿主机 TPROXY 规则会按 iif cube-dev + dport 80/443 匹配,把连接交给 CubeEgress。CubeEgress 再根据完整 rules 判断 scheme、sni、host、method、path、action、audit、inject。6. 普通 NAT/session 路径 ​不需要 L7 的 TCP/UDP/ICMP 会走 CubeVS NAT/session:TCP:只有 SYN 首包创建 session;后续包更新 TCP conntrack 状态。UDP:首个出站包创建 UNREPLIED session,收到回包后进入 REPLIED。ICMP:只处理 Echo Request,identifier 用作类似端口的 session key。SNAT 会把沙箱内部源地址 169.254.68.6 改写为宿主机可路由的 SNAT IP,并分配动态源端口,然后重定向到宿主机网卡。DNS 域名 allow-list 和学习 ​域名策略是这套机制里最容易误解的部分。CubeVS 并不是直接“允许域名”,因为真实数据包只有目的 IP。它的做法是:跟踪已配置域名的 DNS A 查询,再把匹配响应中的 A 记录学习成临时 IP allow 条目。没有配置的域名不会在 DNS 阶段被拦截;它们只是不会创建学习到的 IP allow 条目。DNS allow map 的结构 ​每个沙箱有一个 dns_allow[ifindex] inner map。它是 LPM trie,key 是反转后的域名:配置反转 key 思路匹配语义example.commoc.elpmaxe\0精确匹配 example.com。*.example.commoc.elpmaxe.匹配子域,例如 api.example.com,不匹配 example.com。dns_allow 只存域名规则和规则自身的标记,例如 L7_REQUIRED。沙箱级 DNS policy mode 存放在 ifindex_to_mvmmeta[].dns_policy_flags 中:模式域名来源查询行为学习行为Offallow_out 和 L7 rules 都没有域名DNS 按普通 UDP 出站。不进行 DNS 响应学习。Track至少有一个 allow_out 域名或 L7 rules 域名所有域名都放行;只跟踪命中的已配置域名。命中的 A 记录会学习到 allow_out_v2,并保留 L7 标记。allow_out 域名和 L7 rule 域名都会开启 DNS 学习。两者都不会把 DNS 本身变成 allow-list,CubeVS 也不再为未命中的域名返回合成 NXDOMAIN。DNS 查询阶段 ​当沙箱发出 UDP/53 查询时,from_cube 会尝试解析 DNS payload:只处理标准、格式正确的 IN A 查询。解析 QNAME,转成小写,再反转成 LPM trie key。如果模式为 Off,走普通 UDP NAT。CubeVS 用反转后的 QNAME 匹配域名规则。如果域名未命中,查询正常走 UDP NAT,但不会创建 pending learning entry。如果域名匹配,CubeVS 会在 dns_query_track 写入一个 pending query,key 包含: sandbox ifindexDNS server IPUDP 源端口DNS IDQNAME hashpending query 的生命周期是 10 秒。查询本身继续走 UDP NAT 发往真实 DNS server。因此,无关域名的 DNS 解析在普通 UDP 出网可用时仍会成功。在严格域名 allow-list 配置下,无关目标的失败点在后续 IP 连接阶段:由于没有 DNS 学习出的 allow 条目,目的 IP 会被 deny-all 兜底拒绝。DNS 响应阶段 ​DNS 响应从外部回来时,先进入宿主机网卡上的 from_world:from_world 通过 ingress_sessions 找到对应 UDP session。如果这是 DNS server 端口 53 的响应,会先调用 DNS 响应学习逻辑。只有 ifindex_to_mvmmeta[].dns_policy_flags 带 learning flag 时才执行学习,并且响应必须匹配之前 dns_query_track 中的 pending query。只学习 IPv4 A 记录;AAAA / IPv6 不会写入 allow_out_v2。最多处理响应中的前 8 个 answer。学到的 IP 以 /32 写入 allow_out_v2[ifindex],expires_at_ns 按 DNS TTL 计算。如果原 DNS allow 规则带 L7_REQUIRED,学习出的 IP 也继承这个标记。学习完成或失败后,pending query 会被删除。响应包继续做反向 NAT,返回沙箱。如果同一个 IP 已经是静态 allow_out,DNS 学习不会把它变成会过期的临时条目;静态条目会保持不过期,同时合并 flags。DNS 回收 ​Cubelet 内置 network runtime / CubeVS 用户态侧有 reaper 定期扫描:allow_out_v2:删除已经过期的 DNS-learned 临时条目。dns_query_track:删除超过 10 秒仍未收到响应的 pending query。因此,如果域名 DNS TTL 很短,对应 IP allow 也会较快过期;下一次访问前需要再次 DNS 查询并学习。当前只学习 IPv4 A 记录动态域名授权路径当前只学习 IPv4 A 记录。IPv6 / AAAA 响应不会进入 allow_out_v2,deny_out 也仍然是 IPv4/IP-CIDR 策略。哪些流量会进入 CubeEgress ​CubeEgress 并不会看到所有出站包。进入 CubeEgress 必须同时满足“网络目标来自 L7 规则”和“端口是 HTTP/HTTPS 透明代理端口”。流量是否进入 CubeEgress原因TCP 目的端口 80,目的 IP 命中带 L7_REQUIRED 的 allow_out_v2是HTTP 需要由 CubeEgress 执行完整 rules。TCP 目的端口 443,目的 IP 命中带 L7_REQUIRED 的 allow_out_v2是HTTPS 需要由 CubeEgress 根据 SNI/Host 等执行规则。TCP 80/443,但目的 IP 只来自普通 allow_out,没有 L7 标记否只做 L3/L4 放行和 NAT。TCP 非 80/443,即使命中 L7 目标否CubeEgress 是 HTTP/HTTPS 透明代理,目前只接管 80/443。UDP/53 DNS 查询否由 CubeVS DNS 学习逻辑处理。UDP、ICMP、其他 TCP 端口否走 CubeVS NAT/session。命中 deny_out 且没有先命中 allow 的流量否在 CubeVS 侧拒绝。from_world 回包否回包方向按 session 做反向 NAT,不做 L7 代理入口判断。举例:pythonrules = [ Rule( name="allow_api", match=Match(scheme="https", sni="api.example.com", host="api.example.com"), action=Action(allow=True), ) ]这条规则会让 api.example.com 被写入 dns_allow,并带上 L7_REQUIRED。沙箱访问时流程是:沙箱查询 api.example.com 的 A 记录。CubeVS 允许 DNS 查询,并记录 pending query。DNS 响应回来,CubeVS 学习 A 记录,例如 203.0.113.10/32,并打上 L7_REQUIRED。沙箱连接 203.0.113.10:443。from_cube 命中带 L7 标记的 allow_out_v2 条目,重定向到 cube-dev。TPROXY 把连接交给 CubeEgress。CubeEgress 检查完整规则,决定 allow/deny/audit/inject。常见配置示例 ​示例 1:完全禁止公网 ​适合纯离线代码执行、数据处理等场景:pythonfrom cubesandbox import Sandbox with Sandbox.create( template="tpl_xxx", allow_internet_access=False, ) as sb: result = sb.commands.run("curl -m 2 https://example.com") print(result.stderr)效果:CubeVS 安装 0.0.0.0/0 deny-all。没有显式 allow_out 或 L7 目标时,普通外部访问都会被拒绝。TCP 通常快速收到 RST;UDP/ICMP 通常被 drop。示例 2:只允许访问固定 IP/CIDR ​适合只允许访问固定出口服务、内网服务或指定 DNS server 的场景:pythonfrom cubesandbox import Sandbox with Sandbox.create( template="tpl_xxx", allow_internet_access=False, network={ "allow_out": [ "1.1.1.1/32", "203.0.113.0/24", ], }, ) as sb: sb.commands.run("curl http://1.1.1.1")效果:1.1.1.1/32 和 203.0.113.0/24 写入 allow_out_v2。0.0.0.0/0 在 deny_out 中作为兜底拒绝。allow 优先级高于 deny,所以这两个目标仍然可访问。示例 3:默认允许公网,但拒绝敏感地址段 ​适合保留一般公网访问,同时阻断元数据地址或敏感内网:pythonfrom cubesandbox import Sandbox with Sandbox.create( template="tpl_xxx", allow_internet_access=True, network={ "deny_out": [ "169.254.169.254/32", "10.0.0.0/8", ], }, ) as sb: sb.commands.run("curl https://example.com")效果:未命中 deny 的公网目标默认允许。命中 deny_out 的目标被拒绝。如果某个目标同时也被 allow_out 放行,则 allow 优先。示例 4:只允许访问指定域名 ​适合“只允许访问某几个域名,但不做 HTTP 请求级检查”的场景:pythonfrom cubesandbox import Sandbox with Sandbox.create( template="tpl_xxx", allow_internet_access=False, network={ "allow_out": [ "api.github.com", "*.example.com", ], }, ) as sb: sb.commands.run("curl https://api.github.com")效果:api.github.com 和 *.example.com 写入 dns_allow。允许匹配域名的 DNS A 查询。DNS 响应中的 A 记录临时写入 allow_out_v2。后续到这些 IP 的 TCP/443 直接 NAT 出网,不进入 CubeEgress,因为普通 allow_out 没有 L7 标记。注意:如果应用跳过 DNS,直接访问域名当前解析出的 IP,而该 IP 尚未通过沙箱 DNS 查询被学习,严格 allow-list 下会被 deny-all 拒绝。示例 5:使用 L7 规则限制 HTTP 方法和路径 ​适合只允许调用某个 API 的特定路径:pythonfrom cubesandbox import Sandbox, Rule, Match, Action rules = [ Rule( name="allow_deepseek_chat", match=Match( scheme="https", sni="api.deepseek.com", host="api.deepseek.com", method=["POST"], path="/v1/*", ), action=Action(allow=True, audit="metadata"), ), ] with Sandbox.create( template="tpl_xxx", allow_internet_access=False, network={"rules": rules}, ) as sb: sb.commands.run("curl -s https://api.deepseek.com/v1/chat/completions")效果:api.deepseek.com 从 sni / host 中被抽取为 L7 allow target。DNS A 记录学习到的 IP 带 L7_REQUIRED。TCP/443 进入 CubeEgress。CubeEgress 执行 scheme、sni、host、method、path 的完整匹配。示例 6:L7 deny 返回请求级 403 ​如果希望对某类 HTTP/HTTPS 请求返回明确的请求级拒绝,而不是网络层 drop,可以写 deny 规则:pythonfrom cubesandbox import Sandbox, Rule, Match, Action rules = [ Rule( name="deny_example_subdomains", match=Match(scheme="https", sni="*.example.net"), action=Action(allow=False, audit="metadata"), ), ] with Sandbox.create( template="tpl_xxx", allow_internet_access=False, network={"rules": rules}, ) as sb: sb.commands.run("curl -i https://api.example.net")效果:虽然 action.allow=False,但 *.example.net 仍会作为 L7 target 下发给 CubeVS。这样 TCP/443 能进入 CubeEgress。CubeEgress 匹配 deny 规则后返回请求级 403,并记录审计信息。如果不把目标送入 CubeEgress,底层网络策略只能 drop/RST,无法产生 HTTP 403。示例 7:混合 L3 allow 和 L7 rules ​pythonfrom cubesandbox import Sandbox, Rule, Match, Action rules = [ Rule( name="allow_payment_api", match=Match( scheme="https", sni="pay.example.com", host="pay.example.com", method=["POST"], path="/api/payments/*", ), action=Action(allow=True, audit="full"), ), ] with Sandbox.create( template="tpl_xxx", allow_internet_access=False, network={ "allow_out": [ "1.1.1.1/32", # e.g. DNS resolver or fixed service "downloads.example.com", ], "deny_out": ["203.0.113.0/24"], "rules": rules, }, ) as sb: sb.commands.run("curl https://pay.example.com/api/payments/create")效果:1.1.1.1/32 静态放行,不进 CubeEgress。downloads.example.com 通过 DNS 学习后放行,不进 CubeEgress。pay.example.com 通过 DNS 学习后带 L7_REQUIRED,TCP/443 进 CubeEgress。203.0.113.0/24 会被拒绝,除非某个具体 IP 同时先命中了 allow。行为速查表 ​配置目标结果allow_internet_access=True,无 allow_out/deny_out/rules普通公网 IP默认允许,走 NAT。allow_internet_access=False,无 allow任意公网 IP被 0.0.0.0/0 拒绝。TCP RST,非 TCP drop。allow_internet_access=False + allow_out=["1.1.1.1"]1.1.1.1允许,走 NAT。allow_internet_access=False + allow_out=["api.example.com"]先 DNS,再访问学习到的 IPDNS A 响应学习后允许,走 NAT。allow_internet_access=False + rules 中有 sni="api.example.com"api.example.com:443DNS 学习出的 IP 带 L7 标记,TCP/443 进入 CubeEgress。rules 只写 path="/v1/*",没有 host/sni严格 allow-list可能到不了 CubeEgress,因为 CubeVS 没有网络目标可放行。deny_out=["203.0.113.0/24"]203.0.113.10若未先命中 allow,则拒绝。allow_out=["203.0.113.10"] + deny_out=["203.0.113.0/24"]203.0.113.10allow 优先,允许。allow_out=["*.example.com"]example.com不匹配。allow_out=["*.example.com"]api.example.com匹配。观测与排障 ​查看 CubeVS map ​cubevsmapdump 是宿主机侧查看 CubeVS 业务 map 的工具。发布/安装包会把它安装到正常命令路径中,部署节点上直接使用即可:bashsudo cubevsmapdump --list-maps sudo cubevsmapdump --map dns_allow,dns_query_track,allow_out_v2,deny_out sudo cubevsmapdump --ifindex tapxxx --map dns_allow,allow_out_v2,deny_out常用字段:字段含义ifindex_to_mvmmeta[].dns_policy_flags沙箱级 DNS policy mode 的原始 flags。ifindex_to_mvmmeta[].dns_learning_enabled表示当前沙箱是否启用 DNS 响应学习。dns_allow[].rules[].domain当前沙箱安装的域名策略规则。dns_allow[].rules[].l7_required这个域名学习出的 IP 后续是否需要进入 CubeEgress。dns_allow[].enabled / learning_enabled / flags兼容展示字段,数据来自 ifindex_to_mvmmeta,不是 dns_allow 中存储的条目。dns_query_track当前正在等待响应的 DNS 查询,通常生命周期很短。allow_out_v2[].entries[].expires_at_ns / expires_in0 或空表示静态 allow;非零表示 DNS 学习出的临时条目。allow_out_v2[].entries[].l7_required访问该 IP 的 TCP/80、TCP/443 是否进入 CubeEgress。deny_out当前沙箱的 deny CIDR,包括用户配置、内置内部网段或 deny-all。查看服务状态 ​bashsudo systemctl status cube-sandbox-cubelet.service sudo systemctl status cube-sandbox-cube-egress.service sudo systemctl restart cube-sandbox-compute.target

评论 (0)