CVE-2023-33189 (published 2023-05-30T06:16Z)

Pomerium is an identity and context-aware access proxy. With specially crafted requests, incorrect authorization decisions may be made by Pomerium. This issue has been patched in versions 0.17.4, 0.18.1, 0.19.2, 0.20.1, 0.21.4 and 0.22.2.

CVSS v3 base score: 9.8, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: NVD-CWE-Other
cwe_name: Other
description: NVD is only using a subset of CWE for mapping instead of the entire CWE, and the weakness type is not covered by that subset.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/pomerium/pomerium
commit_details: repo_url: https://github.com/pomerium/pomerium
author: Caleb Doxsey
author_date: 2023-05-26 14:34:21-06:00
msg: Merge pull request from GHSA-pvrc-wvj2-f59p

* authorize: use route id from envoy for policy evaluation

* authorize: normalize URL query params

* config: enable envoy normalize_path option

* fix tests

---------

Co-authored-by: Kenneth Jenkins <51246568+kenjenkins@users.noreply.github.com>
repository: repo_url: https://github.com/pomerium/pomerium
repo_name: pomerium/pomerium
description: Pomerium is an identity and context-aware access proxy.
repo_language: Go
owner: pomerium
file_changes: filename: evaluator.go
change_type: ModificationType.MODIFY
diff: @@ -22,17 +22,12 @@ import (
 	"github.com/pomerium/pomerium/pkg/policy/criteria"
 )
 
-// notFoundOutput is what's returned if a route isn't found for a policy.
-var notFoundOutput = &Result{
-	Deny:    NewRuleResult(true, criteria.ReasonRouteNotFound),
-	Headers: make(http.Header),
-}
-
 // Request contains the inputs needed for evaluation.
 type Request struct {
-	Policy  *config.Policy
-	HTTP    RequestHTTP
-	Session RequestSession
+	IsInternal bool
+	Policy     *config.Policy
+	HTTP       RequestHTTP
+	Session    RequestSession
 }
 
 // RequestHTTP is the HTTP field in the request.
@@ -125,8 +120,60 @@ func (e *Evaluator) Evaluate(ctx context.Context, req *Request) (*Result, error)
 	ctx, span := trace.StartSpan(ctx, "authorize.Evaluator.Evaluate")
 	defer span.End()
 
+	eg, ctx := errgroup.WithContext(ctx)
+
+	var policyOutput *PolicyResponse
+	eg.Go(func() error {
+		var err error
+		if req.IsInternal {
+			policyOutput, err = e.evaluateInternal(ctx, req)
+		} else {
+			policyOutput, err = e.evaluatePolicy(ctx, req)
+		}
+		return err
+	})
+
+	var headersOutput *HeadersResponse
+	eg.Go(func() error {
+		var err error
+		headersOutput, err = e.evaluateHeaders(ctx, req)
+		return err
+	})
+
+	err := eg.Wait()
+	if err != nil {
+		return nil, err
+	}
+
+	res := &Result{
+		Allow:   policyOutput.Allow,
+		Deny:    policyOutput.Deny,
+		Headers: headersOutput.Headers,
+		Traces:  policyOutput.Traces,
+	}
+	return res, nil
+}
+
+func (e *Evaluator) evaluateInternal(_ context.Context, req *Request) (*PolicyResponse, error) {
+	// these endpoints require a logged-in user
+	if req.HTTP.Path == "/.pomerium/webauthn" || req.HTTP.Path == "/.pomerium/jwt" {
+		if req.Session.ID == "" {
+			return &PolicyResponse{
+				Allow: NewRuleResult(false, criteria.ReasonUserUnauthenticated),
+			}, nil
+		}
+	}
+
+	return &PolicyResponse{
+		Allow: NewRuleResult(true, criteria.ReasonPomeriumRoute),
+	}, nil
+}
+
+func (e *Evaluator) evaluatePolicy(ctx context.Context, req *Request) (*PolicyResponse, error) {
 	if req.Policy == nil {
-		return notFoundOutput, nil
+		return &PolicyResponse{
+			Deny: NewRuleResult(true, criteria.ReasonRouteNotFound),
+		}, nil
 	}
 
 	id, err := req.Policy.RouteID()
@@ -136,7 +183,9 @@ func (e *Evaluator) Evaluate(ctx context.Context, req *Request) (*Result, error)
 
 	policyEvaluator, ok := e.policyEvaluators[id]
 	if !ok {
-		return notFoundOutput, nil
+		return &PolicyResponse{
+			Deny: NewRuleResult(true, criteria.ReasonRouteNotFound),
+		}, nil
 	}
 
 	clientCA, err := e.getClientCA(req.Policy)
@@ -149,41 +198,23 @@ func (e *Evaluator) Evaluate(ctx context.Context, req *Request) (*Result, error)
 		return nil, fmt.Errorf("authorize: error validating client certificate: %w", err)
 	}
 
