Upstream Role Based Access Control (RBAC) Filter

The upstream RBAC filter authorizes a request against the selected upstream host, before the upstream connection is initiated. It is configured exactly like the RBAC filter (it reuses the same RBAC configuration), but runs as an upstream HTTP filter and evaluates its policies from the host-selection callback rather than during request decoding.

The primary use case is protecting against SSRF by enforcing a default-deny policy on the upstream IP address. Because evaluation happens after a host has been selected but before the connection is established, the upstream_ip_port matcher receives the resolved upstream address directly from host selection. By contrast, the downstream RBAC filter can only match on the upstream IP when a preceding HTTP filter (the dynamic forward proxy filter with save_upstream_address) has already recorded the selected host in filter state. Evaluating at host-selection time removes that dependency, so the upstream IP match works for all cluster types — including dynamic forward proxy sub_cluster_config, static, EDS, and strict DNS. When the request is denied, no upstream connection is established and a 403 (Forbidden) local reply is returned.

Since the filter is handed the downstream connection and request headers, the full set of RBAC matchers available to the downstream RBAC filter (request headers, source/destination IP and port, SSL properties, dynamic metadata, CEL attributes, …) can be combined with the upstream IP match.

Attention

This filter must be configured as an upstream HTTP filter. There is no top-level upstream_http_filters field on a cluster; the upstream HTTP filter chain lives in one of two places:

Either chain must end with the envoy.filters.http.upstream_codec terminal filter. The filter has no effect in a downstream HTTP filter chain, and is therefore registered only as an upstream filter.

  • This filter should be configured with the type URL type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC.

  • v3 API reference

Example configuration

The following Cluster denies any request whose selected upstream IP falls within a private CIDR range. The upstream filter chain is configured on the cluster, under typed_extension_protocol_optionsenvoy.extensions.upstreams.http.v3.HttpProtocolOptionshttp_filters (the same HttpProtocolOptions entry that carries the upstream protocol config), and ends with the upstream_codec terminal filter:

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

Statistics

The upstream RBAC filter outputs the same statistics as the RBAC filter, with the counter names built as <scope>.rbac.[<rules_stat_prefix>.]{allowed,denied,shadow_allowed,shadow_denied}. The <scope> prefix depends on where the filter is attached, not on the filter itself:

  • When attached to the router via Router.upstream_http_filters, the stats inherit the router’s (HTTP connection manager / listener) scope. On a listener with no stats prefix this is the root scope, so the counters are simply rbac.* (e.g. rbac.<rules_stat_prefix>.allowed). This produces a single aggregate per Envoy, independent of which upstream cluster is selected at request time.

  • When attached to a cluster via HttpProtocolOptions.http_filters, the stats are emitted in that cluster’s scope, i.e. cluster.<name>.rbac.*.

Note

With dynamic forward proxy sub_cluster_config, the cluster selected at request time is a per-host dynamic sub-cluster (DFPCluster:<host>:<port>). Attaching the filter at the cluster scope in that mode would therefore emit one counter series per resolved upstream host — i.e. unbounded cardinality — and scatter deny counts across many series. Prefer attaching the filter at the router scope for the dynamic forward proxy use case so the counters stay a single root-scoped aggregate.

The optional rules_stat_prefix / shadow_rules_stat_prefix fields of the RBAC config add a further segment after rbac. (e.g. rules_stat_prefix: private yields rbac.private.allowed); this is orthogonal to the scope prefix above.

Dynamic Metadata

The upstream RBAC filter emits the same dynamic metadata as the RBAC filter, under the envoy.filters.http.upstream_rbac key namespace.

Per-Route Configuration

Like the downstream RBAC filter, the engine can be overridden or disabled per virtual host / route / weighted cluster by attaching a RBACPerRoute to the route’s typed_per_filter_config, keyed by this filter’s configured name. The override is resolved over the request’s (downstream) route, so the same route entry can carry distinct downstream and upstream RBAC overrides.