Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitcb25bae

Browse files
committed
Document RBAC usage
Signed-off-by: Danny Kopping <danny@coder.com>
1 parentcf1fcab commitcb25bae

File tree

2 files changed

+321
-3
lines changed

2 files changed

+321
-3
lines changed

‎coderd/rbac/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
#Authz
22

3-
Package`authz` implementsAuthoriZation for Coder.
3+
Package`rbac` implementsRole-Based Access Control for Coder.
44

55
##Overview
66

77
Authorization defines what**permission** a**subject** has to perform**actions** to**objects**:
88

99
-**Permission** is binary:_yes_ (allowed) or_no_ (denied).
10-
-**Subject** in this case is anything that implements interface`authz.Subject`.
10+
-**Subject** in this case is anything that implements interface`rbac.Subject`.
1111
-**Action** here is an enumerated list of actions, but we stick to`Create`,`Read`,`Update`, and`Delete` here.
12-
-**Object** here is anything that implements`authz.Object`.
12+
-**Object** here is anything that implements`rbac.Object`.
1313

1414
##Permission Structure
1515

‎coderd/rbac/USAGE.md

Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
#Using RBAC
2+
3+
#Overview
4+
5+
>_NOTE: you should probably read[`README.md`](README.md) beforehand, but it's not essential._
6+
7+
##Basic structure
8+
9+
RBAC is made up of nouns (the objects which are protected by RBAC rules) and verbs (actions which can be performed on
10+
nouns).<br>
11+
For example, a**workspace** (noun) can be**created** (verb), provided the requester has appropriate permissions.
12+
13+
##Roles
14+
15+
We have a number of roles (some of which have legacy connotations back to v1).
16+
17+
These can be found in`coderd/rbac/roles.go`.
18+
19+
| Role| Description| Example resources (non-exhaustive)|
20+
|----------------------|--------------------------------------------------------------------|----------------------------------------------|
21+
|**owner**| Super-user, first user in Coder installation, has all* permissions| all*|
22+
|**member**| A regular user| workspaces, own details, provisioner daemons|
23+
|**auditor**| Viewer of audit log events, read-only access to a few resources| audit logs, templates, users, groups|
24+
|**templateAdmin**| Administrator of templates, read-only access to a few resources| templates, workspaces, users, groups|
25+
|**userAdmin**| Administrator of users| users, groups, role assignments|
26+
|**orgAdmin**| Like**owner**, but scoped to a single organization|_(org-level equivalent)_|
27+
|**orgMember**| Like**member**, but scoped to a single organization|_(org-level equivalent)_|
28+
|**orgAuditor**| Like**auditor**, but scoped to a single organization|_(org-level equivalent)_|
29+
|**orgUserAdmin**| Like**userAdmin**, but scoped to a single organization|_(org-level equivalent)_|
30+
|**orgTemplateAdmin**| Like**templateAdmin**, but scoped to a single organization|_(org-level equivalent)_|
31+
32+
_* except some, which are not important to this overview_
33+
34+
##Actions
35+
36+
Roles are collections of permissions (we call them_actions_).
37+
38+
These can be found in`coderd/rbac/policy/policy.go`.
39+
40+
| Action| Description|
41+
|-------------------------|-----------------------------------------|
42+
|**create**| Create a resource|
43+
|**read**| Read a resource|
44+
|**update**| Update a resource|
45+
|**delete**| Delete a resource|
46+
|**use**| Use a resource|
47+
|**read_personal**| Read owned resource|
48+
|**update_personal**| Update owned resource|
49+
|**ssh**| SSH into a workspace|
50+
|**application_connect**| Connect to workspace apps via a browser|
51+
|**view_insights**| View deployment insights|
52+
|**start**| Start a workspace|
53+
|**stop**| Stop a workspace|
54+
|**assign**| Assign user to role / org|
55+
56+
#Creating a new noun
57+
58+
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)_.
59+
60+
_Refer tohttps://github.com/coder/coder/pull/14055 to see a full implementation._
61+
62+
##Creating a new entity
63+
64+
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.
65+
66+
Let's say we're adding a new table called`frobulators` (we'll use this table later):
67+
68+
```sql
69+
CREATETABLEfrobulators
70+
(
71+
id uuidNOT NULL,
72+
user_id uuidNOT NULL,
73+
model_numberTEXTNOT NULL,
74+
PRIMARY KEY (id),
75+
UNIQUE (model_number),
76+
FOREIGN KEY (user_id)REFERENCES users (id)ON DELETE CASCADE
77+
);
78+
```
79+
80+
Let's now add our frobulator noun to`coderd/rbac/policy/policy.go`:
81+
82+
```go
83+
...
84+
"frobulator": {
85+
Actions:map[Action]ActionDefinition{
86+
ActionCreate:actDef("create a frobulator"),
87+
ActionRead:actDef("read a frobulator"),
88+
ActionUpdate:actDef("update a frobulator"),
89+
ActionDelete:actDef("delete a frobulator"),
90+
},
91+
},
92+
...
93+
```
94+
95+
Entries in the`frobulators` table be created/read/updated/deleted, so we define those actions.
96+
97+
`policy.go` is used to generate code in`coderd/rbac/object_gen.go`, and we can execute this by running`make gen`.
98+
99+
Now we have this change in`coderd/rbac/object_gen.go`:
100+
101+
```go
102+
...
103+
// ResourceFrobulator
104+
// Valid Actions
105+
// - "ActionCreate" ::
106+
// - "ActionDelete" ::
107+
// - "ActionRead" ::
108+
// - "ActionUpdate" ::
109+
ResourceFrobulator =Object{
110+
Type:"frobulator",
111+
}
112+
...
113+
114+
funcAllResources() []Objecter {
115+
...
116+
ResourceFrobulator,
117+
...
118+
}
119+
```
120+
121+
This creates a resource which represents this noun, and adds it to a list of all available resources.
122+
123+
##Role Assignment
124+
125+
In our case, we want**members** to be able to CRUD their own frobulators and we want**owners** to CRUD all members' frobulators.
126+
This is how most resources work, and the RBAC system is setup for this by default.
127+
128+
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`:
129+
130+
```go
131+
funcReloadBuiltinRoles(opts *RoleOptions) {
132+
...
133+
orgAuditor:func(organizationID uuid.UUID) Role {
134+
...
135+
return Role{
136+
...
137+
Org:map[string][]Permission{
138+
organizationID.String():Permissions(map[string][]policy.Action{
139+
...
140+
ResourceFrobulator.Type: {policy.ActionRead},
141+
})
142+
...
143+
...
144+
}
145+
```
146+
147+
## Testing
148+
149+
The RBAC system is configured to test all possible actions on all available resources.
150+
151+
Let's run the RBAC test suite:
152+
153+
`go test github.com/coder/coder/v2/coderd/rbac`
154+
155+
We'll see a failure like this:
156+
157+
```bash
158+
--- FAIL:TestRolePermissions (0.61s)
159+
--- FAIL: TestRolePermissions/frobulator-AllActions (0.00s)
160+
roles_test.go:705:
161+
Error Trace:/tmp/coder/coderd/rbac/roles_test.go:705
162+
Error: Not equal:
163+
expected:map[policy.Action]bool{}
164+
actual :map[policy.Action]bool{"create":true,"delete":true,"read":true,"update":true}
165+
166+
Diff:
167+
--- Expected
168+
+++ Actual
169+
@@ -1,2 +1,6 @@
170+
-(map[policy.Action]bool) {
171+
+(map[policy.Action]bool) (len=4) {
172+
+ (policy.Action) (len=6)"create": (bool)true,
173+
+ (policy.Action) (len=6)"delete": (bool)true,
174+
+ (policy.Action) (len=4)"read": (bool)true,
175+
+ (policy.Action) (len=6)"update": (bool)true
176+
}
177+
Test: TestRolePermissions/frobulator-AllActions
178+
Messages: remaining permissions should be emptyfortype"frobulator"
179+
FAIL
180+
FAILgithub.com/coder/coder/v2/coderd/rbac1.314s
181+
FAIL
182+
```
183+
184+
The message`remaining permissions should be emptyfortype"frobulator"` indicates that we're missing tests which validate
185+
the desired actions on our new noun.
186+
187+
> Take a look at`coderd/rbac/roles_test.go` in the [reference PR](https://github.com/coder/coder/pull/14055) for a complete example
188+
189+
Let's add a test case:
190+
191+
```go
192+
funcTestRolePermissions(t *testing.T) {
193+
...
194+
{
195+
// Users should be able to modify their own frobulators
196+
// Admins from the current organization should be able to modify any other user's frobulators
197+
// Owner should be able to modify any other user's frobulators
198+
Name:"FrobulatorsModify",
199+
Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
200+
Resource: rbac.ResourceFrobulator.WithOwner(currentUser.String()).InOrg(orgID),
201+
AuthorizeMap:map[bool][]hasAuthSubjects{
202+
true: {orgMemberMe, orgAdmin, owner},
203+
false: {setOtherOrg, memberMe, templateAdmin, userAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor},
204+
},
205+
},
206+
{
207+
// Users should be able to read their own frobulators
208+
// Admins from the current organization should be able to read any other user's frobulators
209+
// Auditors should be able to read any other user's frobulators
210+
// Owner should be able to read any other user's frobulators
211+
Name:"FrobulatorsReadOnly",
212+
Actions: []policy.Action{policy.ActionRead},
213+
Resource: rbac.ResourceFrobulator.WithOwner(currentUser.String()).InOrg(orgID),
214+
AuthorizeMap:map[bool][]hasAuthSubjects{
215+
true: {orgMemberMe, orgAdmin, owner, orgAuditor},
216+
false: {setOtherOrg, memberMe, templateAdmin, userAdmin, orgTemplateAdmin, orgUserAdmin},
217+
},
218+
},
219+
```
220+
221+
Note how the`FrobulatorsModify` test case is just validating the`policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete` actions,
222+
and only the **orgMember**, **orgAdmin**, and **owner** can access it.
223+
224+
Similarly, the`FrobulatorsReadOnly` test case is only validating`policy.ActionRead`, which is allowed on all of the above
225+
plus the **orgAuditor** role.
226+
227+
Now the tests pass, because we have covered all the possible scenarios:
228+
229+
```bash
230+
$go test github.com/coder/coder/v2/coderd/rbac -count=1
231+
ok github.com/coder/coder/v2/coderd/rbac1.313s
232+
```
233+
234+
When a case is not covered, you'll see an error like this (I moved the`orgAuditor` option from`true` to`false):
235+
236+
```bash
237+
--- FAIL: TestRolePermissions (0.79s)
238+
--- FAIL: TestRolePermissions/FrobulatorsReadOnly (0.01s)
239+
roles_test.go:737:
240+
Error Trace:/tmp/coder/coderd/rbac/roles_test.go:737
241+
Error: An error is expected but got nil.
242+
Test: TestRolePermissions/FrobulatorsReadOnly
243+
Messages: Should fail: FrobulatorsReadOnly as "org_auditor" doing "read" on "frobulator"
244+
FAIL
245+
FAILgithub.com/coder/coder/v2/coderd/rbac1.390s
246+
FAIL
247+
```
248+
249+
This shows you that the`org_auditor` role has`read` permissions on the frobulator, but no testcase covered it.
250+
251+
**NOTE: don't just add cases which make the tests pass; consider all the way in which your resource must be used, and test
252+
all of those scenarios!**
253+
254+
# Database authorization
255+
256+
Now that we have the RBAC system fully configured, we need to make use of it.
257+
258+
Let's add a SQL query to`coderd/database/queries/frobulators.sql`:
259+
260+
```sql
261+
-- name: GetUserFrobulators :many
262+
SELECT *
263+
FROM frobulators
264+
WHERE user_id = @user_id::uuid;
265+
```
266+
267+
Once we run`make gen`, we'll find some stubbed code in `coderd/database/dbauthz/dbauthz.go`.
268+
269+
```go
270+
...
271+
func (q *querier) GetUserFrobulators(ctx context.Context) ([]database.Frobulator, error) {
272+
panic("not implemented")
273+
}
274+
...
275+
```
276+
277+
Let's modify this function:
278+
279+
```go
280+
...
281+
func (q *querier) GetUserFrobulators(ctx context.Context, userID uuid.UUID) ([]database.Frobulator, error) {
282+
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceFrobulator.WithOwner(userID.String())); err != nil {
283+
return nil, err
284+
}
285+
return q.db.GetUserFrobulators(ctx, userID)
286+
}
287+
...
288+
```
289+
290+
This states that the`policy.ActionRead` permission is required in this query on the`ResourceFrobulator` resources,
291+
and`WithOwner(userID.String())` specifies that this user must own the resource.
292+
293+
All queries are executed through`dbauthz`, and now our little frobulators are protected!
294+
295+
# API authorization
296+
297+
API authorization is not strictly required because we have database authorization in place, but it's a good practice to
298+
reject requests as soon as possible when the requester is unprivileged.
299+
300+
> Take a look at `coderd/frobulators.go` in the [reference PR](https://github.com/coder/coder/pull/14055) for a complete example
301+
302+
```go
303+
...
304+
func (api *API) listUserFrobulators(rw http.ResponseWriter, r *http.Request) {
305+
ctx := r.Context()
306+
key := httpmw.APIKey(r)
307+
if !api.Authorize(r, policy.ActionRead, rbac.ResourceFrobulator.WithOwner(key.UserID.String())) {
308+
httpapi.Forbidden(rw)
309+
return
310+
}
311+
...
312+
}
313+
```
314+
315+
`api.Authorize(r, policy.ActionRead, rbac.ResourceFrobulator.WithOwner(key.UserID.String()))` is specifying that we only
316+
want to permit a user to read their own frobulators. If the requester does not have this permission, we forbid the request.
317+
We're checking the user associated to the API key here because this could also be an **owner** or **orgAdmin**, and we want to
318+
permit those users.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp