Envoy ExternalProcessor Filter Bug 404 Error After Changing Authority

by StackCamp Team 70 views

Introduction

Hey guys! Today, we're diving deep into a tricky issue encountered while using Envoy's ExternalProcessor filter to dynamically route requests. The problem? A perplexing 404 error despite the filter correctly modifying the :authority and :path headers. We'll dissect the configuration, analyze the logs, and try to figure out why Envoy isn't routing the request as expected. So, buckle up, and let's get started!

Configuration and Setup

Our setup involves an Envoy proxy configured with the ExternalProcessor filter and the Dynamic Forward Proxy (DFP) filter. The goal is to use the ExternalProcessor to rewrite the request headers, specifically the :authority and :path, based on some external logic. This allows us to dynamically route requests to different backends. Here’s the relevant Envoy configuration:

admin:
  address:
    socket_address:
      address: 0.0.0.0
      port_value: 9901
static_resources:
  listeners:
    - name: main_listener
      address:
        socket_address:
          address: 0.0.0.0
          port_value: 5001
      filter_chains:
      - filters:
        - name: envoy.filters.network.http_connection_manager
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
            stat_prefix: ingress_http
            route_config:
              name: local_route
              virtual_hosts:
              - name: local_service
                domains:
                  - "*"
                routes:
                - match:
                    prefix: "/"
                  route:
                    cluster: dynamic_forward_proxy_cluster
            http_filters:
            - name: envoy.filters.http.ext_proc
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.filters.http.ext_proc.v3.ExternalProcessor
                mutation_rules:
                  allow_all_routing: true
                grpc_service:
                  envoy_grpc:
                    cluster_name: ext_afproxy
                  timeout: 15s
                processing_mode:
                  request_header_mode: SEND
                  response_header_mode: "SKIP"
                  request_body_mode: "NONE"
                  response_body_mode: "NONE"
                  request_trailer_mode: "SKIP"
                  response_trailer_mode: "SKIP"
            - name: envoy.filters.http.dynamic_forward_proxy
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.filters.http.dynamic_forward_proxy.v3.FilterConfig
                dns_cache_config:
                  name: dynamic_forward_proxy_cache_config
                  dns_lookup_family: V4_ONLY
                  typed_dns_resolver_config:
                    name: envoy.network.dns_resolver.getaddrinfo
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.network.dns_resolver.getaddrinfo.v3.GetAddrInfoDnsResolverConfig
            - name: envoy.filters.http.router
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
  clusters:
  - name: dynamic_forward_proxy_cluster
    lb_policy: CLUSTER_PROVIDED
    cluster_type:
      name: envoy.clusters.dynamic_forward_proxy
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.clusters.dynamic_forward_proxy.v3.ClusterConfig
        dns_cache_config:
          name: dynamic_forward_proxy_cache_config
          dns_lookup_family: V4_ONLY
          typed_dns_resolver_config:
            name: envoy.network.dns_resolver.getaddrinfo
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.network.dns_resolver.getaddrinfo.v3.GetAddrInfoDnsResolverConfig
  - name: ext_afproxy
    connect_timeout: 5s
    type: LOGICAL_DNS
    dns_lookup_family: V4_ONLY
    lb_policy: ROUND_ROBIN
    typed_extension_protocol_options:
      envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
        "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
        explicit_http_config:
          http2_protocol_options: {}
    load_assignment:
      cluster_name: ext_afproxy
      endpoints:
        - lb_endpoints:
            - endpoint:
                address:
                  socket_address:
                    address: localhost
                    port_value: 50052

In this configuration, we have a listener on port 5001 that uses an HTTP connection manager. The HTTP filter chain includes the ExternalProcessor filter, the DFP filter, and the router filter. The ExternalProcessor filter is configured to send request headers to a gRPC service (ext_afproxy) and to skip response headers. It's also set to clear the route cache after applying header mutations, which is crucial for dynamic routing changes to take effect. The DFP filter is set up to handle dynamic forward proxying, and the ext_afproxy cluster represents the gRPC service that processes the header mutations.

The Problem: 404 Errors After Header Rewrites

The core issue is that while the ExternalProcessor filter correctly rewrites the headers, Envoy returns a 404 error. For instance, a request to http://localhost:5001/foo/bar is rewritten to have :authority localhost:4001 and :path /bar. The logs indicate that the header mutation is accepted and that the route cache is cleared. However, the following logs reveal the problem:

[2025-07-25 17:06:29.587][27262002][debug][router] [source/common/router/config_impl.cc:2071] route was resolved but final route list did not match incoming request
[2025-07-25 17:06:29.587][27262002][debug][router] [source/common/router/router.cc:471] [Tags: "ConnectionId":"0","StreamId":"12516043724031728105"] no route match for URL ''

This suggests that after the headers are rewritten, Envoy cannot find a matching route for the request. The