External Authorization

  • External authorization architecture overview

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

  • v3 API reference

The external authorization filter calls an external gRPC or HTTP service to determine whether an incoming HTTP request is authorized. If the request is unauthorized, Envoy returns a 403 (Forbidden) response. It is also possible to send additional custom metadata to the authorization service, and to propagate metadata returned by the authorization service to the upstream or downstream. See the HTTP filter API for details.

The content of the request passed to the authorization service is specified by CheckRequest.

This HTTP filter can be configured to use a gRPC or HTTP service as follows. See the HTTP filter API for all configuration options.

Security Considerations

Attention

Route cache clearing risk: When using per-route ext_authz configuration, subsequent filters in the filter chain may clear the route cache, potentially leading to privilege escalation vulnerabilities where requests bypass authorization checks.

For more information about this security risk, including affected filters and general mitigation strategies, see Filter route mutation security considerations.

The risk is particularly important for External Authorization because it often handles authentication and authorization decisions that directly impact access control. When the route cache is cleared after the ext_authz filter has run, a request may be rerouted to endpoints with different authorization requirements, bypassing those checks entirely.

Example vulnerable configuration:

http_filters:
- name: envoy.filters.http.ext_authz
  typed_config:
    "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
    # ... ext_authz config ...
- name: envoy.filters.http.lua
  typed_config:
    "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
    inline_code: |
      function envoy_on_request(request_handle)
        -- This clears the route cache after ext_authz has run.
        request_handle:clearRouteCache()
        -- The request may now match a different route with different authorization requirements.
      end

In this example, if the initial route had the ext_authz filter disabled but the recomputed route match (after cache clearing) requires authorization, the request bypasses the authorization check entirely.

Configuration Examples

A sample filter configuration for a gRPC authorization server:

26          http_filters:
27          - name: envoy.filters.http.ext_authz
28            typed_config:
29              "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
30              grpc_service:
31                envoy_grpc:
32                  cluster_name: ext-authz
33                # Default is 200ms; override if your server needs e.g. warmup time.
34                timeout: 0.5s
35              include_peer_certificate: true
41  - name: ext-authz
42    type: STATIC
43    typed_extension_protocol_options:
44      envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
45        "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
46        explicit_http_config:
47          http2_protocol_options: {}
48    load_assignment:
49      cluster_name: ext-authz
50      endpoints:
51      - lb_endpoints:
52        - endpoint:
53            address:
54              socket_address:
55                address: 127.0.0.1
56                port_value: 10003

Note

One feature of this filter is sending the HTTP request body to the configured gRPC authorization server as part of the check request.

A sample configuration is as follows:

26          http_filters:
27          - name: envoy.filters.http.ext_authz
28            typed_config:
29              "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
30              grpc_service:
31                envoy_grpc:
32                  cluster_name: ext-authz
33              with_request_body:
34                max_request_bytes: 1024
35                allow_partial_message: true
36                pack_as_bytes: true

By default, the check request carries the HTTP request body as a UTF-8 string in body. To send the request body as raw bytes, set pack_as_bytes to true. In that case, raw_body is set and body is empty.

A sample filter configuration for a raw HTTP authorization server:

26          http_filters:
27          - name: envoy.filters.http.ext_authz
28            typed_config:
29              "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
30              http_service:
31                server_uri:
32                  uri: 127.0.0.1:10003
33                  cluster: ext-authz
34                  timeout: 0.25s
35              failure_mode_allow: false
36              include_peer_certificate: true
41  clusters:
42  - name: ext-authz
43    type: LOGICAL_DNS
44    lb_policy: ROUND_ROBIN
45    load_assignment:
46      cluster_name: ext-authz
47      endpoints:
48      - lb_endpoints:
49        - endpoint:
50            address:
51              socket_address:
52                address: 127.0.0.1
53                port_value: 10003

Per-Route Configuration

15          route_config:
16            name: local_route
17            virtual_hosts:
18            - name: local_service
19              domains: ["*"]
20              typed_per_filter_config:
21                envoy.filters.http.ext_authz:
22                  "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
23                  check_settings:
24                    context_extensions:
25                      virtual_host: local_service
26              routes:
27              - match:
28                  prefix: /static
29                route:
30                  cluster: ext-authz
31                typed_per_filter_config:
32                  envoy.filters.http.ext_authz:
33                    "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
34                    disabled: true
35              - match:
36                  prefix: /
37                route:
38                  cluster: ext-authz

A sample virtual host and route filter configuration. In this example, we add additional context on the virtual host and disable the filter for /static-prefixed routes.

Conditional Filter Activation with Dynamic Metadata

When you need to conditionally invoke the ext_authz filter based on dynamic metadata set by a preceding filter (such as a Lua filter), it is recommended to use ExtensionWithMatcher rather than the filter_enabled_metadata field.

The key differences are:

  • ExtensionWithMatcher: Evaluates matching conditions before filter instantiation. The filter is only created and invoked when the matcher determines it should run. This is the recommended approach for metadata-based conditional invocation.

  • filter_enabled_metadata: Only evaluated after the filter is instantiated. If the filter is marked with disabled: true in the HttpFilter configuration, it will not be instantiated and filter_enabled_metadata will have no effect.

The following example demonstrates using ExtensionWithMatcher to conditionally invoke ext_authz based on dynamic metadata set by a Lua filter:

