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:
Kenneth Jenkins 2023-07-25 10:03:51 -07:00 committed by GitHub
parent 638d9f3d6c
commit 8401170443
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 202 additions and 42 deletions

View file

@ -29,17 +29,22 @@ var testingNow = time.Date(2021, 5, 11, 13, 43, 0, 0, time.Local)
type (
Input struct {
HTTP InputHTTP `json:"http"`
Session InputSession `json:"session"`
HTTP InputHTTP `json:"http"`
Session InputSession `json:"session"`
IsValidClientCertificate bool `json:"is_valid_client_certificate"`
}
InputHTTP struct {
Method string `json:"method"`
Path string `json:"path"`
Headers map[string][]string `json:"headers"`
Method string `json:"method"`
Path string `json:"path"`
Headers map[string][]string `json:"headers"`
ClientCertificate ClientCertificateInfo `json:"client_certificate"`
}
InputSession struct {
ID string `json:"id"`
}
ClientCertificateInfo struct {
Presented bool `json:"presented"`
}
)
func generateRegoFromYAML(raw string) (string, error) {

View file

@ -7,9 +7,14 @@ import (
"github.com/pomerium/pomerium/pkg/policy/parser"
)
var invalidClientCertificateBody = ast.Body{
var validClientCertificateBody = ast.Body{
ast.MustParseExpr(`is_boolean(input.is_valid_client_certificate)`),
ast.MustParseExpr(`not input.is_valid_client_certificate`),
ast.MustParseExpr(`input.is_valid_client_certificate`),
}
var noClientCertificateBody = ast.Body{
ast.MustParseExpr(`is_boolean(input.http.client_certificate.presented)`),
ast.MustParseExpr(`not input.http.client_certificate.presented`),
}
type invalidClientCertificateCriterion struct {
@ -25,10 +30,26 @@ func (invalidClientCertificateCriterion) Name() string {
}
func (c invalidClientCertificateCriterion) GenerateRule(_ string, _ parser.Value) (*ast.Rule, []*ast.Rule, error) {
rule := NewCriterionRule(c.g, c.Name(),
ReasonInvalidClientCertificate, ReasonValidClientCertificate,
invalidClientCertificateBody)
return rule, nil, nil
r1 := c.g.NewRule(c.Name())
r1.Head.Value = NewCriterionTerm(false, ReasonValidClientCertificate)
r1.Body = validClientCertificateBody
r2 := &ast.Rule{
Head: &ast.Head{
Value: NewCriterionTerm(true, ReasonClientCertificateRequired),
},
Body: noClientCertificateBody,
}
r1.Else = r2
r3 := &ast.Rule{
Head: &ast.Head{
Value: NewCriterionTerm(true, ReasonInvalidClientCertificate),
},
}
r2.Else = r3
return r1, nil, nil
}
// InvalidClientCertificate returns a Criterion which returns true if the

View file

@ -0,0 +1,60 @@
package criteria
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestInvalidClientCertificate(t *testing.T) {
t.Parallel()
cases := []struct {
label string
input Input
expected A
}{
{
"not presented",
Input{},
A{true, A{ReasonClientCertificateRequired}, M{}},
},
{
"invalid",
Input{
HTTP: InputHTTP{
ClientCertificate: ClientCertificateInfo{Presented: true},
},
},
A{true, A{ReasonInvalidClientCertificate}, M{}},
},
{
"valid",
Input{
HTTP: InputHTTP{
ClientCertificate: ClientCertificateInfo{Presented: true},
},
IsValidClientCertificate: true,
},
A{false, A{ReasonValidClientCertificate}, M{}},
},
}
const policy = `
deny:
or:
- invalid_client_certificate: true`
for i := range cases {
c := cases[i]
t.Run(c.label, func(t *testing.T) {
t.Parallel()
res, err := evaluate(t, policy, []dataBrokerRecord{}, c.input)
require.NoError(t, err)
assert.Equal(t, A{false, A{}}, res["allow"])
assert.Equal(t, c.expected, res["deny"])
})
}
}

View file

@ -7,31 +7,32 @@ type Reason string
// Well-known reasons.
const (
ReasonAccept = "accept"
ReasonClaimOK = "claim-ok"
ReasonClaimUnauthorized = "claim-unauthorized"
ReasonCORSRequest = "cors-request"
ReasonDeviceOK = "device-ok"
ReasonDeviceUnauthenticated = "device-unauthenticated"
ReasonDeviceUnauthorized = "device-unauthorized"
ReasonDomainOK = "domain-ok"
ReasonDomainUnauthorized = "domain-unauthorized"
ReasonEmailOK = "email-ok"
ReasonEmailUnauthorized = "email-unauthorized"
ReasonHTTPMethodOK = "http-method-ok"
ReasonHTTPMethodUnauthorized = "http-method-unauthorized"
ReasonHTTPPathOK = "http-path-ok"
ReasonHTTPPathUnauthorized = "http-path-unauthorized"
ReasonInvalidClientCertificate = "invalid-client-certificate"
ReasonNonCORSRequest = "non-cors-request"
ReasonNonPomeriumRoute = "non-pomerium-route"
ReasonPomeriumRoute = "pomerium-route"
ReasonReject = "reject"
ReasonRouteNotFound = "route-not-found"
ReasonUserOK = "user-ok"
ReasonUserUnauthenticated = "user-unauthenticated" // user needs to log in
ReasonUserUnauthorized = "user-unauthorized" // user does not have access
ReasonValidClientCertificate = "valid-client-certificate"
ReasonAccept = "accept"
ReasonClaimOK = "claim-ok"
ReasonClaimUnauthorized = "claim-unauthorized"
ReasonClientCertificateRequired = "client-certificate-required"
ReasonCORSRequest = "cors-request"
ReasonDeviceOK = "device-ok"
ReasonDeviceUnauthenticated = "device-unauthenticated"
ReasonDeviceUnauthorized = "device-unauthorized"
ReasonDomainOK = "domain-ok"
ReasonDomainUnauthorized = "domain-unauthorized"
ReasonEmailOK = "email-ok"
ReasonEmailUnauthorized = "email-unauthorized"
ReasonHTTPMethodOK = "http-method-ok"
ReasonHTTPMethodUnauthorized = "http-method-unauthorized"
ReasonHTTPPathOK = "http-path-ok"
ReasonHTTPPathUnauthorized = "http-path-unauthorized"
ReasonInvalidClientCertificate = "invalid-client-certificate"
ReasonNonCORSRequest = "non-cors-request"
ReasonNonPomeriumRoute = "non-pomerium-route"
ReasonPomeriumRoute = "pomerium-route"
ReasonReject = "reject"
ReasonRouteNotFound = "route-not-found"
ReasonUserOK = "user-ok"
ReasonUserUnauthenticated = "user-unauthenticated" // user needs to log in
ReasonUserUnauthorized = "user-unauthorized" // user does not have access
ReasonValidClientCertificate = "valid-client-certificate"
)
// Reasons is a collection of reasons.