mirror of https://github.com/coder/coder.git
228 lines
5.1 KiB
Go
228 lines
5.1 KiB
Go
package rbac
|
|
|
|
import (
|
|
"github.com/open-policy-agent/opa/ast"
|
|
"golang.org/x/xerrors"
|
|
)
|
|
|
|
// regoInputValue returns a rego input value for the given subject, action, and
|
|
// object. This rego input is already parsed and can be used directly in a
|
|
// rego query.
|
|
func regoInputValue(subject Subject, action Action, object Object) (ast.Value, error) {
|
|
regoSubj, err := subject.regoValue()
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("subject: %w", err)
|
|
}
|
|
|
|
s := [2]*ast.Term{
|
|
ast.StringTerm("subject"),
|
|
ast.NewTerm(regoSubj),
|
|
}
|
|
a := [2]*ast.Term{
|
|
ast.StringTerm("action"),
|
|
ast.StringTerm(string(action)),
|
|
}
|
|
o := [2]*ast.Term{
|
|
ast.StringTerm("object"),
|
|
ast.NewTerm(object.regoValue()),
|
|
}
|
|
|
|
input := ast.NewObject(s, a, o)
|
|
|
|
return input, nil
|
|
}
|
|
|
|
// regoPartialInputValue is the same as regoInputValue but only includes the
|
|
// object type. This is for partial evaluations.
|
|
func regoPartialInputValue(subject Subject, action Action, objectType string) (ast.Value, error) {
|
|
regoSubj, err := subject.regoValue()
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("subject: %w", err)
|
|
}
|
|
|
|
s := [2]*ast.Term{
|
|
ast.StringTerm("subject"),
|
|
ast.NewTerm(regoSubj),
|
|
}
|
|
a := [2]*ast.Term{
|
|
ast.StringTerm("action"),
|
|
ast.StringTerm(string(action)),
|
|
}
|
|
o := [2]*ast.Term{
|
|
ast.StringTerm("object"),
|
|
ast.NewTerm(ast.NewObject(
|
|
[2]*ast.Term{
|
|
ast.StringTerm("type"),
|
|
ast.StringTerm(objectType),
|
|
}),
|
|
),
|
|
}
|
|
|
|
input := ast.NewObject(s, a, o)
|
|
|
|
return input, nil
|
|
}
|
|
|
|
// regoValue returns the ast.Object representation of the subject.
|
|
func (s Subject) regoValue() (ast.Value, error) {
|
|
if s.cachedASTValue != nil {
|
|
return s.cachedASTValue, nil
|
|
}
|
|
|
|
subjRoles, err := s.Roles.Expand()
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("expand roles: %w", err)
|
|
}
|
|
|
|
subjScope, err := s.Scope.Expand()
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("expand scope: %w", err)
|
|
}
|
|
subj := ast.NewObject(
|
|
[2]*ast.Term{
|
|
ast.StringTerm("id"),
|
|
ast.StringTerm(s.ID),
|
|
},
|
|
[2]*ast.Term{
|
|
ast.StringTerm("roles"),
|
|
ast.NewTerm(regoSlice(subjRoles)),
|
|
},
|
|
[2]*ast.Term{
|
|
ast.StringTerm("scope"),
|
|
ast.NewTerm(subjScope.regoValue()),
|
|
},
|
|
[2]*ast.Term{
|
|
ast.StringTerm("groups"),
|
|
ast.NewTerm(regoSliceString(s.Groups...)),
|
|
},
|
|
)
|
|
|
|
return subj, nil
|
|
}
|
|
|
|
func (z Object) regoValue() ast.Value {
|
|
userACL := ast.NewObject()
|
|
for k, v := range z.ACLUserList {
|
|
userACL.Insert(ast.StringTerm(k), ast.NewTerm(regoSlice(v)))
|
|
}
|
|
grpACL := ast.NewObject()
|
|
for k, v := range z.ACLGroupList {
|
|
grpACL.Insert(ast.StringTerm(k), ast.NewTerm(regoSlice(v)))
|
|
}
|
|
return ast.NewObject(
|
|
[2]*ast.Term{
|
|
ast.StringTerm("id"),
|
|
ast.StringTerm(z.ID),
|
|
},
|
|
[2]*ast.Term{
|
|
ast.StringTerm("owner"),
|
|
ast.StringTerm(z.Owner),
|
|
},
|
|
[2]*ast.Term{
|
|
ast.StringTerm("org_owner"),
|
|
ast.StringTerm(z.OrgID),
|
|
},
|
|
[2]*ast.Term{
|
|
ast.StringTerm("type"),
|
|
ast.StringTerm(z.Type),
|
|
},
|
|
[2]*ast.Term{
|
|
ast.StringTerm("acl_user_list"),
|
|
ast.NewTerm(userACL),
|
|
},
|
|
[2]*ast.Term{
|
|
ast.StringTerm("acl_group_list"),
|
|
ast.NewTerm(grpACL),
|
|
},
|
|
)
|
|
}
|
|
|
|
// withCachedRegoValue returns a copy of the role with the cachedRegoValue.
|
|
// It does not mutate the underlying role.
|
|
// Avoid using this function if possible, it should only be used if the
|
|
// caller can guarantee the role is static and will never change.
|
|
func (role Role) withCachedRegoValue() Role {
|
|
tmp := role
|
|
tmp.cachedRegoValue = role.regoValue()
|
|
return tmp
|
|
}
|
|
|
|
func (role Role) regoValue() ast.Value {
|
|
if role.cachedRegoValue != nil {
|
|
return role.cachedRegoValue
|
|
}
|
|
orgMap := ast.NewObject()
|
|
for k, p := range role.Org {
|
|
orgMap.Insert(ast.StringTerm(k), ast.NewTerm(regoSlice(p)))
|
|
}
|
|
return ast.NewObject(
|
|
[2]*ast.Term{
|
|
ast.StringTerm("site"),
|
|
ast.NewTerm(regoSlice(role.Site)),
|
|
},
|
|
[2]*ast.Term{
|
|
ast.StringTerm("org"),
|
|
ast.NewTerm(orgMap),
|
|
},
|
|
[2]*ast.Term{
|
|
ast.StringTerm("user"),
|
|
ast.NewTerm(regoSlice(role.User)),
|
|
},
|
|
)
|
|
}
|
|
|
|
func (s Scope) regoValue() ast.Value {
|
|
r, ok := s.Role.regoValue().(ast.Object)
|
|
if !ok {
|
|
panic("developer error: role is not an object")
|
|
}
|
|
r.Insert(
|
|
ast.StringTerm("allow_list"),
|
|
ast.NewTerm(regoSliceString(s.AllowIDList...)),
|
|
)
|
|
return r
|
|
}
|
|
|
|
func (perm Permission) regoValue() ast.Value {
|
|
return ast.NewObject(
|
|
[2]*ast.Term{
|
|
ast.StringTerm("negate"),
|
|
ast.BooleanTerm(perm.Negate),
|
|
},
|
|
[2]*ast.Term{
|
|
ast.StringTerm("resource_type"),
|
|
ast.StringTerm(perm.ResourceType),
|
|
},
|
|
[2]*ast.Term{
|
|
ast.StringTerm("action"),
|
|
ast.StringTerm(string(perm.Action)),
|
|
},
|
|
)
|
|
}
|
|
|
|
func (act Action) regoValue() ast.Value {
|
|
return ast.StringTerm(string(act)).Value
|
|
}
|
|
|
|
type regoValue interface {
|
|
regoValue() ast.Value
|
|
}
|
|
|
|
// regoSlice returns the ast.Array representation of the slice.
|
|
// The slice must contain only types that implement the regoValue interface.
|
|
func regoSlice[T regoValue](slice []T) *ast.Array {
|
|
terms := make([]*ast.Term, len(slice))
|
|
for i, v := range slice {
|
|
terms[i] = ast.NewTerm(v.regoValue())
|
|
}
|
|
return ast.NewArray(terms...)
|
|
}
|
|
|
|
func regoSliceString(slice ...string) *ast.Array {
|
|
terms := make([]*ast.Term, len(slice))
|
|
for i, v := range slice {
|
|
terms[i] = ast.StringTerm(v)
|
|
}
|
|
return ast.NewArray(terms...)
|
|
}
|