mirror of
https://github.com/pomerium/pomerium.git
synced 2025-08-04 01:09:36 +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
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
60
pkg/policy/criteria/invalid_client_certificate_test.go
Normal file
60
pkg/policy/criteria/invalid_client_certificate_test.go
Normal 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"])
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue