- Notifications
You must be signed in to change notification settings - Fork1k
chore: improve rbac and add benchmark tooling#18584
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
1479550
a5a0b82
015b8c0
29222a1
0a2f3e0
e49fd2d
7660085
c2a60fd
ecf6eba
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -148,7 +148,7 @@ func benchmarkUserCases() (cases []benchmarkCase, users uuid.UUID, orgs []uuid.U | ||
// BenchmarkRBACAuthorize benchmarks the rbac.Authorize method. | ||
// | ||
//go test -run=^$ -bench'^BenchmarkRBACAuthorize$' -benchmem -memprofile memprofile.out -cpuprofile profile.out | ||
func BenchmarkRBACAuthorize(b *testing.B) { | ||
benchCases, user, orgs := benchmarkUserCases() | ||
users := append([]uuid.UUID{}, | ||
@@ -178,7 +178,7 @@ func BenchmarkRBACAuthorize(b *testing.B) { | ||
// BenchmarkRBACAuthorizeGroups benchmarks the rbac.Authorize method and leverages | ||
// groups for authorizing rather than the permissions/roles. | ||
// | ||
//go test -bench'^BenchmarkRBACAuthorizeGroups$' -benchmem -memprofile memprofile.out -cpuprofile profile.out | ||
func BenchmarkRBACAuthorizeGroups(b *testing.B) { | ||
benchCases, user, orgs := benchmarkUserCases() | ||
users := append([]uuid.UUID{}, | ||
@@ -229,7 +229,7 @@ func BenchmarkRBACAuthorizeGroups(b *testing.B) { | ||
// BenchmarkRBACFilter benchmarks the rbac.Filter method. | ||
// | ||
//go test -bench'^BenchmarkRBACFilter$' -benchmem -memprofile memprofile.out -cpuprofile profile.out | ||
ssncferreira marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
func BenchmarkRBACFilter(b *testing.B) { | ||
benchCases, user, orgs := benchmarkUserCases() | ||
users := append([]uuid.UUID{}, | ||
ssncferreira marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -29,76 +29,93 @@ import rego.v1 | ||
# different code branches based on the org_owner. 'num's value does, but | ||
# that is the whole point of partial evaluation. | ||
# bool_flip(b) returns the logical negation of a boolean value 'b'. | ||
# You cannot do 'x := !false', but you can do 'x := bool_flip(false)' | ||
bool_flip(b) :=false if { | ||
b | ||
} | ||
bool_flip(b) :=true if { | ||
not b | ||
} | ||
Comment on lines -34 to 40 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Very nice 👍 | ||
# number(set) maps a set of boolean values to one of the following numbers: | ||
# -1: deny (if 'false' value is in the set)=> set is {true, false} or {false} | ||
# 0: no decision (if the set is empty)=> set is {} | ||
# 1: allow (if only 'true' values are in the set)=> set is {true} | ||
# Return -1 if the set contains any 'false' value (i.e., an explicit deny) | ||
number(set) := -1 if { | ||
false in set | ||
} | ||
# Return 0 if the set is empty (no matching permissions) | ||
number(set) := 0 if { | ||
count(set) == 0 | ||
} | ||
# Return 1 if the set is non-empty and contains no 'false' values (i.e., only allows) | ||
number(set) := 1 if { | ||
not false in set | ||
set[_] | ||
} | ||
Comment on lines -53 to 61 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. 👍 | ||
# Permission evaluation is structured into three levels: site, org, and user. | ||
# For each level, two variables are computed: | ||
# - <level>: the decision based on the subject's full set of roles for that level | ||
# - scope_<level>: the decision based on the subject's scoped roles for that level | ||
# | ||
# Each of these variables is assigned one of three values: | ||
# -1 => negative (deny) | ||
# 0 => abstain (no matching permission) | ||
# 1 => positive (allow) | ||
# | ||
# These values are computed by calling the corresponding <level>_allow functions. | ||
# The final decision is derived from combining these values (see 'allow' rule). | ||
# ------------------- | ||
# Site Level Rules | ||
# ------------------- | ||
default site := 0 | ||
site := site_allow(input.subject.roles) | ||
default scope_site := 0 | ||
scope_site := site_allow([input.subject.scope]) | ||
# site_allow receives a list of roles and returns a single number: | ||
# -1 if any matching permission denies access | ||
# 1 if there's at least one allow and no denies | ||
# 0 if there are no matching permissions | ||
site_allow(roles) := num if { | ||
# allow is a set of boolean values(sets don't containduplicates) | ||
allow := {is_allowed | | ||
# Iterate over all site permissions in all roles | ||
perm := roles[_].site[_] | ||
perm.action in [input.action, "*"] | ||
perm.resource_type in [input.object.type, "*"] | ||
#is_allowed is either 'true' or 'false' if a matching permission exists. | ||
is_allowed := bool_flip(perm.negate) | ||
} | ||
num := number(allow) | ||
} | ||
# ------------------- | ||
# Org Level Rules | ||
# ------------------- | ||
# org_members is the list of organizations the actor is apart of. | ||
org_members := {orgID | | ||
input.subject.roles[_].org[orgID] | ||
} | ||
#'org' is the same as 'site' except we need to iterate over each organization | ||
# that the actor is a member of. | ||
default org := 0 | ||
org := org_allow(input.subject.roles) | ||
default scope_org := 0 | ||
scope_org := org_allow([input.scope]) | ||
# org_allow_set is a helper function that iterates over all orgs that the actor | ||
@@ -114,11 +131,14 @@ scope_org := org_allow([input.scope]) | ||
org_allow_set(roles) := allow_set if { | ||
allow_set := {id: num | | ||
id := org_members[_] | ||
set := {is_allowed | | ||
# Iterate over all org permissions in all roles | ||
perm := roles[_].org[id][_] | ||
perm.action in [input.action, "*"] | ||
perm.resource_type in [input.object.type, "*"] | ||
# is_allowed is either 'true' or 'false' if a matching permission exists. | ||
is_allowed := bool_flip(perm.negate) | ||
} | ||
num := number(set) | ||
} | ||
@@ -191,24 +211,30 @@ org_ok if { | ||
not input.object.any_org | ||
} | ||
# ------------------- | ||
# User Level Rules | ||
# ------------------- | ||
# 'user' is the same as 'site', except it only applies if the user owns the object and | ||
# the user is apart of the org (if the object has an org). | ||
default user := 0 | ||
user := user_allow(input.subject.roles) | ||
default scope_user := 0 | ||
scope_user := user_allow([input.scope]) | ||
user_allow(roles) := num if { | ||
input.object.owner != "" | ||
input.subject.id = input.object.owner | ||
allow := {is_allowed | | ||
# Iterate over all user permissions in all roles | ||
perm := roles[_].user[_] | ||
perm.action in [input.action, "*"] | ||
perm.resource_type in [input.object.type, "*"] | ||
# is_allowed is either 'true' or 'false' if a matching permission exists. | ||
is_allowed := bool_flip(perm.negate) | ||
} | ||
num := number(allow) | ||
} | ||
@@ -227,17 +253,9 @@ scope_allow_list if { | ||
input.object.id in input.subject.scope.allow_list | ||
} | ||
# ------------------- | ||
# Role-Specific Rules | ||
# ------------------- | ||
role_allow if { | ||
site = 1 | ||
@@ -258,6 +276,10 @@ role_allow if { | ||
user = 1 | ||
} | ||
# ------------------- | ||
# Scope-Specific Rules | ||
# ------------------- | ||
scope_allow if { | ||
scope_allow_list | ||
scope_site = 1 | ||
@@ -280,6 +302,11 @@ scope_allow if { | ||
scope_user = 1 | ||
} | ||
# ------------------- | ||
# ACL-Specific Rules | ||
# Access Control List | ||
# ------------------- | ||
# ACL for users | ||
acl_allow if { | ||
# Should you have to be a member of the org too? | ||
@@ -308,11 +335,24 @@ acl_allow if { | ||
[input.action, "*"][_] in perms | ||
} | ||
# ------------------- | ||
# Final Allow | ||
# | ||
# The 'allow' block is quite simple. Any set with `-1` cascades down in levels. | ||
# Authorization looks for any `allow` statement that is true. Multiple can be true! | ||
# Note that the absence of `allow` means "unauthorized". | ||
# An explicit `"allow": true` is required. | ||
# | ||
# Scope is also applied. The default scope is "wildcard:wildcard" allowing | ||
# all actions. If the scope is not "1", then the action is not authorized. | ||
# | ||
# Allow query: | ||
#data.authz.role_allow = true | ||
#data.authz.scope_allow = true | ||
# ------------------- | ||
# The role or the ACL must allow the action. Scopes can be used to limit, | ||
# so scope_allow must always be true. | ||
allow if { | ||
role_allow | ||
scope_allow | ||
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.