You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Package `authz` implements AuthoriZation for Coder.
4
+
5
+
## Overview
6
+
7
+
Authorization defines what **permission** an **subject** has to perform **actions** to **resources**:
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
+
-**Resource** here is anything that implements `authz.Resource`.
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 **resource**.
16
+
A **permission** is always applied at a given **level**:
17
+
18
+
-**site** level applies to all resources in a given Coder deployment.
19
+
-**org** level applies to all resources that have an organization owner (`org_owner`)
20
+
-**user** level applies to all resources 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, but interpreted as deny by default)
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>.<resource>.<id>.<action>`, where:
45
+
46
+
-`sign` can be either `+` or `-`. If it is omitted, sign is assumed to be `+`.
47
+
-`level` is either `*`, `site`, `org`, or `user`.
48
+
-`resource` 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.devurl.*.read`: allowed to perform the `read` action against all resources 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.
Copy file name to clipboardExpand all lines: coderd/authz/authztest/README.md
+136-2Lines changed: 136 additions & 2 deletions
Original file line number
Diff line number
Diff line change
@@ -1,11 +1,93 @@
1
1
# Authztest
2
2
3
-
An authz permission is a combination of `level`, `resource_type`, `resource_id`, and `action`. For testing purposes, we can assume only 1 action and resource exists. This package can generate all possible permissions from this.
3
+
Package `authztest` implements _exhaustive_ unit testing for the `authz`package.
4
4
5
-
A `Set` is a slice of permissions. The search space of all possible sets is too large, so instead this package allows generating more meaningful sets for testing. This is equivalent to pruning in AI problems: a technique to reduce the size of the search space by removing parts that do not have significance.
5
+
## Why this exists
6
+
7
+
The `authz.Authorize` function has three* inputs:
8
+
- Subject (for example, a user or API key)
9
+
- Resource (for example, a workspace or a DevURL)
10
+
- Action (for example, read or write).
11
+
12
+
**Not including the ruleset, which we're keeping static for the moment.*
13
+
14
+
Normally to test a pure function like this, you'd write a table test with all of the permutations by hand, for example:
15
+
16
+
```go
17
+
funcTest_Authorize(t *testing.T) {
18
+
....
19
+
testCases:= []struct {
20
+
name string
21
+
subject authz.Subject
22
+
resource authz.Resource
23
+
action authz.Action
24
+
expectedError error
25
+
}{
26
+
{
27
+
name: "site admin can write config",
28
+
subject: &User{ID: "admin"},
29
+
object: &authz.ZObject{
30
+
OrgOwner: "default",
31
+
ResourceType: authz.ResourceSiteConfig,
32
+
},
33
+
expectedError: nil,
34
+
},
35
+
...
36
+
}
37
+
for_, testCase:=range testCases {
38
+
t.Run(testCase.Name, func(t *testing.T) { ... })
39
+
}
40
+
}
41
+
```
42
+
43
+
This approach is problematic because of the cardinality of the RBAC model.
44
+
45
+
Recall that the legacy `pkg/access/authorize`:
46
+
- Exposes 8 possible actions, 5 possible site-level roles, 4 possible org-level roles, and 24 possible resource types
47
+
- Enforces site-wide versus organization-wide permissions separately
48
+
49
+
The new authentication model must maintain backward compatibility with this model, whilst allowing additional features such as:
50
+
- User-level ownership (which means user-level permission enforcement)
51
+
- Resources shared between users (which means permissions granular down to resource IDs)
52
+
- Custom roles
53
+
54
+
The resulting permissions model ([documented in Notion](https://www.notion.so/coderhq/Workspaces-V2-Authz-RBAC-24fd193386eb4cf79a282a2a69e8f917)) results in a large **finite** solution space in the order of **hundreds of millions**.
55
+
56
+
We want to have a high level of confidence that changes to the implementation **do not have unintended side-effects**.
57
+
This means that simply manually writing a set of test cases possibly risks errors slipping through the cracks.
58
+
59
+
Instead, we generate (almost) all possible sets of inputs to the library, and ensure that `authz.Authorize` performs as expected.
60
+
61
+
The actual investigation of the solution space is [documented in Notion](https://www.notion.so/coderhq/Authz-Exhaustive-Testing-7683ea694c6e4c12ab0124439916b13a), but the crucial take-away of that document is:
62
+
- There is a **large** but **finite** number of possible inputs to `authz.Authorize`,
63
+
- The solution space can be broken down into 9 groups, and
64
+
- Most importantly, *each group has the same expected result.*
65
+
66
+
## Testing Methodology
67
+
68
+
69
+
We group the search space into a number of groups. Each group corresponds to a set of test cases with the same expected result. Each group consists of a set of **impactful** permissions and a set of **noise** permissions.
70
+
71
+
**Impactful** permissions are the top-level permissions that are expected to override anything else, and should be the only inputs that determine the expected result.
72
+
**Noise** is simply a set of additional permissions at a lower level that *should not* be impactful.
73
+
74
+
For each group, we take the **impactful set** of permissions, and add **noise**, and combine this into a role.
75
+
76
+
We then take the *set cross-product* of the **impactful set** and the **noise**, and assert that the expected access level of that role to perform a given action.
77
+
78
+
As some of these sets are quite large, we sample some of the noise to reduce the search space.
79
+
80
+
TODO: example.
81
+
82
+
## Permission Permutations
83
+
84
+
Recall that we define a permission as a 4-tuple of `(level, resource_type, resource_id, action)` (for example, `(site, workspace, 123, read)`).
85
+
86
+
A `Set` is a slice of permissions. The search space of all possible permissions is too large, so instead this package allows generating more meaningful sets for testing. This is equivalent to pruning in AI problems: a technique to reduce the size of the search space by removing parts that do not have significance.
6
87
7
88
This is the final pruned search space used in authz. Each set is represented by a Y, N, or _. The leftmost set in a row that is not '_' is the impactful set. The impactful set determines the access result. All other sets are non-impactful, and should include the `<nil>` permission. The resulting search space for a row is the cross product between all sets in said row.
0 commit comments