VYPR
Critical severityNVD Advisory· Published May 30, 2023· Updated Jan 10, 2025

Incorrect Authorization with specially crafted requests

CVE-2023-33189

Description

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.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/pomerium/pomeriumGo
>= 0.22.0, < 0.22.20.22.2
github.com/pomerium/pomeriumGo
>= 0.21.0, < 0.21.40.21.4
github.com/pomerium/pomeriumGo
>= 0.20.0, < 0.20.10.20.1
github.com/pomerium/pomeriumGo
>= 0.19.0, < 0.19.20.19.2
github.com/pomerium/pomeriumGo
>= 0.18.0, < 0.18.10.18.1
github.com/pomerium/pomeriumGo
< 0.17.40.17.4

Affected products

1

Patches

1
d315e683357a

Merge pull request from GHSA-pvrc-wvj2-f59p

https://github.com/pomerium/pomeriumCaleb DoxseyMay 26, 2023via ghsa
13 files changed · +501 287
  • authorize/evaluator/evaluator.go+70 39 modified
    @@ -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
     }
     
    
  • authorize/evaluator/headers_evaluator.go+11 9 modified
    @@ -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
     }
     
    
  • authorize/evaluator/policy_evaluator_test.go+4 4 modified
    @@ -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)
    
  • authorize/grpc.go+7 3 modified
    @@ -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
     	}
    
  • config/envoyconfig/listeners.go+11 23 modified
    @@ -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 {
    
  • config/envoyconfig/per_filter_config.go+53 0 added
    @@ -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
    +}
    
  • config/envoyconfig/route_configurations_test.go+32 11 modified
    @@ -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"
    +									}
    +								}
    +							}
     						}
     					}
     				]
    
  • config/envoyconfig/routes.go+26 29 modified
    @@ -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,19 +131,16 @@ 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
     }
     
     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{
    
  • config/envoyconfig/routes_test.go+285 81 modified
    @@ -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,22 +167,20 @@ 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)
     	})
     }
     
     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,47 +360,55 @@ 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,
     				PassIdentityHeaders: true,
     			},
     			{
     				From:                "https://example.com",
    +				To:                  mustParseWeightedURLs(t, "https://to.example.com"),
     				Prefix:              "/some/prefix/",
     				SetRequestHeaders:   map[string]string{"HEADER-KEY": "HEADER-VALUE"},
     				UpstreamTimeout:     &oneMinute,
     				PassIdentityHeaders: true,
     			},
     			{
     				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,
     				PassIdentityHeaders:  true,
     			},
     			{
     				From:                "https://example.com",
    +				To:                  mustParseWeightedURLs(t, "https://to.example.com"),
     				Path:                "/some/path",
     				AllowSPDY:           true,
     				PreserveHostHeader:  true,
     				PassIdentityHeaders: true,
     			},
     			{
     				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)
    
  • config/envoyconfig/testdata/main_http_connection_manager_filter.json+1 0 modified
    @@ -120,6 +120,7 @@
           ]
         },
         "requestTimeout": "30s",
    +    "normalizePath": true,
         "rds": {
           "configSource": {
             "ads": {},
    
  • config/policy_ppl.go+0 4 modified
    @@ -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{
    
  • config/policy_ppl_test.go+1 19 modified
    @@ -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)
     }
    
  • pkg/policy/criteria/pomerium_routes.go+0 65 removed
    @@ -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)
    -}
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

10

News mentions

0

No linked articles in our index yet.