Files
coder/coderd/rbac/policy.rego
Steven Masley 770c567123 feat: Add RBAC package for managing user permissions (#929)
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>
2022-04-13 08:35:35 -05:00

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[_]
}