Envoy Upstream RBAC 过滤器完全指南:在主机选择阶段实施上游访问控制与 SSRF 防护
2026/9/13 19:19:12 网站建设 项目流程

Envoy Upstream RBAC 过滤器完全指南:在主机选择阶段实施上游访问控制与 SSRF 防护

【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy

本文以 Envoy 仓库中的 Upstream RBAC Filter 官方文档 为骨架,结合source/extensions/filters/http/upstream_rbac/目录下的实现源码、API proto 定义以及单元/集成测试,系统讲解这一上游 HTTP 过滤器的工作原理、配置方法、统计语义与最佳实践。读完本文,你将掌握如何在 Envoy 中于「上游连接建立之前」对选中的上游主机执行基于角色的访问控制(RBAC),尤其是利用upstream_ip_port匹配器对所有集群类型实施默认拒绝的 SSRF 防护,并正确挂载过滤器链、理解其统计作用域与路由级覆盖机制。

一、什么是 Upstream RBAC 过滤器

Upstream RBAC 过滤器(扩展名envoy.filters.http.upstream_rbac)是 Envoy 的一个上游 HTTP 过滤器:它针对已被选中的上游主机(selected upstream host)执行授权检查,且检查发生在上游连接发起之前

它的最大特点是配置方式与下游 RBAC 过滤器 完全一致——直接复用envoy.extensions.filters.http.rbac.v3.RBAC这一配置消息(type URL 为type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC,详见 rbac.proto),但运行时机不同:普通下游 RBAC 在请求解码阶段(decodeHeaders)评估策略,而 Upstream RBAC 从**主机选择回调(onHostSelected)**中评估策略。

关键行为差异体现在:

  • 评估发生在「主机已选定、连接尚未建立」之间,因此upstream_ip_port匹配器可以直接拿到主机选择阶段解析出的上游地址;
  • 当请求被拒绝时,不会建立任何上游连接,直接返回403 (Forbidden)本地应答(local reply)。

从源码看,这一过滤器是下游 RBAC 过滤器的"薄特化":upstream_rbac_filter.h 中UpstreamRoleBasedAccessControlFilter继承自RBACFilter::RoleBasedAccessControlFilter并实现Http::UpstreamCallbacks,其decodeHeaders直接透传返回Continue(注释明确说明 RBAC 检查在onHostSelected()中执行而非解码阶段),真正的授权逻辑集中在onHostSelected()回调里。

二、核心使用场景:基于上游 IP 的 SSRF 防护

该过滤器的主要使用场景是通过默认拒绝策略保护上游 IP 地址,防止 SSRF(服务端请求伪造)攻击

为什么需要一个新的上游过滤器而不是沿用下游 RBAC?关键在于upstream_ip_port匹配器的数据来源:

  • 下游 RBAC 过滤器 只能在上游 IP 已被前序 HTTP 过滤器写入过滤器状态(filter state)后才能匹配上游 IP——例如动态转发代理(dynamic forward proxy)过滤器开启save_upstream_address时。
  • Upstream RBAC 在主机选择时刻评估,直接从主机选择结果中获取已解析的上游地址,去除了这一前置依赖

因此,上游 IP 匹配对所有集群类型均有效,包括:

  • 动态转发代理sub_cluster_config(该场景下上游地址 filter state 通常永远不会被填充);
  • 静态集群(static);
  • EDS 集群;
  • 严格 DNS(strict DNS)集群。

由于过滤器持有下游连接与请求头,下游 RBAC 过滤器可用的全部匹配器(请求头、源/目的 IP 与端口、SSL 属性、动态元数据、CEL 属性等)都可以与上游 IP 匹配自由组合,实现"基于请求特征 + 上游地址"的精细化授权。

三、upstream_ip_port 匹配器配置详解

upstream_ip_port匹配器的配置消息定义在 upstream_ip_port_matcher.proto:

字段类型说明
upstream_ipconfig.core.v3.CidrRange用于匹配上游 IP 的 CIDR 网段,IPv4 与 IPv6 均可匹配
upstream_port_rangetype.v3.Int64Range用于匹配上游端口的整数区间

proto 注释明确了两个约束:

  1. 两个字段虽均为可选,但至少必须提供一个;若只提供其中一个,另一个视为通配(wildcard)匹配;
  2. 该匹配器要求过滤器链中的某个过滤器在执行前已将上游地址保存到过滤器状态中,键为envoy.stream.upstream_address(参见 upstream_address.h);proxy_filter.cc 是填充该 FilterState 的示例。

Upstream RBAC 过滤器在主机选择回调中会主动把本次选中的主机地址写入该 filter state(见下文源码剖析),因此它不依赖其他过滤器预先填充,这正是其能覆盖所有集群类型的原因。

四、过滤器链的挂载位置(重要提醒)

