-
Notifications
You must be signed in to change notification settings - Fork 883
feat: Add RBAC package for managing user permissions #929
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
79 commits
Select commit
Hold shift + click to select a range
ab61328
WIP: This is a massive WIP
Emyrk 03e4d0f
More info in the print
Emyrk 9981291
Fix all()
Emyrk 3ab32da
reduce the amount of memoery allocated
Emyrk e1d5893
Reuse a buffer
Emyrk 84a90f3
fix: use return size over size
Emyrk 1fac0d9
WIP: don't look at this
Emyrk 1e3aac0
WIP: 🍐 auth-> testdata, refactoring and restructuring
johnstcn e977e84
testdata -> authztest
johnstcn 00a7c3f
WIP: start work on SVO
johnstcn 1f04c01
reduce allocations for union sets
Emyrk fbf4db1
fix: Fix nil permissions as Strings()
Emyrk 4946897
chore: Make all permission variant levels
Emyrk 7e6cc66
First full draft of the authz authorize test
Emyrk a0017e5
Tally up failed tests
Emyrk 4b110b3
Change test pkg
Emyrk 65ef4e3
Use an interface for the object
Emyrk d294786
fix: make authztest.Objects return correct type
johnstcn c1f8945
refactor: rename consts {Read,Write,Modify,Delete}Action to Action$1
johnstcn 01f3d40
chore: Define object interface
Emyrk de7de6e
test: Unit test extra properties
Emyrk 4c86e44
Merge remote-tracking branch 'origin/stevenmasley/rbac' into stevenma…
Emyrk 30c6568
put back interface assertion
Emyrk a419a65
Fix some compile errors from merge
Emyrk bbd1c4c
test: Roles, sets, permissions, iterators
Emyrk def010f
Test string functions
Emyrk c4ee590
test: Unit test permission string
Emyrk 84e3ab9
Add A+ and A-
Emyrk c2eec18
Parallelize tests
Emyrk 5a2834a
fix code line in readme
Emyrk 913d141
Merge remote-tracking branch 'origin/main' into stevenmasley/rbac
Emyrk 2804b92
test: ParsePermissions from strings
Emyrk 5698938
use fmt over str builder for easier to read
Emyrk 75ed8ef
Linting
Emyrk b2db661
authz: README.md: update table formatting
johnstcn 26ef1e6
Make action CRUD
Emyrk 19aba30
LevelID -> OrganizationID
Emyrk ceee9cd
feat: authztest: categorize test failures by test name
johnstcn ee8bf04
fixup! feat: authztest: categorize test failures by test name
johnstcn 44c02a1
chore: add documentation for authz and authztest
johnstcn dfb9ad1
fixup! chore: add documentation for authz and authztest
johnstcn e482d2c
chore: more authz/authztest docs
johnstcn a4e038f
Remove underscore from test names
Emyrk 9918c16
zObject does not need exported fields
Emyrk 4cf4808
checkpoint: crowd programming: define and simplify top-level API
johnstcn 359a04d
Add tabled tests for authz
Emyrk 22cf0cc
fix: made roles named and now they span all levels
johnstcn c51ddd1
fixup! fix: made roles named and now they span all levels
johnstcn 891e442
fixup! fixup! fix: made roles named and now they span all levels
johnstcn ad048dd
vscode is illiterate
johnstcn bb28930
fixup! vscode is illiterate
johnstcn 2e23a34
Remove Org & Owner obj interfaces
Emyrk 13466e1
Show a nice builder syntax
Emyrk 3ac2eaa
Add more tabled tests
Emyrk c7dc715
fixup! Add more tabled tests
Emyrk f5d95ef
correct comments
Emyrk 0868301
fixup! correct comments
Emyrk 512c09e
Address PR comments
Emyrk d9f761d
Drop unused resources & roles
Emyrk 81ca08a
Rename chaining functions
Emyrk fced411
Rename OrgOwner -> OrgID
Emyrk f620ebf
feat: Add rego policy implementation
Emyrk 07951a7
Merge remote-tracking branch 'origin/main' into cj/rbac_table_driven
Emyrk 690d41d
Update go mod with opa
Emyrk fae3314
Go mod tidy
Emyrk 856a933
Add rego comments
Emyrk e048104
Use cached rego query
Emyrk adae379
Correct user-deny perm in test
Emyrk 0e7f9fa
tabs to spaces
Emyrk b1c7df4
Rename package to rbac
Emyrk 35574ad
run golangci-lint and goimports
johnstcn 8a16947
authz_test.go: log internal error
johnstcn 2969fc1
fixup! run golangci-lint and goimports
johnstcn b22b723
to-done
johnstcn c1423d4
Merge remote-tracking branch 'origin/main' into cj/rbac_table_driven
johnstcn c2b1dde
Remove unused fields
Emyrk cfdd2cb
Add some comments to rego
Emyrk 5c113a0
Move Authorize param order
Emyrk 44a7679
Drop resources.go file
Emyrk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
# Authz | ||
|
||
Package `authz` implements AuthoriZation for Coder. | ||
|
||
## Overview | ||
|
||
Authorization defines what **permission** a **subject** has to perform **actions** to **objects**: | ||
- **Permission** is binary: *yes* (allowed) or *no* (denied). | ||
- **Subject** in this case is anything that implements interface `authz.Subject`. | ||
- **Action** here is an enumerated list of actions, but we stick to `Create`, `Read`, `Update`, and `Delete` here. | ||
- **Object** here is anything that implements `authz.Object`. | ||
|
||
## Permission Structure | ||
|
||
A **permission** is a rule that grants or denies access for a **subject** to perform an **action** on a **object**. | ||
A **permission** is always applied at a given **level**: | ||
|
||
- **site** level applies to all objects in a given Coder deployment. | ||
- **org** level applies to all objects that have an organization owner (`org_owner`) | ||
- **user** level applies to all objects that have an owner with the same ID as the subject. | ||
|
||
**Permissions** at a higher **level** always override permissions at a **lower** level. | ||
|
||
The effect of a **permission** can be: | ||
- **positive** (allows) | ||
- **negative** (denies) | ||
- **abstain** (neither allows or denies, not applicable) | ||
|
||
**Negative** permissions **always** override **positive** permissions at the same level. | ||
Both **negative** and **positive** permissions override **abstain** at the same level. | ||
|
||
This can be represented by the following truth table, where Y represents *positive*, N represents *negative*, and _ represents *abstain*: | ||
|
||
| Action | Positive | Negative | Result | | ||
|--------|----------|----------|--------| | ||
| read | Y | _ | Y | | ||
| read | Y | N | N | | ||
| read | _ | _ | _ | | ||
| read | _ | N | Y | | ||
|
||
|
||
## Permission Representation | ||
|
||
**Permissions** are represented in string format as `<sign>?<level>.<object>.<id>.<action>`, where: | ||
|
||
- `negated` can be either `+` or `-`. If it is omitted, sign is assumed to be `+`. | ||
- `level` is either `site`, `org`, or `user`. | ||
- `object` is any valid resource type. | ||
- `id` is any valid UUID v4. | ||
- `action` is `create`, `read`, `modify`, or `delete`. | ||
|
||
## Example Permissions | ||
|
||
- `+site.*.*.read`: allowed to perform the `read` action against all objects of type `devurl` in a given Coder deployment. | ||
- `-user.workspace.*.create`: user is not allowed to create workspaces. | ||
|
||
## Roles | ||
|
||
A *role* is a set of permissions. When evaluating a role's permission to form an action, all the relevant permissions for the role are combined at each level. Permissions at a higher level override permissions at a lower level. | ||
|
||
The following table shows the per-level role evaluation. | ||
Y indicates that the role provides positive permissions, N indicates the role provides negative permissions, and _ indicates the role does not provide positive or negative permissions. YN_ indicates that the value in the cell does not matter for the access result. | ||
|
||
| Role (example) | Site | Org | User | Result | | ||
|-----------------|------|-----|------|--------| | ||
| site-admin | Y | YN_ | YN_ | Y | | ||
| no-permission | N | YN_ | YN_ | N | | ||
| org-admin | _ | Y | YN_ | Y | | ||
| non-org-member | _ | N | YN_ | N | | ||
| user | _ | _ | Y | Y | | ||
| | _ | _ | N | N | | ||
| unauthenticated | _ | _ | _ | N | | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package rbac | ||
|
||
// Action represents the allowed actions to be done on an object. | ||
type Action string | ||
|
||
const ( | ||
ActionCreate = "create" | ||
ActionRead = "read" | ||
ActionUpdate = "update" | ||
ActionDelete = "delete" | ||
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package rbac | ||
|
||
import ( | ||
"context" | ||
_ "embed" | ||
|
||
"golang.org/x/xerrors" | ||
|
||
"github.com/open-policy-agent/opa/rego" | ||
) | ||
|
||
// RegoAuthorizer will use a prepared rego query for performing authorize() | ||
type RegoAuthorizer struct { | ||
query rego.PreparedEvalQuery | ||
} | ||
|
||
// Load the policy from policy.rego in this directory. | ||
//go:embed policy.rego | ||
var policy string | ||
|
||
func NewAuthorizer() (*RegoAuthorizer, error) { | ||
ctx := context.Background() | ||
query, err := rego.New( | ||
// allowed is the `allow` field from the prepared query. This is the field to check if authorization is | ||
// granted. | ||
rego.Query("allowed = data.authz.allow"), | ||
rego.Module("policy.rego", policy), | ||
).PrepareForEval(ctx) | ||
|
||
if err != nil { | ||
return nil, xerrors.Errorf("prepare query: %w", err) | ||
} | ||
return &RegoAuthorizer{query: query}, nil | ||
} | ||
|
||
type authSubject struct { | ||
ID string `json:"id"` | ||
Roles []Role `json:"roles"` | ||
} | ||
|
||
func (a RegoAuthorizer) Authorize(ctx context.Context, subjectID string, roles []Role, action Action, object Object) error { | ||
input := map[string]interface{}{ | ||
"subject": authSubject{ | ||
ID: subjectID, | ||
Roles: roles, | ||
}, | ||
"object": object, | ||
"action": action, | ||
} | ||
|
||
results, err := a.query.Eval(ctx, rego.EvalInput(input)) | ||
if err != nil { | ||
return ForbiddenWithInternal(xerrors.Errorf("eval rego: %w, err"), input, results) | ||
} | ||
|
||
if len(results) != 1 { | ||
return ForbiddenWithInternal(xerrors.Errorf("expect only 1 result, got %d", len(results)), input, results) | ||
} | ||
|
||
allowedResult, ok := (results[0].Bindings["allowed"]).(bool) | ||
if !ok { | ||
return ForbiddenWithInternal(xerrors.Errorf("expected allowed to be a bool but got %T", allowedResult), input, results) | ||
} | ||
|
||
if !allowedResult { | ||
return ForbiddenWithInternal(xerrors.Errorf("policy disallows request"), input, results) | ||
} | ||
|
||
return nil | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we do this in
init
and panic if an error occurs? Then users don't have to hold a struct, and can just callAuthorize
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking about that, and I just hate
init()
functions. I figure we can put theinit()
anywhere if it's a pain to pass around, meaning we can put the init incoderd
somewhere too. So we can decide toinit()
once we use this.We will need more PRs for integration, and that is when I think we can make that call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough fair enough