Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
ab61328
WIP: This is a massive WIP
Emyrk Mar 26, 2022
03e4d0f
More info in the print
Emyrk Mar 26, 2022
9981291
Fix all()
Emyrk Mar 27, 2022
3ab32da
reduce the amount of memoery allocated
Emyrk Mar 27, 2022
e1d5893
Reuse a buffer
Emyrk Mar 28, 2022
84a90f3
fix: use return size over size
Emyrk Mar 29, 2022
1fac0d9
WIP: don't look at this
Emyrk Mar 29, 2022
1e3aac0
WIP: 🍐 auth-> testdata, refactoring and restructuring
johnstcn Mar 30, 2022
e977e84
testdata -> authztest
johnstcn Mar 30, 2022
00a7c3f
WIP: start work on SVO
johnstcn Mar 30, 2022
1f04c01
reduce allocations for union sets
Emyrk Mar 31, 2022
fbf4db1
fix: Fix nil permissions as Strings()
Emyrk Mar 31, 2022
4946897
chore: Make all permission variant levels
Emyrk Mar 31, 2022
7e6cc66
First full draft of the authz authorize test
Emyrk Mar 31, 2022
a0017e5
Tally up failed tests
Emyrk Mar 31, 2022
4b110b3
Change test pkg
Emyrk Mar 31, 2022
65ef4e3
Use an interface for the object
Emyrk Mar 31, 2022
d294786
fix: make authztest.Objects return correct type
johnstcn Apr 1, 2022
c1f8945
refactor: rename consts {Read,Write,Modify,Delete}Action to Action$1
johnstcn Apr 1, 2022
01f3d40
chore: Define object interface
Emyrk Apr 1, 2022
de7de6e
test: Unit test extra properties
Emyrk Apr 1, 2022
4c86e44
Merge remote-tracking branch 'origin/stevenmasley/rbac' into stevenma…
Emyrk Apr 1, 2022
30c6568
put back interface assertion
Emyrk Apr 1, 2022
a419a65
Fix some compile errors from merge
Emyrk Apr 1, 2022
bbd1c4c
test: Roles, sets, permissions, iterators
Emyrk Apr 1, 2022
def010f
Test string functions
Emyrk Apr 1, 2022
c4ee590
test: Unit test permission string
Emyrk Apr 4, 2022
84e3ab9
Add A+ and A-
Emyrk Apr 4, 2022
c2eec18
Parallelize tests
Emyrk Apr 4, 2022
5a2834a
fix code line in readme
Emyrk Apr 4, 2022
913d141
Merge remote-tracking branch 'origin/main' into stevenmasley/rbac
Emyrk Apr 4, 2022
2804b92
test: ParsePermissions from strings
Emyrk Apr 4, 2022
5698938
use fmt over str builder for easier to read
Emyrk Apr 4, 2022
75ed8ef
Linting
Emyrk Apr 4, 2022
b2db661
authz: README.md: update table formatting
johnstcn Apr 5, 2022
26ef1e6
Make action CRUD
Emyrk Apr 5, 2022
19aba30
LevelID -> OrganizationID
Emyrk Apr 5, 2022
ceee9cd
feat: authztest: categorize test failures by test name
johnstcn Apr 5, 2022
ee8bf04
fixup! feat: authztest: categorize test failures by test name
johnstcn Apr 5, 2022
44c02a1
chore: add documentation for authz and authztest
johnstcn Apr 6, 2022
dfb9ad1
fixup! chore: add documentation for authz and authztest
johnstcn Apr 6, 2022
e482d2c
chore: more authz/authztest docs
johnstcn Apr 6, 2022
a4e038f
Remove underscore from test names
Emyrk Apr 6, 2022
9918c16
zObject does not need exported fields
Emyrk Apr 6, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
chore: add documentation for authz and authztest
  • Loading branch information
johnstcn committed Apr 6, 2022
commit 44c02a13b33f4c6b1973161a3a3c4d3d7b42be69
73 changes: 73 additions & 0 deletions coderd/authz/README.md
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** an **subject** has to perform **actions** to **resources**:
- **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.
- **Resource** here is anything that implements `authz.Resource`.

## Permission Structure

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

- **site** level applies to all resources in a given Coder deployment.
- **org** level applies to all resources that have an organization owner (`org_owner`)
- **user** level applies to all resources 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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we default to allows in the absence of denies?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that's a good idea.
Default deny is the de-facto behaviour of operation for the other authorization implementations I reviewed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just so I understand, workspaces.*.create would be interpreted as a deny by default in those implementations?

Copy link
Member

@johnstcn johnstcn Apr 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, for example here: https://sourcegraph.com/github.com/fleetdm/fleet/-/blob/server/authz/policy_test.go?L69

A nil user is not allowed to either read or write session. Obviously what you want to deny-by-default depends on what you're building.

- **negative** (denies)
- **abstain** (neither allows or denies, but interpreted as deny by default)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need abstain?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's part of the permission model.
Some permissions are not applicable to a given request.
They will return an abstain result in this case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this so we could display to customers which permission node is truly having an impact on the success/failure of an auth request?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not. Abstain is just naming the case where a permission node is irrelevant. So it's just naming this case so our language is complete in that we can describe all permissions.

Example: If the input action is read, then the perm *.*.*.update has no effect and passes no judgement. We named this abstain.

By default, if the only effect from a set of permissions is abstain, then we default to deny. Since they have no perm to access the object.


**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>.<resource>.<id>.<action>`, where:

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

## Example Permissions

- `+site.devurl.*.read`: allowed to perform the `read` action against all resources of type `devurl` in a given Coder deployment.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add docs on why site is required here? This seems like it'd be attached to a workspace.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eh, it's probably a bad example. +site:*.*.read might be better here.

- `-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 |

138 changes: 136 additions & 2 deletions coderd/authz/authztest/README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,93 @@
# Authztest

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.
Package `authztest` implements _exhaustive_ unit testing for the `authz` package.

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.
## Why this exists

The `authz.Authorize` function has three* inputs:
- Subject (for example, a user or API key)
- Resource (for example, a workspace or a DevURL)
- Action (for example, read or write).

**Not including the ruleset, which we're keeping static for the moment.*

Normally to test a pure function like this, you'd write a table test with all of the permutations by hand, for example:

```go
func Test_Authorize(t *testing.T) {
....
testCases := []struct {
name string
subject authz.Subject
resource authz.Resource
action authz.Action
expectedError error
}{
{
name: "site admin can write config",
subject: &User{ID: "admin"},
object: &authz.ZObject{
OrgOwner: "default",
ResourceType: authz.ResourceSiteConfig,
},
expectedError: nil,
},
...
}
for _, testCase := range testCases {
t.Run(testCase.Name, func(t *testing.T) { ... })
}
}
```

This approach is problematic because of the cardinality of the RBAC model.

Recall that the legacy `pkg/access/authorize`:
- Exposes 8 possible actions, 5 possible site-level roles, 4 possible org-level roles, and 24 possible resource types
- Enforces site-wide versus organization-wide permissions separately

The new authentication model must maintain backward compatibility with this model, whilst allowing additional features such as:
- User-level ownership (which means user-level permission enforcement)
- Resources shared between users (which means permissions granular down to resource IDs)
- Custom roles

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

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.

Instead, we generate (almost) all possible sets of inputs to the library, and ensure that `authz.Authorize` performs as expected.

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:
- There is a **large** but **finite** number of possible inputs to `authz.Authorize`,
- The solution space can be broken down into 9 groups, and
- Most importantly, *each group has the same expected result.*

## Testing Methodology


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.

**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.
**Noise** is simply a set of additional permissions at a lower level that *should not* be impactful.

For each group, we take the **impactful set** of permissions, and add **noise**, and combine this into a role.

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.

As some of these sets are quite large, we sample some of the noise to reduce the search space.

TODO: example.

## Permission Permutations

Recall that we define a permission as a 4-tuple of `(level, resource_type, resource_id, action)` (for example, `(site, workspace, 123, read)`).

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.

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.


| Row | * | Site | Org | Org:mem | User | Access |
|-----|------|------|------|---------|------|--------|
| W+ | Y+_ | YN_ | YN_ | YN_ | YN_ | Y |
Expand All @@ -21,3 +103,55 @@ This is the final pruned search space used in authz. Each set is represented by
| A+ | _ | _ | _ | _ | Y+_ | Y |
| A- | _ | _ | _ | _ | _ | N |

Each row in the above table corresponds to a set of test cases. These are described in the next section.

## Test Cases

There are 12 possible permutations.

### Case 1: W+

TODO

### Case 2: W-

TODO

### Case 3: S+

TODO

### Case 4: S-

TODO

### Case 5: O+

TODO

### Case 6: O-

TODO

### Case 7: M+

TODO

### Case 8: M-

TODO

### Case 9: U+

TODO

### Case 10: U-

TODO

### Case 11: A+

TODO
### Case 12: A-

TODO