-	eg, ectx := errgroup.WithContext(ctx)
-
-	var policyOutput *PolicyResponse
-	eg.Go(func() error {
-		var err error
-		policyOutput, err = policyEvaluator.Evaluate(ectx, &PolicyRequest{
-			HTTP:                     req.HTTP,
-			Session:                  req.Session,
-			IsValidClientCertificate: isValidClientCertificate,
-		})
-		return err
-	})
-
-	var headersOutput *HeadersResponse
-	eg.Go(func() error {
-		headersReq := NewHeadersRequestFromPolicy(req.Policy, req.HTTP.Hostname)
-		headersReq.Session = req.Session
-		var err error
-		headersOutput, err = e.headersEvaluators.Evaluate(ectx, headersReq)
-		return err
+	return policyEvaluator.Evaluate(ctx, &PolicyRequest{
+		HTTP:                     req.HTTP,
+		Session:                  req.Session,
+		IsValidClientCertificate: isValidClientCertificate,
 	})
+}
 
-	err = eg.Wait()
+func (e *Evaluator) evaluateHeaders(ctx context.Context, req *Request) (*HeadersResponse, error) {
+	headersReq := NewHeadersRequestFromPolicy(req.Policy, req.HTTP.Hostname)
+	headersReq.Session = req.Session
+	res, err := e.headersEvaluators.Evaluate(ctx, headersReq)
 	if err != nil {
 		return nil, err
 	}
 
-	carryOverJWTAssertion(headersOutput.Headers, req.HTTP.Headers)
+	carryOverJWTAssertion(res.Headers, req.HTTP.Headers)
 
-	res := &Result{
-		Allow:   policyOutput.Allow,
-		Deny:    policyOutput.Deny,
-		Headers: headersOutput.Headers,
-		Traces:  policyOutput.Traces,
-	}
 	return res, nil
 }
 

programming_language: Go
filename: headers_evaluator.go
change_type: ModificationType.MODIFY
diff: @@ -30,16 +30,18 @@ type HeadersRequest struct {
 // NewHeadersRequestFromPolicy creates a new HeadersRequest from a policy.
 func NewHeadersRequestFromPolicy(policy *config.Policy, hostname string) *HeadersRequest {
 	input := new(HeadersRequest)
-	input.EnableGoogleCloudServerlessAuthentication = policy.EnableGoogleCloudServerlessAuthentication
-	input.EnableRoutingKey = policy.EnvoyOpts.GetLbPolicy() == envoy_config_cluster_v3.Cluster_RING_HASH ||
-		policy.EnvoyOpts.GetLbPolicy() == envoy_config_cluster_v3.Cluster_MAGLEV
-	input.Issuer = hostname
-	input.KubernetesServiceAccountToken = policy.KubernetesServiceAccountToken
-	for _, wu := range policy.To {
-		input.ToAudience = "https://" + wu.URL.Hostname()
+	if policy != nil {
+		input.EnableGoogleCloudServerlessAuthentication = policy.EnableGoogleCloudServerlessAuthentication
+		input.EnableRoutingKey = policy.EnvoyOpts.GetLbPolicy() == envoy_config_cluster_v3.Cluster_RING_HASH ||
+			policy.EnvoyOpts.GetLbPolicy() == envoy_config_cluster_v3.Cluster_MAGLEV
+		input.Issuer = hostname
+		input.KubernetesServiceAccountToken = policy.KubernetesServiceAccountToken
+		for _, wu := range policy.To {
+			input.ToAudience = "https://" + wu.URL.Hostname()
+		}
+		input.PassAccessToken = policy.GetSetAuthorizationHeader() == configpb.Route_ACCESS_TOKEN
+		input.PassIDToken = policy.GetSetAuthorizationHeader() == configpb.Route_ID_TOKEN
 	}
-	input.PassAccessToken = policy.GetSetAuthorizationHeader() == configpb.Route_ACCESS_TOKEN
-	input.PassIDToken = policy.GetSetAuthorizationHeader() == configpb.Route_ID_TOKEN
 	return input
 }
 

programming_language: Go
filename: policy_evaluator_test.go
change_type: ModificationType.MODIFY
diff: @@ -111,7 +111,7 @@ func TestPolicyEvaluator(t *testing.T) {
 			})
 		require.NoError(t, err)
 		assert.Equal(t, &PolicyResponse{
-			Allow:  NewRuleResult(false, criteria.ReasonEmailUnauthorized, criteria.ReasonNonPomeriumRoute, criteria.ReasonUserUnauthorized),
+			Allow:  NewRuleResult(false, criteria.ReasonEmailUnauthorized, criteria.ReasonUserUnauthorized),
 			Deny:   NewRuleResult(false, criteria.ReasonValidClientCertificateOrNoneRequired),
 			Traces: []contextutil.PolicyEvaluationTrace{{}},
 		}, output)
@@ -172,7 +172,7 @@ func TestPolicyEvaluator(t *testing.T) {
 				})
 			require.NoError(t, err)
 			assert.Equal(t, &PolicyResponse{
-				Allow:  NewRuleResult(false, criteria.ReasonNonPomeriumRoute),
+				Allow:  NewRuleResult(false),
 				Deny:   NewRuleResult(true, criteria.ReasonAccept),
 				Traces: []contextutil.PolicyEvaluationTrace{{}, {ID: "p1", Deny: true}},
 			}, output)
@@ -203,7 +203,7 @@ func TestPolicyEvaluator(t *testing.T) {
 				})
 			require.NoError(t, err)
 			assert.Equal(t, &PolicyResponse{
-				Allow:  NewRuleResult(false, criteria.ReasonNonPomeriumRoute),
+				Allow:  NewRuleResult(false),
 				Deny:   NewRuleResult(true, criteria.ReasonAccept, criteria.ReasonInvalidClientCertificate),
 				Traces: []contextutil.PolicyEvaluationTrace{{Deny: true}, {ID: "p1", Deny: true}},
 			}, output)
@@ -289,7 +289,7 @@ func TestPolicyEvaluator(t *testing.T) {
 			})
 		require.NoError(t, err)
 		assert.Equal(t, &PolicyResponse{
-			Allow:  NewRuleResult(false, criteria.ReasonNonPomeriumRoute, criteria.ReasonUserUnauthenticated),
+			Allow:  NewRuleResult(false, criteria.ReasonUserUnauthenticated),
 			Deny:   NewRuleResult(false, criteria.ReasonValidClientCertificateOrNoneRequired),
 			Traces: []contextutil.PolicyEvaluationTrace{{Allow: false}},
 		}, output)

