mirror of
https://github.com/pomerium/pomerium.git
synced 2025-08-03 00:40:25 +02:00
authorize: add "client-certificate-required" reason (#4389)
Add a new reason "client-certificate-required" that will be returned by the invalid_client_certificate criterion in the case that no client certificate was provided. Determine this using the new 'presented' field populated from the Envoy metadata.
This commit is contained in:
parent
638d9f3d6c
commit
8401170443
8 changed files with 202 additions and 42 deletions
|
@ -34,7 +34,7 @@ func (a *Authorize) handleResult(
|
|||
) (*envoy_service_auth_v3.CheckResponse, error) {
|
||||
// If a client certificate is required, but the client did not provide a
|
||||
// valid certificate, deny right away. Do not redirect to authenticate.
|
||||
if result.Deny.Reasons.Has(criteria.ReasonInvalidClientCertificate) {
|
||||
if invalidClientCertReason(result.Deny.Reasons) {
|
||||
return a.handleResultDenied(ctx, in, request, result, result.Deny.Reasons)
|
||||
}
|
||||
|
||||
|
@ -93,7 +93,7 @@ func (a *Authorize) handleResultDenied(
|
|||
case reasons.Has(criteria.ReasonRouteNotFound):
|
||||
denyStatusCode = http.StatusNotFound
|
||||
denyStatusText = httputil.DetailsText(http.StatusNotFound)
|
||||
case reasons.Has(criteria.ReasonInvalidClientCertificate):
|
||||
case invalidClientCertReason(reasons):
|
||||
denyStatusCode = httputil.StatusInvalidClientCertificate
|
||||
denyStatusText = httputil.DetailsText(httputil.StatusInvalidClientCertificate)
|
||||
}
|
||||
|
@ -101,6 +101,11 @@ func (a *Authorize) handleResultDenied(
|
|||
return a.deniedResponse(ctx, in, denyStatusCode, denyStatusText, nil)
|
||||
}
|
||||
|
||||
func invalidClientCertReason(reasons criteria.Reasons) bool {
|
||||
return reasons.Has(criteria.ReasonClientCertificateRequired) ||
|
||||
reasons.Has(criteria.ReasonInvalidClientCertificate)
|
||||
}
|
||||
|
||||
func (a *Authorize) okResponse(headers http.Header) *envoy_service_auth_v3.CheckResponse {
|
||||
var requestHeaders []*envoy_config_core_v3.HeaderValueOption
|
||||
for k, vs := range headers {
|
||||
|
|
|
@ -89,7 +89,7 @@ func TestAuthorize_handleResult(t *testing.T) {
|
|||
})
|
||||
})
|
||||
t.Run("invalid-client-certificate", func(t *testing.T) {
|
||||
// Even if the user is unauthenticated, if a client certificate was required and no valid
|
||||
// Even if the user is unauthenticated, if a client certificate was required and an invalid
|
||||
// certificate was provided, access should be denied (no login redirect).
|
||||
res, err := a.handleResult(context.Background(),
|
||||
&envoy_service_auth_v3.CheckRequest{},
|
||||
|
@ -101,6 +101,19 @@ func TestAuthorize_handleResult(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Equal(t, 495, int(res.GetDeniedResponse().GetStatus().GetCode()))
|
||||
})
|
||||
t.Run("client-certificate-required", func(t *testing.T) {
|
||||
// Likewise, if a client certificate was required and no certificate
|
||||
// was presented, access should be denied (no login redirect).
|
||||
res, err := a.handleResult(context.Background(),
|
||||
&envoy_service_auth_v3.CheckRequest{},
|
||||
&evaluator.Request{},
|
||||
&evaluator.Result{
|
||||
Allow: evaluator.NewRuleResult(false, criteria.ReasonUserUnauthenticated),
|
||||
Deny: evaluator.NewRuleResult(true, criteria.ReasonClientCertificateRequired),
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 495, int(res.GetDeniedResponse().GetStatus().GetCode()))
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthorize_okResponse(t *testing.T) {
|
||||
|
|
|
@ -131,9 +131,19 @@ func TestEvaluator(t *testing.T) {
|
|||
// Clone the existing options and add a default client CA.
|
||||
options := append([]Option(nil), options...)
|
||||
options = append(options, WithClientCA([]byte(testCA)))
|
||||
t.Run("missing", func(t *testing.T) {
|
||||
res, err := eval(t, options, nil, &Request{
|
||||
Policy: &policies[0],
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, NewRuleResult(true, criteria.ReasonClientCertificateRequired), res.Deny)
|
||||
})
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
res, err := eval(t, options, nil, &Request{
|
||||
Policy: &policies[0],
|
||||
HTTP: RequestHTTP{
|
||||
ClientCertificate: ClientCertificateInfo{Presented: true},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, NewRuleResult(true, criteria.ReasonInvalidClientCertificate), res.Deny)
|
||||
|
@ -150,11 +160,35 @@ func TestEvaluator(t *testing.T) {
|
|||
})
|
||||
})
|
||||
t.Run("client certificate (per-policy CA)", func(t *testing.T) {
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
t.Run("missing", func(t *testing.T) {
|
||||
res, err := eval(t, options, nil, &Request{
|
||||
Policy: &policies[10],
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, NewRuleResult(true, criteria.ReasonClientCertificateRequired), res.Deny)
|
||||
})
|
||||
t.Run("invalid (Envoy)", func(t *testing.T) {
|
||||
res, err := eval(t, options, nil, &Request{
|
||||
Policy: &policies[10],
|
||||
HTTP: RequestHTTP{
|
||||
ClientCertificate: ClientCertificateInfo{Presented: true},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, NewRuleResult(true, criteria.ReasonInvalidClientCertificate), res.Deny)
|
||||
})
|
||||
t.Run("invalid (authorize)", func(t *testing.T) {
|
||||
res, err := eval(t, options, nil, &Request{
|
||||
Policy: &policies[10],
|
||||
HTTP: RequestHTTP{
|
||||
ClientCertificate: ClientCertificateInfo{
|
||||
Presented: true,
|
||||
Validated: true,
|
||||
Leaf: testUnsignedCert,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, NewRuleResult(true, criteria.ReasonInvalidClientCertificate), res.Deny)
|
||||
})
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
|
|
|
@ -118,7 +118,7 @@ func TestPolicyEvaluator(t *testing.T) {
|
|||
Traces: []contextutil.PolicyEvaluationTrace{{Allow: true}},
|
||||
}, output)
|
||||
})
|
||||
t.Run("invalid cert", func(t *testing.T) {
|
||||
t.Run("no cert", func(t *testing.T) {
|
||||
output, err := eval(t,
|
||||
p1,
|
||||
[]proto.Message{s1, u1, s2, u2},
|
||||
|
@ -129,6 +129,27 @@ func TestPolicyEvaluator(t *testing.T) {
|
|||
IsValidClientCertificate: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, &PolicyResponse{
|
||||
Allow: NewRuleResult(true, criteria.ReasonEmailOK),
|
||||
Deny: NewRuleResult(true, criteria.ReasonClientCertificateRequired),
|
||||
Traces: []contextutil.PolicyEvaluationTrace{{Allow: true, Deny: true}},
|
||||
}, output)
|
||||
})
|
||||
t.Run("invalid cert", func(t *testing.T) {
|
||||
output, err := eval(t,
|
||||
p1,
|
||||
[]proto.Message{s1, u1, s2, u2},
|
||||
&PolicyRequest{
|
||||
HTTP: RequestHTTP{
|
||||
Method: http.MethodGet,
|
||||
URL: "https://from.example.com/path",
|
||||
ClientCertificate: ClientCertificateInfo{Presented: true},
|
||||
},
|
||||
Session: RequestSession{ID: "s1"},
|
||||
|
||||
IsValidClientCertificate: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, &PolicyResponse{
|
||||
Allow: NewRuleResult(true, criteria.ReasonEmailOK),
|
||||
Deny: NewRuleResult(true, criteria.ReasonInvalidClientCertificate),
|
||||
|
@ -241,7 +262,7 @@ func TestPolicyEvaluator(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
assert.Equal(t, &PolicyResponse{
|
||||
Allow: NewRuleResult(false),
|
||||
Deny: NewRuleResult(true, criteria.ReasonAccept, criteria.ReasonInvalidClientCertificate),
|
||||
Deny: NewRuleResult(true, criteria.ReasonAccept, criteria.ReasonClientCertificateRequired),
|
||||
Traces: []contextutil.PolicyEvaluationTrace{{Deny: true}, {ID: "p1", Deny: true}},
|
||||
}, output)
|
||||
})
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue