-
Notifications
You must be signed in to change notification settings - Fork 90
/
rego.go
120 lines (104 loc) · 2.95 KB
/
rego.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
package rbac
import (
"context"
"github.com/open-policy-agent/opa/ast"
"github.com/open-policy-agent/opa/rego"
"github.com/pkg/errors"
"github.com/replicatedhq/kots/pkg/rbac/types"
)
/*
The RBAC module defined in this rego document is deny by default.
Deny will always take precedence over allow.
Policies are defined with glob matching patterns with dots as separators.
Multiple roles can be used as input.
Logic is as follows:
1. if deny, stop and deny
2. if allow, stop and allow
3. deny
*/
var regoModule string = `
package rbac
default allow = false
default deny = false
allow {
not deny
# for each role in that list
r := input.roles[_]
# lookup the policies list for role r
policies := input.allowRolePolicies[r]
# for each policy
p := policies[_]
# check if the permission granted to r matches the roles's request
glob.match(p.action, [], input.action)
glob.match(p.resource, [], input.resource)
}
deny {
# for each role in that list
r := input.roles[_]
# lookup the policies list for role r
policies := input.denyRolePolicies[r]
# for each policy
p := policies[_]
# check if the permission granted to r matches the roles's request
glob.match(p.action, [], input.action)
glob.match(p.resource, [], input.resource)
}
`
var compiler *ast.Compiler
func init() {
var err error
compiler, err = ast.CompileModules(map[string]string{
"rbac": regoModule,
})
if err != nil {
panic(errors.Wrap(err, "failed to compile rego module"))
}
}
func CheckAccess(ctx context.Context, roles []types.Role, action, resource string, sessionRoles []string) (bool, error) {
for _, role := range roles {
i := map[string]interface{}{
"action": action,
"resource": resource,
"roles": sessionRoles,
"allowRolePolicies": roleToAllowRolePolicies(role),
"denyRolePolicies": roleToDenyRolePolicies(role),
}
allow, err := regoEval(ctx, i)
if err != nil {
return false, errors.Wrapf(err, "failed to evaluate role %s", role.ID)
} else if allow {
return true, nil
}
}
return false, nil
}
func roleToAllowRolePolicies(role types.Role) map[string][]types.Policy {
return map[string][]types.Policy{
role.ID: role.Allow,
}
}
func roleToDenyRolePolicies(role types.Role) map[string][]types.Policy {
return map[string][]types.Policy{
role.ID: role.Deny,
}
}
func regoEval(ctx context.Context, input map[string]interface{}) (bool, error) {
query := rego.New(
rego.Query("data.rbac.allow"),
rego.Compiler(compiler),
rego.Input(input),
)
results, err := query.Eval(ctx)
if err != nil {
return false, errors.Wrap(err, "failed to evaluate query")
} else if len(results) == 0 {
return false, errors.New("empty result set")
} else if len(results[0].Expressions) == 0 {
return false, errors.New("empty expressions")
}
allow, ok := results[0].Expressions[0].Value.(bool)
if !ok {
return false, errors.New("unexpected result type")
}
return allow, nil
}