Skip to content

Commit 4a1a133

Browse files
committed
chore: Merge branch 'main' of github.com:coder/coder into bq/992/notifications
2 parents 42a827e + 5ecc823 commit 4a1a133

File tree

15 files changed

+1335
-7
lines changed

15 files changed

+1335
-7
lines changed

cli/templatecreate.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ func templateCreate() *cobra.Command {
113113
"The "+cliui.Styles.Keyword.Render(templateName)+" template has been created! "+
114114
"Developers can provision a workspace with this template using:")+"\n")
115115

116-
_, _ = fmt.Fprintln(cmd.OutOrStdout(), " "+cliui.Styles.Code.Render("coder workspace create "+templateName))
116+
_, _ = fmt.Fprintln(cmd.OutOrStdout(), " "+cliui.Styles.Code.Render("coder workspaces create "+templateName))
117117
_, _ = fmt.Fprintln(cmd.OutOrStdout())
118118

119119
return nil

coderd/rbac/README.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Authz
2+
3+
Package `authz` implements AuthoriZation for Coder.
4+
5+
## Overview
6+
7+
Authorization defines what **permission** a **subject** has to perform **actions** to **objects**:
8+
- **Permission** is binary: *yes* (allowed) or *no* (denied).
9+
- **Subject** in this case is anything that implements interface `authz.Subject`.
10+
- **Action** here is an enumerated list of actions, but we stick to `Create`, `Read`, `Update`, and `Delete` here.
11+
- **Object** here is anything that implements `authz.Object`.
12+
13+
## Permission Structure
14+
15+
A **permission** is a rule that grants or denies access for a **subject** to perform an **action** on a **object**.
16+
A **permission** is always applied at a given **level**:
17+
18+
- **site** level applies to all objects in a given Coder deployment.
19+
- **org** level applies to all objects that have an organization owner (`org_owner`)
20+
- **user** level applies to all objects that have an owner with the same ID as the subject.
21+
22+
**Permissions** at a higher **level** always override permissions at a **lower** level.
23+
24+
The effect of a **permission** can be:
25+
- **positive** (allows)
26+
- **negative** (denies)
27+
- **abstain** (neither allows or denies, not applicable)
28+
29+
**Negative** permissions **always** override **positive** permissions at the same level.
30+
Both **negative** and **positive** permissions override **abstain** at the same level.
31+
32+
This can be represented by the following truth table, where Y represents *positive*, N represents *negative*, and _ represents *abstain*:
33+
34+
| Action | Positive | Negative | Result |
35+
|--------|----------|----------|--------|
36+
| read | Y | _ | Y |
37+
| read | Y | N | N |
38+
| read | _ | _ | _ |
39+
| read | _ | N | Y |
40+
41+
42+
## Permission Representation
43+
44+
**Permissions** are represented in string format as `<sign>?<level>.<object>.<id>.<action>`, where:
45+
46+
- `negated` can be either `+` or `-`. If it is omitted, sign is assumed to be `+`.
47+
- `level` is either `site`, `org`, or `user`.
48+
- `object` is any valid resource type.
49+
- `id` is any valid UUID v4.
50+
- `action` is `create`, `read`, `modify`, or `delete`.
51+
52+
## Example Permissions
53+
54+
- `+site.*.*.read`: allowed to perform the `read` action against all objects of type `devurl` in a given Coder deployment.
55+
- `-user.workspace.*.create`: user is not allowed to create workspaces.
56+
57+
## Roles
58+
59+
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.
60+
61+
The following table shows the per-level role evaluation.
62+
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.
63+
64+
| Role (example) | Site | Org | User | Result |
65+
|-----------------|------|-----|------|--------|
66+
| site-admin | Y | YN_ | YN_ | Y |
67+
| no-permission | N | YN_ | YN_ | N |
68+
| org-admin | _ | Y | YN_ | Y |
69+
| non-org-member | _ | N | YN_ | N |
70+
| user | _ | _ | Y | Y |
71+
| | _ | _ | N | N |
72+
| unauthenticated | _ | _ | _ | N |
73+

coderd/rbac/action.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package rbac
2+
3+
// Action represents the allowed actions to be done on an object.
4+
type Action string
5+
6+
const (
7+
ActionCreate = "create"
8+
ActionRead = "read"
9+
ActionUpdate = "update"
10+
ActionDelete = "delete"
11+
)

coderd/rbac/authz.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package rbac
2+
3+
import (
4+
"context"
5+
_ "embed"
6+
7+
"golang.org/x/xerrors"
8+
9+
"github.com/open-policy-agent/opa/rego"
10+
)
11+
12+
// RegoAuthorizer will use a prepared rego query for performing authorize()
13+
type RegoAuthorizer struct {
14+
query rego.PreparedEvalQuery
15+
}
16+
17+
// Load the policy from policy.rego in this directory.
18+
//go:embed policy.rego
19+
var policy string
20+
21+
func NewAuthorizer() (*RegoAuthorizer, error) {
22+
ctx := context.Background()
23+
query, err := rego.New(
24+
// allowed is the `allow` field from the prepared query. This is the field to check if authorization is
25+
// granted.
26+
rego.Query("allowed = data.authz.allow"),
27+
rego.Module("policy.rego", policy),
28+
).PrepareForEval(ctx)
29+
30+
if err != nil {
31+
return nil, xerrors.Errorf("prepare query: %w", err)
32+
}
33+
return &RegoAuthorizer{query: query}, nil
34+
}
35+
36+
type authSubject struct {
37+
ID string `json:"id"`
38+
Roles []Role `json:"roles"`
39+
}
40+
41+
func (a RegoAuthorizer) Authorize(ctx context.Context, subjectID string, roles []Role, action Action, object Object) error {
42+
input := map[string]interface{}{
43+
"subject": authSubject{
44+
ID: subjectID,
45+
Roles: roles,
46+
},
47+
"object": object,
48+
"action": action,
49+
}
50+
51+
results, err := a.query.Eval(ctx, rego.EvalInput(input))
52+
if err != nil {
53+
return ForbiddenWithInternal(xerrors.Errorf("eval rego: %w, err"), input, results)
54+
}
55+
56+
if len(results) != 1 {
57+
return ForbiddenWithInternal(xerrors.Errorf("expect only 1 result, got %d", len(results)), input, results)
58+
}
59+
60+
allowedResult, ok := (results[0].Bindings["allowed"]).(bool)
61+
if !ok {
62+
return ForbiddenWithInternal(xerrors.Errorf("expected allowed to be a bool but got %T", allowedResult), input, results)
63+
}
64+
65+
if !allowedResult {
66+
return ForbiddenWithInternal(xerrors.Errorf("policy disallows request"), input, results)
67+
}
68+
69+
return nil
70+
}

0 commit comments

Comments
 (0)