官方文档特别给出attention级提醒:该过滤器必须以「上游」HTTP 过滤器身份配置。集群上没有顶层的upstream_http_filters字段;上游 HTTP 过滤器链只存在于两处:

  1. 集群级(per cluster):位于集群的typed_extension_protocol_optionsenvoy.extensions.upstreams.http.v3.HttpProtocolOptions条目下的http_filters列表里(该字段定义于 http_protocol_options.proto);
  2. 路由器级(on the router,对所有集群生效):通过Router.upstream_http_filters配置(定义于 router.proto)。

两条链都必须以envoy.filters.http.upstream_codec终结过滤器(terminal filter)收尾。此外,该过滤器在下游 HTTP 过滤器链中不产生任何效果,因此仅注册为上游过滤器——这一点在 config.cc 中通过REGISTER_FACTORY(..., Server::Configuration::UpstreamHttpFilterConfigFactory)得到印证。

五、完整配置示例:拒绝私有网段上游

以下Cluster配置会拒绝任何选中上游 IP 落在私有 CIDR 网段内的请求。上游过滤器链配置在集群级,路径为typed_extension_protocol_optionsenvoy.extensions.upstreams.http.v3.HttpProtocolOptionshttp_filters(即承载上游协议配置的同一个HttpProtocolOptions条目),并以upstream_codec终结过滤器结尾:

clusters: - name: egress_cluster connect_timeout: 5s type: STRICT_DNS load_assignment: {} # ... cluster endpoints ... typed_extension_protocol_options: envoy.extensions.upstreams.http.v3.HttpProtocolOptions: "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions # Upstream protocol selection (required by HttpProtocolOptions). explicit_http_config: http_protocol_options: {} # Upstream HTTP filter chain. Must end with envoy.filters.http.upstream_codec. http_filters: - name: envoy.filters.http.upstream_rbac typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC rules: action: DENY policies: "deny-private-ips": permissions: - matcher: name: envoy.rbac.matchers.upstream_ip_port typed_config: "@type": type.googleapis.com/envoy.extensions.rbac.matchers.upstream_ip_port.v3.UpstreamIpPortMatcher upstream_ip: address_prefix: 10.0.0.0 prefix_len: 8 principals: - any: true - name: envoy.filters.http.upstream_codec typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.upstream_codec.v3.UpstreamCodec

要点解读:

  • action: DENY配合principals: any: true,形成「凡上游 IP 命中10.0.0.0/8即拒绝」的默认拒绝式策略;若想放行全部、仅拦特定网段,这正是 SSRF 防护的典型写法;
  • rules支持ALLOW/DENY/LOG动作,且rulesmatcher(匹配树)二选一,两者同时配置时rules被忽略;matcher为空但已配置时同样会拒绝所有请求(语义见 rbac.proto);
  • 若改为路由器级挂载,则将上述两个过滤器放入HttpConnectionManagerhttp_filters后追加的router过滤器配置的upstream_http_filters列表中,其余结构不变。

六、统计指标:命名规则与作用域语义

Upstream RBAC 过滤器输出与下游 RBAC 过滤器 相同的统计项,计数器命名形如:

<scope>.rbac.[<rules_stat_prefix>.]{allowed,denied,shadow_allowed,shadow_denied}

其中<scope>前缀取决于过滤器挂载的位置,而非过滤器自身:

