chore: improve rbac and add benchmark tooling (#18584)

## Description

This PR improves the RBAC package by refactoring the policy, enhancing
documentation, and adding utility scripts.

## Changes

* Refactored `policy.rego` for clarity and readability
* Updated README with OPA section
* Added `benchmark_authz.sh` script for authz performance testing and
comparison
* Added `gen_input.go` to generate input for `opa eval` testing
This commit is contained in:
Susana Ferreira
2025-06-27 12:05:34 +01:00
committed by GitHub
parent a5bfb200fc
commit 3cb9b20b11
5 changed files with 369 additions and 56 deletions
+91 -3
View File
@@ -102,18 +102,106 @@ Example of a scope for a workspace agent token, using an `allow_list` containing
}
```
## OPA (Open Policy Agent)
Open Policy Agent (OPA) is an open source tool used to define and enforce policies.
Policies are written in a high-level, declarative language called Rego.
Coder’s RBAC rules are defined in the [`policy.rego`](policy.rego) file under the `authz` package.
When OPA evaluates policies, it binds input data to a global variable called `input`.
In the `rbac` package, this structured data is defined as JSON and contains the action, object and subject (see `regoInputValue` in [astvalue.go](astvalue.go)).
OPA evaluates whether the subject is allowed to perform the action on the object across three levels: `site`, `org`, and `user`.
This is determined by the final rule `allow`, which aggregates the results of multiple rules to decide if the user has the necessary permissions.
Similarly to the input, OPA produces structured output data, which includes the `allow` variable as part of the evaluation result.
Authorization succeeds only if `allow` explicitly evaluates to `true`. If no `allow` is returned, it is considered unauthorized.
To learn more about OPA and Rego, see https://www.openpolicyagent.org/docs.
### Application and Database Integration
- [`rbac/authz.go`](authz.go) – Application layer integration: provides the core authorization logic that integrates with Rego for policy evaluation.
- [`database/dbauthz/dbauthz.go`](../database/dbauthz/dbauthz.go) – Database layer integration: wraps the database layer with authorization checks to enforce access control.
There are two types of evaluation in OPA:
- **Full evaluation**: Produces a decision that can be enforced.
This is the default evaluation mode, where OPA evaluates the policy using `input` data that contains all known values and returns output data with the `allow` variable.
- **Partial evaluation**: Produces a new policy that can be evaluated later when the _unknowns_ become _known_.
This is an optimization in OPA where it evaluates as much of the policy as possible without resolving expressions that depend on _unknown_ values from the `input`.
To learn more about partial evaluation, see this [OPA blog post](https://blog.openpolicyagent.org/partial-evaluation-162750eaf422).
Application of Full and Partial evaluation in `rbac` package:
- **Full Evaluation** is handled by the `RegoAuthorizer.Authorize()` method in [`authz.go`](authz.go).
This method determines whether a subject (user) can perform a specific action on an object.
It performs a full evaluation of the Rego policy, which returns the `allow` variable to decide whether access is granted (`true`) or denied (`false` or undefined).
- **Partial Evaluation** is handled by the `RegoAuthorizer.Prepare()` method in [`authz.go`](authz.go).
This method compiles OPA’s partial evaluation queries into `SQL WHERE` clauses.
These clauses are then used to enforce authorization directly in database queries, rather than in application code.
Authorization Patterns:
- Fetch-then-authorize: an object is first retrieved from the database, and a single authorization check is performed using full evaluation via `Authorize()`.
- Authorize-while-fetching: Partial evaluation via `Prepare()` is used to inject SQL filters directly into queries, allowing efficient authorization of many objects of the same type.
`dbauthz` methods that enforce authorization directly in the SQL query are prefixed with `Authorized`, for example, `GetAuthorizedWorkspaces`.
## Testing
You can test outside of golang by using the `opa` cli.
- OPA Playground: https://play.openpolicyagent.org/
- OPA CLI (`opa eval`): useful for experimenting with different inputs and understanding how the policy behaves under various conditions.
`opa eval` returns the constraints that must be satisfied for a rule to evaluate to `true`.
- `opa eval` requires an `input.json` file containing the input data to run the policy against.
You can generate this file using the [gen_input.go](../../scripts/rbac-authz/gen_input.go) script.
Note: the script currently produces a fixed input. You may need to tweak it for your specific use case.
**Evaluation**
### Full Evaluation
```bash
opa eval --format=pretty "data.authz.allow" -d policy.rego -i input.json
```
**Partial Evaluation**
This command fully evaluates the policy in the `policy.rego` file using the input data from `input.json`, and returns the result of the `allow` variable:
- `data.authz.allow` accesses the `allow` rule within the `authz` package.
- `data.authz` on its own would return the entire output object of the package.
This command answers the question: “Is the user allowed?”
### Partial Evaluation
```bash
opa eval --partial --format=pretty 'data.authz.allow' -d policy.rego --unknowns input.object.owner --unknowns input.object.org_owner --unknowns input.object.acl_user_list --unknowns input.object.acl_group_list -i input.json
```
This command performs a partial evaluation of the policy, specifying a set of unknown input parameters.
The result is a set of partial queries that can be converted into `SQL WHERE` clauses and injected into SQL queries.
This command answers the question: “What conditions must be met for the user to be allowed?”
### Benchmarking
Benchmark tests to evaluate the performance of full and partial evaluation can be found in `authz_test.go`.
You can run these tests with the `-bench` flag, for example:
```bash
go test -bench=BenchmarkRBACFilter -run=^$
```
To capture memory and CPU profiles, use the following flags:
- `-memprofile memprofile.out`
- `-cpuprofile cpuprofile.out`
The script [`benchmark_authz.sh`](../../scripts/rbac-authz/benchmark_authz.sh) runs the `authz` benchmark tests on the current Git branch or compares benchmark results between two branches using [`benchstat`](https://pkg.go.dev/golang.org/x/perf/cmd/benchstat).
`benchstat` compares the performance of a baseline benchmark against a new benchmark result and highlights any statistically significant differences.
- To run benchmark on the current branch:
```bash
benchmark_authz.sh --single
```
- To compare benchmarks between 2 branches:
```bash
benchmark_authz.sh --compare main prebuild_policy
```