programming_language: Go
filename: grpc.go
change_type: ModificationType.MODIFY
diff: @@ -11,6 +11,7 @@ import (
 
 	"github.com/pomerium/pomerium/authorize/evaluator"
 	"github.com/pomerium/pomerium/config"
+	"github.com/pomerium/pomerium/config/envoyconfig"
 	"github.com/pomerium/pomerium/internal/log"
 	"github.com/pomerium/pomerium/internal/sessions"
 	"github.com/pomerium/pomerium/internal/telemetry/requestid"
@@ -93,6 +94,7 @@ func (a *Authorize) getEvaluatorRequestFromCheckRequest(
 ) (*evaluator.Request, error) {
 	requestURL := getCheckRequestURL(in)
 	req := &evaluator.Request{
+		IsInternal: envoyconfig.ExtAuthzContextExtensionsIsInternal(in.GetAttributes().GetContextExtensions()),
 		HTTP: evaluator.NewRequestHTTP(
 			in.GetAttributes().GetRequest().GetHttp().GetMethod(),
 			requestURL,
@@ -106,15 +108,16 @@ func (a *Authorize) getEvaluatorRequestFromCheckRequest(
 			ID: sessionState.ID,
 		}
 	}
-	req.Policy = a.getMatchingPolicy(requestURL)
+	req.Policy = a.getMatchingPolicy(envoyconfig.ExtAuthzContextExtensionsRouteID(in.Attributes.GetContextExtensions()))
 	return req, nil
 }
 
-func (a *Authorize) getMatchingPolicy(requestURL url.URL) *config.Policy {
+func (a *Authorize) getMatchingPolicy(routeID uint64) *config.Policy {
 	options := a.currentOptions.Load()
 
 	for _, p := range options.GetAllPolicies() {
-		if p.Matches(requestURL) {
+		id, _ := p.RouteID()
+		if id == routeID {
 			return &p
 		}
 	}
@@ -159,6 +162,7 @@ func getCheckRequestURL(req *envoy_service_auth_v3.CheckRequest) url.URL {
 	path := h.GetPath()
 	if idx := strings.Index(path, "?"); idx != -1 {
 		u.RawPath, u.RawQuery = path[:idx], path[idx+1:]
+		u.RawQuery = u.Query().Encode()
 	} else {
 		u.RawPath = path
 	}

programming_language: Go
filename: listeners.go
change_type: ModificationType.MODIFY
diff: @@ -13,11 +13,9 @@ import (
 	envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
 	envoy_config_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
 	envoy_config_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
-	envoy_extensions_filters_http_ext_authz_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/ext_authz/v3"
 	envoy_http_connection_manager "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
 	envoy_extensions_transport_sockets_tls_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
 	envoy_type_v3 "github.com/envoyproxy/go-control-plane/envoy/type/v3"
-	"github.com/golang/protobuf/ptypes/any"
 	"github.com/golang/protobuf/ptypes/wrappers"
 	"google.golang.org/protobuf/types/known/durationpb"
 	"google.golang.org/protobuf/types/known/wrapperspb"
@@ -32,27 +30,16 @@ import (
 
 const listenerBufferLimit uint32 = 32 * 1024
 
-var (
-	disableExtAuthz *any.Any
-	tlsParams       = &envoy_extensions_transport_sockets_tls_v3.TlsParameters{
-		CipherSuites: []string{
-			"ECDHE-ECDSA-AES256-GCM-SHA384",
-			"ECDHE-RSA-AES256-GCM-SHA384",
-			"ECDHE-ECDSA-AES128-GCM-SHA256",
-			"ECDHE-RSA-AES128-GCM-SHA256",
-			"ECDHE-ECDSA-CHACHA20-POLY1305",
-			"ECDHE-RSA-CHACHA20-POLY1305",
-		},
-		TlsMinimumProtocolVersion: envoy_extensions_transport_sockets_tls_v3.TlsParameters_TLSv1_2,
-	}
-)
-
-func init() {
-	disableExtAuthz = marshalAny(&envoy_extensions_filters_http_ext_authz_v3.ExtAuthzPerRoute{
-		Override: &envoy_extensions_filters_http_ext_authz_v3.ExtAuthzPerRoute_Disabled{
-			Disabled: true,
-		},
-	})
+var tlsParams = &envoy_extensions_transport_sockets_tls_v3.TlsParameters{
+	CipherSuites: []string{
+		"ECDHE-ECDSA-AES256-GCM-SHA384",
+		"ECDHE-RSA-AES256-GCM-SHA384",
+		"ECDHE-ECDSA-AES128-GCM-SHA256",
+		"ECDHE-RSA-AES128-GCM-SHA256",
+		"ECDHE-ECDSA-CHACHA20-POLY1305",
+		"ECDHE-RSA-CHACHA20-POLY1305",
+	},
+	TlsMinimumProtocolVersion: envoy_extensions_transport_sockets_tls_v3.TlsParameters_TLSv1_2,
 }
 
 // BuildListeners builds envoy listeners from the given config.
@@ -312,6 +299,7 @@ func (b *Builder) buildMainHTTPConnectionManagerFilter(
 		SkipXffAppend:     cfg.Options.SkipXffAppend,
 		XffNumTrustedHops: cfg.Options.XffNumTrustedHops,
 		LocalReplyConfig:  b.buildLocalReplyConfig(cfg.Options, false),
+		NormalizePath:     wrapperspb.Bool(true),
 	}
 
 	if fullyStatic {

programming_language: Go
filename: per_filter_config.go
change_type: ModificationType.ADD
diff: @@ -0,0 +1,53 @@
+package envoyconfig
+
+import (
+	"strconv"
+
+	envoy_extensions_filters_http_ext_authz_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/ext_authz/v3"
+	"github.com/golang/protobuf/ptypes/any"
+)
+
+// PerFilterConfigExtAuthzName is the name of the ext authz filter to apply config to
+const PerFilterConfigExtAuthzName = "envoy.filters.http.ext_authz"
+
+// PerFilterConfigExtAuthzContextExtensions returns a per-filter config for ext authz that disables ext-authz.
+func PerFilterConfigExtAuthzContextExtensions(authzContextExtensions map[string]string) *any.Any {
+	return marshalAny(&envoy_extensions_filters_http_ext_authz_v3.ExtAuthzPerRoute{
+		Override: &envoy_extensions_filters_http_ext_authz_v3.ExtAuthzPerRoute_CheckSettings{
+			CheckSettings: &envoy_extensions_filters_http_ext_authz_v3.CheckSettings{
+				ContextExtensions: authzContextExtensions,
+			},
+		},
+	})
+}
+
+// PerFilterConfigExtAuthzDisabled returns a per-filter config for ext authz that disables ext-authz.
+func PerFilterConfigExtAuthzDisabled() *any.Any {
+	return marshalAny(&envoy_extensions_filters_http_ext_authz_v3.ExtAuthzPerRoute{
+		Override: &envoy_extensions_filters_http_ext_authz_v3.ExtAuthzPerRoute_Disabled{
+			Disabled: true,
+		},
+	})
+}
+
+// MakeExtAuthzContextExtensions makes the ext authz context extensions.
+func MakeExtAuthzContextExtensions(internal bool, routeID uint64) map[string]string {
+	return map[string]string{
+		"internal": strconv.FormatBool(internal),
+		"route_id": strconv.FormatUint(routeID, 10),
+	}
+}
+
+// ExtAuthzContextExtensionsIsInternal returns true if the context extensions indicates the route is internal.
+func ExtAuthzContextExtensionsIsInternal(extAuthzContextExtensions map[string]string) bool {
+	return extAuthzContextExtensions != nil && extAuthzContextExtensions["internal"] == "true"
+}
+
+// ExtAuthzContextExtensionsRouteID returns the route id for the context extensions.
+func ExtAuthzContextExtensionsRouteID(extAuthzContextExtensions map[string]string) uint64 {
+	if extAuthzContextExtensions == nil {
+		return 0
+	}
+	routeID, _ := strconv.ParseUint(extAuthzContextExtensions["route_id"], 10, 64)
+	return routeID
+}

programming_language: Go
filename: route_configurations_test.go
change_type: ModificationType.MODIFY
diff: @@ -26,6 +26,7 @@ func TestBuilder_buildMainRouteConfiguration(t *testing.T) {
 		Policies: []config.Policy{
 			{
 				From: "https://*.example.com",
+				To:   mustParseWeightedURLs(t, "https://www.example.com"),
 			},
 		},
 	}}
@@ -40,15 +41,13 @@ func TestBuilder_buildMainRouteConfiguration(t *testing.T) {
 				"name": "catch-all",
 				"domains": ["*"],
 				"routes": [
-					`+protojson.Format(b.buildControlPlanePathRoute(cfg.Options, "/.pomerium/jwt", true, false))+`,
-					`+protojson.Format(b.buildControlPlanePathRoute(cfg.Options, "/.pomerium/webauthn", true, false))+`,
-					`+protojson.Format(b.buildControlPlanePathRoute(cfg.Options, "/ping", false, false))+`,
-					`+protojson.Format(b.buildControlPlanePathRoute(cfg.Options, "/healthz", false, false))+`,
-					`+protojson.Format(b.buildControlPlanePathRoute(cfg.Options, "/.pomerium", false, false))+`,
-					`+protojson.Format(b.buildControlPlanePrefixRoute(cfg.Options, "/.pomerium/", false, false))+`,
-					`+protojson.Format(b.buildControlPlanePathRoute(cfg.Options, "/.well-known/pomerium", false, false))+`,
-					`+protojson.Format(b.buildControlPlanePrefixRoute(cfg.Options, "/.well-known/pomerium/", false, false))+`,
-					`+protojson.Format(b.buildControlPlanePathRoute(cfg.Options, "/robots.txt", false, false))+`,
+					`+protojson.Format(b.buildControlPlanePathRoute(cfg.Options, "/ping", false))+`,
+					`+protojson.Format(b.buildControlPlanePathRoute(cfg.Options, "/healthz", false))+`,
+					`+protojson.Format(b.buildControlPlanePathRoute(cfg.Options, "/.pomerium", false))+`,
+					`+protojson.Format(b.buildControlPlanePrefixRoute(cfg.Options, "/.pomerium/", false))+`,
+					`+protojson.Format(b.buildControlPlanePathRoute(cfg.Options, "/.well-known/pomerium", false))+`,
+					`+protojson.Format(b.buildControlPlanePrefixRoute(cfg.Options, "/.well-known/pomerium/", false))+`,
+					`+protojson.Format(b.buildControlPlanePathRoute(cfg.Options, "/robots.txt", false))+`,
 					{
 						"name": "policy-0",
 						"match": {
@@ -79,7 +78,7 @@ func TestBuilder_buildMainRouteConfiguration(t *testing.T) {
 						],
 						"route": {
 							"autoHostRewrite": true,
-							"cluster": "route-0",
+							"cluster": "route-5feb9fe8bd89aa97",
 							"hashPolicy": [
 								{ "header": { "headerName": "x-pomerium-routing-key" }, "terminal": true },
 								{ "connectionProperties": { "sourceIp": true }, "terminal": true }
@@ -89,6 +88,17 @@ func TestBuilder_buildMainRouteConfiguration(t *testing.T) {
 								{ "enabled": false, "upgradeType": "websocket" },
 								{ "enabled": false, "upgradeType": "spdy/3.1" }
 							]
+						},
+						"typedPerFilterConfig": {
+							"envoy.filters.http.ext_authz": {
+								"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
+								"checkSettings": {
+									"contextExtensions": {
+										"internal": "false",
+										"route_id": "6911793875091303063"
+									}
+								}
+							}
 						}
 					},
 					{
@@ -121,7 +131,7 @@ func TestBuilder_buildMainRouteConfiguration(t *testing.T) {
 						],
 						"route": {
 							"autoHostRewrite": true,
-							"cluster": "route-0",
+							"cluster": "route-5feb9fe8bd89aa97",
 							"hashPolicy": [
 								{ "header": { "headerName": "x-pomerium-routing-key" }, "terminal": true },
 								{ "connectionProperties": { "sourceIp": true }, "terminal": true }
@@ -131,6 +141,17 @@ func TestBuilder_buildMainRouteConfiguration(t *testing.T) {
 								{ "enabled": false, "upgradeType": "websocket" },
 								{ "enabled": false, "upgradeType": "spdy/3.1" }
 							]
+						},
+						"typedPerFilterConfig": {
+							"envoy.filters.http.ext_authz": {
+								"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
+								"checkSettings": {
+									"contextExtensions": {
+										"internal": "false",
+										"route_id": "6911793875091303063"
+									}
+								}
+							}
 						}
 					}
 				]

programming_language: Go
filename: routes.go
change_type: ModificationType.MODIFY
diff: @@ -45,7 +45,7 @@ func (b *Builder) buildGRPCRoutes() ([]*envoy_config_route_v3.Route, error) {
 		},
 		Action: action,
 		TypedPerFilterConfig: map[string]*any.Any{
-			"envoy.filters.http.ext_authz": disableExtAuthz,
+			PerFilterConfigExtAuthzName: PerFilterConfigExtAuthzDisabled(),
 		},
 	}}, nil
 }
@@ -65,20 +65,16 @@ func (b *Builder) buildPomeriumHTTPRoutes(
 	}
 	if !isFrontingAuthenticate {
 		routes = append(routes,
-			// enable ext_authz
-			b.buildControlPlanePathRoute(options, "/.pomerium/jwt", true, requireStrictTransportSecurity),
-			b.buildControlPlanePathRoute(options, urlutil.WebAuthnURLPath, true, requireStrictTransportSecurity),
-			// disable ext_authz and passthrough to proxy handlers
-			b.buildControlPlanePathRoute(options, "/ping", false, requireStrictTransportSecurity),
-			b.buildControlPlanePathRoute(options, "/healthz", false, requireStrictTransportSecurity),
-			b.buildControlPlanePathRoute(options, "/.pomerium", false, requireStrictTransportSecurity),
-			b.buildControlPlanePrefixRoute(options, "/.pomerium/", false, requireStrictTransportSecurity),
-			b.buildControlPlanePathRoute(options, "/.well-known/pomerium", false, requireStrictTransportSecurity),
-			b.buildControlPlanePrefixRoute(options, "/.well-known/pomerium/", false, requireStrictTransportSecurity),
+			b.buildControlPlanePathRoute(options, "/ping", requireStrictTransportSecurity),
+			b.buildControlPlanePathRoute(options, "/healthz", requireStrictTransportSecurity),
+			b.buildControlPlanePathRoute(options, "/.pomerium", requireStrictTransportSecurity),
+			b.buildControlPlanePrefixRoute(options, "/.pomerium/", requireStrictTransportSecurity),
+			b.buildControlPlanePathRoute(options, "/.well-known/pomerium", requireStrictTransportSecurity),
+			b.buildControlPlanePrefixRoute(options, "/.well-known/pomerium/", requireStrictTransportSecurity),
 		)
 		// per #837, only add robots.txt if there are no unauthenticated routes
 		if !hasPublicPolicyMatchingURL(options, url.URL{Scheme: "https", Host: host, Path: "/robots.txt"}) {
-			routes = append(routes, b.buildControlPlanePathRoute(options, "/robots.txt", false, requireStrictTransportSecurity))
+			routes = append(routes, b.buildControlPlanePathRoute(options, "/robots.txt", requireStrictTransportSecurity))
 		}
 	}
 
@@ -109,8 +105,8 @@ func (b *Builder) buildPomeriumAuthenticateHTTPRoutes(
 		}
 		if urlMatchesHost(u, host) {
 			return []*envoy_config_route_v3.Route{
-				b.buildControlPlanePathRoute(options, options.AuthenticateCallbackPath, false, requireStrictTransportSecurity),
-				b.buildControlPlanePathRoute(options, "/", false, requireStrictTransportSecurity),
+				b.buildControlPlanePathRoute(options, options.AuthenticateCallbackPath, requireStrictTransportSecurity),
+				b.buildControlPlanePathRoute(options, "/", requireStrictTransportSecurity),
 			}, nil
 		}
 	}
@@ -120,7 +116,6 @@ func (b *Builder) buildPomeriumAuthenticateHTTPRoutes(
 func (b *Builder) buildControlPlanePathRoute(
 	options *config.Options,
 	path string,
-	protected bool,
 	requireStrictTransportSecurity bool,
 ) *envoy_config_route_v3.Route {
 	r := &envoy_config_route_v3.Route{
@@ -136,11 +131,9 @@ func (b *Builder) buildControlPlanePathRoute(
 			},
 		},
 		ResponseHeadersToAdd: toEnvoyHeaders(options.GetSetResponseHeaders(requireStrictTransportSecurity)),
-	}
-	if !protected {
-		r.TypedPerFilterConfig = map[string]*any.Any{
-			"envoy.filters.http.ext_authz": disableExtAuthz,
-		}
+		TypedPerFilterConfig: map[string]*any.Any{
+			PerFilterConfigExtAuthzName: PerFilterConfigExtAuthzContextExtensions(MakeExtAuthzContextExtensions(true, 0)),
+		},
 	}
 	return r
 }
@@ -148,7 +141,6 @@ func (b *Builder) buildControlPlanePathRoute(
 func (b *Builder) buildControlPlanePrefixRoute(
 	options *config.Options,
 	prefix string,
-	protected bool,
 	requireStrictTransportSecurity bool,
 ) *envoy_config_route_v3.Route {
 	r := &envoy_config_route_v3.Route{
@@ -164,11 +156,9 @@ func (b *Builder) buildControlPlanePrefixRoute(
 			},
 		},
 		ResponseHeadersToAdd: toEnvoyHeaders(options.GetSetResponseHeaders(requireStrictTransportSecurity)),
-	}
-	if !protected {
-		r.TypedPerFilterConfig = map[string]*any.Any{
-			"envoy.filters.http.ext_authz": disableExtAuthz,
-		}
+		TypedPerFilterConfig: map[string]*any.Any{
+			PerFilterConfigExtAuthzName: PerFilterConfigExtAuthzContextExtensions(MakeExtAuthzContextExtensions(true, 0)),
+		},
 	}
 	return r
 }
@@ -288,6 +278,11 @@ func (b *Builder) buildRouteForPolicyAndMatch(
 		return nil, err
 	}
 
+	routeID, err := policy.RouteID()
+	if err != nil {
+		return nil, err
+	}
+
 	requireStrictTransportSecurity := cryptutil.HasCertificateForServerName(certs, fromURL.Hostname())
 
 	route := &envoy_config_route_v3.Route{
@@ -323,9 +318,12 @@ func (b *Builder) buildRouteForPolicyAndMatch(
 	}
 	if isFrontingAuthenticate {
 		route.TypedPerFilterConfig = map[string]*any.Any{
-			"envoy.filters.http.ext_authz": disableExtAuthz,
+			PerFilterConfigExtAuthzName: PerFilterConfigExtAuthzDisabled(),
 		}
 	} else {
+		route.TypedPerFilterConfig = map[string]*any.Any{
+			PerFilterConfigExtAuthzName: PerFilterConfigExtAuthzContextExtensions(MakeExtAuthzContextExtensions(false, routeID)),
+		}
 		luaMetadata["remove_pomerium_cookie"] = &structpb.Value{
 			Kind: &structpb.Value_StringValue{
 				StringValue: cfg.Options.CookieName,
@@ -344,8 +342,7 @@ func (b *Builder) buildRouteForPolicyAndMatch(
 	}
 
 	if policy.IsForKubernetes() {
-		policyID, _ := policy.RouteID()
-		for _, hdr := range b.reproxy.GetPolicyIDHeaders(policyID) {
+		for _, hdr := range b.reproxy.GetPolicyIDHeaders(routeID) {
 			route.RequestHeadersToAdd = append(route.RequestHeadersToAdd,
 				&envoy_config_core_v3.HeaderValueOption{
 					Header: &envoy_config_core_v3.HeaderValue{

programming_language: Go
filename: routes_test.go
change_type: ModificationType.MODIFY
diff: @@ -16,7 +16,6 @@ import (
 	"github.com/pomerium/pomerium/config"
 	"github.com/pomerium/pomerium/config/envoyconfig/filemgr"
 	"github.com/pomerium/pomerium/internal/testutil"
-	"github.com/pomerium/pomerium/internal/urlutil"
 	"github.com/pomerium/pomerium/pkg/cryptutil"
 )
 
@@ -56,43 +55,43 @@ func Test_buildGRPCRoutes(t *testing.T) {
 
 func Test_buildPomeriumHTTPRoutes(t *testing.T) {
 	b := &Builder{filemgr: filemgr.NewManager()}
-	routeString := func(typ, name string, protected bool) string {
+	routeString := func(typ, name string) string {
 		str := `{
-				"name": "pomerium-` + typ + `-` + name + `",
-				"match": {
-					"` + typ + `": "` + name + `"
+			"name": "pomerium-` + typ + `-` + name + `",
+			"match": {
+				"` + typ + `": "` + name + `"
+			},
+			"responseHeadersToAdd": [
+				{
+					"appendAction": "OVERWRITE_IF_EXISTS_OR_ADD",
+					"header": {
+						"key": "X-Frame-Options",
+						"value": "SAMEORIGIN"
+					}
 				},
-				"responseHeadersToAdd": [
-					{
-						"appendAction": "OVERWRITE_IF_EXISTS_OR_ADD",
-						"header": {
-						  "key": "X-Frame-Options",
-						  "value": "SAMEORIGIN"
-						}
-					},
-					{
-						"appendAction": "OVERWRITE_IF_EXISTS_OR_ADD",
-						"header": {
-						  "key": "X-XSS-Protection",
-						  "value": "1; mode=block"
-						}
+				{
+					"appendAction": "OVERWRITE_IF_EXISTS_OR_ADD",
+					"header": {
+						"key": "X-XSS-Protection",
+						"value": "1; mode=block"
 					}
-				],
-				"route": {
-					"cluster": "pomerium-control-plane-http"
 				}
-			`
-		if !protected {
-			str += `,
-				"typedPerFilterConfig": {
-					"envoy.filters.http.ext_authz": {
-						"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
-						"disabled": true
+			],
+			"route": {
+				"cluster": "pomerium-control-plane-http"
+			},
+			"typedPerFilterConfig": {
+				"envoy.filters.http.ext_authz": {
+					"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
+					"checkSettings": {
+						"contextExtensions": {
+							"internal": "true",
+							"route_id": "0"
+						}
 					}
 				}
-			`
-		}
-		str += "}"
+			}
+		}`
 		return str
 	}
 	t.Run("authenticate", func(t *testing.T) {
@@ -105,17 +104,15 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) {
 		require.NoError(t, err)
 
 		testutil.AssertProtoJSONEqual(t, `[
-			`+routeString("path", "/.pomerium/jwt", true)+`,
-			`+routeString("path", urlutil.WebAuthnURLPath, true)+`,
-			`+routeString("path", "/ping", false)+`,
-			`+routeString("path", "/healthz", false)+`,
-			`+routeString("path", "/.pomerium", false)+`,
-			`+routeString("prefix", "/.pomerium/", false)+`,
-			`+routeString("path", "/.well-known/pomerium", false)+`,
-			`+routeString("prefix", "/.well-known/pomerium/", false)+`,
-			`+routeString("path", "/robots.txt", false)+`,
-			`+routeString("path", "/oauth2/callback", false)+`,
-			`+routeString("path", "/", false)+`
+			`+routeString("path", "/ping")+`,
+			`+routeString("path", "/healthz")+`,
+			`+routeString("path", "/.pomerium")+`,
+			`+routeString("prefix", "/.pomerium/")+`,
+			`+routeString("path", "/.well-known/pomerium")+`,
+			`+routeString("prefix", "/.well-known/pomerium/")+`,
+			`+routeString("path", "/robots.txt")+`,
+			`+routeString("path", "/oauth2/callback")+`,
+			`+routeString("path", "/")+`
 		]`, routes)
 	})
 	t.Run("proxy fronting authenticate", func(t *testing.T) {
@@ -144,15 +141,13 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) {
 		require.NoError(t, err)
 
 		testutil.AssertProtoJSONEqual(t, `[
-			`+routeString("path", "/.pomerium/jwt", true)+`,
-			`+routeString("path", urlutil.WebAuthnURLPath, true)+`,
-			`+routeString("path", "/ping", false)+`,
-			`+routeString("path", "/healthz", false)+`,
-			`+routeString("path", "/.pomerium", false)+`,
-			`+routeString("prefix", "/.pomerium/", false)+`,
-			`+routeString("path", "/.well-known/pomerium", false)+`,
-			`+routeString("prefix", "/.well-known/pomerium/", false)+`,
-			`+routeString("path", "/robots.txt", false)+`
+			`+routeString("path", "/ping")+`,
+			`+routeString("path", "/healthz")+`,
+			`+routeString("path", "/.pomerium")+`,
+			`+routeString("prefix", "/.pomerium/")+`,
+			`+routeString("path", "/.well-known/pomerium")+`,
+			`+routeString("prefix", "/.well-known/pomerium/")+`,
+			`+routeString("path", "/robots.txt")+`
 		]`, routes)
 	})
 
@@ -172,14 +167,12 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) {
 		require.NoError(t, err)
 
 		testutil.AssertProtoJSONEqual(t, `[
-			`+routeString("path", "/.pomerium/jwt", true)+`,
-			`+routeString("path", urlutil.WebAuthnURLPath, true)+`,
-			`+routeString("path", "/ping", false)+`,
-			`+routeString("path", "/healthz", false)+`,
-			`+routeString("path", "/.pomerium", false)+`,
-			`+routeString("prefix", "/.pomerium/", false)+`,
-			`+routeString("path", "/.well-known/pomerium", false)+`,
-			`+routeString("prefix", "/.well-known/pomerium/", false)+`
+			`+routeString("path", "/ping")+`,
+			`+routeString("path", "/healthz")+`,
+			`+routeString("path", "/.pomerium")+`,
+			`+routeString("prefix", "/.pomerium/")+`,
+			`+routeString("path", "/.well-known/pomerium")+`,
+			`+routeString("prefix", "/.well-known/pomerium/")+`
 		]`, routes)
 	})
 }
@@ -187,7 +180,7 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) {
 func Test_buildControlPlanePathRoute(t *testing.T) {
 	options := config.NewDefaultOptions()
 	b := &Builder{filemgr: filemgr.NewManager()}
-	route := b.buildControlPlanePathRoute(options, "/hello/world", false, false)
+	route := b.buildControlPlanePathRoute(options, "/hello/world", false)
 	testutil.AssertProtoJSONEqual(t, `
 		{
 			"name": "pomerium-path-/hello/world",
@@ -216,7 +209,12 @@ func Test_buildControlPlanePathRoute(t *testing.T) {
 			"typedPerFilterConfig": {
 				"envoy.filters.http.ext_authz": {
 					"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
-					"disabled": true
+					"checkSettings": {
+						"contextExtensions": {
+							"internal": "true",
+							"route_id": "0"
+						}
+					}
 				}
 			}
 		}
@@ -226,7 +224,7 @@ func Test_buildControlPlanePathRoute(t *testing.T) {
 func Test_buildControlPlanePrefixRoute(t *testing.T) {
 	options := config.NewDefaultOptions()
 	b := &Builder{filemgr: filemgr.NewManager()}
-	route := b.buildControlPlanePrefixRoute(options, "/hello/world/", false, false)
+	route := b.buildControlPlanePrefixRoute(options, "/hello/world/", false)
 	testutil.AssertProtoJSONEqual(t, `
 		{
 			"name": "pomerium-prefix-/hello/world/",
@@ -255,7 +253,12 @@ func Test_buildControlPlanePrefixRoute(t *testing.T) {
 			"typedPerFilterConfig": {
 				"envoy.filters.http.ext_authz": {
 					"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
-					"disabled": true
+					"checkSettings": {
+						"contextExtensions": {
+							"internal": "true",
+							"route_id": "0"
+						}
+					}
 				}
 			}
 		}
@@ -301,6 +304,7 @@ func TestTimeouts(t *testing.T) {
 			Policies: []config.Policy{
 				{
 					From:            "https://example.com",
+					To:              mustParseWeightedURLs(t, "https://to.example.com"),
 					Path:            "/test",
 					UpstreamTimeout: getDuration(tc.upstream),
 					IdleTimeout:     getDuration(tc.idle),
@@ -356,14 +360,17 @@ func Test_buildPolicyRoutes(t *testing.T) {
 		Policies: []config.Policy{
 			{
 				From:                "https://ignore.example.com",
+				To:                  mustParseWeightedURLs(t, "https://to.example.com"),
 				PassIdentityHeaders: true,
 			},
 			{
 				From:                "https://example.com",
+				To:                  mustParseWeightedURLs(t, "https://to.example.com"),
 				PassIdentityHeaders: true,
 			},
 			{
 				From:                "https://example.com",
+				To:                  mustParseWeightedURLs(t, "https://to.example.com"),
 				Path:                "/some/path",
 				AllowWebsockets:     true,
 				PreserveHostHeader:  true,
@@ -371,6 +378,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
 			},
 			{
 				From:                "https://example.com",
+				To:                  mustParseWeightedURLs(t, "https://to.example.com"),
 				Prefix:              "/some/prefix/",
 				SetRequestHeaders:   map[string]string{"HEADER-KEY": "HEADER-VALUE"},
 				UpstreamTimeout:     &oneMinute,
@@ -378,11 +386,13 @@ func Test_buildPolicyRoutes(t *testing.T) {
 			},
 			{
 				From:                "https://example.com",
+				To:                  mustParseWeightedURLs(t, "https://to.example.com"),
 				Regex:               `^/[a]+$`,
 				PassIdentityHeaders: true,
 			},
 			{
 				From:                 "https://example.com",
+				To:                   mustParseWeightedURLs(t, "https://to.example.com"),
 				Prefix:               "/some/prefix/",
 				RemoveRequestHeaders: []string{"HEADER-KEY"},
 				UpstreamTimeout:      &oneMinute,
@@ -390,6 +400,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
 			},
 			{
 				From:                "https://example.com",
+				To:                  mustParseWeightedURLs(t, "https://to.example.com"),
 				Path:                "/some/path",
 				AllowSPDY:           true,
 				PreserveHostHeader:  true,
@@ -397,6 +408,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
 			},
 			{
 				From:                "https://example.com",
+				To:                  mustParseWeightedURLs(t, "https://to.example.com"),
 				Path:                "/some/path",
 				AllowSPDY:           true,
 				AllowWebsockets:     true,
@@ -405,6 +417,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
 			},
 			{
 				From:                "https://example.com",
+				To:                  mustParseWeightedURLs(t, "https://to.example.com"),
 				Path:                "/websocket-timeout",
 				AllowWebsockets:     true,
 				PreserveHostHeader:  true,
@@ -474,7 +487,18 @@ func Test_buildPolicyRoutes(t *testing.T) {
 						  "value": "1; mode=block"
 						}
 					}
-				]
+				],
+				"typedPerFilterConfig": {
+					"envoy.filters.http.ext_authz": {
+						"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
+						"checkSettings": {
+							"contextExtensions": {
+								"internal": "false",
+								"route_id": "11444765232398592404"
+							}
+						}
+					}
+				}
 			},
 			{
 				"name": "policy-2",
@@ -534,7 +558,18 @@ func Test_buildPolicyRoutes(t *testing.T) {
 						  "value": "1; mode=block"
 						}
 					}
-				]
+				],
+				"typedPerFilterConfig": {
+					"envoy.filters.http.ext_authz": {
+						"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
+						"checkSettings": {
+							"contextExtensions": {
+								"internal": "false",
+								"route_id": "2990091139764155677"
+							}
+						}
+					}
+				}
 			},
 			{
 				"name": "policy-3",
@@ -600,7 +635,18 @@ func Test_buildPolicyRoutes(t *testing.T) {
 						  "value": "1; mode=block"
 						}
 					}
-				]
+				],
+				"typedPerFilterConfig": {
+					"envoy.filters.http.ext_authz": {
+						"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
+						"checkSettings": {
+							"contextExtensions": {
+								"internal": "false",
+								"route_id": "2544588842279234006"
+							}
+						}
+					}
+				}
 			},
 			{
 				"name": "policy-4",
@@ -661,7 +707,18 @@ func Test_buildPolicyRoutes(t *testing.T) {
 						  "value": "1; mode=block"
 						}
 					}
-				]
+				],
+				"typedPerFilterConfig": {
+					"envoy.filters.http.ext_authz": {
+						"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
+						"checkSettings": {
+							"contextExtensions": {
+								"internal": "false",
+								"route_id": "10244970664102670752"
+							}
+						}
+					}
+				}
 			},
 			{
 				"name": "policy-5",
@@ -721,7 +778,18 @@ func Test_buildPolicyRoutes(t *testing.T) {
 						  "value": "1; mode=block"
 						}
 					}
-				]
+				],
+				"typedPerFilterConfig": {
+					"envoy.filters.http.ext_authz": {
+						"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
+						"checkSettings": {
+							"contextExtensions": {
+								"internal": "false",
+								"route_id": "2544588842279234006"
+							}
+						}
+					}
+				}
 			},
 			{
 				"name": "policy-6",
@@ -780,7 +848,18 @@ func Test_buildPolicyRoutes(t *testing.T) {
 						  "value": "1; mode=block"
 						}
 					}
-				]
+				],
+				"typedPerFilterConfig": {
+					"envoy.filters.http.ext_authz": {
+						"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
+						"checkSettings": {
+							"contextExtensions": {
+								"internal": "false",
+								"route_id": "2990091139764155677"
+							}
+						}
+					}
+				}
 			},
 			{
 				"name": "policy-7",
@@ -840,7 +919,18 @@ func Test_buildPolicyRoutes(t *testing.T) {
 						  "value": "1; mode=block"
 						}
 					}
-				]
+				],
+				"typedPerFilterConfig": {
+					"envoy.filters.http.ext_authz": {
+						"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
+						"checkSettings": {
+							"contextExtensions": {
+								"internal": "false",
+								"route_id": "2990091139764155677"
+							}
+						}
+					}
+				}
 			},
 			{
 				"name": "policy-8",
@@ -900,7 +990,18 @@ func Test_buildPolicyRoutes(t *testing.T) {
 						  "value": "1; mode=block"
 						}
 					}
-				]
+				],
+				"typedPerFilterConfig": {
+					"envoy.filters.http.ext_authz": {
+						"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
+						"checkSettings": {
+							"contextExtensions": {
+								"internal": "false",
+								"route_id": "1052418080698022187"
+							}
+						}
+					}
+				}
 			}
 		]
 	`, routes)
@@ -915,6 +1016,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
 			Policies: []config.Policy{
 				{
 					From:                "https://authenticate.example.com",
+					To:                  mustParseWeightedURLs(t, "https://authenticate.internal"),
 					PassIdentityHeaders: true,
 				},
 			},
@@ -997,10 +1099,12 @@ func Test_buildPolicyRoutes(t *testing.T) {
 			Policies: []config.Policy{
 				{
 					From:                "tcp+https://example.com:22",
+					To:                  mustParseWeightedURLs(t, "https://to.example.com"),
 					PassIdentityHeaders: true,
 				},
 				{
 					From:                "tcp+https://example.com:22",
+					To:                  mustParseWeightedURLs(t, "https://to.example.com"),
 					PassIdentityHeaders: true,
 					UpstreamTimeout:     &ten,
 				},
@@ -1069,7 +1173,18 @@ func Test_buildPolicyRoutes(t *testing.T) {
 						  "value": "1; mode=block"
 						}
 					}
-				]
+				],
+				"typedPerFilterConfig": {
+					"envoy.filters.http.ext_authz": {
+						"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
+						"checkSettings": {
+							"contextExtensions": {
+								"internal": "false",
+								"route_id": "2226589900561460978"
+							}
+						}
+					}
+				}
 			},
 			{
 				"name": "policy-1",
@@ -1130,7 +1245,18 @@ func Test_buildPolicyRoutes(t *testing.T) {
 						  "value": "1; mode=block"
 						}
 					}
-				]
+				],
+				"typedPerFilterConfig": {
+					"envoy.filters.http.ext_authz": {
+						"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
+						"checkSettings": {
+							"contextExtensions": {
+								"internal": "false",
+								"route_id": "2226589900561460978"
+							}
+						}
+					}
+				}
 			}
 		]
 	`, routes)
@@ -1149,6 +1275,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
 			Policies: []config.Policy{
 				{
 					From: "https://from.example.com",
+					To:   mustParseWeightedURLs(t, "https://to.example.com"),
 				},
 			},
 		}}, nil, "from.example.com")
@@ -1216,7 +1343,18 @@ func Test_buildPolicyRoutes(t *testing.T) {
 							  "value": "1; mode=block"
 							}
 						}
-					]
+					],
+					"typedPerFilterConfig": {
+						"envoy.filters.http.ext_authz": {
+							"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
+							"checkSettings": {
+								"contextExtensions": {
+									"internal": "false",
+									"route_id": "15508081512033148378"
+								}
+							}
+						}
+					}
 				}
 			]
 		`, routes)
@@ -1335,7 +1473,18 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) {
 						  "value": "1; mode=block"
 						}
 					}
-				]
+				],
+				"typedPerFilterConfig": {
+					"envoy.filters.http.ext_authz": {
+						"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
+						"checkSettings": {
+							"contextExtensions": {
+								"internal": "false",
+								"route_id": "285016060542193864"
+							}
+						}
+					}
+				}
 			},
 			{
 				"name": "policy-1",
@@ -1395,7 +1544,18 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) {
 						  "value": "1; mode=block"
 						}
 					}
-				]
+				],
+				"typedPerFilterConfig": {
+					"envoy.filters.http.ext_authz": {
+						"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
+						"checkSettings": {
+							"contextExtensions": {
+								"internal": "false",
+								"route_id": "285016060542193864"
+							}
+						}
+					}
+				}
 			},
 			{
 				"name": "policy-2",
@@ -1460,7 +1620,18 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) {
 						  "value": "1; mode=block"
 						}
 					}
-				]
+				],
+				"typedPerFilterConfig": {
+					"envoy.filters.http.ext_authz": {
+						"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
+						"checkSettings": {
+							"contextExtensions": {
+								"internal": "false",
+								"route_id": "285016060542193864"
+							}
+						}
+					}
+				}
 			},
 			{
 				"name": "policy-3",
@@ -1520,7 +1691,18 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) {
 						  "value": "1; mode=block"
 						}
 					}
-				]
+				],
+				"typedPerFilterConfig": {
+					"envoy.filters.http.ext_authz": {
+						"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
+						"checkSettings": {
+							"contextExtensions": {
+								"internal": "false",
+								"route_id": "285016060542193864"
+							}
+						}
+					}
+				}
 			},
 			{
 				"name": "policy-4",
@@ -1580,7 +1762,18 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) {
 						  "value": "1; mode=block"
 						}
 					}