挂载位置统计作用域计数示例
路由器级(Router.upstream_http_filters继承路由器(HTTP 连接管理器 / 监听器)的作用域;监听器未设置 stats 前缀时为根作用域rbac.<rules_stat_prefix>.allowed
集群级(HttpProtocolOptions.http_filters集群的作用域cluster.<name>.rbac.*

两种挂载方式的行为差异值得注意:

  • 路由器级:每个 Envoy 只产生一组聚合计数,与请求时选中的上游集群无关;
  • 集群级:计数进入各自集群作用域。

此外,可选字段rules_stat_prefix/shadow_rules_stat_prefix会在rbac.之后再追加一段(例如rules_stat_prefix: private得到rbac.private.allowed),该前缀与上述作用域前缀是正交的。

动态转发代理下的基数警告

官方文档特别提示:当配合动态转发代理的sub_cluster_config使用时,请求时选中的集群是按主机动态创建的子集群(形如DFPCluster:<host>:<port>)。若在该模式下将过滤器挂在集群级,则每个被解析的上游主机都会产生一组计数器序列——即无界基数(unbounded cardinality),拒绝计数会被打散到大量序列中。

因此,动态转发代理场景应优先将过滤器挂在路由器级,让计数器保持为单个根作用域聚合。源码层面,统计前缀的解析逻辑位于 config.cc:通过运行时开关envoy.reloadable_features.upstream_http_filters_correct_stats_prefix控制是否使用extra_context.stats_prefix(携带父级命名空间,如路由器的http.<stat_prefix>.或集群的cluster.<name>.),关闭时回退到空前缀。

七、动态元数据

Upstream RBAC 过滤器输出与下游 RBAC 过滤器相同的动态元数据(dynamic metadata),但命名空间键为:

envoy.filters.http.upstream_rbac

该键在 upstream_rbac_filter.h 中以常量DynamicMetadataKey定义,并在onHostSelected()中通过setDynamicMetadata写入评估结果(shadow 引擎或 enforced 引擎被评估时)。下游过滤器(如 access log、其他基于动态元数据的过滤器)可据此感知上游 RBAC 的判定结果。

八、路由级覆盖配置(RBACPerRoute)

与下游 RBAC 过滤器 相同,Upstream RBAC 的策略引擎也可以按虚拟主机 / 路由 / 加权集群被覆盖或禁用:将 RBACPerRoute 挂到路由的typed_per_filter_config上,键为该过滤器配置的名字即可。

覆盖解析基于请求的(下游)路由进行,因此同一条路由可以同时携带不同的下游与上游 RBAC 覆盖,互不干扰。RBACPerRouterbac字段缺省时,该路由上的 RBAC 策略将被禁用。

九、源码剖析:onHostSelected 处的授权调用链

upstream_rbac_filter.cc 完整呈现了授权时机的实现细节:

  1. 注册回调setDecoderFilterCallbacks中获取upstreamCallbacks()并注册自身,使onHostSelected()能在主机选定后被调用;
  2. 地址守卫onHostSelected()首先取host->address(),若选中主机没有已解析地址(例如逻辑主机 logical host),直接告警跳过评估,避免解引用空地址;
  3. 写入 filter statesetUpstreamAddress()将本次选中的地址写入StreamInfo::UpstreamAddress::key()envoy.stream.upstream_address),生命周期为LifeSpan::Request。注释强调:上游地址 filter state 按请求作用域、在重试间共享,且可能被动态转发代理过滤器预先填充,因此必须总是覆写本次尝试的主机地址,绝不能信任陈旧的(stale)或外部写入的地址做 SSRF 拒绝决策——这正是防止"重试时绕过拒绝"的关键设计;
  4. 连接守卫:代理请求必然持有下游连接,但仍以OptRef判空保护,避免解引用空连接;
  5. 取请求头:由于onHostSelected()发生在下游路由器的decodeHeaders()期间、早于本上游过滤器链自身decodeHeaders(),因此从(下游)stream info 中读取请求头,为空时回退到静态空头映射;
  6. 复用评估逻辑:调用下游 RBAC 过滤器基类RoleBasedAccessControlFilterevaluateShadowEngine()evaluateEnforcedEngine()(定义于 rbac_filter.h),后者在拒绝时发送403本地应答(中止上游连接)并返回StopIteration

十、测试验证:行为与数据双重印证

仓库中的测试完整覆盖了上述行为,可作为理解与验证该过滤器的直接依据:

  • upstream_rbac_filter_test.cc(单元测试)验证了:DENY规则命中选中主机时发送403本地应答并写入上游地址 filter state、非命中主机放行、ALLOW规则命中放行、请求头与上游 IP 的 AND 组合匹配、重试时忽略陈旧 filter state 并重新评估(回归测试)、动态元数据写入envoy.filters.http.upstream_rbac键、空引擎配置时全放行、路由级覆盖优先于全局配置、无地址主机/无下游连接/无请求头三种边界跳过路径、decodeHeaders透传,以及路由器/集群两种上下文下的统计前缀(http.ingress.rbac.allowedcluster.cluster_0.rbac.allowed且不重复加前缀);
  • upstream_rbac_integration_test.cc(集成测试)在真实双栈(IPv4/IPv6)环境验证:命中拒绝规则时响应码为403cluster.cluster_0.upstream_cx_total保持为 0(证明连接确实未建立)、未命中时正常返回200、集群级统计为cluster.cluster_0.rbac.denied且不存在双重前缀。

十一、最佳实践总结

综合文档与源码,使用 Upstream RBAC 过滤器时建议遵循以下原则:

  1. SSRF 防护首选默认拒绝:以DENY动作 +upstream_ip_port匹配器拒绝 RFC1918 私有网段(10.0.0.0/8172.16.0.0/12192.168.0.0/16)、链路本地与回环地址,并将principals置为any: true形成兜底;
  2. 动态转发代理场景挂路由器级:避免DFPCluster:<host>:<port>带来的统计基数爆炸,同时保证单一聚合计数;
  3. 集群级挂载时善用集群作用域统计cluster.<name>.rbac.*便于按集群粒度观测拦截量,适合规则按集群差异化配置的场景;
  4. 务必以envoy.filters.http.upstream_codec收尾,且该过滤器不能出现在下游过滤器链中;
  5. 结合其他匹配器做精细化授权:请求头、源/目的 IP 与端口、SSL 属性、动态元数据、CEL 表达式等均可与上游 IP 组合,实现"请求特征 + 上游地址"的多维策略;
  6. 利用 shadow 规则先行演练:通过shadow_rules/shadow_matchershadow_rules_stat_prefix在真实流量上观察命中情况(shadow_allowed/shadow_denied),确认无误后再切换为强制执行,降低误伤风险。

【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询