Merge pull request from GHSA-pvrc-wvj2-f59p

* authorize: normalize URL query params

* config: enable envoy normalize_path option

* authorize: use route id from envoy for policy evaluation
This commit is contained in:
Kenneth Jenkins 2023-05-26 13:34:21 -07:00 committed by GitHub
parent 32985aabe6
commit 2bc2be793f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 255 additions and 208 deletions

View file

@ -31,6 +31,7 @@ var notFoundOutput = &Result{
// Request contains the inputs needed for evaluation. // Request contains the inputs needed for evaluation.
type Request struct { type Request struct {
IsInternal bool
Policy *config.Policy Policy *config.Policy
HTTP RequestHTTP HTTP RequestHTTP
Session RequestSession Session RequestSession
@ -124,10 +125,18 @@ func (e *Evaluator) Evaluate(ctx context.Context, req *Request) (*Result, error)
ctx, span := trace.StartSpan(ctx, "authorize.Evaluator.Evaluate") ctx, span := trace.StartSpan(ctx, "authorize.Evaluator.Evaluate")
defer span.End() defer span.End()
if req.Policy == nil { eg, ectx := errgroup.WithContext(ctx)
return notFoundOutput, nil
}
var policyOutput *PolicyResponse
if req.IsInternal {
var err error
policyOutput, err = e.evaluateInternal(ctx, req)
if err != nil {
return nil, err
}
} else if req.Policy == nil {
return notFoundOutput, nil
} else {
id, err := req.Policy.RouteID() id, err := req.Policy.RouteID()
if err != nil { if err != nil {
return nil, fmt.Errorf("authorize: error computing policy route id: %w", err) return nil, fmt.Errorf("authorize: error computing policy route id: %w", err)
@ -148,9 +157,6 @@ func (e *Evaluator) Evaluate(ctx context.Context, req *Request) (*Result, error)
return nil, fmt.Errorf("authorize: error validating client certificate: %w", err) return nil, fmt.Errorf("authorize: error validating client certificate: %w", err)
} }
eg, ectx := errgroup.WithContext(ctx)
var policyOutput *PolicyResponse
eg.Go(func() error { eg.Go(func() error {
var err error var err error
policyOutput, err = policyEvaluator.Evaluate(ectx, &PolicyRequest{ policyOutput, err = policyEvaluator.Evaluate(ectx, &PolicyRequest{
@ -160,6 +166,7 @@ func (e *Evaluator) Evaluate(ctx context.Context, req *Request) (*Result, error)
}) })
return err return err
}) })
}
var headersOutput *HeadersResponse var headersOutput *HeadersResponse
eg.Go(func() error { eg.Go(func() error {
@ -170,7 +177,7 @@ func (e *Evaluator) Evaluate(ctx context.Context, req *Request) (*Result, error)
return err return err
}) })
err = eg.Wait() err := eg.Wait()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -186,6 +193,21 @@ func (e *Evaluator) Evaluate(ctx context.Context, req *Request) (*Result, error)
return res, nil 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/jwt" {
if req.Session.ID == "" {
return &PolicyResponse{
Allow: NewRuleResult(false, criteria.ReasonUserUnauthenticated),
}, nil
}
}
return &PolicyResponse{
Allow: NewRuleResult(true, criteria.ReasonPomeriumRoute),
}, nil
}
func (e *Evaluator) getClientCA(policy *config.Policy) (string, error) { func (e *Evaluator) getClientCA(policy *config.Policy) (string, error) {
if policy != nil && policy.TLSDownstreamClientCA != "" { if policy != nil && policy.TLSDownstreamClientCA != "" {
bs, err := base64.StdEncoding.DecodeString(policy.TLSDownstreamClientCA) bs, err := base64.StdEncoding.DecodeString(policy.TLSDownstreamClientCA)

View file

@ -31,6 +31,7 @@ type HeadersRequest struct {
// NewHeadersRequestFromPolicy creates a new HeadersRequest from a policy. // NewHeadersRequestFromPolicy creates a new HeadersRequest from a policy.
func NewHeadersRequestFromPolicy(policy *config.Policy) *HeadersRequest { func NewHeadersRequestFromPolicy(policy *config.Policy) *HeadersRequest {
input := new(HeadersRequest) input := new(HeadersRequest)
if policy != nil {
input.EnableGoogleCloudServerlessAuthentication = policy.EnableGoogleCloudServerlessAuthentication input.EnableGoogleCloudServerlessAuthentication = policy.EnableGoogleCloudServerlessAuthentication
input.EnableRoutingKey = policy.EnvoyOpts.GetLbPolicy() == envoy_config_cluster_v3.Cluster_RING_HASH || input.EnableRoutingKey = policy.EnvoyOpts.GetLbPolicy() == envoy_config_cluster_v3.Cluster_RING_HASH ||
policy.EnvoyOpts.GetLbPolicy() == envoy_config_cluster_v3.Cluster_MAGLEV policy.EnvoyOpts.GetLbPolicy() == envoy_config_cluster_v3.Cluster_MAGLEV
@ -43,6 +44,7 @@ func NewHeadersRequestFromPolicy(policy *config.Policy) *HeadersRequest {
} }
input.PassAccessToken = policy.GetSetAuthorizationHeader() == configpb.Route_ACCESS_TOKEN input.PassAccessToken = policy.GetSetAuthorizationHeader() == configpb.Route_ACCESS_TOKEN
input.PassIDToken = policy.GetSetAuthorizationHeader() == configpb.Route_ID_TOKEN input.PassIDToken = policy.GetSetAuthorizationHeader() == configpb.Route_ID_TOKEN
}
return input return input
} }

View file

@ -111,7 +111,7 @@ func TestPolicyEvaluator(t *testing.T) {
}) })
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, &PolicyResponse{ 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), Deny: NewRuleResult(false, criteria.ReasonValidClientCertificateOrNoneRequired),
Traces: []contextutil.PolicyEvaluationTrace{{}}, Traces: []contextutil.PolicyEvaluationTrace{{}},
}, output) }, output)
@ -172,7 +172,7 @@ func TestPolicyEvaluator(t *testing.T) {
}) })
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, &PolicyResponse{ assert.Equal(t, &PolicyResponse{
Allow: NewRuleResult(false, criteria.ReasonNonPomeriumRoute), Allow: NewRuleResult(false),
Deny: NewRuleResult(true, criteria.ReasonAccept), Deny: NewRuleResult(true, criteria.ReasonAccept),
Traces: []contextutil.PolicyEvaluationTrace{{}, {ID: "p1", Deny: true}}, Traces: []contextutil.PolicyEvaluationTrace{{}, {ID: "p1", Deny: true}},
}, output) }, output)
@ -203,7 +203,7 @@ func TestPolicyEvaluator(t *testing.T) {
}) })
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, &PolicyResponse{ assert.Equal(t, &PolicyResponse{
Allow: NewRuleResult(false, criteria.ReasonNonPomeriumRoute), Allow: NewRuleResult(false),
Deny: NewRuleResult(true, criteria.ReasonAccept, criteria.ReasonInvalidClientCertificate), Deny: NewRuleResult(true, criteria.ReasonAccept, criteria.ReasonInvalidClientCertificate),
Traces: []contextutil.PolicyEvaluationTrace{{Deny: true}, {ID: "p1", Deny: true}}, Traces: []contextutil.PolicyEvaluationTrace{{Deny: true}, {ID: "p1", Deny: true}},
}, output) }, output)
@ -289,7 +289,7 @@ func TestPolicyEvaluator(t *testing.T) {
}) })
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, &PolicyResponse{ assert.Equal(t, &PolicyResponse{
Allow: NewRuleResult(false, criteria.ReasonNonPomeriumRoute, criteria.ReasonUserUnauthenticated), Allow: NewRuleResult(false, criteria.ReasonUserUnauthenticated),
Deny: NewRuleResult(false, criteria.ReasonValidClientCertificateOrNoneRequired), Deny: NewRuleResult(false, criteria.ReasonValidClientCertificateOrNoneRequired),
Traces: []contextutil.PolicyEvaluationTrace{{Allow: false}}, Traces: []contextutil.PolicyEvaluationTrace{{Allow: false}},
}, output) }, output)

