mirror of
https://github.com/pomerium/pomerium.git
synced 2025-07-19 09:38:03 +02:00
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:
parent
37c8dcc9db
commit
d315e68335
13 changed files with 503 additions and 289 deletions
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue