Skip to content

Commit e482d2c

Browse files
committed
chore: more authz/authztest docs
1 parent dfb9ad1 commit e482d2c

File tree

2 files changed

+70
-19
lines changed

2 files changed

+70
-19
lines changed

coderd/authz/README.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,27 @@ Package `authz` implements AuthoriZation for Coder.
44

55
## Overview
66

7-
Authorization defines what **permission** an **subject** has to perform **actions** to **resources**:
7+
Authorization defines what **permission** an **subject** has to perform **actions** to **objects**:
88
- **Permission** is binary: *yes* (allowed) or *no* (denied).
99
- **Subject** in this case is anything that implements interface `authz.Subject`.
1010
- **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`.
11+
- **Object** here is anything that implements `authz.Object`.
1212

1313
## Permission Structure
1414

15-
A **permission** is a rule that grants or denies access for a **subject** to perform an **action** on a **resource**.
15+
A **permission** is a rule that grants or denies access for a **subject** to perform an **action** on a **object**.
1616
A **permission** is always applied at a given **level**:
1717

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.
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.
2121

2222
**Permissions** at a higher **level** always override permissions at a **lower** level.
2323

2424
The effect of a **permission** can be:
2525
- **positive** (allows)
2626
- **negative** (denies)
27-
- **abstain** (neither allows or denies, but interpreted as deny by default)
27+
- **abstain** (neither allows or denies, not applicable)
2828

2929
**Negative** permissions **always** override **positive** permissions at the same level.
3030
Both **negative** and **positive** permissions override **abstain** at the same level.
@@ -41,24 +41,24 @@ This can be represented by the following truth table, where Y represents *positi
4141

4242
## Permission Representation
4343

44-
**Permissions** are represented in string format as `<sign>?<level>.<resource>.<id>.<action>`, where:
44+
**Permissions** are represented in string format as `<sign>?<level>.<object>.<id>.<action>`, where:
4545

4646
- `sign` can be either `+` or `-`. If it is omitted, sign is assumed to be `+`.
4747
- `level` is either `*`, `site`, `org`, or `user`.
48-
- `resource` is any valid resource type.
48+
- `object` is any valid resource type.
4949
- `id` is any valid UUID v4.
5050
- `action` is `create`, `read`, `modify`, or `delete`.
5151

5252
## Example Permissions
5353

54-
- `+site.devurl.*.read`: allowed to perform the `read` action against all resources of type `devurl` in a given Coder deployment.
54+
- `+site.*.*.read`: allowed to perform the `read` action against all objects of type `devurl` in a given Coder deployment.
5555
- `-user.workspace.*.create`: user is not allowed to create workspaces.
5656

5757
## Roles
5858

5959
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.
6060

61-
The following table shows the per-level role evaluation logic.
61+
The following table shows the per-level role evaluation.
6262
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.
6363

6464
| Role (example) | Site | Org | User | Result |

coderd/authz/authztest/README.md

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
# Authztest
22
Package `authztest` implements _exhaustive_ unit testing for the `authz` package.
3+
34
## Why this exists
45
The `authz.Authorize` function has three* inputs:
56
- Subject (for example, a user or API key)
6-
- Resource (for example, a workspace or a DevURL)
7+
- Object (for example, a workspace or a DevURL)
78
- Action (for example, read or write).
9+
810
**Not including the ruleset, which we're keeping static for the moment.*
11+
912
Normally to test a pure function like this, you'd write a table test with all of the permutations by hand, for example:
13+
1014
```go
1115
func Test_Authorize(t *testing.T) {
1216
....
1317
testCases := []struct {
1418
name string
1519
subject authz.Subject
16-
resource authz.Resource
20+
resource authz.Object
1721
action authz.Action
1822
expectedError error
1923
}{
@@ -22,7 +26,7 @@ func Test_Authorize(t *testing.T) {
2226
subject: &User{ID: "admin"},
2327
object: &authz.ZObject{
2428
OrgOwner: "default",
25-
ResourceType: authz.ResourceSiteConfig,
29+
ObjectType: authz.ObjectSiteConfig,
2630
},
2731
expectedError: nil,
2832
},
@@ -33,36 +37,63 @@ func Test_Authorize(t *testing.T) {
3337
}
3438
}
3539
```
40+
3641
This approach is problematic because of the cardinality of the RBAC model.
42+
3743
Recall that the legacy `pkg/access/authorize`:
44+
3845
- Exposes 8 possible actions, 5 possible site-level roles, 4 possible org-level roles, and 24 possible resource types
3946
- Enforces site-wide versus organization-wide permissions separately
47+
4048
The new authentication model must maintain backward compatibility with this model, whilst allowing additional features such as:
49+
4150
- User-level ownership (which means user-level permission enforcement)
42-
- Resources shared between users (which means permissions granular down to resource IDs)
51+
- Objects shared between users (which means permissions granular down to resource IDs)
4352
- Custom roles
53+
4454
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**.
45-
We want to have a high level of confidence that changes to the implementation **do not have unintended side-effects**.
46-
This means that simply manually writing a set of test cases possibly risks errors slipping through the cracks.
55+
56+
We want to have a high level of confidence that changes to the implementation **do not have unintended side-effects**. This means that simply manually writing a set of test cases possibly risks errors slipping through the cracks.
57+
4758
Instead, we generate (almost) all possible sets of inputs to the library, and ensure that `authz.Authorize` performs as expected.
59+
4860
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:
4961
- There is a **large** but **finite** number of possible inputs to `authz.Authorize`,
5062
- The solution space can be broken down into 9 groups, and
5163
- Most importantly, *each group has the same expected result.*
64+
65+
5266
## Testing Methodology
67+
5368
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.
69+
5470
**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.
71+
5572
**Noise** is simply a set of additional permissions at a lower level that *should not* be impactful.
73+
5674
For each group, we take the **impactful set** of permissions, and add **noise**, and combine this into a role.
75+
5776
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+
5878
As some of these sets are quite large, we sample some of the noise to reduce the search space.
79+
80+
We also perform permutation on the **objects** of the test case, explained in [Object Permutations](#object-permutations)
81+
5982
**Example:**
60-
`+site:resource:abc123:create` will always override `-user:resource:*:*`, `-user:*:abc123:*`, `-org:resource:*:create`, and so on. All permutations of those sorts of noise permissions should never change the expected result.
83+
84+
`+site:*:*:create` will always override `-user:resource:*:*`, `-user:*:abc123:*`, `-org:resource:*:create`, and so on. All permutations of those sorts of noise permissions should never change the expected result.
85+
86+
6187
## Role Permutations
88+
6289
Recall that we define a permission as a 4-tuple of `(level, resource_type, resource_id, action)` (for example, `(site, workspace, 123, read)`).
90+
6391
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.
64-
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.
92+
93+
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.
94+
6595
The resulting search space for a row is the cross product between all sets in said row. `+` indicates the union of two sets. For example, Y+_ indicates the union of all positive permissions and abstain permissions.
96+
6697
| Row | * | Site | Org | Org:mem | User | Access |
6798
|-----|------|------|------|---------|------|--------|
6899
| W+ | Y+_ | YN_ | YN_ | YN_ | YN_ | Y |
@@ -77,8 +108,11 @@ The resulting search space for a row is the cross product between all sets in sa
77108
| U- | _ | _ | _ | _ | N+Y_ | N |
78109
| A+ | _ | _ | _ | _ | Y+_ | Y |
79110
| A- | _ | _ | _ | _ | _ | N |
111+
80112
Each row in the above table corresponds to a set of role permutations.
113+
81114
There are 12 possible groups of role permutations:
115+
82116
- Case 1 (W+):
83117
- Impactful set: positive wildcard permissions.
84118
- Noise: positive, negative, abstain across site, org, org-member, and user levels.
@@ -127,3 +161,20 @@ There are 12 possible groups of role permutations:
127161
- Impactful set: nil permission.
128162
- Noise: abstain on user level.
129163
- Expected result: deny.
164+
165+
166+
## Object Permutations
167+
168+
Aside from the test inputs, we also perform permutations on the object. There are 9 possible permuations based on the object, and these 9 test cases all have four distinct possibilities. These are illustrated by the below table:
169+
170+
| # | Owner | Org-Owner | Result |
171+
|---|---------|-----------|----------------------------------------|
172+
| 1 | `me` | `mem` | Defer |
173+
| 2 | `other` | `mem` | `U+` and `U-` return `false`. |
174+
| 3 | `""` | `mem` | As above. |
175+
| 4 | `me` | `non-mem` | `O+`, `O-`, `U+`, `U-` return `false`. |
176+
| 5 | `other` | `non-mem` | As above. |
177+
| 6 | `other` | `""` | As above. |
178+
| 7 | `""` | `non-mem` | As above. |
179+
| 8 | `""` | `""` | As above. |
180+
| 9 | ` me` | `""` | `O+` and `O-` abstain. Defer to user. |

0 commit comments

Comments
 (0)