26          http_filters:
27          # Lua filter sets dynamic metadata that controls whether ext_authz runs.
28          - name: envoy.filters.http.lua
29            typed_config:
30              "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
31              default_source_code:
32                inline_string: |
33                  function envoy_on_request(request_handle)
34                    -- Set metadata to conditionally enable ext_authz.
35                    -- For example, enable auth for requests to /secure paths.
36                    local path = request_handle:headers():get(":path")
37                    if string.match(path, "^/secure") then
38                      request_handle:streamInfo():dynamicMetadata():set("envoy.filters.http.ext_authz", "require_auth", "true")
39                    else
40                      request_handle:streamInfo():dynamicMetadata():set("envoy.filters.http.ext_authz", "require_auth", "false")
41                    end
42                  end
43          # ExtensionWithMatcher wraps ext_authz and conditionally invokes it based on dynamic metadata.
44          - name: ext-authz-with-matcher
45            typed_config:
46              "@type": type.googleapis.com/envoy.extensions.common.matching.v3.ExtensionWithMatcher
47              extension_config:
48                name: envoy.filters.http.ext_authz
49                typed_config:
50                  "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
51                  grpc_service:
52                    envoy_grpc:
53                      cluster_name: ext-authz
54                    timeout: 0.5s
55                  include_peer_certificate: true
56              # The xds matcher evaluates dynamic metadata to decide whether to invoke ext_authz.
57              # We use matcher_list with custom_match because DynamicMetadataInput returns a custom
58              # MetadataMatchData type that requires a custom matcher and not exact_match_map.
59              xds_matcher:
60                matcher_list:
61                  matchers:
62                  - predicate:
63                      single_predicate:
64                        input:
65                          name: envoy.matching.inputs.dynamic_metadata
66                          typed_config:
67                            "@type": type.googleapis.com/envoy.extensions.matching.common_inputs.network.v3.DynamicMetadataInput
68                            filter: envoy.filters.http.ext_authz
69                            path:
70                            - key: require_auth
71                        custom_match:
72                          name: envoy.matching.matchers.metadata_matcher
73                          typed_config:
74                            "@type": type.googleapis.com/envoy.extensions.matching.input_matchers.metadata.v3.Metadata
75                            value:
76                              string_match:
77                                exact: "false"
78                    # When require_auth is "false", skip ext_authz.
79                    on_match:
80                      action:
81                        name: skip
82                        typed_config:
83                          "@type": type.googleapis.com/envoy.extensions.filters.common.matcher.action.v3.SkipFilter

In this configuration:

  • The Lua filter examines the request path and sets dynamic metadata (envoy.filters.http.ext_authz.require_auth) to indicate whether authorization is required.

  • The ExtensionWithMatcher uses DynamicMetadataInput to read this metadata.

  • When require_auth is true, the ext_authz filter is invoked.

  • When require_auth is false, the SkipFilter action causes the filter to be skipped.

This pattern provides clean separation between the decision logic (in the Lua filter) and the authorization enforcement (in ext_authz), while ensuring the ext_authz filter is only instantiated and invoked when needed.

Statistics

The HTTP filter outputs statistics in the cluster.<route target cluster>.ext_authz. namespace.

Name

Type

Description

ok

Counter

Total responses from the authorization service that allowed the request.

error

Counter

Total errors contacting the external service.

denied

Counter

Total responses from the authorization service that denied the request.

disabled

Counter

Total requests that were allowed without calling the external service because the filter is disabled.

failure_mode_allowed

Counter

Total error responses that were allowed through because failure_mode_allow is set to true.

invalid

Counter

Total responses rejected due to invalid header or query parameter mutations.

omitted_response_headers

Counter

Total responses for which ext_authz rejected any number of headers due to the header map constraints.

request_header_limits_reached

Counter

Total requests for which ext_authz sent a local reply because it couldn’t apply all header mutations

response_header_limits_reached

Counter

Total responses for which ext_authz sent a local reply because it couldn’t apply all header mutations

Dynamic Metadata

The External Authorization filter supports emitting dynamic metadata as an opaque google.protobuf.Struct.

When using a gRPC authorization server, dynamic metadata will be emitted only when the CheckResponse contains a non-empty dynamic_metadata field.

When using an HTTP authorization server, dynamic metadata will be emitted only when there are response headers from the authorization server that match the configured dynamic_metadata_from_headers, if set. For every response header that matches, the filter will emit dynamic metadata whose key is the name of the matched header and whose value is the value of the matched header.

Both the HTTP and gRPC external authorization filters support a dynamic metadata field called ext_authz_duration which records the time it takes to complete an authorization request in milliseconds. This field will not be populated if the request does not complete.

Runtime

The fraction of requests for which the filter is enabled can be configured via the runtime_key value of the filter_enabled field.

Tracing

The ext_authz span keeps the sampling status of the parent span, i.e. in the tracing backend we will either see both the parent span and the child ext_authz span, or none of them.

Logging

When emit_filter_state_stats is set to true, the ext_authz filter exposes fields latency_us, bytesSent and bytesReceived for use in CEL and logging.

Note

The bytesSent and bytesReceived fields are populated only when using the Envoy gRPC client type.

  • filter_state["envoy.filters.http.ext_authz"].latency_us

  • %FILTER_STATE(envoy.filters.http.ext_authz:FIELD:latency_us)%

  • %FILTER_STATE(envoy.filters.http.ext_authz:FIELD:bytesSent)%

  • %FILTER_STATE(envoy.filters.http.ext_authz:FIELD:bytesReceived)%