- Notifications
You must be signed in to change notification settings - Fork928
chore: document RBAC usage#14065
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 from1 commit
d1b6de6
23e01c5
706a3d8
4681b9d
6f240c9
c2f29d0
26ca907
cf25746
5fa2b96
257962b
02f7447
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
Signed-off-by: Danny Kopping <danny@coder.com>
- Loading branch information
Uh oh!
There was an error while loading.Please reload this page.
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -2,34 +2,36 @@ | ||
# Overview | ||
> _NOTE: you should probably read [`README.md`](README.md) beforehand, but it's | ||
> not essential._ | ||
## Basic structure | ||
RBAC is made up of nouns (the objects which are protected by RBAC rules) and | ||
verbs (actions which can be performed on nouns).<br> For example, a | ||
**workspace** (noun) can be **created** (verb), provided the requester has | ||
appropriate permissions. | ||
## Roles | ||
We have a number of roles (some of which have legacy connotations back to v1). | ||
These can be found in `coderd/rbac/roles.go`. | ||
| Role | Description| Example resources (non-exhaustive) | | ||
|-------------------- |------------------------------------------------------------------- |--------------------------------------------| | ||
| **owner** | Super-user, first user in Coder installation, has all\* permissions | all\* | | ||
| **member** | A regular user| workspaces, own details, provisioner daemons | | ||
| **auditor** | Viewer of audit log events, read-only access to a few resources| audit logs, templates, users, groups | | ||
| **templateAdmin** | Administrator of templates, read-only access to a few resources| templates, workspaces, users, groups | | ||
| **userAdmin** | Administrator of users| users, groups, role assignments | | ||
| **orgAdmin** | Like **owner**, but scoped to a single organization| _(org-level equivalent)_ | | ||
| **orgMember** | Like **member**, but scoped to a single organization| _(org-level equivalent)_ | | ||
| **orgAuditor** | Like **auditor**, but scoped to a single organization| _(org-level equivalent)_ | | ||
| **orgUserAdmin** | Like **userAdmin**, but scoped to a single organization| _(org-level equivalent)_ | | ||
| **orgTemplateAdmin** | Like **templateAdmin**, but scoped to a single organization| _(org-level equivalent)_ | | ||
_\* except some, which are not important to this overview_ | ||
johnstcn marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
## Actions | ||
@@ -38,7 +40,7 @@ Roles are collections of permissions (we call them _actions_). | ||
These can be found in `coderd/rbac/policy/policy.go`. | ||
| Action | Description | | ||
|----------------------- |---------------------------------------| | ||
| **create** | Create a resource | | ||
| **read** | Read a resource | | ||
| **update** | Update a resource | | ||
@@ -55,15 +57,19 @@ These can be found in `coderd/rbac/policy/policy.go`. | ||
# Creating a new noun | ||
In the following example, we're going to create a new RBAC noun for a new entity | ||
called a "frobulator" _(just some nonsense word for demonstration purposes)_. | ||
_Refer to https://github.com/coder/coder/pull/14055 to see a full | ||
implementation._ | ||
## Creating a new entity | ||
If you're creating a new resource which has to be owned by users of differing | ||
roles, you need to create a new RBAC resource. | ||
Let's say we're adding a new table called `frobulators` (we'll use this table | ||
later): | ||
```sql | ||
CREATE TABLE frobulators | ||
@@ -92,15 +98,17 @@ Let's now add our frobulator noun to `coderd/rbac/policy/policy.go`: | ||
... | ||
``` | ||
Entries in the `frobulators` table be created/read/updated/deleted, so we define | ||
dannykopping marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
those actions. | ||
`policy.go` is used to generate code in `coderd/rbac/object_gen.go`, and we can | ||
execute this by running `make gen`. | ||
Now we have this change in `coderd/rbac/object_gen.go`: | ||
```go | ||
... | ||
// ResourceFrobulator | ||
// Valid Actions | ||
// - "ActionCreate" :: | ||
// - "ActionDelete" :: | ||
@@ -109,44 +117,48 @@ Now we have this change in `coderd/rbac/object_gen.go`: | ||
ResourceFrobulator = Object{ | ||
Type: "frobulator", | ||
} | ||
... | ||
func AllResources() []Objecter { | ||
... | ||
ResourceFrobulator, | ||
... | ||
} | ||
``` | ||
This creates a resource which represents this noun, and adds it to a list of all | ||
available resources. | ||
## Role Assignment | ||
In our case, we want **members** to be able to CRUD their own frobulators and we | ||
want **owners** to CRUD all members' frobulators. This is how most resources | ||
work, and the RBAC system is setup for this by default. | ||
However, let's say we want **auditors** to have read-only access to all members' | ||
frobulators; we need to add it to `coderd/rbac/roles.go`: | ||
```go | ||
func ReloadBuiltinRoles(opts *RoleOptions) { | ||
... | ||
orgAuditor: func(organizationID uuid.UUID) Role { | ||
... | ||
return Role{ | ||
... | ||
Org: map[string][]Permission{ | ||
organizationID.String(): Permissions(map[string][]policy.Action{ | ||
... | ||
ResourceFrobulator.Type: {policy.ActionRead}, | ||
}) | ||
... | ||
... | ||
} | ||
``` | ||
## Testing | ||
The RBAC system is configured to test all possible actions on all available | ||
resources. | ||
Let's run the RBAC test suite: | ||
@@ -181,10 +193,13 @@ FAILgithub.com/coder/coder/v2/coderd/rbac1.314s | ||
FAIL | ||
``` | ||
The message `remaining permissions should be empty for type "frobulator"` | ||
indicates that we're missing tests which validate the desired actions on our new | ||
noun. | ||
> Take a look at `coderd/rbac/roles_test.go` in the | ||
> [reference PR](https://github.com/coder/coder/pull/14055) for a complete | ||
> example | ||
Let's add a test case: | ||
@@ -218,11 +233,13 @@ func TestRolePermissions(t *testing.T) { | ||
}, | ||
``` | ||
Note how the `FrobulatorsModify` test case is just validating the | ||
`policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete` actions, and | ||
only the **orgMember**, **orgAdmin**, and **owner** can access it. | ||
Similarly, the `FrobulatorsReadOnly` test case is only validating | ||
`policy.ActionRead`, which is allowed on all of the above plus the | ||
**orgAuditor** role. | ||
Now the tests pass, because we have covered all the possible scenarios: | ||
@@ -231,7 +248,8 @@ $ go test github.com/coder/coder/v2/coderd/rbac -count=1 | ||
ok github.com/coder/coder/v2/coderd/rbac1.313s | ||
``` | ||
When a case is not covered, you'll see an error like this (I moved the | ||
`orgAuditor` option from `true` to `false): | ||
dannykopping marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
```bash | ||
--- FAIL: TestRolePermissions (0.79s) | ||
@@ -246,10 +264,11 @@ FAILgithub.com/coder/coder/v2/coderd/rbac1.390s | ||
FAIL | ||
``` | ||
This shows you that the `org_auditor` role has `read` permissions on the | ||
frobulator, but no test case covered it. | ||
**NOTE: don't just add cases which make the tests pass; consider all the way in | ||
dannykopping marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
which your resource must be used, and testall of those scenarios!** | ||
dannykopping marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
# Database authorization | ||
@@ -264,7 +283,8 @@ FROM frobulators | ||
WHERE user_id = @user_id::uuid; | ||
``` | ||
Once we run `make gen`, we'll find some stubbed code in | ||
`coderd/database/dbauthz/dbauthz.go`. | ||
```go | ||
... | ||
@@ -287,17 +307,22 @@ func (q *querier) GetUserFrobulators(ctx context.Context, userID uuid.UUID) ([]d | ||
... | ||
``` | ||
This states that the `policy.ActionRead` permission is required in this query on | ||
the `ResourceFrobulator` resources, and `WithOwner(userID.String())` specifies | ||
that this user must own the resource. | ||
dannykopping marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
All queries are executed through `dbauthz`, and now our little frobulators are | ||
protected! | ||
# API authorization | ||
API authorization is not strictly required because we have database | ||
authorization in place, but it's a good practice to reject requests as soon as | ||
possible when the requester is unprivileged. | ||
> Take a look at `coderd/frobulators.go` in the | ||
> [reference PR](https://github.com/coder/coder/pull/14055) for a complete | ||
> example | ||
```go | ||
... | ||
@@ -312,7 +337,8 @@ func (api *API) listUserFrobulators(rw http.ResponseWriter, r *http.Request) { | ||
} | ||
``` | ||
`api.Authorize(r, policy.ActionRead, rbac.ResourceFrobulator.WithOwner(key.UserID.String()))` | ||
is specifying that we only want to permit a user to read their own frobulators. | ||
If the requester does not have this permission, we forbid the request. We're | ||
checking the user associated to the API key here because this could also be an | ||
**owner** or **orgAdmin**, and we want to permit those users. |