-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Description
Title: Add pre-connection hook (onHostSelected) to upstream HTTP filters
Problem Statement
When using the Dynamic Forward Proxy (DFP) HTTP filter with sub_cluster_config mode, the save_upstream_address option does not work. This prevents the envoy.rbac.matchers.upstream_ip_port RBAC matcher from functioning because it depends on the filter-state being populated.
Additionally, the upstream_ip_port RBAC matcher is only functional when using DFP in dns_cache_config mode — any other cluster type (static, EDS, strict_dns, or DFP with sub_cluster_config) cannot use IP-based RBAC at all today.
Security Concern
The Envoy DFP documentation explicitly warns about security risks and recommends RBAC protection:
⚠️ Warning: Servers operating dynamic forward proxy in environments where either client or destination are untrusted are subject to confused deputy attacks.Malicious clients may exploit the proxy to access localhost, link-local addresses, cloud metadata servers, or private networks.
Recommended mitigations: Protect dynamic forward proxy servers through network firewalls, default-deny RBAC, container or kernel networking restrictions.
However, using sub_cluster_config mode prevents implementing RBAC-based SSRF protection because the upstream_ip_port matcher cannot function without the filter-state being set.
Technical Details
save_upstream_address only works with dns_cache_config:
The DFP filter calls addHostAddressToFilterState() only in the DNS cache code paths:
- proxy_filter.cc:288 — synchronous cache hit
- proxy_filter.cc:420 — async DNS resolution callback
In sub_cluster_config mode, the filter only loads the cluster (proxy_filter.cc:340-346) and never calls addHostAddressToFilterState().
upstream_ip_port matcher requires filter-state — reads from filter-state and returns false if not set (upstream_ip_port.cc:37-39):
auto address_obj = info.filterState().getDataReadOnly<StreamInfo::UpstreamAddress>(
StreamInfo::UpstreamAddress::key());
if (address_obj == nullptr) {
return false; // Always returns false if filter-state not set!
}Why It Doesn't Work Today
| Mode | When Address is Resolved | Filter-State Set? | RBAC Works? |
|---|---|---|---|
dns_cache_config |
At HTTP filter layer (decodeHeaders) | ✅ Yes | ✅ Yes |
sub_cluster_config |
At cluster load balancer layer (later) | ❌ No | ❌ No |
Proposed Solution: Pre-Connection Hook for Upstream HTTP Filters
Extend the UpstreamCallbacks interface to add an onHostSelected(host) hook that fires before the upstream connection is initiated.
Interface Change (envoy/http/filter.h)
virtual void onHostSelected(Upstream::HostDescriptionConstSharedPtr host) {
UNREFERENCED_PARAMETER(host);
// Default: no-op (backward compatible)
}Proposed PR
Next Steps
- Create an Upstream RBAC Filter (
envoy.filters.http.upstream_rbac) usingonHostSelected()to check host IP against CIDR rules - Deprecate
envoy.rbac.matchers.upstream_ip_portin favor of the new filter - Future Cleanup: remove
save_upstream_address,envoy.stream.upstream_address, andupstream_ip_portmatcher