View file

@ -11,6 +11,7 @@ import (
"github.com/pomerium/pomerium/authorize/evaluator" "github.com/pomerium/pomerium/authorize/evaluator"
"github.com/pomerium/pomerium/config" "github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/config/envoyconfig"
"github.com/pomerium/pomerium/internal/log" "github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/sessions" "github.com/pomerium/pomerium/internal/sessions"
"github.com/pomerium/pomerium/internal/telemetry/requestid" "github.com/pomerium/pomerium/internal/telemetry/requestid"
@ -117,6 +118,7 @@ func (a *Authorize) getEvaluatorRequestFromCheckRequest(
) (*evaluator.Request, error) { ) (*evaluator.Request, error) {
requestURL := getCheckRequestURL(in) requestURL := getCheckRequestURL(in)
req := &evaluator.Request{ req := &evaluator.Request{
IsInternal: envoyconfig.ExtAuthzContextExtensionsIsInternal(in.GetAttributes().GetContextExtensions()),
HTTP: evaluator.NewRequestHTTP( HTTP: evaluator.NewRequestHTTP(
in.GetAttributes().GetRequest().GetHttp().GetMethod(), in.GetAttributes().GetRequest().GetHttp().GetMethod(),
requestURL, requestURL,
@ -183,6 +185,7 @@ func getCheckRequestURL(req *envoy_service_auth_v3.CheckRequest) url.URL {
path := h.GetPath() path := h.GetPath()
if idx := strings.Index(path, "?"); idx != -1 { if idx := strings.Index(path, "?"); idx != -1 {
u.RawPath, u.RawQuery = path[:idx], path[idx+1:] u.RawPath, u.RawQuery = path[:idx], path[idx+1:]
u.RawQuery = u.Query().Encode()
} else { } else {
u.RawPath = path u.RawPath = path
} }

View file

@ -386,6 +386,7 @@ func (b *Builder) buildMainHTTPConnectionManagerFilter(
SkipXffAppend: options.SkipXffAppend, SkipXffAppend: options.SkipXffAppend,
XffNumTrustedHops: options.XffNumTrustedHops, XffNumTrustedHops: options.XffNumTrustedHops,
LocalReplyConfig: b.buildLocalReplyConfig(options, requireStrictTransportSecurity), LocalReplyConfig: b.buildLocalReplyConfig(options, requireStrictTransportSecurity),
NormalizePath: wrapperspb.Bool(true),
}), nil }), nil
} }

View file

@ -215,6 +215,7 @@ func Test_buildMainHTTPConnectionManagerFilter(t *testing.T) {
} }
} }
], ],
"normalizePath": true,
"requestTimeout": "30s", "requestTimeout": "30s",
"routeConfig": { "routeConfig": {
"name": "main", "name": "main",
@ -244,15 +245,6 @@ func Test_buildMainHTTPConnectionManagerFilter(t *testing.T) {
} }
}], }],
"routes": [ "routes": [
{
"name": "pomerium-path-/.pomerium/jwt",
"match": {
"path": "/.pomerium/jwt"
},
"route": {
"cluster": "pomerium-control-plane-http"
}
},
{ {
"name": "pomerium-path-/ping", "name": "pomerium-path-/ping",
"match": { "match": {
@ -264,7 +256,11 @@ func Test_buildMainHTTPConnectionManagerFilter(t *testing.T) {
"typedPerFilterConfig": { "typedPerFilterConfig": {
"envoy.filters.http.ext_authz": { "envoy.filters.http.ext_authz": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
"disabled": true "checkSettings": {
"contextExtensions": {
"internal": "true"
}
}
} }
} }
}, },
@ -279,7 +275,11 @@ func Test_buildMainHTTPConnectionManagerFilter(t *testing.T) {
"typedPerFilterConfig": { "typedPerFilterConfig": {
"envoy.filters.http.ext_authz": { "envoy.filters.http.ext_authz": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
"disabled": true "checkSettings": {
"contextExtensions": {
"internal": "true"
}
}
} }
} }
}, },
@ -294,7 +294,11 @@ func Test_buildMainHTTPConnectionManagerFilter(t *testing.T) {
"typedPerFilterConfig": { "typedPerFilterConfig": {
"envoy.filters.http.ext_authz": { "envoy.filters.http.ext_authz": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
"disabled": true "checkSettings": {
"contextExtensions": {
"internal": "true"
}
}
} }
} }
}, },
@ -309,7 +313,11 @@ func Test_buildMainHTTPConnectionManagerFilter(t *testing.T) {
"typedPerFilterConfig": { "typedPerFilterConfig": {
"envoy.filters.http.ext_authz": { "envoy.filters.http.ext_authz": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
"disabled": true "checkSettings": {
"contextExtensions": {
"internal": "true"
}
}
} }
} }
}, },
@ -324,7 +332,11 @@ func Test_buildMainHTTPConnectionManagerFilter(t *testing.T) {
"typedPerFilterConfig": { "typedPerFilterConfig": {
"envoy.filters.http.ext_authz": { "envoy.filters.http.ext_authz": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
"disabled": true "checkSettings": {
"contextExtensions": {
"internal": "true"
}
}
} }
} }
}, },
@ -339,7 +351,11 @@ func Test_buildMainHTTPConnectionManagerFilter(t *testing.T) {
"typedPerFilterConfig": { "typedPerFilterConfig": {
"envoy.filters.http.ext_authz": { "envoy.filters.http.ext_authz": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
"disabled": true "checkSettings": {
"contextExtensions": {
"internal": "true"
}
}
} }
} }
}, },
@ -354,7 +370,11 @@ func Test_buildMainHTTPConnectionManagerFilter(t *testing.T) {
"typedPerFilterConfig": { "typedPerFilterConfig": {
"envoy.filters.http.ext_authz": { "envoy.filters.http.ext_authz": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
"disabled": true "checkSettings": {
"contextExtensions": {
"internal": "true"
}
}
} }
} }
} }
@ -385,15 +405,6 @@ func Test_buildMainHTTPConnectionManagerFilter(t *testing.T) {
} }
}], }],
"routes": [ "routes": [
{
"name": "pomerium-path-/.pomerium/jwt",
"match": {
"path": "/.pomerium/jwt"
},
"route": {
"cluster": "pomerium-control-plane-http"
}
},
{ {
"name": "pomerium-path-/ping", "name": "pomerium-path-/ping",
"match": { "match": {
@ -405,7 +416,11 @@ func Test_buildMainHTTPConnectionManagerFilter(t *testing.T) {
"typedPerFilterConfig": { "typedPerFilterConfig": {
"envoy.filters.http.ext_authz": { "envoy.filters.http.ext_authz": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
"disabled": true "checkSettings": {
"contextExtensions": {
"internal": "true"
}
}
} }
} }
}, },
@ -420,7 +435,11 @@ func Test_buildMainHTTPConnectionManagerFilter(t *testing.T) {
"typedPerFilterConfig": { "typedPerFilterConfig": {
"envoy.filters.http.ext_authz": { "envoy.filters.http.ext_authz": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
"disabled": true "checkSettings": {
"contextExtensions": {
"internal": "true"
}
}
} }
} }
}, },
@ -435,7 +454,11 @@ func Test_buildMainHTTPConnectionManagerFilter(t *testing.T) {
"typedPerFilterConfig": { "typedPerFilterConfig": {
"envoy.filters.http.ext_authz": { "envoy.filters.http.ext_authz": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
"disabled": true "checkSettings": {
"contextExtensions": {
"internal": "true"
}
}
} }
} }
}, },
@ -450,7 +473,11 @@ func Test_buildMainHTTPConnectionManagerFilter(t *testing.T) {
"typedPerFilterConfig": { "typedPerFilterConfig": {
"envoy.filters.http.ext_authz": { "envoy.filters.http.ext_authz": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
"disabled": true "checkSettings": {
"contextExtensions": {
"internal": "true"
}
}
} }
} }
}, },
@ -465,7 +492,11 @@ func Test_buildMainHTTPConnectionManagerFilter(t *testing.T) {
"typedPerFilterConfig": { "typedPerFilterConfig": {
"envoy.filters.http.ext_authz": { "envoy.filters.http.ext_authz": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
"disabled": true "checkSettings": {
"contextExtensions": {
"internal": "true"
}
}
} }
} }
}, },
@ -480,7 +511,11 @@ func Test_buildMainHTTPConnectionManagerFilter(t *testing.T) {
"typedPerFilterConfig": { "typedPerFilterConfig": {
"envoy.filters.http.ext_authz": { "envoy.filters.http.ext_authz": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
"disabled": true "checkSettings": {
"contextExtensions": {
"internal": "true"
}
}
} }
} }
}, },
@ -495,7 +530,11 @@ func Test_buildMainHTTPConnectionManagerFilter(t *testing.T) {
"typedPerFilterConfig": { "typedPerFilterConfig": {
"envoy.filters.http.ext_authz": { "envoy.filters.http.ext_authz": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
"disabled": true "checkSettings": {
"contextExtensions": {
"internal": "true"
}
}
} }
} }
} }

View file

@ -0,0 +1,43 @@
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),
}
}
// 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"
}

View file

@ -58,19 +58,16 @@ func (b *Builder) buildPomeriumHTTPRoutes(options *config.Options, domain string
} }
if !isFrontingAuthenticate { if !isFrontingAuthenticate {
routes = append(routes, routes = append(routes,
// enable ext_authz b.buildControlPlanePathRoute("/ping"),
b.buildControlPlanePathRoute("/.pomerium/jwt", true), b.buildControlPlanePathRoute("/healthz"),
// disable ext_authz and passthrough to proxy handlers b.buildControlPlanePathRoute("/.pomerium"),
b.buildControlPlanePathRoute("/ping", false), b.buildControlPlanePrefixRoute("/.pomerium/"),
b.buildControlPlanePathRoute("/healthz", false), b.buildControlPlanePathRoute("/.well-known/pomerium"),
b.buildControlPlanePathRoute("/.pomerium", false), b.buildControlPlanePrefixRoute("/.well-known/pomerium/"),
b.buildControlPlanePrefixRoute("/.pomerium/", false),
b.buildControlPlanePathRoute("/.well-known/pomerium", false),
b.buildControlPlanePrefixRoute("/.well-known/pomerium/", false),
) )
// per #837, only add robots.txt if there are no unauthenticated routes // per #837, only add robots.txt if there are no unauthenticated routes
if !hasPublicPolicyMatchingURL(options, url.URL{Scheme: "https", Host: domain, Path: "/robots.txt"}) { if !hasPublicPolicyMatchingURL(options, url.URL{Scheme: "https", Host: domain, Path: "/robots.txt"}) {
routes = append(routes, b.buildControlPlanePathRoute("/robots.txt", false)) routes = append(routes, b.buildControlPlanePathRoute("/robots.txt"))
} }
} }
// if we're handling authentication, add the oauth2 callback url // if we're handling authentication, add the oauth2 callback url
@ -80,8 +77,8 @@ func (b *Builder) buildPomeriumHTTPRoutes(options *config.Options, domain string
} }
if config.IsAuthenticate(options.Services) && hostMatchesDomain(authenticateURL, domain) { if config.IsAuthenticate(options.Services) && hostMatchesDomain(authenticateURL, domain) {
routes = append(routes, routes = append(routes,
b.buildControlPlanePathRoute(options.AuthenticateCallbackPath, false), b.buildControlPlanePathRoute(options.AuthenticateCallbackPath),
b.buildControlPlanePathRoute("/", false), b.buildControlPlanePathRoute("/"),
) )
} }
// if we're the proxy and this is the forward-auth url // if we're the proxy and this is the forward-auth url
@ -164,7 +161,7 @@ func (b *Builder) buildControlPlanePathAndQueryRoute(path string, queryparams []
}, nil }, nil
} }
func (b *Builder) buildControlPlanePathRoute(path string, protected bool) *envoy_config_route_v3.Route { func (b *Builder) buildControlPlanePathRoute(path string) *envoy_config_route_v3.Route {
r := &envoy_config_route_v3.Route{ r := &envoy_config_route_v3.Route{
Name: "pomerium-path-" + path, Name: "pomerium-path-" + path,
Match: &envoy_config_route_v3.RouteMatch{ Match: &envoy_config_route_v3.RouteMatch{
@ -177,16 +174,14 @@ func (b *Builder) buildControlPlanePathRoute(path string, protected bool) *envoy
}, },
}, },
}, },
} TypedPerFilterConfig: map[string]*any.Any{
if !protected { PerFilterConfigExtAuthzName: PerFilterConfigExtAuthzContextExtensions(MakeExtAuthzContextExtensions(true, 0)),
r.TypedPerFilterConfig = map[string]*any.Any{ },
"envoy.filters.http.ext_authz": disableExtAuthz,
}
} }
return r return r
} }
func (b *Builder) buildControlPlanePrefixRoute(prefix string, protected bool) *envoy_config_route_v3.Route { func (b *Builder) buildControlPlanePrefixRoute(prefix string) *envoy_config_route_v3.Route {
r := &envoy_config_route_v3.Route{ r := &envoy_config_route_v3.Route{
Name: "pomerium-prefix-" + prefix, Name: "pomerium-prefix-" + prefix,
Match: &envoy_config_route_v3.RouteMatch{ Match: &envoy_config_route_v3.RouteMatch{
@ -199,11 +194,9 @@ func (b *Builder) buildControlPlanePrefixRoute(prefix string, protected bool) *e
}, },
}, },
}, },
} TypedPerFilterConfig: map[string]*any.Any{
if !protected { PerFilterConfigExtAuthzName: PerFilterConfigExtAuthzContextExtensions(MakeExtAuthzContextExtensions(true, 0)),
r.TypedPerFilterConfig = map[string]*any.Any{ },
"envoy.filters.http.ext_authz": disableExtAuthz,
}
} }
return r return r
} }

