-
Notifications
You must be signed in to change notification settings - Fork 1.6k
docs/website: Improve partial evaluation / data filtering documentation #8625
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
mmzzuu
wants to merge
4
commits into
open-policy-agent:main
Choose a base branch
from
mmzzuu:feature/8316-improve-docs-partial-eval
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+282
−2
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
f74a822
docs/website: Improve partial evaluation / data filtering documentation
mmzzuu a17069f
docs/website: fix formatting on partial eval documentation changes
mmzzuu e233fc4
Fixed changes requested by @charlieegan3 (squash before merge)
mmzzuu 3292365
fix formatting
mmzzuu File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,194 @@ | ||
| --- | ||
| title: "Tutorial: SQL Data Filtering" | ||
| sidebar_position: 6 | ||
| --- | ||
|
|
||
| This tutorial demonstrates end-to-end data filtering with OPA around a concrete question: **whose salaries can a Director see?** | ||
|
|
||
| You will write an authorization policy, use OPA's partial evaluation to derive a SQL `WHERE` clause, and apply that filter to a real database query. | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| - [OPA installed](../#1-download-opa) | ||
| - [sqlite3](https://sqlite.org/index.html) (pre-installed on macOS and most Linux distributions) | ||
| - `curl` and `jq` | ||
|
|
||
| ## Steps | ||
|
|
||
| ### 1. Create and populate the database | ||
|
|
||
| We'll work with the following dataset: | ||
|
|
||
| | name | department | role | salary | | ||
| | ----- | ----------- | -------- | ------ | | ||
| | Alice | engineering | director | 130000 | | ||
| | Bob | engineering | engineer | 90000 | | ||
| | Carol | engineering | engineer | 85000 | | ||
| | Dave | marketing | director | 120000 | | ||
| | Eve | marketing | manager | 95000 | | ||
|
|
||
| Save the following SQL to a file named `employees.sql`: | ||
|
|
||
| ```sql title="employees.sql" | ||
| CREATE TABLE employees (name TEXT, department TEXT, role TEXT, salary INTEGER); | ||
| INSERT INTO employees VALUES ('Alice', 'engineering', 'director', 130000); | ||
| INSERT INTO employees VALUES ('Bob', 'engineering', 'engineer', 90000); | ||
| INSERT INTO employees VALUES ('Carol', 'engineering', 'engineer', 85000); | ||
| INSERT INTO employees VALUES ('Dave', 'marketing', 'director', 120000); | ||
| INSERT INTO employees VALUES ('Eve', 'marketing', 'manager', 95000); | ||
| ``` | ||
|
|
||
| Then create the database by loading that file: | ||
|
|
||
| ```shell | ||
| sqlite3 company.db < employees.sql | ||
| ``` | ||
|
|
||
| ### 2. Write the policy | ||
|
|
||
| The rule is: Directors may see the salaries of employees in their own department. | ||
|
|
||
| `input.employees` is declared as _unknown_ — it represents database rows that OPA has not seen yet. `input.user` is _known_ at query time and its values will be substituted during partial evaluation. | ||
|
|
||
| Save the following Rego code to a file named `policy.rego`: | ||
|
|
||
| ```rego title="policy.rego" | ||
| # METADATA | ||
| # scope: package | ||
| # compile: | ||
| # unknowns: [input.employees] | ||
| package filters | ||
|
|
||
| include if { | ||
| input.user.role == "director" | ||
| input.employees.department == input.user.department | ||
| } | ||
| ``` | ||
|
|
||
| ### 3. Start OPA | ||
|
|
||
| ```shell | ||
| opa run --server policy.rego | ||
| ``` | ||
|
|
||
| OPA is now listening on `http://localhost:8181`. | ||
|
|
||
| ### 4. Ask OPA for a SQL filter | ||
|
|
||
| In another terminal, call the compile endpoint with the logged-in user as input. Alice is a Director in Engineering: | ||
|
|
||
| ```shell | ||
| curl -s -X POST http://localhost:8181/v1/compile/filters/include \ | ||
| -H "Content-Type: application/json" \ | ||
| -H "Accept: application/vnd.opa.sql.sqlite+json" \ | ||
| -d '{"input": {"user": {"name": "alice", "role": "director", "department": "engineering"}}}' | ||
| ``` | ||
|
|
||
| OPA partially evaluates the policy: | ||
|
|
||
| - `input.user.role == "director"` — both sides are known; the condition is true, so it is consumed. | ||
| - `input.employees.department == input.user.department` — the left hand side is unknown; the known right hand side (`"engineering"`) is substituted, yielding the SQL condition. | ||
|
|
||
| The response: | ||
|
|
||
| ```json | ||
| { | ||
| "result": { | ||
| "query": "WHERE employees.department = 'engineering'" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### 5. Query the database | ||
|
|
||
| Extract the filter and use it in a SQL query: | ||
|
|
||
| ```shell | ||
| FILTER=$(curl -s -X POST http://localhost:8181/v1/compile/filters/include \ | ||
| -H "Content-Type: application/json" \ | ||
| -H "Accept: application/vnd.opa.sql.sqlite+json" \ | ||
| -d '{"input": {"user": {"name": "alice", "role": "director", "department": "engineering"}}}' \ | ||
| | jq -r '.result.query') | ||
|
|
||
| sqlite3 company.db "SELECT name, salary FROM employees $FILTER;" | ||
| ``` | ||
|
|
||
| Output — Alice sees all Engineering salaries: | ||
|
|
||
| | name | salary | | ||
| | ----- | ------ | | ||
| | Alice | 130000 | | ||
| | Bob | 90000 | | ||
| | Carol | 85000 | | ||
|
|
||
| Dave is a Director in Marketing, so he gets a different filter from the same policy: | ||
|
|
||
| ```shell | ||
| FILTER=$(curl -s -X POST http://localhost:8181/v1/compile/filters/include \ | ||
| -H "Content-Type: application/json" \ | ||
| -H "Accept: application/vnd.opa.sql.sqlite+json" \ | ||
| -d '{"input": {"user": {"name": "dave", "role": "director", "department": "marketing"}}}' \ | ||
| | jq -r '.result.query') | ||
|
|
||
| sqlite3 company.db "SELECT name, salary FROM employees $FILTER;" | ||
| ``` | ||
|
|
||
| Output — Dave sees all Marketing salaries: | ||
|
|
||
| | name | salary | | ||
| | ---- | ------ | | ||
| | Dave | 120000 | | ||
| | Eve | 95000 | | ||
|
|
||
| ### 6. Non-Directors are denied | ||
|
|
||
| Bob is an Engineer, not a Director. The `input.user.role == "director"` condition is known and false, so no rule body can ever be satisfied — the policy unconditionally denies: | ||
|
|
||
| ```shell | ||
| curl -s -X POST http://localhost:8181/v1/compile/filters/include \ | ||
| -H "Content-Type: application/json" \ | ||
| -H "Accept: application/vnd.opa.sql.sqlite+json" \ | ||
| -d '{"input": {"user": {"name": "bob", "role": "engineer", "department": "engineering"}}}' | ||
| ``` | ||
|
|
||
| Response — the `query` key is absent: | ||
|
|
||
| ```json | ||
| {} | ||
| ``` | ||
|
|
||
| An absent `query` means unconditional deny. The application should return zero rows without issuing a database query. | ||
|
|
||
| :::warning Ensure safe defaults | ||
| OPA returns the filter — it does not enforce it. The application is responsible to use it as intended. | ||
|
|
||
| In this example, if the user is not a Director, no rule body can be satisfied and OPA returns an unconditional deny — represented as a missing `query` key in the result — meaning the application should safely return zero rows. | ||
| ::: | ||
|
|
||
| ## What partial evaluation did | ||
|
|
||
| OPA evaluated the policy with `input.user` fully known. The expressions that involved only known values (`input.user.role == "director"`) were fully evaluated and consumed — they do not appear in the output. Only expressions involving the unknown `input.employees` survived as residual conditions, which OPA then translated into SQL. | ||
|
|
||
| The application never needs to know _how_ the policy decides which salaries are visible. It sends user context and receives a SQL filter (or a deny) to act on. | ||
|
|
||
| ## Handling unconditional results | ||
|
|
||
| | OPA response | Meaning | Application action | | ||
| | -------------------------- | ------------------- | ---------------------------- | | ||
| | `{ "query": "WHERE ..." }` | Conditional allow | Append filter to SQL query | | ||
| | `{ "query": "" }` | Unconditional allow | Run query with no `WHERE` | | ||
| | `{}` | Unconditional deny | Return zero rows, skip query | | ||
|
|
||
| ## Clean up | ||
|
|
||
| Stop the OPA server with `Ctrl+C` in the terminal where it is running, then remove the files created during this tutorial: | ||
|
|
||
| ```shell | ||
| rm employees.sql policy.rego company.db | ||
| ``` | ||
|
|
||
| ## Next steps | ||
|
|
||
| - [Evaluating a Data Filter Policy](./partial-evaluation) — a step-by-step walkthrough of partial evaluation | ||
| - [Writing valid Data Filtering Policies](./fragment) — which Rego constructs are supported as filter conditions | ||
| - [Language SDKs](/ecosystem#languages) — in a production setup, using a language SDK is recommended over raw `curl` calls. The ecosystem page lists SDKs for Go, Java, Python, JavaScript, and more, all of which provide typed clients for the compile API used in this tutorial. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This metadata comment is in a bit of a strange place? Did you mean to use the package scope?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for your review @charlieegan3 ! And sorry for my late response!
I changed the metadata comments to use package scope consistently, I agree that this is more suitable / intuitive.