-				]
+				],
+				"typedPerFilterConfig": {
+					"envoy.filters.http.ext_authz": {
+						"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
+						"checkSettings": {
+							"contextExtensions": {
+								"internal": "false",
+								"route_id": "285016060542193864"
+							}
+						}
+					}
+				}
 			},
 			{
 				"name": "policy-5",
@@ -1645,7 +1838,18 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) {
 						  "value": "1; mode=block"
 						}
 					}
-				]
+				],
+				"typedPerFilterConfig": {
+					"envoy.filters.http.ext_authz": {
+						"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
+						"checkSettings": {
+							"contextExtensions": {
+								"internal": "false",
+								"route_id": "285016060542193864"
+							}
+						}
+					}
+				}
 			}
 		]
 	`, routes)

programming_language: Go
filename: main_http_connection_manager_filter.json
change_type: ModificationType.MODIFY
diff: @@ -120,6 +120,7 @@
       ]
     },
     "requestTimeout": "30s",
+    "normalizePath": true,
     "rds": {
       "configSource": {
         "ads": {},

programming_language: Lua
filename: policy_ppl.go
change_type: ModificationType.MODIFY
diff: @@ -13,10 +13,6 @@ func (p *Policy) ToPPL() *parser.Policy {
 	ppl := &parser.Policy{}
 
 	allowRule := parser.Rule{Action: parser.ActionAllow}
-	allowRule.Or = append(allowRule.Or,
-		parser.Criterion{
-			Name: "pomerium_routes",
-		})
 	if p.AllowPublicUnauthenticatedAccess {
 		allowRule.Or = append(allowRule.Or,
 			parser.Criterion{

programming_language: Go
filename: policy_ppl_test.go
change_type: ModificationType.MODIFY
diff: @@ -57,24 +57,6 @@ default allow = [false, set()]
 
 default deny = [false, set()]
 
-pomerium_routes_0 = [true, {"pomerium-route"}] {
-	session := get_session(input.session.id)
-	session.id != ""
-	contains(input.http.url, "/.pomerium/")
-}
-
-else = [true, {"pomerium-route"}] {
-	contains(input.http.url, "/.pomerium/")
-	not contains(input.http.url, "/.pomerium/jwt")
-	not contains(input.http.url, "/.pomerium/webauthn")
-}
-
-else = [false, {"user-unauthenticated"}] {
-	contains(input.http.url, "/.pomerium/")
-}
-
-else = [false, {"non-pomerium-route"}]
-
 accept_0 = [true, {"accept"}]
 
 cors_preflight_0 = [true, {"cors-request"}] {
@@ -380,7 +362,7 @@ else = [false, {"email-unauthorized"}] {
 else = [false, {"user-unauthenticated"}]
 
 or_0 = v {
-	results := [pomerium_routes_0, accept_0, cors_preflight_0, authenticated_user_0, domain_0, domain_1, domain_2, domain_3, domain_4, claim_0, claim_1, claim_2, claim_3, user_0, email_0, user_1, email_1, user_2, email_2, user_3, email_3, user_4, email_4]
+	results := [accept_0, cors_preflight_0, authenticated_user_0, domain_0, domain_1, domain_2, domain_3, domain_4, claim_0, claim_1, claim_2, claim_3, user_0, email_0, user_1, email_1, user_2, email_2, user_3, email_3, user_4, email_4]
 	normalized := [normalize_criterion_result(x) | x := results[i]]
 	v := merge_with_or(normalized)
 }

programming_language: Go
filename: pomerium_routes.go
change_type: ModificationType.DELETE
diff: @@ -1,65 +0,0 @@
-package criteria
-
-import (
-	"github.com/open-policy-agent/opa/ast"
-
-	"github.com/pomerium/pomerium/internal/urlutil"
-	"github.com/pomerium/pomerium/pkg/policy/generator"
-	"github.com/pomerium/pomerium/pkg/policy/parser"
-	"github.com/pomerium/pomerium/pkg/policy/rules"
-)
-
-type pomeriumRoutesCriterion struct {
-	g *Generator
-}
-
-func (pomeriumRoutesCriterion) DataType() generator.CriterionDataType {
-	return generator.CriterionDataTypeUnused
-}
-
-func (pomeriumRoutesCriterion) Name() string {
-	return "pomerium_routes"
-}
-
-func (c pomeriumRoutesCriterion) GenerateRule(_ string, _ parser.Value) (*ast.Rule, []*ast.Rule, error) {
-	r1 := c.g.NewRule(c.Name())
-	r1.Head.Value = NewCriterionTerm(true, ReasonPomeriumRoute)
-	r1.Body = ast.Body{
-		ast.MustParseExpr(`session := get_session(input.session.id)`),
-		ast.MustParseExpr(`session.id != ""`),
-		ast.MustParseExpr(`contains(input.http.url, "/.pomerium/")`),
-	}
-
-	r2 := c.g.NewRule(c.Name())
-	r2.Head.Value = NewCriterionTerm(true, ReasonPomeriumRoute)
-	r2.Body = ast.Body{
-		ast.MustParseExpr(`contains(input.http.url, "/.pomerium/")`),
-		ast.MustParseExpr(`not contains(input.http.url, "/.pomerium/jwt")`),
-		ast.MustParseExpr(`not contains(input.http.url, "` + urlutil.WebAuthnURLPath + `")`),
-	}
-	r1.Else = r2
-
-	r3 := c.g.NewRule(c.Name())
-	r3.Head.Value = NewCriterionTerm(false, ReasonUserUnauthenticated)
-	r3.Body = ast.Body{
-		ast.MustParseExpr(`contains(input.http.url, "/.pomerium/")`),
-	}
-	r2.Else = r3
-
-	r4 := c.g.NewRule(c.Name())
-	r4.Head.Value = NewCriterionTerm(false, ReasonNonPomeriumRoute)
-	r3.Else = r4
-
-	return r1, []*ast.Rule{
-		rules.GetSession(),
-	}, nil
-}
-
-// PomeriumRoutes returns a Criterion on that allows access to pomerium routes.
-func PomeriumRoutes(generator *Generator) Criterion {
-	return pomeriumRoutesCriterion{g: generator}
-}
-
-func init() {
-	Register(PomeriumRoutes)
-}

programming_language: unknown