mirror of
https://github.com/pomerium/pomerium.git
synced 2025-06-04 11:52:53 +02:00
* authorize: add support for webauthn device policy enforcement * update docs * group statuses
214 lines
5.7 KiB
Go
214 lines
5.7 KiB
Go
// Package criteria contains all the pre-defined criteria as well as a registry to add new criteria.
|
|
package criteria
|
|
|
|
import (
|
|
"sync"
|
|
|
|
"github.com/open-policy-agent/opa/ast"
|
|
|
|
"github.com/pomerium/pomerium/pkg/policy/generator"
|
|
)
|
|
|
|
// re-exported types
|
|
type (
|
|
// A Generator generates a rego script from a policy.
|
|
Generator = generator.Generator
|
|
// A Criterion generates rego rules based on data.
|
|
Criterion = generator.Criterion
|
|
// A CriterionConstructor is a function which returns a Criterion for a Generator.
|
|
CriterionConstructor = generator.CriterionConstructor
|
|
// The CriterionDataType indicates the expected type of data for the criterion.
|
|
CriterionDataType = generator.CriterionDataType
|
|
)
|
|
|
|
var allCriteria struct {
|
|
sync.Mutex
|
|
a []CriterionConstructor
|
|
}
|
|
|
|
// All returns all the known criterion constructors.
|
|
func All() []CriterionConstructor {
|
|
allCriteria.Lock()
|
|
a := allCriteria.a
|
|
allCriteria.Unlock()
|
|
return a
|
|
}
|
|
|
|
// Register registers a criterion.
|
|
func Register(criterionConstructor CriterionConstructor) {
|
|
allCriteria.Lock()
|
|
a := make([]CriterionConstructor, 0, len(allCriteria.a)+1)
|
|
a = append(a, allCriteria.a...)
|
|
a = append(a, criterionConstructor)
|
|
allCriteria.a = a
|
|
allCriteria.Unlock()
|
|
}
|
|
|
|
const (
|
|
// CriterionDataTypeStringListMatcher indicates the expected data type is a string list matcher.
|
|
CriterionDataTypeStringListMatcher CriterionDataType = "string_list_matcher"
|
|
// CriterionDataTypeStringMatcher indicates the expected data type is a string matcher.
|
|
CriterionDataTypeStringMatcher CriterionDataType = "string_matcher"
|
|
)
|
|
|
|
// NewCriterionRule generates a new rule for a criterion.
|
|
func NewCriterionRule(
|
|
g *generator.Generator,
|
|
name string,
|
|
passReason, failReason Reason,
|
|
body ast.Body,
|
|
) *ast.Rule {
|
|
r1 := g.NewRule(name)
|
|
r1.Head.Value = NewCriterionTerm(true, passReason)
|
|
r1.Body = body
|
|
|
|
r2 := &ast.Rule{
|
|
Head: &ast.Head{
|
|
Value: NewCriterionTerm(false, failReason),
|
|
},
|
|
Body: ast.Body{
|
|
ast.NewExpr(ast.BooleanTerm(true)),
|
|
},
|
|
}
|
|
r1.Else = r2
|
|
|
|
return r1
|
|
}
|
|
|
|
// NewCriterionDeviceRule generates a new rule for a criterion which
|
|
// requires a device and session. If there is no device "device-unauthenticated"
|
|
// is returned. If there is no session "user-unauthenticated" is returned.
|
|
func NewCriterionDeviceRule(
|
|
g *generator.Generator,
|
|
name string,
|
|
passReason, failReason Reason,
|
|
body ast.Body,
|
|
deviceType string,
|
|
) *ast.Rule {
|
|
r1 := g.NewRule(name)
|
|
|
|
additionalData := map[string]interface{}{
|
|
"device_type": deviceType,
|
|
}
|
|
|
|
sharedBody := ast.Body{
|
|
ast.Assign.Expr(ast.VarTerm("device_type_id"), ast.StringTerm(deviceType)),
|
|
ast.MustParseExpr(`session := get_session(input.session.id)`),
|
|
ast.MustParseExpr(`device_credential := get_device_credential(session, device_type_id)`),
|
|
ast.MustParseExpr(`device_enrollment := get_device_enrollment(device_credential)`),
|
|
}
|
|
|
|
// case 1: rule passes, session exists, device exists
|
|
r1.Head.Value = NewCriterionTermWithAdditionalData(true, passReason, additionalData)
|
|
r1.Body = append(sharedBody, body...)
|
|
|
|
// case 2: rule fails, session exists, device exists
|
|
r2 := &ast.Rule{
|
|
Head: &ast.Head{
|
|
Value: NewCriterionTermWithAdditionalData(false, failReason, additionalData),
|
|
},
|
|
Body: append(sharedBody, ast.Body{
|
|
ast.MustParseExpr(`session.id != ""`),
|
|
ast.MustParseExpr(`device_credential.id != ""`),
|
|
ast.MustParseExpr(`device_enrollment.id != ""`),
|
|
}...),
|
|
}
|
|
r1.Else = r2
|
|
|
|
// case 3: device not authenticated, session exists, device does not exist
|
|
r3 := &ast.Rule{
|
|
Head: &ast.Head{
|
|
Value: NewCriterionTermWithAdditionalData(false, ReasonDeviceUnauthenticated, additionalData),
|
|
},
|
|
Body: append(sharedBody, ast.Body{
|
|
ast.MustParseExpr(`session.id != ""`),
|
|
}...),
|
|
}
|
|
r2.Else = r3
|
|
|
|
// case 4: user not authenticated, session does not exist
|
|
r4 := &ast.Rule{
|
|
Head: &ast.Head{
|
|
Value: NewCriterionTermWithAdditionalData(false, ReasonUserUnauthenticated, additionalData),
|
|
},
|
|
Body: ast.Body{
|
|
ast.NewExpr(ast.BooleanTerm(true)),
|
|
},
|
|
}
|
|
r3.Else = r4
|
|
|
|
return r1
|
|
}
|
|
|
|
// NewCriterionSessionRule generates a new rule for a criterion which
|
|
// requires a session. If there is no session "user-unauthenticated"
|
|
// is returned.
|
|
func NewCriterionSessionRule(
|
|
g *generator.Generator,
|
|
name string,
|
|
passReason, failReason Reason,
|
|
body ast.Body,
|
|
) *ast.Rule {
|
|
r1 := g.NewRule(name)
|
|
r1.Head.Value = NewCriterionTerm(true, passReason)
|
|
r1.Body = body
|
|
|
|
r2 := &ast.Rule{
|
|
Head: &ast.Head{
|
|
Value: NewCriterionTerm(false, failReason),
|
|
},
|
|
Body: ast.Body{
|
|
ast.MustParseExpr(`session := get_session(input.session.id)`),
|
|
ast.MustParseExpr(`session.id != ""`),
|
|
},
|
|
}
|
|
r1.Else = r2
|
|
|
|
r3 := &ast.Rule{
|
|
Head: &ast.Head{
|
|
Value: NewCriterionTerm(false, ReasonUserUnauthenticated),
|
|
},
|
|
Body: ast.Body{
|
|
ast.NewExpr(ast.BooleanTerm(true)),
|
|
},
|
|
}
|
|
r2.Else = r3
|
|
|
|
return r1
|
|
}
|
|
|
|
// NewCriterionTerm creates a new rego term for a criterion:
|
|
//
|
|
// [true, {"reason"}]
|
|
//
|
|
func NewCriterionTerm(value bool, reasons ...Reason) *ast.Term {
|
|
var terms []*ast.Term
|
|
for _, r := range reasons {
|
|
terms = append(terms, ast.StringTerm(string(r)))
|
|
}
|
|
return ast.ArrayTerm(
|
|
ast.BooleanTerm(value),
|
|
ast.SetTerm(terms...),
|
|
)
|
|
}
|
|
|
|
// NewCriterionTermWithAdditionalData creates a new rego term for a criterion with additional data:
|
|
//
|
|
// [true, {"reason"}, {"key": "value"}]
|
|
//
|
|
func NewCriterionTermWithAdditionalData(value bool, reason Reason, additionalData map[string]interface{}) *ast.Term {
|
|
var kvs [][2]*ast.Term
|
|
for k, v := range additionalData {
|
|
kvs = append(kvs, [2]*ast.Term{
|
|
ast.StringTerm(k),
|
|
ast.NewTerm(ast.MustInterfaceToValue(v)),
|
|
})
|
|
}
|
|
var terms []*ast.Term
|
|
terms = append(terms, ast.StringTerm(string(reason)))
|
|
return ast.ArrayTerm(
|
|
ast.BooleanTerm(value),
|
|
ast.SetTerm(terms...),
|
|
ast.ObjectTerm(kvs...),
|
|
)
|
|
}
|