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>
This commit is contained in:
Caleb Doxsey 2023-05-26 14:34:21 -06:00 committed by GitHub
parent 37c8dcc9db
commit d315e68335
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 503 additions and 289 deletions

View file

@ -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
return policyEvaluator.Evaluate(ctx, &PolicyRequest{
HTTP: req.HTTP,
Session: req.Session,
IsValidClientCertificate: isValidClientCertificate,
})
}
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
})
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
}

View file

@ -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
}

View file

@ -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)

View file

@ -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
}