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

Commitcf25746

Browse files
committed
Update usage docs
Signed-off-by: Danny Kopping <danny@coder.com>
1 parent26ca907 commitcf25746

File tree

1 file changed

+100
-33
lines changed

1 file changed

+100
-33
lines changed

‎coderd/rbac/USAGE.md

Lines changed: 100 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ implementation._
6868

6969
##Creating a new entity
7070

71-
If you're creating a new resource which has to beownedby users of differing
72-
roles, you need to create a new RBAC resource.
71+
If you're creating a new resource which has to beacted uponby users of
72+
differingroles, you need to create a new RBAC resource.
7373

7474
Let's say we're adding a new table called`frobulators` (we'll use this table
7575
later):
@@ -79,10 +79,12 @@ CREATE TABLE frobulators
7979
(
8080
id uuidNOT NULL,
8181
user_id uuidNOT NULL,
82+
org_id uuidNOT NULL,
8283
model_numberTEXTNOT NULL,
8384
PRIMARY KEY (id),
8485
UNIQUE (model_number),
85-
FOREIGN KEY (user_id)REFERENCES users (id)ON DELETE CASCADE
86+
FOREIGN KEY (user_id)REFERENCES users (id)ON DELETE CASCADE,
87+
FOREIGN KEY (org_id)REFERENCES organizations (id)ON DELETE CASCADE
8688
);
8789
```
8890

@@ -92,10 +94,10 @@ Let's now add our frobulator noun to `coderd/rbac/policy/policy.go`:
9294
...
9395
"frobulator": {
9496
Actions:map[Action]ActionDefinition{
95-
ActionCreate:actDef("create a frobulator"),
96-
ActionRead:actDef("read a frobulator"),
97-
ActionUpdate:actDef("update a frobulator"),
98-
ActionDelete:actDef("delete a frobulator"),
97+
ActionCreate:{Description:"create a frobulator"},
98+
ActionRead:{Description:"read a frobulator"},
99+
ActionUpdate:{Description:"update a frobulator"},
100+
ActionDelete:{Description:"delete a frobulator"},
99101
},
100102
},
101103
...
@@ -144,20 +146,34 @@ all organization's frobulators; we need to add it to `coderd/rbac/roles.go`:
144146
```go
145147
funcReloadBuiltinRoles(opts *RoleOptions) {
146148
...
149+
auditorRole:= Role{
150+
Identifier:RoleAuditor(),
151+
DisplayName:"Auditor",
152+
Site:Permissions(map[string][]policy.Action{
153+
...
154+
// The site-wide auditor is allowed to read *all* frobulators, regardless of who owns them.
155+
ResourceFrobulator.Type: {policy.ActionRead},
156+
...
157+
158+
//
147159
orgAuditor:func(organizationID uuid.UUID) Role {
148160
...
149161
return Role{
150162
...
151163
Org:map[string][]Permission{
152164
organizationID.String():Permissions(map[string][]policy.Action{
153165
...
166+
// The org-wide auditor is allowed to read *all* frobulators in their own org, regardless of who owns them.
154167
ResourceFrobulator.Type: {policy.ActionRead},
155168
})
156169
...
157170
...
158171
}
159172
```
160173
174+
Note how we added the permission to both the **site-wide** auditor role and the
175+
**org-level** auditor role.
176+
161177
## Testing
162178
163179
The RBAC system is configured to test all possible actions on all available
@@ -222,16 +238,15 @@ func TestRolePermissions(t *testing.T) {
222238
},
223239
},
224240
{
225-
// Users should be able to read their own frobulators
226241
// Admins from the current organization should be able to read any other members' frobulators
227242
// Auditors should be able to read any other members' frobulators
228243
// Owner should be able to read any other user's frobulators
229-
Name:"FrobulatorsReadOnly",
244+
Name:"FrobulatorsReadAnyUserInOrg",
230245
Actions: []policy.Action{policy.ActionRead},
231-
Resource: rbac.ResourceFrobulator.WithOwner(currentUser.String()).InOrg(orgID),
246+
Resource: rbac.ResourceFrobulator.WithOwner(uuid.New().String()).InOrg(orgID),// read frobulators of any user
232247
AuthorizeMap:map[bool][]hasAuthSubjects{
233-
true: {orgMemberMe, orgAdmin, owner, orgAuditor},
234-
false: {setOtherOrg, memberMe, templateAdmin, userAdmin, orgTemplateAdmin, orgUserAdmin},
248+
true: {owner, orgAdmin, orgAuditor},
249+
false: {memberMe, orgMemberMe, setOtherOrg, templateAdmin, userAdmin, orgTemplateAdmin, orgUserAdmin},
235250
},
236251
},
237252
```
@@ -240,11 +255,14 @@ Note how the `FrobulatorsModify` test case is just validating the
240255
`policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete` actions, and
241256
only the **orgMember**, **orgAdmin**, and **owner** can access it.
242257
243-
Similarly, the`FrobulatorsReadOnly` test case is only validating
244-
`policy.ActionRead`, which is allowed on all of the above plus the
245-
**orgAuditor** role.
258+
The`FrobulatorsReadAnyUserInOrg` test case is validating that owners, org
259+
admins & auditors have the`policy.ActionRead` policy which enables them to read
260+
frobulators belonging to any user in a given organization.
261+
262+
The above tests are illustrative not exhaustive, see
263+
[the reference PR](https://github.com/coder/coder/pull/14055) for the rest.
246264
247-
Now the tests pass, becausewe have covered all the possible scenarios:
265+
Oncewe have covered all the possible scenarios, the tests will pass:
248266
249267
```bash
250268
$go test github.com/coder/coder/v2/coderd/rbac -count=1
@@ -280,18 +298,18 @@ Now that we have the RBAC system fully configured, we need to make use of it.
280298
Let's add a SQL query to`coderd/database/queries/frobulators.sql`:
281299

282300
```sql
283-
-- name:GetUserFrobulators :many
301+
-- name:GetFrobulators :many
284302
SELECT *
285303
FROM frobulators
286-
WHERE user_id =@user_id::uuid;
304+
WHERE user_id =$1 AND org_id = $2;
287305
```
288306

289307
Once we run`make gen`, we'll find some stubbed code in
290308
`coderd/database/dbauthz/dbauthz.go`.
291309
292310
```go
293311
...
294-
func (q *querier)GetUserFrobulators(ctx context.Context) ([]database.Frobulator, error) {
312+
func (q *querier)GetFrobulators(ctx context.Context, arg database.GetFrobulatorsParams) ([]database.Frobulator, error) {
295313
panic("not implemented")
296314
}
297315
...
@@ -301,15 +319,33 @@ Let's modify this function:
301319

302320
```go
303321
...
304-
func (q *querier)GetUserFrobulators(ctx context.Context,userID uuid.UUID) ([]database.Frobulator, error) {
305-
returnfetch(q.log, q.auth, q.db.GetUserFrobulators)(ctx,id)
322+
func (q *querier)GetFrobulators(ctx context.Context,arg database.GetFrobulatorsParams) ([]database.Frobulator, error) {
323+
returnfetchWithPostFilter(q.auth, policy.ActionRead, q.db.GetFrobulators)(ctx,arg)
306324
}
307325
...
308326
```
309327

310-
This states that the`policy.ActionRead` permission is required in this query on
311-
the`ResourceFrobulator` resources, and`WithOwner(userID.String())` specifies
312-
that this user must own the resource.
328+
This states that the`policy.ActionRead` permission is enforced on all entries
329+
returned from the database, ensuring that each requested frobulator is readable
330+
by the given actor.
331+
332+
In orderfor this to work, we need to implement the`rbac.Objector`interface.
333+
334+
`coderd/database/modelmethods.go` is where we implement thisinterfacefor all
335+
RBAC objects:
336+
337+
```go
338+
func (f Frobulator) RBACObject() rbac.Object {
339+
return rbac.ResourceFrobulator.
340+
WithID(f.ID). // Each frobulator has a unique identity.
341+
WithOwner(f.UserID.String()). // It is owned by one and only one user.
342+
InOrg(f.OrgID) // It belongs to an organization.
343+
}
344+
```
345+
346+
These values obviously have to be set on the`Frobulator` instance before this
347+
function can work, hence why we have to fetch the object from the store first
348+
before wevalidate (this explains the`fetchWithPostFilter` naming).
313349

314350
All queries are executed through`dbauthz`, and now our little frobulators are
315351
protected!
@@ -326,19 +362,50 @@ possible when the requester is unprivileged.
326362
327363
```go
328364
...
329-
func (api *API)listUserFrobulators(rw http.ResponseWriter, r *http.Request) {
365+
func (api *API)createFrobulator(rw http.ResponseWriter, r *http.Request) {
330366
ctx := r.Context()
331-
key := httpmw.APIKey(r)
332-
if !api.Authorize(r, policy.ActionRead, rbac.ResourceFrobulator.WithOwner(key.UserID.String())) {
333-
httpapi.Forbidden(rw)
367+
member := httpmw.OrganizationMemberParam(r)
368+
org := httpmw.OrganizationParam(r)
369+
370+
var req codersdk.InsertFrobulatorRequest
371+
if !httpapi.Read(ctx, rw, r, &req) {
372+
return
373+
}
374+
375+
frob, err := api.Database.InsertFrobulator(ctx, database.InsertFrobulatorParams{
376+
ID: uuid.New(),
377+
UserID: member.UserID,
378+
OrgID: org.ID,
379+
ModelNumber: req.ModelNumber,
380+
})
381+
382+
// This will catch forbidden errors as well.
383+
if httpapi.Is404Error(err) {
384+
httpapi.ResourceNotFound(rw)
334385
return
335386
}
336387
...
388+
```
389+
390+
If we look at the implementation of `httpapi.Is404Error`:
391+
392+
```go
393+
// Is404Error returns true if the given error should return a 404 status code.
394+
// Both actual 404s and unauthorized errors should return 404s to not leak
395+
// information about the existence of resources.
396+
func Is404Error(err error) bool {
397+
if err == nil {
398+
return false
399+
}
400+
401+
// This tests for dbauthz.IsNotAuthorizedError and rbac.IsUnauthorizedError.
402+
if IsUnauthorizedError(err) {
403+
return true
404+
}
405+
return xerrors.Is(err, sql.ErrNoRows)
337406
}
338407
```
339408
340-
`api.Authorize(r, policy.ActionRead, rbac.ResourceFrobulator.WithOwner(key.UserID.String()))`
341-
is specifying that we only want to permit a user to read their own frobulators.
342-
If the requester does not have this permission, we forbid the request. We're
343-
checking the user associated to the API key here because this could also be an
344-
**owner** or **orgAdmin**, and we want to permit those users.
409+
With this, we're able to handle unauthorized access to the resource butreturn a
410+
`404 Not Found` to not leak the fact that the resources exist but are not
411+
accessible by the given actor.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp