mirror of
https://github.com/coder/coder.git
synced 2025-07-08 11:39:50 +00:00
This PR adds an RBAC package for managing using permissions: - The top-level `authz.Authorize` function is the main user-facing entrypoint to the package. - Actual permission evaluation is handled in `policy.rego`. - Unit tests for `authz.Authorize` are in `authz_test.go` - Documentation for the package is in `README.md`. Co-authored-by: Cian Johnston <cian@coder.com>
140 lines
4.1 KiB
Rego
140 lines
4.1 KiB
Rego
package authz
|
|
import future.keywords.in
|
|
import future.keywords.every
|
|
|
|
# A great playground: https://play.openpolicyagent.org/
|
|
# TODO: Add debug instructions to do in the cli. Running really short on time, the
|
|
# playground is sufficient for now imo. In the future we can provide a tidy bash
|
|
# script for running this against predefined input.
|
|
|
|
# bool_flip lets you assign a value to an inverted bool.
|
|
# You cannot do 'x := !false', but you can do 'x := bool_flip(false)'
|
|
bool_flip(b) = flipped {
|
|
b
|
|
flipped = false
|
|
}
|
|
|
|
bool_flip(b) = flipped {
|
|
not b
|
|
flipped = true
|
|
}
|
|
|
|
# perms_grant returns a set of boolean values {true, false}.
|
|
# True means a positive permission in the set, false is a negative permission.
|
|
# It will only return `bool_flip(perm.negate)` for permissions that affect a given
|
|
# resource_type, resource_id, and action.
|
|
# The empty set is returned if no relevant permissions are found.
|
|
perms_grant(permissions) = grants {
|
|
# If there are no permissions, this value is the empty set {}.
|
|
grants := { x |
|
|
# All permissions ...
|
|
perm := permissions[_]
|
|
# Such that the permission action, type, and resource_id matches
|
|
perm.action in [input.action, "*"]
|
|
perm.resource_type in [input.object.type, "*"]
|
|
perm.resource_id in [input.object.id, "*"]
|
|
x := bool_flip(perm.negate)
|
|
}
|
|
}
|
|
|
|
# Site & User are both very simple. We default both to the empty set '{}'. If no permissions are present, then the
|
|
# result is the default value.
|
|
default site = {}
|
|
site = grant {
|
|
# Boolean set for all site wide permissions.
|
|
grant = { v | # Use set comprehension to remove duplicate values
|
|
# For each role, grab the site permission.
|
|
# Find the grants on this permission list.
|
|
v = perms_grant(input.subject.roles[_].site)[_]
|
|
}
|
|
}
|
|
|
|
default user = {}
|
|
user = grant {
|
|
# Only apply user permissions if the user owns the resource
|
|
input.object.owner != ""
|
|
input.object.owner == input.subject.id
|
|
grant = { v |
|
|
# For each role, grab the user permissions.
|
|
# Find the grants on this permission list.
|
|
v = perms_grant(input.subject.roles[_].user)[_]
|
|
}
|
|
}
|
|
|
|
# Organizations are more complex. If the user has no roles that specifically indicate the org_id of the object,
|
|
# then we want to block the action. This is because that means the user is not a member of the org.
|
|
# A non-member cannot access any org resources.
|
|
|
|
# org_member returns the set of permissions associated with a user if the user is a member of the
|
|
# organization
|
|
org_member = grant {
|
|
input.object.org_owner != ""
|
|
grant = { v |
|
|
v = perms_grant(input.subject.roles[_].org[input.object.org_owner])[_]
|
|
}
|
|
}
|
|
|
|
# If a user is not part of an organization, 'org_non_member' is set to true
|
|
org_non_member {
|
|
input.object.org_owner != ""
|
|
# Identify if the user is in the org
|
|
roles := input.subject.roles
|
|
every role in roles {
|
|
not role.org[input.object.org_owner]
|
|
}
|
|
}
|
|
|
|
# org is two rules that equate to the following
|
|
# if org_non_member { return {false} }
|
|
# else { org_member }
|
|
#
|
|
# It is important both rules cannot be true, as the `org` rules cannot produce multiple outputs.
|
|
default org = {}
|
|
org = set {
|
|
# We have to do !org_non_member because rego rules must evaluate to 'true'
|
|
# to have a value set.
|
|
# So we do "not not-org-member" which means "subject is in org"
|
|
not org_non_member
|
|
set = org_member
|
|
}
|
|
|
|
org = set {
|
|
org_non_member
|
|
set = {false}
|
|
}
|
|
|
|
# The allow block is quite simple. Any set with `false` cascades down in levels.
|
|
# Authorization looks for any `allow` statement that is true. Multiple can be true!
|
|
# Note that the absense of `allow` means "unauthorized".
|
|
# An explicit `"allow": true` is required.
|
|
|
|
# site allow
|
|
allow {
|
|
# No site wide deny
|
|
not false in site
|
|
# And all permissions are positive
|
|
site[_]
|
|
}
|
|
|
|
# OR
|
|
|
|
# org allow
|
|
allow {
|
|
# No site or org deny
|
|
not false in site
|
|
not false in org
|
|
# And all permissions are positive
|
|
org[_]
|
|
}
|
|
|
|
# OR
|
|
|
|
# user allow
|
|
allow {
|
|
# No site, org, or user deny
|
|
not false in site
|
|
not false in org
|
|
not false in user
|
|
# And all permissions are positive
|
|
user[_]
|
|
} |