1
1
package authz
2
- import future.keywords
2
+
3
+ import rego.v1
4
+
3
5
# A great playground: https://play.openpolicyagent.org/
4
6
# Helpful cli commands to debug.
5
7
# opa eval --format=pretty 'data.authz.allow' -d policy.rego -i input.json
@@ -29,67 +31,74 @@ import future.keywords
29
31
30
32
# bool_flip lets you assign a value to an inverted bool.
31
33
# You cannot do 'x := !false', but you can do 'x := bool_flip(false)'
32
- bool_flip (b) = flipped {
33
- b
34
- flipped = false
34
+ bool_flip (b) : = flipped if {
35
+ b
36
+ flipped = false
35
37
}
36
38
37
- bool_flip (b) = flipped {
38
- not b
39
- flipped = true
39
+ bool_flip (b) : = flipped if {
40
+ not b
41
+ flipped = true
40
42
}
41
43
42
44
# number is a quick way to get a set of {true, false} and convert it to
43
45
# -1: {false, true} or {false}
44
46
# 0: {}
45
47
# 1: {true}
46
- number (set) = c {
48
+ number (set) : = c if {
47
49
count (set) == 0
48
- c := 0
50
+ c := 0
49
51
}
50
52
51
- number (set) = c {
53
+ number (set) : = c if {
52
54
false in set
53
- c := - 1
55
+ c := - 1
54
56
}
55
57
56
- number (set) = c {
58
+ number (set) : = c if {
57
59
not false in set
58
60
set[_]
59
- c := 1
61
+ c := 1
60
62
}
61
63
62
64
# site, org, and user rules are all similar. Each rule should return a number
63
65
# from [-1, 1]. The number corresponds to "negative", "abstain", and "positive"
64
66
# for the given level. See the 'allow' rules for how these numbers are used.
65
- default site = 0
67
+ default site := 0
68
+
66
69
site := site_allow (input.subject.roles)
70
+
67
71
default scope_site := 0
72
+
68
73
scope_site := site_allow ([input.subject.scope])
69
74
70
- site_allow (roles) := num {
75
+ site_allow (roles) := num if {
71
76
# allow is a set of boolean values without duplicates.
72
- allow := { x |
77
+ allow := {x |
73
78
# Iterate over all site permissions in all roles
74
- perm := roles[_].site[_]
75
- perm.action in [input.action, " *" ]
79
+ perm := roles[_].site[_]
80
+ perm.action in [input.action, " *" ]
76
81
perm.resource_type in [input.object.type, " *" ]
82
+
77
83
# x is either 'true' or 'false' if a matching permission exists.
78
- x := bool_flip (perm.negate)
79
- }
80
- num := number (allow)
84
+ x := bool_flip (perm.negate)
85
+ }
86
+ num := number (allow)
81
87
}
82
88
83
89
# org_members is the list of organizations the actor is apart of.
84
- org_members := { orgID |
90
+ org_members := {orgID |
85
91
input.subject.roles[_].org[orgID]
86
92
}
87
93
88
94
# org is the same as 'site' except we need to iterate over each organization
89
95
# that the actor is a member of.
90
- default org = 0
96
+ default org := 0
97
+
91
98
org := org_allow (input.subject.roles)
99
+
92
100
default scope_org := 0
101
+
93
102
scope_org := org_allow ([input.scope])
94
103
95
104
# org_allow_set is a helper function that iterates over all orgs that the actor
@@ -102,10 +111,10 @@ scope_org := org_allow([input.scope])
102
111
# The reason we calculate this for all orgs, and not just the input.object.org_owner
103
112
# is that sometimes the input.object.org_owner is unknown. In those cases
104
113
# we have a list of org_ids that can we use in a SQL 'WHERE' clause.
105
- org_allow_set (roles) := allow_set {
106
- allow_set := { id: num |
114
+ org_allow_set (roles) := allow_set if {
115
+ allow_set := {id: num |
107
116
id := org_members[_]
108
- set := { x |
117
+ set := {x |
109
118
perm := roles[_].org[id][_]
110
119
perm.action in [input.action, " *" ]
111
120
perm.resource_type in [input.object.type, " *" ]
@@ -115,7 +124,7 @@ org_allow_set(roles) := allow_set {
115
124
}
116
125
}
117
126
118
- org_allow (roles) := num {
127
+ org_allow (roles) := num if {
119
128
# If the object has "any_org" set to true, then use the other
120
129
# org_allow block.
121
130
not input.object.any_org
@@ -135,78 +144,82 @@ org_allow(roles) := num {
135
144
# This is useful for UI elements when we want to conclude, "Can the user create
136
145
# a new template in any organization?"
137
146
# It is easier than iterating over every organization the user is apart of.
138
- org_allow (roles) := num {
147
+ org_allow (roles) := num if {
139
148
input.object.any_org # if this is false, this code block is not used
140
149
allow := org_allow_set (roles)
141
150
142
-
143
151
# allow is a map of {"<org_id>": <number>}. We only care about values
144
152
# that are 1, and ignore the rest.
145
153
num := number ([
146
- keep |
147
- # for every value in the mapping
148
- value := allow[_]
149
- # only keep values > 0.
150
- # 1 = allow, 0 = abstain, -1 = deny
151
- # We only need 1 explicit allow to allow the action.
152
- # deny's and abstains are intentionally ignored.
153
- value > 0
154
- # result set is a set of [true,false,...]
155
- # which "number()" will convert to a number.
156
- keep := true
154
+ keep |
155
+ # for every value in the mapping
156
+ value := allow[_]
157
+
158
+ # only keep values > 0.
159
+ # 1 = allow, 0 = abstain, -1 = deny
160
+ # We only need 1 explicit allow to allow the action.
161
+ # deny's and abstains are intentionally ignored.
162
+ value > 0
163
+
164
+ # result set is a set of [true,false,...]
165
+ # which "number()" will convert to a number.
166
+ keep := true
157
167
])
158
168
}
159
169
160
170
# 'org_mem' is set to true if the user is an org member
161
171
# If 'any_org' is set to true, use the other block to determine org membership.
162
- org_mem := true {
172
+ org_mem if {
163
173
not input.object.any_org
164
174
input.object.org_owner != " "
165
175
input.object.org_owner in org_members
166
176
}
167
177
168
- org_mem := true {
178
+ org_mem if {
169
179
input.object.any_org
170
180
count (org_members) > 0
171
181
}
172
182
173
- org_ok {
183
+ org_ok if {
174
184
org_mem
175
185
}
176
186
177
187
# If the object has no organization, then the user is also considered part of
178
188
# the non-existent org.
179
- org_ok {
189
+ org_ok if {
180
190
input.object.org_owner == " "
181
191
not input.object.any_org
182
192
}
183
193
184
194
# User is the same as the site, except it only applies if the user owns the object and
185
195
# the user is apart of the org (if the object has an org).
186
- default user = 0
196
+ default user := 0
197
+
187
198
user := user_allow (input.subject.roles)
199
+
188
200
default user_scope := 0
201
+
189
202
scope_user := user_allow ([input.scope])
190
203
191
- user_allow (roles) := num {
192
- input.object.owner != " "
193
- input.subject.id = input.object.owner
194
- allow := { x |
195
- perm := roles[_].user[_]
196
- perm.action in [input.action, " *" ]
204
+ user_allow (roles) := num if {
205
+ input.object.owner != " "
206
+ input.subject.id = input.object.owner
207
+ allow := {x |
208
+ perm := roles[_].user[_]
209
+ perm.action in [input.action, " *" ]
197
210
perm.resource_type in [input.object.type, " *" ]
198
- x := bool_flip (perm.negate)
199
- }
200
- num := number (allow)
211
+ x := bool_flip (perm.negate)
212
+ }
213
+ num := number (allow)
201
214
}
202
215
203
216
# Scope allow_list is a list of resource IDs explicitly allowed by the scope.
204
217
# If the list is '*', then all resources are allowed.
205
- scope_allow_list {
218
+ scope_allow_list if {
206
219
" *" in input.subject.scope.allow_list
207
220
}
208
221
209
- scope_allow_list {
222
+ scope_allow_list if {
210
223
# If the wildcard is listed in the allow_list, we do not care about the
211
224
# object.id. This line is included to prevent partial compilations from
212
225
# ever needing to include the object.id.
@@ -226,66 +239,70 @@ scope_allow_list {
226
239
# Allow query:
227
240
# data.authz.role_allow = true data.authz.scope_allow = true
228
241
229
- role_allow {
242
+ role_allow if {
230
243
site = 1
231
244
}
232
245
233
- role_allow {
246
+ role_allow if {
234
247
not site = - 1
235
248
org = 1
236
249
}
237
250
238
- role_allow {
251
+ role_allow if {
239
252
not site = - 1
240
253
not org = - 1
254
+
241
255
# If we are not a member of an org, and the object has an org, then we are
242
256
# not authorized. This is an "implied -1" for not being in the org.
243
257
org_ok
244
258
user = 1
245
259
}
246
260
247
- scope_allow {
261
+ scope_allow if {
248
262
scope_allow_list
249
263
scope_site = 1
250
264
}
251
265
252
- scope_allow {
266
+ scope_allow if {
253
267
scope_allow_list
254
268
not scope_site = - 1
255
269
scope_org = 1
256
270
}
257
271
258
- scope_allow {
272
+ scope_allow if {
259
273
scope_allow_list
260
274
not scope_site = - 1
261
275
not scope_org = - 1
276
+
262
277
# If we are not a member of an org, and the object has an org, then we are
263
278
# not authorized. This is an "implied -1" for not being in the org.
264
279
org_ok
265
280
scope_user = 1
266
281
}
267
282
268
283
# ACL for users
269
- acl_allow {
284
+ acl_allow if {
270
285
# Should you have to be a member of the org too?
271
286
perms := input.object.acl_user_list[input.subject.id]
287
+
272
288
# Either the input action or wildcard
273
289
[input.action, " *" ][_] in perms
274
290
}
275
291
276
292
# ACL for groups
277
- acl_allow {
293
+ acl_allow if {
278
294
# If there is no organization owner, the object cannot be owned by an
279
295
# org_scoped team.
280
296
org_mem
281
297
group := input.subject.groups[_]
282
298
perms := input.object.acl_group_list[group]
299
+
283
300
# Either the input action or wildcard
284
301
[input.action, " *" ][_] in perms
285
302
}
286
303
287
304
# ACL for 'all_users' special group
288
- acl_allow {
305
+ acl_allow if {
289
306
org_mem
290
307
perms := input.object.acl_group_list[input.object.org_owner]
291
308
[input.action, " *" ][_] in perms
@@ -296,13 +313,13 @@ acl_allow {
296
313
# The role or the ACL must allow the action. Scopes can be used to limit,
297
314
# so scope_allow must always be true.
298
315
299
- allow {
316
+ allow if {
300
317
role_allow
301
318
scope_allow
302
319
}
303
320
304
321
# ACL list must also have the scope_allow to pass
305
- allow {
322
+ allow if {
306
323
acl_allow
307
324
scope_allow
308
325
}
0 commit comments