View file

@ -54,28 +54,26 @@ func Test_buildGRPCRoutes(t *testing.T) {
func Test_buildPomeriumHTTPRoutes(t *testing.T) { func Test_buildPomeriumHTTPRoutes(t *testing.T) {
b := &Builder{filemgr: filemgr.NewManager()} b := &Builder{filemgr: filemgr.NewManager()}
routeString := func(typ, name string, protected bool) string { routeString := func(typ, name string) string {
str := `{ return `{
"name": "pomerium-` + typ + `-` + name + `", "name": "pomerium-` + typ + `-` + name + `",
"match": { "match": {
"` + typ + `": "` + name + `" "` + typ + `": "` + name + `"
}, },
"route": { "route": {
"cluster": "pomerium-control-plane-http" "cluster": "pomerium-control-plane-http"
} },
`
if !protected {
str += `,
"typedPerFilterConfig": { "typedPerFilterConfig": {
"envoy.filters.http.ext_authz": { "envoy.filters.http.ext_authz": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
"disabled": true "checkSettings": {
"contextExtensions": {
"internal": "true"
} }
} }
`
} }
str += "}" }
return str }`
} }
t.Run("authenticate", func(t *testing.T) { t.Run("authenticate", func(t *testing.T) {
options := &config.Options{ options := &config.Options{
@ -88,16 +86,15 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
testutil.AssertProtoJSONEqual(t, `[ testutil.AssertProtoJSONEqual(t, `[
`+routeString("path", "/.pomerium/jwt", true)+`, `+routeString("path", "/ping")+`,
`+routeString("path", "/ping", false)+`, `+routeString("path", "/healthz")+`,
`+routeString("path", "/healthz", false)+`, `+routeString("path", "/.pomerium")+`,
`+routeString("path", "/.pomerium", false)+`, `+routeString("prefix", "/.pomerium/")+`,
`+routeString("prefix", "/.pomerium/", false)+`, `+routeString("path", "/.well-known/pomerium")+`,
`+routeString("path", "/.well-known/pomerium", false)+`, `+routeString("prefix", "/.well-known/pomerium/")+`,
`+routeString("prefix", "/.well-known/pomerium/", false)+`, `+routeString("path", "/robots.txt")+`,
`+routeString("path", "/robots.txt", false)+`, `+routeString("path", "/oauth2/callback")+`,
`+routeString("path", "/oauth2/callback", false)+`, `+routeString("path", "/")+`
`+routeString("path", "/", false)+`
]`, routes) ]`, routes)
}) })
t.Run("proxy fronting authenticate", func(t *testing.T) { t.Run("proxy fronting authenticate", func(t *testing.T) {
@ -127,14 +124,13 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
testutil.AssertProtoJSONEqual(t, `[ testutil.AssertProtoJSONEqual(t, `[
`+routeString("path", "/.pomerium/jwt", true)+`, `+routeString("path", "/ping")+`,
`+routeString("path", "/ping", false)+`, `+routeString("path", "/healthz")+`,
`+routeString("path", "/healthz", false)+`, `+routeString("path", "/.pomerium")+`,
`+routeString("path", "/.pomerium", false)+`, `+routeString("prefix", "/.pomerium/")+`,
`+routeString("prefix", "/.pomerium/", false)+`, `+routeString("path", "/.well-known/pomerium")+`,
`+routeString("path", "/.well-known/pomerium", false)+`, `+routeString("prefix", "/.well-known/pomerium/")+`,
`+routeString("prefix", "/.well-known/pomerium/", false)+`, `+routeString("path", "/robots.txt")+`
`+routeString("path", "/robots.txt", false)+`
]`, routes) ]`, routes)
}) })
@ -155,20 +151,19 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
testutil.AssertProtoJSONEqual(t, `[ testutil.AssertProtoJSONEqual(t, `[
`+routeString("path", "/.pomerium/jwt", true)+`, `+routeString("path", "/ping")+`,
`+routeString("path", "/ping", false)+`, `+routeString("path", "/healthz")+`,
`+routeString("path", "/healthz", false)+`, `+routeString("path", "/.pomerium")+`,
`+routeString("path", "/.pomerium", false)+`, `+routeString("prefix", "/.pomerium/")+`,
`+routeString("prefix", "/.pomerium/", false)+`, `+routeString("path", "/.well-known/pomerium")+`,
`+routeString("path", "/.well-known/pomerium", false)+`, `+routeString("prefix", "/.well-known/pomerium/")+`
`+routeString("prefix", "/.well-known/pomerium/", false)+`
]`, routes) ]`, routes)
}) })
} }
func Test_buildControlPlanePathRoute(t *testing.T) { func Test_buildControlPlanePathRoute(t *testing.T) {
b := &Builder{filemgr: filemgr.NewManager()} b := &Builder{filemgr: filemgr.NewManager()}
route := b.buildControlPlanePathRoute("/hello/world", false) route := b.buildControlPlanePathRoute("/hello/world")
testutil.AssertProtoJSONEqual(t, ` testutil.AssertProtoJSONEqual(t, `
{ {
"name": "pomerium-path-/hello/world", "name": "pomerium-path-/hello/world",
@ -181,7 +176,11 @@ func Test_buildControlPlanePathRoute(t *testing.T) {
"typedPerFilterConfig": { "typedPerFilterConfig": {
"envoy.filters.http.ext_authz": { "envoy.filters.http.ext_authz": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
"disabled": true "checkSettings": {
"contextExtensions": {
"internal": "true"
}
}
} }
} }
} }
@ -190,7 +189,7 @@ func Test_buildControlPlanePathRoute(t *testing.T) {
func Test_buildControlPlanePrefixRoute(t *testing.T) { func Test_buildControlPlanePrefixRoute(t *testing.T) {
b := &Builder{filemgr: filemgr.NewManager()} b := &Builder{filemgr: filemgr.NewManager()}
route := b.buildControlPlanePrefixRoute("/hello/world/", false) route := b.buildControlPlanePrefixRoute("/hello/world/")
testutil.AssertProtoJSONEqual(t, ` testutil.AssertProtoJSONEqual(t, `
{ {
"name": "pomerium-prefix-/hello/world/", "name": "pomerium-prefix-/hello/world/",
@ -203,7 +202,11 @@ func Test_buildControlPlanePrefixRoute(t *testing.T) {
"typedPerFilterConfig": { "typedPerFilterConfig": {
"envoy.filters.http.ext_authz": { "envoy.filters.http.ext_authz": {
"@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute", "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute",
"disabled": true "checkSettings": {
"contextExtensions": {
"internal": "true"
}
}
} }
} }
} }

View file

@ -13,10 +13,6 @@ func (p *Policy) ToPPL() *parser.Policy {
ppl := &parser.Policy{} ppl := &parser.Policy{}
allowRule := parser.Rule{Action: parser.ActionAllow} allowRule := parser.Rule{Action: parser.ActionAllow}
allowRule.Or = append(allowRule.Or,
parser.Criterion{
Name: "pomerium_routes",
})
if p.AllowPublicUnauthenticatedAccess { if p.AllowPublicUnauthenticatedAccess {
allowRule.Or = append(allowRule.Or, allowRule.Or = append(allowRule.Or,
parser.Criterion{ parser.Criterion{

View file

@ -57,15 +57,6 @@ default allow = [false, set()]
default deny = [false, set()] default deny = [false, set()]
pomerium_routes_0 = [true, {"pomerium-route"}] {
contains(input.http.url, "/.pomerium/")
not contains(input.http.url, "/.pomerium/jwt")
}
else = [false, {"non-pomerium-route"}] {
true
}
accept_0 = [true, {"accept"}] accept_0 = [true, {"accept"}]
cors_preflight_0 = [true, {"cors-request"}] { cors_preflight_0 = [true, {"cors-request"}] {
@ -413,7 +404,7 @@ else = [false, {"user-unauthenticated"}] {
} }
or_0 = v { 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]] normalized := [normalize_criterion_result(x) | x := results[i]]
v := merge_with_or(normalized) v := merge_with_or(normalized)
} }

View file

@ -1,46 +0,0 @@
package criteria
import (
"github.com/open-policy-agent/opa/ast"
"github.com/pomerium/pomerium/pkg/policy/generator"
"github.com/pomerium/pomerium/pkg/policy/parser"
)
var pomeriumRoutesBody = ast.Body{
ast.MustParseExpr(`
contains(input.http.url, "/.pomerium/")
`),
ast.MustParseExpr(`
not contains(input.http.url, "/.pomerium/jwt")
`),
}
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) {
rule := NewCriterionRule(c.g, c.Name(),
ReasonPomeriumRoute, ReasonNonPomeriumRoute,
pomeriumRoutesBody)
return rule, nil, 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)
}