mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-12 19:32:48 -06:00
docs: Update documentation to be more consistent and sound more like reference docs (#8786)
There are a lot of edits here. I am trying to make the docs more consistent between pages and sections, each commit is one category of change so it might be easier to go through commit by commit to see all similar changes in the same place. The main goal was to make the language sound more like reference docs than product marketing, i.e. a little more formal with fewer filler words in places. Rephrase to avoid informal 'we/our' guidance, formatting updates for things like admonitions. --------- Signed-off-by: Charlie Egan <charlie_egan@apple.com>
This commit is contained in:
@@ -48,7 +48,7 @@ standard GitHub fork workflow. See [OPA Dev Instructions](DEVELOPMENT.md)
|
||||
```
|
||||
|
||||
Note: This stage can fail if you have not registered an [SSH key](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/adding-a-new-ssh-key-to-your-github-account)
|
||||
on your Github account.
|
||||
on your GitHub account.
|
||||
|
||||
1. Create a release branch off of `main`, to ensure you don't mangle your
|
||||
fork while creating the release:
|
||||
|
||||
@@ -10,7 +10,7 @@ invoked before a resource is created, updated or deleted.
|
||||
|
||||
AWS currently supports hooks written in either Java or Python, and provides a
|
||||
[sample repository](https://github.com/aws-cloudformation/aws-cloudformation-samples), which includes example hooks
|
||||
written in both languages. Since we'd rather use OPA for this purpose, we'd need some code to process the requests
|
||||
written in both languages. Since OPA is preferred for this purpose, some code is needed to process the requests
|
||||
handled by the hook and send them forward to OPA for policy decisions via its
|
||||
[REST API](https://www.openpolicyagent.org/docs/rest-api) using
|
||||
the [OPA AWS CloudFormation Hook](https://github.com/StyraOSS/opa-aws-cloudformation-hook).
|
||||
@@ -19,10 +19,10 @@ the [OPA AWS CloudFormation Hook](https://github.com/StyraOSS/opa-aws-cloudforma
|
||||
|
||||
This tutorial shows how to deploy an AWS CloudFormation Hook that forwards requests to OPA for policy decisions,
|
||||
allowing us to use policy to determine whether a request to create, update or delete a resource should be
|
||||
allowed or denied. We'll learn how to author policies that take the input structure of CloudFormation Templates into
|
||||
allowed or denied. This tutorial covers authoring policies that take the input structure of CloudFormation Templates into
|
||||
account, and some special considerations to be aware of in this environment.
|
||||
|
||||
In addition, this tutorial shows how we can leverage dynamic policy composition to group and structure our policies in a
|
||||
In addition, this tutorial shows how dynamic policy composition can group and structure policies in a
|
||||
way that follows the domain to which they apply.
|
||||
|
||||
## Prerequisites
|
||||
@@ -106,7 +106,7 @@ The hook is now installed, configured and activated!
|
||||
|
||||
### 3. Learn the Domain
|
||||
|
||||
Before we proceed to write our first policy, let's take a closer look at the data we'll be working with.
|
||||
Before writing the first policy, take a closer look at the data used in this tutorial.
|
||||
|
||||
#### AWS CloudFormation Templates
|
||||
|
||||
@@ -167,9 +167,9 @@ Any request denied will be logged in [AWS CloudWatch](https://aws.amazon.com/clo
|
||||
|
||||
### 4. Write a CloudFormation Hook Policy
|
||||
|
||||
With knowledge of the domain and the data model, we're ready to write our first CloudFormation Hook policy. Since we'll
|
||||
have a single OPA endpoint servicing requests for all types of resources, we'll use the
|
||||
[default decision](./configuration/#miscellaneous) policy, which by default queries the `system.main` rule. Let's add a
|
||||
With knowledge of the domain and the data model, it is time to write the first CloudFormation Hook policy. Since
|
||||
a single OPA endpoint services requests for all types of resources, the tutorial uses the
|
||||
[default decision](./configuration/#miscellaneous) policy, which by default queries the `system.main` rule. Add a
|
||||
simple policy to block an S3 Bucket unless it has an `AccessControl` attribute set to `Private`:
|
||||
|
||||
```rego
|
||||
@@ -197,8 +197,8 @@ bucket_is_private if {
|
||||
}
|
||||
```
|
||||
|
||||
Since we know that CloudFormation Templates may contain only the bare minimum of information, we can't assume that there
|
||||
will be an `AccessControl` attribute present in the input at all. Using negation of boolean rules inside of our `deny`
|
||||
Since CloudFormation Templates may contain only the bare minimum of information, it is not safe to assume that there
|
||||
will be an `AccessControl` attribute present in the input at all. Using negation of boolean rules inside the `deny`
|
||||
rules help alleviate the problem of values potentially being undefined. Compare to the following deny rule, which might
|
||||
look correct at a first glance:
|
||||
|
||||
@@ -240,7 +240,7 @@ block_public_acls if {
|
||||
|
||||
### 5. Policy Enforcement Testing
|
||||
|
||||
With the above policy loaded into OPA, we may proceed to try it out. Let's deploy the minimal S3 Bucket from the
|
||||
With the above policy loaded into OPA, deploy the minimal S3 Bucket from the
|
||||
previous template example. Save the below minimal template to a file called `s3bucket.yaml`:
|
||||
|
||||
```yaml
|
||||
@@ -249,15 +249,15 @@ Resources:
|
||||
Type: AWS::S3::Bucket
|
||||
```
|
||||
|
||||
Since our S3 bucket doesn't have an `AccessControl` attribute, it should be denied by the hook. We
|
||||
deploy a template by creating a **stack**:
|
||||
Since the S3 bucket doesn't have an `AccessControl` attribute, it should be denied by the hook.
|
||||
Deploy a template by creating a **stack**:
|
||||
|
||||
```shell
|
||||
aws cloudformation create-stack --stack-name cfn-s3 --template-body file://s3bucket.yaml
|
||||
```
|
||||
|
||||
The output of the above command will simply be a confirmation that the stack was deployed. It won't tell us whether the
|
||||
deployment was successful or not. In order to know that, we'll need to check the stack events:
|
||||
deployment was successful or not. In order to know that, check the stack events:
|
||||
|
||||
```shell
|
||||
aws cloudformation describe-stack-events --stack-name cfn-s3
|
||||
@@ -288,8 +288,7 @@ should now find an item describing that the hook denied the request, and its rea
|
||||
}
|
||||
```
|
||||
|
||||
Congratulations! You've just successfully enforced your first CloudFormation Hook policy using OPA. Let's update the
|
||||
template so that it passes our policy requirement:
|
||||
The policy is now enforced. Update the template so that it passes the policy requirement:
|
||||
|
||||
**s3bucket.yaml**
|
||||
|
||||
@@ -301,14 +300,14 @@ Resources:
|
||||
AccessControl: Private
|
||||
```
|
||||
|
||||
Even though our stack did not create an S3 bucket (as the change got rolled back), the **stack** still exists.
|
||||
In order to try again, we'll first need to delete the existing stack:
|
||||
Even though the stack did not create an S3 bucket (as the change got rolled back), the **stack** still exists.
|
||||
To try again, first delete the existing stack:
|
||||
|
||||
```shell
|
||||
aws cloudformation delete-stack --stack-name cfn-s3
|
||||
```
|
||||
|
||||
Now, let's try again:
|
||||
Try again:
|
||||
|
||||
```shell
|
||||
aws cloudformation create-stack --stack-name cfn-s3 --template-body file://s3bucket.yaml
|
||||
@@ -335,7 +334,7 @@ a bit later.
|
||||
}
|
||||
```
|
||||
|
||||
Note: once our stack is successfully deployed, we can use the `update-stack` command after we've made changes to our
|
||||
Note: once the stack is successfully deployed, the `update-stack` command can be used after changes are made to
|
||||
templates:
|
||||
|
||||
```shell
|
||||
@@ -346,11 +345,11 @@ aws cloudformation update-stack --stack-name cfn-s3 --template-body file://s3buc
|
||||
|
||||
### Dynamic Policy Composition
|
||||
|
||||
Having a single policy file for all rules will quickly become unwieldy. Could we improve this somehow? One way of doing
|
||||
Having a single policy file for all rules will quickly become unwieldy. Is there room for improvement? One way of doing
|
||||
that would be to use dynamic policy composition, where a single main policy acts as a "router", and forwards queries to
|
||||
other packages based on attributes from the input. A natural attribute to use for CloudFormation templates might for
|
||||
example be the resource type, allowing us to group our policies by the resource type they're meant to act on. Let's
|
||||
take a look at what such a main policy might look like:
|
||||
example be the resource type, allowing policies to be grouped by the resource type they are meant to act on.
|
||||
Here is what such a main policy might look like:
|
||||
|
||||
```rego title="main.rego"
|
||||
# METADATA
|
||||
@@ -422,15 +421,15 @@ The above policy will invoke the `route` rule to determine which package should
|
||||
`input.resource.type`, transforming a value such as `AWS::S3::Bucket` into a call to the `data.aws.s3.bucket` package,
|
||||
where each rule named `deny` will be evaluated, and the result aggregated into the final decision.
|
||||
|
||||
Since most of our policies will only deal with `CREATE` or `UPDATE` actions, we'd rather want to avoid having to check
|
||||
for this in all of our rules. Instead, we'll have the router append `.delete` to the package name for `DELETE`
|
||||
Since most policies only deal with `CREATE` or `UPDATE` actions, it is better to avoid checking
|
||||
for this in all rules. Instead, the router appends `.delete` to the package name for `DELETE`
|
||||
operations, so that a request to delete e.g. an S3 bucket would invoke the `data.aws.s3.bucket.delete` package (if it
|
||||
exists).
|
||||
|
||||
Additionally, we'll also do some simple input validation at this stage, so that we may avoid doing so in our resource
|
||||
specific policies.
|
||||
Additionally, some simple input validation is done at this stage, to avoid repeating it in each
|
||||
resource-specific policy.
|
||||
|
||||
We can now modify our original policy to verify S3 bucket resources only:
|
||||
Modify the original policy to verify S3 bucket resources only:
|
||||
|
||||
```rego
|
||||
package aws.s3.bucket
|
||||
@@ -444,7 +443,7 @@ bucket_is_private if {
|
||||
}
|
||||
```
|
||||
|
||||
Note how we no longer need the `bucket_create_or_update` rule, as that is already asserted by the main policy.
|
||||
Note that the `bucket_create_or_update` rule is no longer needed, as that is already asserted by the main policy.
|
||||
Quite an improvement in terms of readability, and a good foundation for further policy authoring. If you'd like to see
|
||||
more examples of policy utilizing this pattern, check out the
|
||||
[policy directory](https://github.com/StyraOSS/opa-aws-cloudformation-hook/tree/main/examples/policy) in the OPA AWS
|
||||
|
||||
@@ -5,13 +5,12 @@ sidebar_position: 1
|
||||
|
||||
# Using OPA in CI/CD Pipelines
|
||||
|
||||
OPA is a great tool for implementing policy-as-code guardrails in
|
||||
OPA supports implementing policy-as-code guardrails in
|
||||
<abbr title="continuous integration/continuous deployment">CI/CD</abbr>
|
||||
pipelines. With OPA, you can automatically verify configurations, validate
|
||||
outputs, and enforce organizational policies before code reaches production. OPA
|
||||
serves as a powerful 'swiss army knife' for implementing custom checks required
|
||||
by your organization that might be difficult to implement in a script or in
|
||||
another tool.
|
||||
can implement custom checks required by your organization that might be
|
||||
difficult to implement in a script or in another tool.
|
||||
|
||||
For users looking to parse and validate configuration files or Infrastructure as
|
||||
Code (IaC) committed to git, [Conftest](https://www.conftest.dev) is typically
|
||||
@@ -19,8 +18,8 @@ the better choice as it supports many file formats (HCL, Jsonnet etc.).
|
||||
However, OPA's `eval` command excels at connecting other tools and making checks
|
||||
against runtime data, as it can only parse JSON and YAML formats.
|
||||
|
||||
OPA as a CLI tool provides powerful capabilities for testing and validating
|
||||
various types of data in your continuous integration workflows:
|
||||
OPA's CLI supports testing and validating various types of data in your
|
||||
continuous integration workflows:
|
||||
|
||||
- **Repository governance** - Use OPA to call GitHub APIs to validate commit
|
||||
message formats and pull request metadata compliance.
|
||||
@@ -89,7 +88,7 @@ jobs:
|
||||
'input.results[_].coverage < 0.7'
|
||||
```
|
||||
|
||||
Here's some examples of how we use these actions in our own CI/CD pipelines for OPA!
|
||||
Here are some examples of how these actions are used in OPA's CI/CD pipelines:
|
||||
|
||||
- [Pull Request Workflow File](https://github.com/open-policy-agent/opa/blob/main/.github/workflows/pull-request.yaml)
|
||||
- [PR Where Action was Introduced](https://github.com/open-policy-agent/opa/pull/8183/files#diff-a8619735ff14304aa0514284f86ff5145b0a6bae2e76a37faeb0ad899a3d8db4R27-R30)
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
title: Access Control Systems
|
||||
---
|
||||
|
||||
Often the easiest way to understand a new tool or language is by comparing it to
|
||||
what you already know. Here we show how policies from several existing policy
|
||||
Comparing a new tool or language to what you already know can help build familiarity. This page shows how policies from several existing policy
|
||||
systems can be implemented with the Open Policy Agent and Rego.
|
||||
|
||||
:::tip
|
||||
Looking for comparisons between OPA's Rego language and languages like Go, Java
|
||||
and Python? Head to our [language comparison](../comparisons/languages)
|
||||
and Python? See the [language comparison](../comparisons/languages)
|
||||
documentation.
|
||||
:::
|
||||
|
||||
@@ -25,7 +24,7 @@ Once you provide RBAC with both those assignments, RBAC tells you
|
||||
how to make an authorization decision. A user is authorized for
|
||||
all those permissions assigned to any of the roles she is assigned to.
|
||||
|
||||
For example, we might have the following user/role assignments:
|
||||
For example, consider the following user/role assignments:
|
||||
|
||||
| User | Role |
|
||||
| ------- | ------------- |
|
||||
@@ -115,8 +114,7 @@ in each pair below would violate SOD.
|
||||
|
||||
OPA's API does not yet let you enforce SOD by rejecting improper role-assignments,
|
||||
but it does let you express SOD constraints and ask for all SOD violations,
|
||||
as shown below. (Here we assume the statements below are added to the RBAC
|
||||
statements above.)
|
||||
as shown below. (The statements below extend the RBAC statements above.)
|
||||
|
||||
```rego
|
||||
# Pairs of roles that no user can be assigned to simultaneously
|
||||
@@ -152,7 +150,7 @@ It has three main components:
|
||||
- Attributes for objects
|
||||
- Logic dictating which attribute combinations are authorized
|
||||
|
||||
For example, we might know the following attributes for our users
|
||||
For example, consider the following attributes for users
|
||||
|
||||
- alice
|
||||
- joined the company 15 years ago
|
||||
@@ -161,7 +159,7 @@ For example, we might know the following attributes for our users
|
||||
- joined the company 5 years ago
|
||||
- is an analyst
|
||||
|
||||
We would also have attributes for the objects, in this case stock ticker symbols.
|
||||
Attributes for the objects, in this case stock ticker symbols, are also needed.
|
||||
|
||||
- MSFT
|
||||
- is sold on NASDAQ
|
||||
@@ -237,7 +235,7 @@ allow if {
|
||||
In OPA, there's nothing special about users and objects. You can attach
|
||||
attributes to anything. And the attributes can themselves be structured JSON objects
|
||||
and have attributes on attributes on attributes, etc. Because OPA was designed to work
|
||||
with arbitrarily nested JSON data, it supports incredibly rich ABAC policies.
|
||||
with arbitrarily nested JSON data, it supports expressive ABAC policies.
|
||||
|
||||
## Amazon Web Services IAM
|
||||
|
||||
@@ -246,7 +244,7 @@ and selected resources. You write `allow` and `deny` statements to enforce which
|
||||
execute which API calls on which resources under certain conditions.
|
||||
By default all API access requests are implicitly denied (i.e., not allowed). Policy statements
|
||||
can explicitly allow or deny API requests. If a request is both allowed and denied, it is always denied.
|
||||
Let's assume that the following [customer managed policy](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_managed-vs-inline.html#customer-managed-policies) is defined in AWS:
|
||||
Assume that the following [customer managed policy](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_managed-vs-inline.html#customer-managed-policies) is defined in AWS:
|
||||
|
||||
```json
|
||||
{
|
||||
|
||||
@@ -441,7 +441,7 @@ request OPA will re-read the credentials from the file and use them for authenti
|
||||
#### Using SSO Profile Credentials
|
||||
|
||||
If specifying `sso_credentials`, OPA will expect to find an sso profile configured as explained in [SSO Profiles](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html) and stored in the [config](https://docs.aws.amazon.com/sdkref/latest/guide/file-format.html) file on disk.
|
||||
On each request, Opa will try to use cached token acquired credentials using the SSO credentials. In case the current token has expired, OPA will try to refresh the token using the SSO refresh token, assuming the SSO session is still valid. New token will be cached in memory.
|
||||
On each request, OPA will try to use cached token acquired credentials using the SSO credentials. In case the current token has expired, OPA will try to refresh the token using the SSO refresh token, assuming the SSO session is still valid. New token will be cached in memory.
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| --------------------------------------------------------------- | -------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -657,7 +657,7 @@ signing requests to other AWS services.
|
||||
|
||||
A special case is that bearer authentication works differently to normal service authentication. The OCI downloader base64-encodes the credentials for you so that they need to be supplied in plain text.
|
||||
|
||||
For _GHCR_ (Github Container Registry) you can use a developer PAT (personal access token) when downloading a private image. These can be supplied as:
|
||||
For _GHCR_ (GitHub Container Registry) you can use a developer PAT (personal access token) when downloading a private image. These can be supplied as:
|
||||
|
||||
```yaml
|
||||
credentials:
|
||||
@@ -863,10 +863,12 @@ included in the actual bundle gzipped tarball.
|
||||
| `discovery.signing.exclude_files` | `array` | No | Files in the bundle to exclude during verification. |
|
||||
| `discovery.persist` | `bool` | No | Persist activated discovery bundle to disk. |
|
||||
|
||||
> ⚠️ The plugin trigger mode configured on the discovery plugin will be inherited by the bundle, decision log
|
||||
> and status plugins. For example, if the discovery plugin is configured to use the manual trigger mode, all other
|
||||
> plugins will use manual triggering as well. If any of the plugins explicitly specify a different mode (for ex. periodic),
|
||||
> OPA will generate a configuration error.
|
||||
:::warning
|
||||
The plugin trigger mode configured on the discovery plugin will be inherited by the bundle, decision log
|
||||
and status plugins. For example, if the discovery plugin is configured to use the manual trigger mode, all other
|
||||
plugins will use manual triggering as well. If any of the plugins explicitly specify a different mode (for ex. periodic),
|
||||
OPA will generate a configuration error.
|
||||
:::
|
||||
|
||||
The following `discovery` configuration fields are supported but deprecated:
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ The following example adds a simple built-in function, `repeat(string, int)`, th
|
||||
|
||||
### Declare and Register
|
||||
|
||||
In `ast/builtins.go`, we declare the structure of our built-in function with a `Builtin` struct instance:
|
||||
In `ast/builtins.go`, declare the structure of the built-in function with a `Builtin` struct instance:
|
||||
|
||||
```go
|
||||
var Repeat = &Builtin{
|
||||
@@ -44,7 +44,7 @@ var Repeat = &Builtin{
|
||||
}
|
||||
```
|
||||
|
||||
To register the new built-in function, we locate the `DefaultBuiltins` array in `ast/builtins.go`, and add the `Builtin` instance to it:
|
||||
To register the new built-in function, locate the `DefaultBuiltins` array in `ast/builtins.go` and add the `Builtin` instance to it:
|
||||
|
||||
```go
|
||||
var DefaultBuiltins = [...]*Builtin{
|
||||
@@ -56,9 +56,9 @@ var DefaultBuiltins = [...]*Builtin{
|
||||
|
||||
### Implement
|
||||
|
||||
In the `topdown` package, we locate a suitable source file for our new built-in function, or add a new file, as appropriate.
|
||||
In the `topdown` package, locate a suitable source file for the new built-in function, or add a new file, as appropriate.
|
||||
|
||||
In this example, we introduce a new source file, `topdown/repeat.go`:
|
||||
This example introduces a new source file, `topdown/repeat.go`:
|
||||
|
||||
```go
|
||||
package topdown
|
||||
@@ -105,7 +105,7 @@ The call to `RegisterBuiltinFunc(...)` in `init()` adds the built-in function to
|
||||
All built-in function implementations must include a test suite.
|
||||
Test cases for built-in functions are written in YAML and located under `test/cases/testdata/v1`.
|
||||
|
||||
We create two new test cases (one positive, expecting a string output; and one negative, expecting an error) for our built-in function:
|
||||
Create two new test cases (one positive, expecting a string output; and one negative, expecting an error) for the built-in function:
|
||||
|
||||
```yaml
|
||||
cases:
|
||||
@@ -142,7 +142,7 @@ See [test/cases/testdata/helloworld](https://github.com/open-policy-agent/opa/tr
|
||||
for a more detailed example of how to implement tests for your built-in functions.
|
||||
|
||||
:::info
|
||||
Note: We can manually test our new built-in function by [building](./contrib-development#getting-started)
|
||||
Note: The new built-in function can be manually tested by [building](./contrib-development#getting-started)
|
||||
and running the `eval` command. E.g.: `$./opa_<OS>_<ARCH> eval 'repeat("Foo", 3)'`
|
||||
:::
|
||||
|
||||
@@ -150,7 +150,7 @@ and running the `eval` command. E.g.: `$./opa_<OS>_<ARCH> eval 'repeat("Foo", 3)
|
||||
|
||||
All built-in functions will automatically be documented in `docs/content/policy-reference.md` under an appropriate subsection.
|
||||
|
||||
For this example, we'll get an entry for our new function under the `Strings` section.
|
||||
For this example, the new function appears as an entry under the `Strings` section.
|
||||
|
||||
### Add a capability
|
||||
|
||||
@@ -159,10 +159,10 @@ Read more about extending the default capabilities list for built-ins [in the Op
|
||||
:::
|
||||
|
||||
One of the security features of OPA is [capabilities](./operations#capabilities) checks on policies, allowing users to restrict which built-in functions will be available to policies at runtime.
|
||||
To ensure that our new `repeat` function will be available to callers, we'll need to add it to the `capabilities.json` file at the root of the repo.
|
||||
We can have this entry auto-generated for us by running `make generate`.
|
||||
To ensure the new `repeat` function is available to callers, add it to the `capabilities.json` file at the root of the repo.
|
||||
This entry can be auto-generated by running `make generate`.
|
||||
|
||||
After running `make generate` we should see a new JSON object entry in the list under the `"builtins"` key:
|
||||
After running `make generate`, a new JSON object entry appears in the list under the `"builtins"` key:
|
||||
|
||||
```json
|
||||
...
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title: Contributing Code
|
||||
---
|
||||
|
||||
We are thrilled that you're interested in contributing to OPA! This document
|
||||
Thanks for your interest in contributing to OPA! This document
|
||||
outlines some of the important guidelines when getting started as a new
|
||||
contributor.
|
||||
|
||||
@@ -28,7 +28,7 @@ When contributing please consider the following pointers:
|
||||
implement however they come with their own cost for both OPA developers and
|
||||
OPA users (e.g., vendoring conflicts, security, debugging, etc.)
|
||||
- **AI Tooling**: You can use generative AI tooling to assist your work on OPA,
|
||||
but please review our project's [AI Guidelines](#ai-guidelines) below before doing so to
|
||||
but please review the project's [AI Guidelines](#ai-guidelines) below before doing so to
|
||||
help us help you.
|
||||
|
||||
:::tip
|
||||
@@ -121,7 +121,7 @@ off by a human author.
|
||||
The OPA maintainers reserve the right to request additional information about
|
||||
patches and reject PRs where code origin cannot be verified.
|
||||
|
||||
See more in our [AI Guidelines](#ai-guidelines).
|
||||
See more in the [AI Guidelines](#ai-guidelines).
|
||||
:::
|
||||
|
||||
## Code Review
|
||||
@@ -151,7 +151,7 @@ their own commit and added to the PR.
|
||||
|
||||
If your Pull Request is small though, it is acceptable to squash changes during
|
||||
the review process. Use your judgement about what constitutes a small Pull
|
||||
Request. If you aren't sure, send a message to the OPA slack or post a comment
|
||||
Request. If you aren't sure, send a message to the OPA Slack or post a comment
|
||||
on the Pull Request.
|
||||
|
||||
## Vulnerability scanning
|
||||
@@ -180,9 +180,8 @@ in the `.trivyignore` file.
|
||||
|
||||
## AI Guidelines
|
||||
|
||||
We are really excited for you to contribute to OPA! In order for us (the OPA
|
||||
maintainers) to help you effectively, we have some guidelines that we request
|
||||
you follow:
|
||||
Contributing to OPA is encouraged! In order for the OPA
|
||||
maintainers to help effectively, please follow these guidelines:
|
||||
|
||||
1. Follow the
|
||||
[Linux Foundation Guidelines](https://www.linuxfoundation.org/legal/generative-ai)
|
||||
@@ -207,15 +206,15 @@ you follow:
|
||||
members who took the time to review your code and provide you with personal
|
||||
feedback is considered disrespectful and will have your PR rejected.
|
||||
|
||||
3. Don't be afraid to get it wrong! We are friendly, and will answer questions
|
||||
you might have about contributing to our project. You can always:
|
||||
3. Don't be afraid to get it wrong! The team is friendly and will answer questions
|
||||
you might have about contributing to the project. You can always:
|
||||
|
||||
- Ask for clarification of a review comment if you don't understand it.
|
||||
- Ask for input on a technical implementation, ideally before investing your
|
||||
time into it.
|
||||
- Correct maintainers when you think they've misunderstood something.
|
||||
|
||||
Together we can learn and build a better OPA!
|
||||
Together the community can learn and build a better OPA!
|
||||
|
||||
## Contribution process
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ title: Development
|
||||
---
|
||||
|
||||
This page details the process for getting up and running locally for OPA
|
||||
development. If you're a first time contributor, we recommend you read through
|
||||
development. First-time contributors are encouraged to read through
|
||||
the [Contributing to OPA](./contrib-code) page first.
|
||||
|
||||
OPA is written in the [Go](https://go.dev/) programming language.
|
||||
@@ -126,8 +126,8 @@ Pull Request, please mention it in the discussion.
|
||||
## Benchmarks
|
||||
|
||||
Several packages in this repository implement benchmark tests. To execute the
|
||||
benchmarks you can run `make perf` in the top-level directory. We use the Go
|
||||
benchmarking framework for all benchmarks.
|
||||
benchmarks, run `make perf` in the top-level directory. The Go
|
||||
benchmarking framework is used for all benchmarks.
|
||||
|
||||
## Dependencies
|
||||
|
||||
@@ -138,7 +138,7 @@ All `go` commands from the [Makefile](https://github.com/open-policy-agent/opa/b
|
||||
module mode by setting `GO111MODULE=on`.
|
||||
|
||||
To update a dependency ensure that `GO111MODULE` is either on, or the repository
|
||||
qualifies for `auto` to enable module mode. Then simply use `go get ..` to get
|
||||
qualifies for `auto` to enable module mode. Then use `go get ..` to get
|
||||
the version desired. This should update the [go.mod](https://github.com/open-policy-agent/opa/blob/main/go.mod) and (potentially)
|
||||
[go.sum](https://github.com/open-policy-agent/opa/blob/main/go.sum) files.
|
||||
|
||||
@@ -176,9 +176,9 @@ performant, that's great! Some things should however be considered before you su
|
||||
OPA uses Github Actions defined in the [.github/workflows](https://github.com/open-policy-agent/opa/tree/main/.github/workflows)
|
||||
directory.
|
||||
|
||||
### Github Action Secrets
|
||||
### GitHub Action Secrets
|
||||
|
||||
The following secrets are used by the Github Action workflows:
|
||||
The following secrets are used by the GitHub Action workflows:
|
||||
|
||||
| Name | Description |
|
||||
| -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -186,7 +186,7 @@ The following secrets are used by the Github Action workflows:
|
||||
| AWS_ACCESS_KEY_ID | AWS credentials required to upload to the configured `S3_RELEASE_BUCKET`. Optional -- If not provided the release upload steps are skipped. |
|
||||
| AWS_SECRET_ACCESS_KEY | AWS credentials required to upload to the configured `S3_RELEASE_BUCKET`. Optional -- If not provided the release upload steps are skipped. |
|
||||
| DOCKER_IMAGE | Full docker image name (with org) to tag and publish OPA images. Optional -- If not provided the image defaults to `openpolicyagent/opa`. |
|
||||
| DOCKER_WASM_BUILDER_IMAGE | Full docker image name (with org) to tag and publish WASM builder images. Optional -- If not provided the image defaults to `openpolicyagent/opa-wasm-builder`. |
|
||||
| DOCKER_WASM_BUILDER_IMAGE | Full docker image name (with org) to tag and publish Wasm builder images. Optional -- If not provided the image defaults to `openpolicyagent/opa-wasm-builder`. |
|
||||
| DOCKER_USER | Docker username for uploading release images. Will be used with `docker login`. Optional -- If not provided the image push steps are skipped. |
|
||||
| DOCKER_PASSWORD | Docker password or API token for the configured `DOCKER_USER`. Will be used with `docker login`. Optional -- If not provided the image push steps are skipped. |
|
||||
| SLACK_NOTIFICATION_WEBHOOK | Slack webhook for sending notifications. Optional -- If not provided the notification steps are skipped. |
|
||||
@@ -194,7 +194,7 @@ The following secrets are used by the Github Action workflows:
|
||||
|
||||
### Periodic Workflows
|
||||
|
||||
Some of the Github Action workflows are triggered on a schedule, and not included in the
|
||||
Some of the GitHub Action workflows are triggered on a schedule, and not included in the
|
||||
post-merge, pull-request, etc actions. These are reserved for time consuming or potentially
|
||||
non-deterministic jobs (race detection tests, fuzzing, etc).
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
title: Contributing Documentation
|
||||
---
|
||||
|
||||
Contributing to our documentation is one of the best ways to get started
|
||||
Contributing to the documentation is one of the best ways to get started
|
||||
contributing to the OPA project. The OPA documentation is often the first place
|
||||
people go for help and so any improvements can be very impactful.
|
||||
**Thank you in advance for contributing to our documentation!**
|
||||
**Thank you in advance for contributing to the documentation!**
|
||||
|
||||
## Local Development
|
||||
|
||||
|
||||
@@ -25,13 +25,10 @@ Overflow.
|
||||
|
||||
## I'd like to contribute code
|
||||
|
||||
If you have found a bug and would like to work on a fix, **we always encourage you
|
||||
file a [GitHub Issue](https://github.com/open-policy-agent/opa/issues)** to talk
|
||||
about the problem and the solution you have in mind. This allows you to get
|
||||
feedback from maintainers before committing time to something that might already
|
||||
If you have found a bug and would like to work on a fix, **filing a [GitHub Issue](https://github.com/open-policy-agent/opa/issues)** is always encouraged. It is a good way to discuss the problem and your proposed solution, and gather feedback from maintainers before committing time to something that might already
|
||||
have a solution or might not be the right approach.
|
||||
|
||||
If you have an idea for a new feature, we also **request that you file an
|
||||
If you have an idea for a new feature, **file an
|
||||
issue** to discuss it first. This again allows you to get feedback from
|
||||
the maintainer team and the community before you start working on it.
|
||||
|
||||
@@ -45,19 +42,19 @@ else, head over to
|
||||
Slack. This is also a great place to ask for ideas if you want to contribute, but
|
||||
aren't sure what to work on!
|
||||
|
||||
If you are ready to start contributing code, please see our
|
||||
If you are ready to start contributing code, please see the
|
||||
[Contributing Code](./contrib-code/) guide for pointers on how to get
|
||||
started. Please note we have some restrictions around the use of AI tooling
|
||||
started. Please note there are some restrictions around the use of AI tooling
|
||||
which are documented here.
|
||||
|
||||
## I'd like to help improve the documentation
|
||||
|
||||
Great! Please see our [Contributing Documentation](./contrib-docs) guide for
|
||||
See the [Contributing Documentation](./contrib-docs) guide for
|
||||
more details.
|
||||
|
||||
## I have an OPA project or talk I'd like to share
|
||||
|
||||
Awesome! For OPA-based projects, we have our [Ecosystem page](/ecosystem/).
|
||||
Awesome! For OPA-based projects, the [Ecosystem page](/ecosystem/) is available.
|
||||
This is a great place to showcase your project and how it uses OPA.
|
||||
|
||||
You can create a markdown file in:
|
||||
@@ -73,8 +70,8 @@ If you have a talk or blog you'd like to share please feel free to post in:
|
||||
|
||||
## I'm interested in something else
|
||||
|
||||
Sounds interesting, we'd love to hear all about it,
|
||||
[sign up for our Slack](https://slack.openpolicyagent.org/) and
|
||||
Sounds interesting, reach out to share it,
|
||||
[sign up for the OPA Slack](https://slack.openpolicyagent.org/) and
|
||||
drop a message in the
|
||||
[#contributors](https://openpolicyagent.slack.com/archives/C02L1TLPN59)
|
||||
channel.
|
||||
|
||||
@@ -27,8 +27,7 @@ Read more about the supported editors of this debugging method in the
|
||||
|
||||
## OPA REPL and Playground
|
||||
|
||||
Often it can take a few tries to get a Rego policy correct, the OPA REPL and Playground are great tools for
|
||||
reducing the feedback loop when debugging policies.
|
||||
Often it can take a few tries to get a Rego policy correct, the OPA REPL and Playground help reduce the feedback loop when debugging policies.
|
||||
|
||||
The REPL can be run locally and loaded with the policy and data files you are working on:
|
||||
|
||||
@@ -38,7 +37,7 @@ opa run [policy-files] [data-files]
|
||||
|
||||
The [Rego Playground](http://play.openpolicyagent.org) is a web-based Rego development environment that can be
|
||||
used to test policies with different inputs and data. If you are interested in asking for help in the
|
||||
[OPA Slack](https://slack.openpolicyagent.org), the playground is a great way to share your policy and data with
|
||||
[OPA Slack](https://slack.openpolicyagent.org), the playground can be used to share your policy and data with
|
||||
others.
|
||||
|
||||
## Using the `print` Built-in Function
|
||||
@@ -69,7 +68,7 @@ debugging OPA in these environments.
|
||||
|
||||
### OPA Logs
|
||||
|
||||
OPA logs are a great place to start when debugging issues. The logs can be used to understand what OPA is doing
|
||||
OPA logs are a useful starting point when debugging issues. The logs can be used to understand what OPA is doing
|
||||
at any given time. Common issues such as failing to load in policy or data bundles will be shown here.
|
||||
|
||||
You can also enable debug logging to get more detailed information about what OPA is doing with `--log-level debug`.
|
||||
|
||||
@@ -4,11 +4,11 @@ sidebar_position: 2
|
||||
title: Deploying OPA on Docker
|
||||
---
|
||||
|
||||
Docker makes OPA easy to deploy in different types of environments.
|
||||
Docker can be used to deploy OPA in different types of environments.
|
||||
|
||||
This section explains how to use the official OPA Docker images. If this is your
|
||||
first time deploying OPA and you plan to use one of the Docker images, we
|
||||
recommend you review this section to familiarize yourself with the basics.
|
||||
first time deploying OPA and you plan to use one of the Docker images, it is
|
||||
recommended that you review this section to familiarize yourself with the basics.
|
||||
|
||||
OPA releases are available as images on Docker Hub
|
||||
([`openpolicyagent/opa`](https://hub.docker.com/r/openpolicyagent/opa/)).
|
||||
@@ -18,7 +18,7 @@ OPA releases are available as images on Docker Hub
|
||||
If you start OPA outside of Docker without any arguments, it prints a list of
|
||||
available commands. By default, the official OPA Docker image executes the `run`
|
||||
command which starts an instance of OPA as an interactive shell. This is nice
|
||||
for development, however, for deployments, we want to run OPA as a server.
|
||||
for development, however, for deployments, run OPA as a server.
|
||||
|
||||
The `run` command accepts a `--server` (or `-s`) flag that starts OPA as a
|
||||
server. See `--help` for more information on other arguments. The most important
|
||||
@@ -31,7 +31,7 @@ command line arguments for OPA's server mode are:
|
||||
By default, OPA listens for normal HTTP connections on `localhost:8181`. To make
|
||||
OPA listen for HTTPS connections, see [Security](../../security).
|
||||
|
||||
We can run OPA as a server using Docker:
|
||||
Run OPA as a server using Docker:
|
||||
|
||||
```bash
|
||||
docker run -p 8181:8181 openpolicyagent/opa \
|
||||
@@ -39,7 +39,7 @@ docker run -p 8181:8181 openpolicyagent/opa \
|
||||
```
|
||||
|
||||
:::info
|
||||
We have to use `--addr` here to bind to all interfaces to ensure OPA is
|
||||
Use `--addr` to bind to all interfaces to ensure OPA is
|
||||
accessible from outside the container. This is not necessary when running OPA
|
||||
in other environments.
|
||||
|
||||
@@ -74,7 +74,9 @@ If the log level is set to `debug` the request and response message bodies will
|
||||
The default log format is json and intended for production use. For more human readable
|
||||
formats use "json-pretty" or "text".
|
||||
|
||||
> **Note:** The `text` log format is not performance optimized or intended for production use.
|
||||
:::note
|
||||
The `text` log format is not performance optimized or intended for production use.
|
||||
:::
|
||||
|
||||
### Volume Mounts
|
||||
|
||||
@@ -115,8 +117,8 @@ The Docker Hub repository contains tags for every release of OPA. For more
|
||||
information on each release see the [GitHub Releases](https://github.com/open-policy-agent/opa/releases) page.
|
||||
|
||||
The "latest" tag refers to the most recent release. The latest tag is convenient
|
||||
if you want to quickly try out OPA however for production deployments, we
|
||||
recommend using an explicit version tag.
|
||||
if you want to quickly try out OPA however for production deployments, it is
|
||||
recommended to use an explicit version tag.
|
||||
|
||||
Development builds are also available on Docker Hub. For each version the
|
||||
`{version}-dev` tag refers the most recent development build for that version.
|
||||
@@ -147,7 +149,7 @@ First, create a ConfigMap containing a test policy.
|
||||
|
||||
In this case, the policy file does not contain sensitive information so it's
|
||||
fine to store as a ConfigMap. If the file contained sensitive information, then
|
||||
we recommend you store it as a Secret.
|
||||
store it as a Secret.
|
||||
|
||||
```rego title="example.rego"
|
||||
package example
|
||||
|
||||
@@ -19,7 +19,7 @@ in OPA.
|
||||
> application while still keeping up with the size, complexity, and dynamic
|
||||
> nature of modern applications.
|
||||
|
||||
For the purpose of this tutorial, we want to use OPA to enforce a policy that
|
||||
For the purpose of this tutorial, OPA is used to enforce a policy that
|
||||
prevents users from running insecure containers.
|
||||
|
||||
This tutorial illustrates two key concepts:
|
||||
@@ -64,15 +64,14 @@ allow := true
|
||||
```
|
||||
|
||||
This policy defines a single rule named `allow` that always produces the
|
||||
decision `true`. Once all the components are running, we will come back to
|
||||
the policy.
|
||||
decision `true`. The tutorial returns to this policy once all the components are running.
|
||||
|
||||
### 2. Create policy bundle and OPA configuration
|
||||
|
||||
For the purpose of this example, we are going to use [Nginx](https://www.openpolicyagent.org/docs/management-bundles#nginx)
|
||||
For the purpose of this example, [Nginx](https://www.openpolicyagent.org/docs/management-bundles#nginx) is used
|
||||
to serve bundles from the same machine Docker is running on.
|
||||
|
||||
With nginx running, simply build the policy bundle placed into the nginx web root directory.
|
||||
With Nginx running, build the policy bundle placed into the Nginx web root directory.
|
||||
|
||||
```shell
|
||||
opa build --bundle --output /var/www/html/bundle.tar.gz .
|
||||
@@ -95,9 +94,9 @@ decision_logs:
|
||||
console: true
|
||||
```
|
||||
|
||||
Save the above file as `opa-config.yaml`. We'll need to place this somewhere where the plugin can find it.
|
||||
The `/etc/docker` directory will be mounted as `/opa` in the container running the plugin, so let's create a
|
||||
sub-directory for our configuration file there.
|
||||
Save the above file as `opa-config.yaml`. Place this file somewhere the plugin can find it.
|
||||
The `/etc/docker` directory will be mounted as `/opa` in the container running the plugin, so create a
|
||||
sub-directory for the configuration file there.
|
||||
|
||||
```shell
|
||||
sudo mkdir -p /etc/docker/config
|
||||
@@ -139,7 +138,7 @@ expect to see log messages from OPA and the plugin.
|
||||
|
||||
### 5. Test that the policy definition is working
|
||||
|
||||
Let’s modify our policy to **deny** all requests:
|
||||
Modify the policy to **deny** all requests:
|
||||
|
||||
**authz.rego**:
|
||||
|
||||
@@ -159,7 +158,7 @@ In OPA, rules defines the content of documents. Documents be boolean values
|
||||
(true/false) or they can represent more complex structures using arrays,
|
||||
objects, strings, etc.
|
||||
|
||||
In the example above we modified the policy to always return `false` so that
|
||||
In the example above, the policy was modified to always return `false` so that
|
||||
requests will be rejected.
|
||||
|
||||
```shell
|
||||
@@ -178,7 +177,7 @@ With this policy in place, users will not be able to run any Docker commands. Go
|
||||
ahead and try other commands such as `docker run` or `docker pull`. They will
|
||||
all be rejected.
|
||||
|
||||
Now let's change the policy so that it's a bit more useful.
|
||||
Now change the policy to be more useful.
|
||||
|
||||
### 6. Update the policy to reject requests with the unconfined [seccomp](https://en.wikipedia.org/wiki/Seccomp) profile
|
||||
|
||||
@@ -353,8 +352,7 @@ prevented by the policy):
|
||||
docker run --security-opt seccomp:unconfined hello-world
|
||||
```
|
||||
|
||||
Congratulations! You have successfully prevented containers from running without
|
||||
seccomp!
|
||||
Containers without a seccomp profile are now blocked.
|
||||
|
||||
The rest of the tutorial shows how you can grant fine-grained access to specific
|
||||
clients.
|
||||
@@ -384,7 +382,7 @@ EOF
|
||||
|
||||
> Docker does not currently provide a way to authenticate clients. But in Docker
|
||||
> 1.12, clients can be authenticated using TLS and there are plans to include
|
||||
> other means of authentication. For the purpose of this tutorial, we assume that
|
||||
> other means of authentication. For the purpose of this tutorial, assume that
|
||||
> an authentication system is place.
|
||||
|
||||
### 9. Update the policy to include basic user access controls
|
||||
@@ -441,5 +439,3 @@ Because the configured user is `"alice"`, the request will succeed:
|
||||
```shell
|
||||
docker run hello-world
|
||||
```
|
||||
|
||||
That's it!
|
||||
|
||||
@@ -22,7 +22,7 @@ evaluation, policy coverage, and more.
|
||||
:::info
|
||||
**Your editor missing? Built a Rego integration for your editor?** Drop us a
|
||||
message on [Slack](https://slack.openpolicyagent.org)
|
||||
We also have our [Ecosystem page](/ecosystem/). This is a great place to
|
||||
The [Ecosystem page](/ecosystem/) is also a great place to
|
||||
showcase your project. See
|
||||
[these instructions](./contrib-docs#opa-ecosystem-additions)
|
||||
to get it listed.
|
||||
|
||||
@@ -5,7 +5,7 @@ sidebar_position: 6
|
||||
|
||||
This page provides some pointers that could assist in addressing issues encountered while using the
|
||||
OPA-Envoy plugin. If none of these tips work, feel free to join
|
||||
[our slack](https://slack.openpolicyagent.org) and ask for help.
|
||||
[our Slack](https://slack.openpolicyagent.org) and ask for help.
|
||||
|
||||
## Debugging Performance Issues
|
||||
|
||||
|
||||
@@ -43,8 +43,10 @@ sequenceDiagram
|
||||
Envoy->>+Client: Response (HTTP)
|
||||
```
|
||||
|
||||
> 💡 The OPA-Envoy plugin is frequently deployed in Kubernetes environments as a sidecar container however it can also
|
||||
> be used in other environments as a standalone process running next to Envoy.
|
||||
:::tip
|
||||
The OPA-Envoy plugin is frequently deployed in Kubernetes environments as a sidecar container however it can also
|
||||
be used in other environments as a standalone process running next to Envoy.
|
||||
:::
|
||||
|
||||
## Configuration
|
||||
|
||||
|
||||
@@ -110,7 +110,7 @@ layered_runtime:
|
||||
|
||||
### OPA-Envoy Plugin
|
||||
|
||||
Now let's deploy OPA as an External Authorization server. Below is a sample configuration for the OPA-Envoy container:
|
||||
Now deploy OPA as an External Authorization server. Below is a sample configuration for the OPA-Envoy container:
|
||||
|
||||
<EvergreenCodeBlock>
|
||||
```yaml
|
||||
@@ -145,14 +145,18 @@ containers:
|
||||
```
|
||||
</EvergreenCodeBlock>
|
||||
|
||||
> 💡 Consider specifying CPU and memory resource requests and limits for the OPA and other containers to prevent
|
||||
> deployments from resource starvation.
|
||||
> OPA automatically sets `GOMAXPROCS` via [`automaxprocs`](https://github.com/uber-go/automaxprocs) and `GOMEMLIMIT`
|
||||
> via [`automemlimit`](https://github.com/KimMachineGun/automemlimit) based on cgroup limits. Both can be overridden
|
||||
> by setting the environment variables explicitly.
|
||||
>
|
||||
> 💡 The OPA-Envoy plugin can be configured to listen on a UNIX Domain Socket. A complete example of such a setup
|
||||
> can be found [in the opa-envoy-plugin examples](https://github.com/open-policy-agent/opa-envoy-plugin/tree/main/examples/envoy-uds).
|
||||
::: tip
|
||||
Consider specifying CPU and memory resource requests and limits for the OPA and other containers to prevent
|
||||
deployments from resource starvation.
|
||||
OPA automatically sets `GOMAXPROCS` via [`automaxprocs`](https://github.com/uber-go/automaxprocs) and `GOMEMLIMIT`
|
||||
via [`automemlimit`](https://github.com/KimMachineGun/automemlimit) based on cgroup limits. Both can be overridden
|
||||
by setting the environment variables explicitly.
|
||||
:::
|
||||
|
||||
::: tip
|
||||
The OPA-Envoy plugin can be configured to listen on a UNIX Domain Socket. A complete example of such a setup
|
||||
can be found [in the opa-envoy-plugin examples](https://github.com/open-policy-agent/opa-envoy-plugin/tree/main/examples/envoy-uds).
|
||||
:::
|
||||
|
||||
### Load Generator and Measurement Tool
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ This page covers how to write policies for the content of the requests that are
|
||||
|
||||
## Writing Policies
|
||||
|
||||
Let's start with an example policy that restricts access to an endpoint based on a user's role and permissions.
|
||||
Start with an example policy that restricts access to an endpoint based on a user's role and permissions.
|
||||
|
||||
```rego
|
||||
package envoy.authz
|
||||
@@ -65,7 +65,7 @@ The above policy uses the `io.jwt.decode_verify` builtin function to parse and v
|
||||
information about the user making the request. It uses other builtins like `glob.match`, `lower`, `base64url.decode` etc.
|
||||
OPA has 150+ builtins detailed in the [policy reference](../policy-reference).
|
||||
|
||||
The dot notation seen in multiple places in the policy for ex. `input.parsed_body.firstname` simply descends through
|
||||
The dot notation seen in multiple places in the policy for ex. `input.parsed_body.firstname` descends through
|
||||
the hierarchy to access the requested value. The dot (.) operator never throws any errors; if the path does not exist
|
||||
the value of the expression is `undefined`.
|
||||
|
||||
@@ -213,7 +213,7 @@ When Envoy receives a policy decision, it expects a JSON object with the followi
|
||||
- `query_parameters_to_remove` (optional): is an array containing the names of string query parameters to be removed.
|
||||
- `query_parameters_to_set` (optional): an object mapping parameter names to values (string) or arrays of values (for multiple values with the same key)
|
||||
|
||||
To construct that output object using the policies demonstrated in the last section, you can use the following Rego snippet. Notice that we are using partial object rules so that any variables with undefined values simply have no key in the `result` object.
|
||||
To construct that output object using the policies demonstrated in the last section, you can use the following Rego snippet. Note that partial object rules are used so that any variables with undefined values have no key in the `result` object.
|
||||
|
||||
```rego
|
||||
result["allowed"] := allow
|
||||
|
||||
@@ -11,7 +11,7 @@ The purpose of this tutorial is to show how OPA could be used with Gloo Edge to
|
||||
|
||||
## Prerequisites
|
||||
|
||||
This tutorial requires Kubernetes 1.14 or later. To run the tutorial locally, we recommend using [minikube](https://minikube.sigs.k8s.io/docs/start/) in version v1.0+ with Kubernetes 1.14+.
|
||||
This tutorial requires Kubernetes 1.14 or later. To run the tutorial locally, [minikube](https://minikube.sigs.k8s.io/docs/start/) (version v1.0+) with Kubernetes 1.14+ is recommended.
|
||||
|
||||
The tutorial also requires [Helm](https://helm.sh/docs/intro/install/) to install Gloo Edge on a Kubernetes cluster.
|
||||
|
||||
@@ -85,7 +85,7 @@ kubectl port-forward deployment/gateway-proxy 8080:8080
|
||||
```
|
||||
|
||||
The `VirtualService` created in the previous step forwards requests to [httpbin.org](http://httpbin.org)
|
||||
Let's test that Gloo works properly by running the below command in the first terminal.
|
||||
Test that Gloo works properly by running the below command in the first terminal.
|
||||
|
||||
```bash
|
||||
curl -XGET -Is localhost:8080/get | head -n 1
|
||||
@@ -163,13 +163,13 @@ The sample input can be seen below using Alice's token, Alice should be able to
|
||||
|
||||
<RunSnippet id="input.json"/>
|
||||
|
||||
Next we build an OPA bundle.
|
||||
Next, build an OPA bundle.
|
||||
|
||||
```bash
|
||||
opa build policy.rego
|
||||
```
|
||||
|
||||
And now we serve the OPA bundle created above using Nginx.
|
||||
Serve the OPA bundle created above using Nginx.
|
||||
|
||||
```bash
|
||||
docker run --rm --name bundle-server -d -p 8888:80 -v ${PWD}:/usr/share/nginx/html:ro nginx:latest
|
||||
@@ -251,7 +251,7 @@ spec:
|
||||
|
||||
### 7. Configure Gloo Edge to use OPA
|
||||
|
||||
To use OPA as a custom auth server, we need to add the `extauth` attribute as described below:
|
||||
To use OPA as a custom auth server, add the `extauth` attribute as described below:
|
||||
|
||||
**gloo.yaml**
|
||||
|
||||
@@ -282,7 +282,7 @@ spec:
|
||||
customAuth: {}
|
||||
```
|
||||
|
||||
Then apply the patch to our `VirtualService` as shown below:
|
||||
Then apply the patch to the `VirtualService` as shown below:
|
||||
|
||||
```bash
|
||||
kubectl patch vs httpbin --type=merge --patch "$(cat vs-patch.yaml)"
|
||||
@@ -290,28 +290,28 @@ kubectl patch vs httpbin --type=merge --patch "$(cat vs-patch.yaml)"
|
||||
|
||||
### 8. Exercise the OPA Policy
|
||||
|
||||
Before we exercise the policy, for convenience sake, we will want to store Alice and Bob's tokens in environment variables as such:
|
||||
Before exercising the policy, store Alice and Bob's tokens in environment variables:
|
||||
|
||||
```bash
|
||||
export ALICE_TOKEN="eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJleHAiOiAyMjQxMDgxNTM5LCAibmJmIjogMTUxNDg1MTEzOSwgInJvbGUiOiAiZ3Vlc3QiLCAic3ViIjogIllXeHBZMlU9In0.Uk5hgUqMuUfDLvBLnlXMD0-X53aM_Hlziqg3vhOsCc8"
|
||||
export BOB_TOKEN="eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJleHAiOiAyMjQxMDgxNTM5LCAibmJmIjogMTUxNDg1MTEzOSwgInJvbGUiOiAiYWRtaW4iLCAic3ViIjogIlltOWkifQ.5qsm7rRTvqFHAgiB6evX0a_hWnGbWquZC0HImVQPQo8"
|
||||
```
|
||||
|
||||
Now let's verify that OPA only allows **Alice** to perform `GET` requests.
|
||||
Verify that OPA only allows **Alice** to perform `GET` requests.
|
||||
|
||||
```bash
|
||||
curl -XGET -Is -H "Authorization: Bearer $ALICE_TOKEN" localhost:8080/get
|
||||
HTTP/1.1 200 OK
|
||||
```
|
||||
|
||||
And with a `POST` request, we get:
|
||||
With a `POST` request, the result is:
|
||||
|
||||
```bash
|
||||
curl http -XPOST -Is -H "Authorization: Bearer $ALICE_TOKEN" localhost:8080/post
|
||||
HTTP/1.1 403 Forbidden
|
||||
```
|
||||
|
||||
And for **Bob**, we should be able to `GET` and `POST`:
|
||||
For **Bob**, both `GET` and `POST` should succeed:
|
||||
|
||||
```bash
|
||||
curl -XGET -Is -H "Authorization: Bearer $BOB_TOKEN" localhost:8080/get
|
||||
@@ -331,8 +331,6 @@ Check OPA's decision logs to view the inputs received by OPA from Gloo Edge and
|
||||
kubectl logs deployment/opa -n gloo-system
|
||||
```
|
||||
|
||||
## Wrap Up
|
||||
|
||||
Congratulations for finishing the tutorial!
|
||||
## Summary
|
||||
|
||||
This tutorial showed how you can use OPA with [Gloo Edge](https://docs.solo.io/gloo-edge/latest/) to apply security policies for upstream services and how to create and test a policy that would allow `GET` or `POST` requests based on your user role.
|
||||
|
||||
@@ -206,9 +206,7 @@ curl --user bob:password -i http://$SERVICE_HOST/productpage
|
||||
curl --user bob:password -i http://$SERVICE_HOST/api/v1/products
|
||||
```
|
||||
|
||||
## Wrap Up
|
||||
|
||||
Congratulations for finishing the tutorial !
|
||||
## Summary
|
||||
|
||||
This tutorial showed how Istio's [AuthorizationPolicy API](https://istio.io/latest/docs/tasks/security/authorization/authz-custom/)
|
||||
can be configured to use OPA as an External authorization service.
|
||||
|
||||
@@ -11,8 +11,8 @@ policies over the HTTP request body.
|
||||
|
||||
## Overview
|
||||
|
||||
In this tutorial we'll see how to use OPA as an External
|
||||
Authorization service for the Envoy proxy. We'll do this by:
|
||||
In this tutorial, OPA is used as an External
|
||||
Authorization service for the Envoy proxy. The tutorial covers:
|
||||
|
||||
- Running a local Kubernetes cluster
|
||||
- Creating a simple authorization policy in Rego and serving it via the Bundle API
|
||||
@@ -24,7 +24,7 @@ are co-located in the same pod.
|
||||
|
||||
## Running a local Kubernetes cluster
|
||||
|
||||
To start a local Kubernetes cluster to run our demo, we'll be using
|
||||
To start a local Kubernetes cluster to run the demo, use
|
||||
[kind](https://kind.sigs.k8s.io/).
|
||||
|
||||
:::info
|
||||
@@ -64,7 +64,7 @@ NAME STATUS ROLES AGE VERSION
|
||||
opa-envoy-control-plane Ready control-plane 2m35s v1.33.1
|
||||
```
|
||||
|
||||
## Creating & Serving our Policy Bundle
|
||||
## Creating & Serving the Policy Bundle
|
||||
|
||||
This tutorial assumes you have some Rego knowledge, in summary the policy below does the following:
|
||||
|
||||
@@ -124,8 +124,8 @@ Create a file called `policy.rego` with the above content and store it in a Conf
|
||||
kubectl create configmap authz-policy --from-file policy.rego
|
||||
```
|
||||
|
||||
Now that the policy is stored in a ConfigMap, we can spin up an HTTP server to make it
|
||||
available as a Bundle to OPA when it's making decisions for our application:
|
||||
Now that the policy is stored in a ConfigMap, spin up an HTTP server to make it
|
||||
available as a Bundle to OPA when it's making decisions for the application:
|
||||
|
||||
```yaml
|
||||
# bundle-server.yaml
|
||||
@@ -195,7 +195,7 @@ Create a file called `bundle-server.yaml` with the above content and apply it to
|
||||
kubectl apply -f bundle-server.yaml
|
||||
```
|
||||
|
||||
Once the deployment is running, we can check that the bundle is available by running:
|
||||
Once the deployment is running, check that the bundle is available by running:
|
||||
|
||||
```shell
|
||||
kubectl port-forward service/bundle-server 8080:80
|
||||
@@ -214,12 +214,12 @@ from inside the cluster from now on.
|
||||
|
||||
## Deploying an application with Envoy and OPA sidecars
|
||||
|
||||
In this tutorial, we are manually configuring the Envoy proxy sidecar to intermediate
|
||||
HTTP traffic from clients and our application. Envoy will consult OPA to
|
||||
In this tutorial, the Envoy proxy sidecar is manually configured to intermediate
|
||||
HTTP traffic from clients and the application. Envoy will consult OPA to
|
||||
make authorization decisions for each request by sending `CheckRequest` messages over
|
||||
a gRPC connection.
|
||||
|
||||
We will use the following Envoy configuration to achieve this. In summary, this
|
||||
The following Envoy configuration achieves this. In summary, this
|
||||
configures Envoy to:
|
||||
|
||||
- Listen on port `8000` for HTTP traffic
|
||||
@@ -308,13 +308,13 @@ Create a `ConfigMap` containing the above configuration by running:
|
||||
kubectl create configmap proxy-config --from-file envoy.yaml
|
||||
```
|
||||
|
||||
Our application will be configured using a `Deployment` and `Service`.
|
||||
The application is configured using a `Deployment` and `Service`.
|
||||
There are a few things to note:
|
||||
|
||||
- the pods have an `initContainer` that configures the `iptables` rules to
|
||||
redirect traffic to the Envoy proxy.
|
||||
- the `demo-test-server` container is a simple user store using in-memory state.
|
||||
- the `envoy` container is configured to use the `proxy-config` `ConfigMap` we
|
||||
- the `envoy` container is configured to use the `proxy-config` `ConfigMap`
|
||||
created earlier.
|
||||
- The OPA container is configured to download policy bundles from
|
||||
the in-cluster bundle server (`bundle-server.default.svc.cluster.local`).
|
||||
@@ -432,7 +432,7 @@ example-app-74b4bc88-5d4wh 3/3 Running 0 1m
|
||||
|
||||
## See the Policy in Action
|
||||
|
||||
Run a shell inside the cluster to use for testing. We will use this in-cluster
|
||||
Run a shell inside the cluster to use for testing. Use this in-cluster
|
||||
shell for the rest of the tutorial.
|
||||
|
||||
```shell
|
||||
@@ -440,7 +440,7 @@ kubectl run curl --restart=Never -it --rm --image curlimages/curl:8.1.2 -- sh
|
||||
```
|
||||
|
||||
Set two tokens for two users, Alice and Bob with different permissions.
|
||||
As defined by our policy:
|
||||
As defined by the policy:
|
||||
|
||||
```shell
|
||||
export ALICE_TOKEN="eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJleHAiOiAyMjQxMDgxNTM5LCAibmJmIjogMTUxNDg1MTEzOSwgInJvbGUiOiAiZ3Vlc3QiLCAic3ViIjogIllXeHBZMlU9In0.Uk5hgUqMuUfDLvBLnlXMD0-X53aM_Hlziqg3vhOsCc8"
|
||||
@@ -515,7 +515,7 @@ server: envoy
|
||||
|
||||
### Creating People: Conflict
|
||||
|
||||
Our policy also blocks users from creating users with the same name, test that
|
||||
The policy also blocks users from creating users with the same name. Test that
|
||||
functionality with this request:
|
||||
|
||||
```shell
|
||||
@@ -543,9 +543,7 @@ Deleting cluster "opa-envoy" ...
|
||||
Deleted nodes: ["opa-envoy-control-plane"]
|
||||
```
|
||||
|
||||
## Wrap Up
|
||||
|
||||
Congratulations on finishing the tutorial !
|
||||
## Summary
|
||||
|
||||
This tutorial showed how to use OPA as an External authorization service to
|
||||
enforce custom policies by leveraging Envoy’s External authorization filter.
|
||||
|
||||
+2
-2
@@ -16,7 +16,7 @@ multiple outputs, or "return values".
|
||||
|
||||
## Examples
|
||||
|
||||
The most trivial example of this would be to simply — and unconditionally — assign two different values to a rule:
|
||||
The clearest example of this is to unconditionally assign two different values to a rule:
|
||||
|
||||
```rego
|
||||
package policy
|
||||
@@ -62,7 +62,7 @@ should be considered a best practice to account for this type of scenario in you
|
||||
|
||||
## How To Fix It
|
||||
|
||||
To fix this — simply ensure that the conditions for a rule to be assigned a conflicting value are mutually exclusive.
|
||||
To fix this, ensure that the conditions for a rule to be assigned a conflicting value are mutually exclusive.
|
||||
A common way to do this is to use negation (i.e. `not`) in one of the rule bodies:
|
||||
|
||||
```rego
|
||||
|
||||
@@ -28,7 +28,7 @@ obj := {k: v |
|
||||
}
|
||||
```
|
||||
|
||||
In this example, we are attempting to create an object like `{"foo": 1, "foo": 2}` which contains duplicate keys.
|
||||
In this example, the policy attempts to create an object like `{"foo": 1, "foo": 2}` which contains duplicate keys.
|
||||
This issue is also commonly seen with partial rules constructing objects. For example:
|
||||
|
||||
```rego
|
||||
@@ -125,7 +125,7 @@ Instead of the output being:
|
||||
}
|
||||
```
|
||||
|
||||
We're trying to create an impossible output like this:
|
||||
The policy is trying to create an impossible output like this:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -146,8 +146,7 @@ construct objects, ensure that there are not duplicated values. If you're using
|
||||
`some x in y` in rules to create values for an object, this is a common source of this error as any more than one
|
||||
value in `y` could result in duplicate keys.
|
||||
|
||||
Sometimes, you might need to restructure your policy to avoid this error. For example, in the policy above, we could
|
||||
change it to work like this:
|
||||
Sometimes, the policy needs to be restructured to avoid this error. For example, the policy above can be rewritten to work like this:
|
||||
|
||||
```rego
|
||||
package policy
|
||||
|
||||
+11
-13
@@ -7,8 +7,7 @@ image: /img/opa-errors.png
|
||||
# OPA Errors Guide
|
||||
|
||||
This guide is designed to help you understand the most common errors you'll encounter when working with OPA. Each
|
||||
document provides examples of the error, why it's an error, and how to fix it. A perfect companion for your debugging
|
||||
session!
|
||||
document provides examples of the error, why it's an error, and how to fix it.
|
||||
|
||||
The errors currently documented are:
|
||||
|
||||
@@ -52,7 +51,7 @@ any of these stages will stop the evaluation process and have the error(s) repor
|
||||
|
||||
The first stage is **parsing**. In this step, OPA takes the raw Rego policy and parses it into an abstract syntax tree
|
||||
(AST), which is then handed to the compiler. Errors at this stage are normally syntax errors, meaning the Rego provided
|
||||
in a policy simply isn't valid. An example of this might be forgetting to terminate a string with a closing quote:
|
||||
in a policy isn't valid. An example of this might be forgetting to terminate a string with a closing quote:
|
||||
|
||||
```rego
|
||||
package policy
|
||||
@@ -79,11 +78,11 @@ policy.rego:9: rego_parse_error: illegal token
|
||||
^
|
||||
```
|
||||
|
||||
At this point, further processing isn't possible, and the error must be fixed before we can proceed.
|
||||
At this point, further processing isn't possible, and the error must be fixed before proceeding.
|
||||
|
||||
#### Compilation
|
||||
|
||||
While we may not think of Rego as a "compiled language", any policy passes through a compilation step before it can be
|
||||
While Rego may not seem like a "compiled language", any policy passes through a compilation step before it can be
|
||||
evaluated. During compilation, OPA will run several stages of analysis on the policy (which is now an AST) to ensure
|
||||
that it's valid. This includes things like checking that functions are called with the right number of arguments,
|
||||
that types are used correctly, or that variables are defined before they're used. A typical example of a compilation
|
||||
@@ -95,14 +94,14 @@ package policy
|
||||
x := y
|
||||
```
|
||||
|
||||
Since `y` isn't defined in our policy, the compiler considers it unsafe:
|
||||
Since `y` isn't defined in the policy, the compiler considers it unsafe:
|
||||
|
||||
```txt
|
||||
1 error occurred: policy.rego:3: rego_unsafe_var_error: var y is unsafe
|
||||
```
|
||||
|
||||
**Tip:** when using `opa eval`, you can pass the `--strict` flag to enable additional compiler checks — like unused
|
||||
variables or function arguments. This is a great way to spot mistakes and errors as soon as possible, and is highly
|
||||
variables or function arguments. This helps catch mistakes and errors early, and is highly
|
||||
recommended.
|
||||
|
||||
#### Evaluation
|
||||
@@ -126,23 +125,22 @@ policy.rego:3: eval_conflict_error: complete rules must not produce multiple out
|
||||
```
|
||||
|
||||
Important to know is that not all "errors" at this stage will be reported as errors! Some things that would be
|
||||
considered an error during compilation, like passing the wrong type of value in a function argument, would simply
|
||||
result in evaluation being undefined during evaluation time.
|
||||
considered an error during compilation, like passing the wrong type of value in a function argument, would instead leave the result undefined at evaluation time.
|
||||
|
||||
```rego
|
||||
startswith("100", 1)
|
||||
```
|
||||
|
||||
As the `startswith` function expects two strings — and this is known by the compiler — this would fail during
|
||||
compilation. But if we replace the `1` with a value from `input`:
|
||||
compilation. If the `1` is replaced with a value from `input`:
|
||||
|
||||
```rego
|
||||
startswith("100", input.x)
|
||||
```
|
||||
|
||||
The compiler can't know the value of `input.x`. During evaluation, we _do_ know the value of `input.x`,
|
||||
but does it mean we want a malformed value to stop policy evaluation entirely? Probably not! By default, evaluation
|
||||
will simply consider that case to be _undefined_, and move on with evaluating the rest of the policy.
|
||||
The compiler can't know the value of `input.x`. At evaluation time, the value is known, but a
|
||||
malformed value does not stop policy evaluation entirely. By default, evaluation
|
||||
will consider that case to be _undefined_, and move on with evaluating the rest of the policy.
|
||||
|
||||
**Tip:** If you're using `opa eval` to evaluate policies, you can pass the `--strict-builtin-errors` flag to have
|
||||
an error from a built-in function halt evaluation and have the error reported. Additionally, the
|
||||
|
||||
@@ -52,15 +52,15 @@ deny contains message if {
|
||||
}
|
||||
```
|
||||
|
||||
Here, we can see the intent, the `message` should be set to `user is not admin` if the user is not an admin. However,
|
||||
Here, the intent is that `message` should be set to `user is not admin` if the user is not an admin. However,
|
||||
the variable `msg` is assigned instead.
|
||||
|
||||
## How To Fix It
|
||||
|
||||
Based on the examples above, we can see that there are two main ways to fix this error:
|
||||
Based on the examples above, there are two main ways to fix this error:
|
||||
|
||||
- Remove the assignments of unused variables. In the first example, we can simply remove the line `user := input.user`.
|
||||
The other option is of course to use the variable that was assigned. Often orphaned variables like this are the result
|
||||
- Remove the assignments of unused variables. In the first example, remove the line `user := input.user`.
|
||||
The other option is to use the variable that was assigned. Often orphaned variables like this are the result
|
||||
a refactoring and can be safely removed if they aren't making the rule more readable.
|
||||
- Check for typos and mis-named variables. As well as normal typos, it's also easy to make mistakes and swap variables
|
||||
for alternative names. For example, sometimes `msg` instead of `message` or `user` instead of `username`.
|
||||
|
||||
@@ -83,5 +83,5 @@ Typically, the way to resolve this to find and correct misplaced whitespace
|
||||
using the location in the error message.
|
||||
|
||||
This can be tricky in larger files and often happens when moving code between
|
||||
files. We recommend migrating functions and rules incrementally to reduce the
|
||||
files. Migrating functions and rules incrementally is recommended to reduce the
|
||||
risk of this happening.
|
||||
|
||||
@@ -50,7 +50,7 @@ internal_user email if {
|
||||
|
||||
## How To Fix It
|
||||
|
||||
If caused by a missing import like the `if` keyword, simply add the import at
|
||||
If caused by a missing import like the `if` keyword, add the import at
|
||||
the top of your package. Other cases are likely caused by having forgotten to
|
||||
add an assignment operator between the rule name and the value to assign. Once
|
||||
you have identified the cause, fix the syntax error or adding the missing import
|
||||
|
||||
@@ -65,7 +65,7 @@ While overloading functions on arity might work in some languages, it is not sup
|
||||
## How To Fix It
|
||||
|
||||
Use a composite type, like objects, to return multiple values — like a boolean and a set of strings — from a rule.
|
||||
In the example below, we've added a `decision` rule, which compiles its value from both the boolean `deny` rule and
|
||||
In the example below, a `decision` rule has been added, which compiles its value from both the boolean `deny` rule and
|
||||
the `reasons` set-generating rule:
|
||||
|
||||
```rego
|
||||
@@ -93,7 +93,7 @@ reasons contains reason if {
|
||||
}
|
||||
```
|
||||
|
||||
For the case of functions, simply ensure that the same arity is used for all declarations of the function. Use the
|
||||
For the case of functions, ensure that the same arity is used for all declarations of the function. Use the
|
||||
wildcard operator (`_`) for arguments that are unused in any given declaration:
|
||||
|
||||
```rego
|
||||
|
||||
@@ -48,7 +48,7 @@ When compiled, this will result in the following error:
|
||||
## How To Fix It
|
||||
|
||||
In order to find the function that's being called with the wrong arity, you first need to find the line number
|
||||
in the error message - `16` in the example above. On that line, we need to update the function call to pass the
|
||||
in the error message - `16` in the example above. On that line, update the function call to pass the
|
||||
correct number of arguments. The example above might be fixed like so:
|
||||
|
||||
```rego
|
||||
|
||||
@@ -5,7 +5,7 @@ image: /img/opa-errors.png
|
||||
|
||||
# `rego_type_error`: match error
|
||||
|
||||
Just like the category suggests, this error is emitted by the _type checker_ during the compilation stage. This error
|
||||
As the category suggests, this error is emitted by the _type checker_ during the compilation stage. This error
|
||||
is commonly triggered by comparing two values of different types, like a string and an integer (`"1" == 1`). In order
|
||||
for the error to be reported, the compiler must be able to determine the types involved in the expression.
|
||||
|
||||
@@ -53,7 +53,7 @@ user2 := {"name": "jane"}
|
||||
same_user if user1 == user2
|
||||
```
|
||||
|
||||
We're clearly comparing two objects, so should this really be a match error? The compiler would say yes:
|
||||
Two objects are being compared here. Should this really be a match error? The compiler would say yes:
|
||||
|
||||
```txt
|
||||
1 error occurred: policy.rego:8: rego_type_error: match error
|
||||
@@ -61,7 +61,7 @@ We're clearly comparing two objects, so should this really be a match error? The
|
||||
right : object<name: string>
|
||||
```
|
||||
|
||||
The reason for this is that while we may have objects on both sides, the type checker compares _recursively_. In doing
|
||||
The reason is that while objects appear on both sides, the type checker compares _recursively_. In doing
|
||||
this it'll see that one object has an attribute (`age`) that the other doesn't, and hence considered to be of
|
||||
different type after all. This is sometimes considered
|
||||
[confusing](https://github.com/open-policy-agent/opa/issues/2132), but given that it also allows catching some bugs at
|
||||
@@ -69,7 +69,7 @@ compile time, it's likely the right behavior.
|
||||
|
||||
## How To Fix It
|
||||
|
||||
Fixing this is simple: just change the types to match on both sides of a comparison. For the few cases where one
|
||||
Fixing this requires changing the types to match on both sides of a comparison. For the few cases where one
|
||||
_really_ wants to compare two values of different types, a helper function can be used to "wash" off the type
|
||||
information from the comparison:
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@ This error is raised when multiple `default` rules are found for a single rule.
|
||||
|
||||
## Examples
|
||||
|
||||
A trivial example of a policy that contains this error is this one, where we have two `default`s
|
||||
defined for the `allow` rule:
|
||||
A trivial example of a policy that contains this error is this one, where two `default`s are defined
|
||||
for the `allow` rule:
|
||||
|
||||
```rego
|
||||
package policy
|
||||
@@ -58,7 +58,7 @@ allow if {
|
||||
}
|
||||
```
|
||||
|
||||
If we were to run OPA loading these two files, we would see the error:
|
||||
Running OPA loading these two files produces the error:
|
||||
|
||||
```shell
|
||||
$ opa run -s *.rego
|
||||
@@ -70,5 +70,5 @@ $ opa run -s *.rego
|
||||
In almost all cases, if you have an error like this: `multiple default rules data.policy.allow found`,
|
||||
searching for `default allow` in your Rego files will show up the various duplicates.
|
||||
|
||||
Just remember that they could be spread across multiple files, and you want to make sure to check only
|
||||
within the package in the error message, `policy` in our example above.
|
||||
Remember that they could be spread across multiple files, and you want to make sure to check only
|
||||
within the package in the error message, `policy` in the example above.
|
||||
|
||||
@@ -52,7 +52,7 @@ package example
|
||||
default allow := true
|
||||
```
|
||||
|
||||
We would see an error like this when using the two files:
|
||||
Using the two files produces an error like this:
|
||||
|
||||
```shell
|
||||
$ opa eval data.example.allow -d example1.rego -d example2.rego
|
||||
@@ -66,6 +66,6 @@ search for the other definitions.
|
||||
|
||||
## How To Fix It
|
||||
|
||||
Fixing this error is usually simple: just remove one of the repeated `default` definitions if they are setting the same
|
||||
Fixing this error is usually straightforward: remove one of the repeated `default` definitions if they are setting the same
|
||||
default value. If the intention is to set different default values, this is trickier, but perhaps you'd be better served
|
||||
by two different rules.
|
||||
|
||||
+1
-1
@@ -22,5 +22,5 @@ thought it would be a good idea to put certain restrictions in place.
|
||||
## How To Fix It
|
||||
|
||||
Check the capabilities configuration provided to OPA when executed, and the `--capabilities` flag in particular.
|
||||
If you're encountering this on the Rego Playground, simply run the policy on your own machine using e.g. `opa eval`
|
||||
If you're encountering this on the Rego Playground, run the policy on your own machine using e.g. `opa eval`
|
||||
or `opa run` instead.
|
||||
|
||||
@@ -5,7 +5,7 @@ image: /img/opa-errors.png
|
||||
|
||||
# `rego_unsafe_var_error`: var `{name}` is unsafe
|
||||
|
||||
This is one of the most common errors reported by OPA. When a variable is "unsafe" it simply means that OPA wasn't able
|
||||
This is one of the most common errors reported by OPA. When a variable is "unsafe" it means that OPA wasn't able
|
||||
to determine where to find it. This is commonly caused by misspelling the name of the variable, or perhaps by
|
||||
referencing a rule or function that doesn't (yet) exist. Note that this check happens at _compile time_. This means that
|
||||
references like `input.username` will not be considered unsafe even when there is no `username` attribute in the `input`
|
||||
@@ -32,13 +32,13 @@ allow if user_is_admin
|
||||
user_is_developer if "developer" in input.user.roles
|
||||
```
|
||||
|
||||
Not that easy! But luckily we won't have to, as the compiler does it for us:
|
||||
Fortunately the compiler reports it automatically:
|
||||
|
||||
```shell
|
||||
1 error occurred: policy.rego:8: rego_unsafe_var_error: var user_is_admin is unsafe
|
||||
```
|
||||
|
||||
Of course! We had forgotten to provide an actual `user_is_admin` rule before we used it, and hence why it's
|
||||
The `user_is_admin` rule was not defined before use, which is why it's
|
||||
considered unsafe.
|
||||
|
||||
Sometimes, the location of the errors isn't as clear-cut, even when reading the errors.
|
||||
@@ -67,15 +67,15 @@ foo.rego:6: rego_unsafe_var_error: var title_upper is unsafe
|
||||
```
|
||||
|
||||
Since `users` is unsafe, every variable that depend on `users` is _also_ considered unsafe. This means that _all_ the
|
||||
variables in our `allow` rule will be considered unsafe, and we'll have to do some investigative work to figure out
|
||||
variables in the `allow` rule will be considered unsafe, requiring some investigation to figure out
|
||||
what the actual root cause was. Whether this should be needed or not is
|
||||
[up for debate](https://github.com/open-policy-agent/opa/issues/6393), and perhaps we'll be able to skip this in future
|
||||
[up for debate](https://github.com/open-policy-agent/opa/issues/6393), and this limitation may be removed in future
|
||||
OPA versions.
|
||||
|
||||
## How To Fix It
|
||||
|
||||
Once you've found the unsafe variable (the compiler should help here, as we see
|
||||
above), first we must figure out _why_ it's considered unsafe. There are two
|
||||
Once the unsafe variable is found (the compiler should help here, as shown
|
||||
above), determine _why_ it is considered unsafe. There are two
|
||||
main reasons that a variable is considered unsafe:
|
||||
|
||||
- You have a typo in the policy pointing the compiler to the wrong place.
|
||||
@@ -87,16 +87,15 @@ there too.
|
||||
|
||||
If the variable name and definition are correct, the issue is likely that the
|
||||
definition of the rule has not been loaded into OPA. One thing to keep in mind
|
||||
is that all OPA commands that accept a file may just as well be provided a
|
||||
directory, which will be loaded recursively. This is often the best way to
|
||||
ensure all the files you may depend on are loaded and available during
|
||||
compilation.
|
||||
is that all OPA commands that accept a file can also be provided a
|
||||
directory, which will be loaded recursively. This is often the best way to ensure all the files you may depend on are
|
||||
loaded and available during compilation.
|
||||
|
||||
## More Information
|
||||
|
||||
Remember how we said earlier that the compiler won't consider a reference like `input.usrname` (note the typo!)
|
||||
unsafe, even though we clearly intended to say `input.username`? Wouldn't it be great if we had some way to tell the
|
||||
OPA compiler what the `input` object should look like, and have it include that in this type of check? Luckily, there
|
||||
is! The desired structure (i.e. the _schema_) of both `input` and `data` may be provided to the compiler via OPA's
|
||||
Recall that the compiler won't consider a reference like `input.usrname` (note the typo!)
|
||||
unsafe, even though the intent was `input.username`. Schema annotations provide a way to tell the
|
||||
OPA compiler what the `input` object should look like, and have it include that in this type of check.
|
||||
The desired structure (i.e. the _schema_) of both `input` and `data` may be provided to the compiler via OPA's
|
||||
[JSON schema capability](https://www.openpolicyagent.org/docs/policy-language/#schema), thus extending the
|
||||
compiler and the type checker with this information. It'll take some work to set up, but it's well worth it!
|
||||
|
||||
@@ -94,7 +94,7 @@ The example above highlights a few important points.
|
||||
- The function indicates it's undefined by returning `nil` for the first return
|
||||
argument.
|
||||
|
||||
Let's look at another example. Imagine you want to expose GitHub repository
|
||||
The following example shows another use case. Imagine you want to expose GitHub repository
|
||||
metadata to your policies. One option is to implement a custom built-in
|
||||
function to fetch the data for specific repositories on-the-fly.
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ JWTs have a limited size in practice, so if your organization has too many user
|
||||
|
||||
## Option 2: Overload `input`
|
||||
|
||||
Often policies require external data that's not available to the authentication system, ruling out JWTs. The calling system can include external data as part of `input` (necessitating of course that the policy is written accordingly).
|
||||
Often policies require external data that's not available to the authentication system, ruling out JWTs. The calling system can include external data as part of `input` (necessitating that the policy is written accordingly).
|
||||
|
||||
For example, suppose your policy says that only a file's owner may delete it. The authentication system does not track resource-ownership, but the system responsible for files certainly does.
|
||||
|
||||
@@ -124,7 +124,7 @@ The lag between a data update and OPA having the update is the sum of the lag fo
|
||||
|
||||
### Size limitations
|
||||
|
||||
OPA stores the entire datasource at once in memory. Obviously this can be a problem with large external data sets. Because the centralized server handles both policy and data it can prune data to just that which is needed for the policies.
|
||||
OPA stores the entire datasource at once in memory. This can be a problem with large external data sets. Because the centralized server handles both policy and data it can prune data to just that which is needed for the policies.
|
||||
|
||||
<!-- **Security**
|
||||
* Don't expose OPA's API except through localhost
|
||||
@@ -176,7 +176,7 @@ The total lag between the external data source being updated and OPA being updat
|
||||
|
||||
### Size limitations
|
||||
|
||||
The entirety of the external data source is stored in memory, which can obviously be a problem with large external data sources. But unlike the bundle API, this approach does allow updates to data.
|
||||
The entirety of the external data source is stored in memory, which can be a problem with large external data sources. But unlike the bundle API, this approach does allow updates to data.
|
||||
|
||||
<!--
|
||||
**Security**
|
||||
|
||||
+8
-8
@@ -89,7 +89,7 @@ ratelimit := 4 if {
|
||||
|
||||
## Which Equality Operator Should I Use?
|
||||
|
||||
Rego supports three kinds of equality: assignment (`:=`), comparison (`==`), and unification `=`. We recommend using assignment (`:=`) and comparison (`==`) whenever possible for policies that are easier to read and write.
|
||||
Rego supports three kinds of equality: assignment (`:=`), comparison (`==`), and unification `=`. Using assignment (`:=`) and comparison (`==`) is recommended whenever possible for policies that are easier to read and write.
|
||||
|
||||
```rego
|
||||
# Assignment: declare local variable x and give it value 7
|
||||
@@ -167,7 +167,7 @@ result := trim_and_split(" hello.world ")
|
||||
|
||||
<RunSnippet command="data.functions.result"/>
|
||||
|
||||
The other way to factor out common logic is with a _rule_. Rules differ in that (i) they support automatic iteration and (ii) they are only defined for finitely many inputs. (Those obviously go hand-in-hand.) For example, you could define a rule that maps an application to the hostnames that app is running on:
|
||||
The other way to factor out common logic is with a _rule_. Rules differ in that (i) they support automatic iteration and (ii) they are only defined for finitely many inputs. (Those go hand-in-hand.) For example, you could define a rule that maps an application to the hostnames that app is running on:
|
||||
|
||||
```rego
|
||||
package rules
|
||||
@@ -226,7 +226,7 @@ sites := [
|
||||
|
||||
<RunSnippet id="data.rego"/>
|
||||
|
||||
And then we can iterate over all the key/value pairs of that app-to-hostname mapping (just like we could iterate over all key/value pairs of a hardcoded JSON object). You can also iterate over just the keys or just the values or you can look up the value for a key or lookup all the keys for a single value.
|
||||
It is then possible to iterate over all the key/value pairs of that app-to-hostname mapping (just like all key/value pairs of a hardcoded JSON object). You can also iterate over just the keys or just the values, or look up the value for a key or all the keys for a single value.
|
||||
|
||||
```rego
|
||||
package example
|
||||
@@ -259,13 +259,13 @@ result.where contains k if {
|
||||
|
||||
<RunSnippet files="#data.rego" command="data.example.result"/>
|
||||
|
||||
Obviously with the `trim_and_split` function we cannot ask for all the inputs/outputs since there are infinitely many. We can't provide 1 input and ask for all the other inputs that make the function return true, again, because there could be infinitely many. The only thing we can do with a function is provide it all the inputs and ask for the output.
|
||||
With the `trim_and_split` function it is not possible to ask for all the inputs/outputs since there are infinitely many. It is not possible to provide 1 input and ask for all the other inputs that make the function return true, again, because there could be infinitely many. The only option with a function is to provide all the inputs and ask for the output.
|
||||
|
||||
Functions allow you to factor out common logic that has infinitely-many input/output pairs; rules allow you to factor out common logic with finitely many input/outputs and allow you to iterate over them in the same way as native JSON objects.
|
||||
|
||||
To achieve automatic iteration, there is an additional syntactic requirement on a rule that is NOT present for a function: `safety`. See the FAQ entry on safety for technical details. Every rule must be `safe`, which guarantees that OPA can figure out a finite list of possible values for every variable in the body and head of a rule.
|
||||
|
||||
We recommend using rules where possible and using functions when rules do not work.
|
||||
Use rules where possible; use functions when rules do not work.
|
||||
|
||||
## Safety
|
||||
|
||||
@@ -305,7 +305,7 @@ p[x] { some x; not q[x]; r[x] }
|
||||
|
||||
Safety has one implication about negation: you don't iterate over values NOT in a rule like `q`. Instead, you iterate over values in another rule like `r` and then use negation to CHECK whether if that value is NOT in `q`.
|
||||
|
||||
Embedded terms like `not p[q[_]]` sometimes produce difficult to decipher error messages. We recommend pulling the embedded terms out into the rule--the meaning is the same and often creates easier to read error messages:
|
||||
Embedded terms like `not p[q[_]]` sometimes produce difficult to decipher error messages. Pulling the embedded terms out into the rule is recommended -- the meaning is the same and often creates easier to read error messages:
|
||||
|
||||
```rego
|
||||
x := q[_]
|
||||
@@ -386,7 +386,7 @@ runtime.env.PROD_CERTIFICATE
|
||||
|
||||
## How do I Write Policies Securely?
|
||||
|
||||
Depending on the use case and the integration with OPA that you are using, the style of policy you choose can impact your overall security posture. Below we show three styles of authoring policy and compare them.
|
||||
Depending on the use case and the integration with OPA that you are using, the style of policy you choose can impact your overall security posture. The following compares three styles of authoring policy.
|
||||
|
||||
**Default allow**. This style of policy allows every request by default. The rules you write dictate which requests should be rejected.
|
||||
|
||||
@@ -400,7 +400,7 @@ deny if { ... }
|
||||
deny if { ... }
|
||||
```
|
||||
|
||||
If you assume all of the rules you write are correct, then you know that every rejection the policy produces should truly be rejected. However, there could be requests that are allowed that you may not truly want allowed, but you simply neglected to write the rule for. For operations, this is often a useful style of policy authoring because it allows you to incrementally tighten the controls for a system from wherever that system starts. For security, this style is less appropriate because it allows unknown bad actions to occur.
|
||||
If you assume all of the rules you write are correct, then you know that every rejection the policy produces should truly be rejected. However, there could be requests that are allowed that you may not truly want allowed, but you have not yet written the rule for. For operations, this is often a useful style of policy authoring because it allows you to incrementally tighten the controls for a system from wherever that system starts. For security, this style is less appropriate because it allows unknown bad actions to occur.
|
||||
|
||||
**Default deny**. This style of policy rejects every request by default. The rules you write dictate which requests should be allowed.
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ You will develop an intuition for what valid masking rules look like, and how to
|
||||
|
||||
## What is Column Masking?
|
||||
|
||||
For our data filtering use case, a row might be returned from the database that has a sensitive column present.
|
||||
We still want the application to be able to display everything it can to the user, but ideally hiding or modifying the sensitive values before display.
|
||||
In the data filtering use case, a row might be returned from the database that has a sensitive column present.
|
||||
The application should still be able to display everything it can to the user, but the sensitive values should be hidden or modified before display.
|
||||
|
||||
## Format of a Column Masks Object
|
||||
|
||||
@@ -66,9 +66,9 @@ Note that the value keyed under `users.id` is **empty**, which implies "show val
|
||||
|
||||
## Creating a default-deny style masking policy
|
||||
|
||||
For our running example, we will be adding column masking to a support ticket application, where the fields of a tickets that a user can see is determined by their role.
|
||||
In the running example, column masking is added to a support ticket application, where the fields of a ticket that a user can see are determined by their role.
|
||||
|
||||
Our masking policy will mask the `tickets.description` field with the dummy value `"<description>"` by default, and will allow the real value through for users with the `admin` role.
|
||||
The masking policy masks the `tickets.description` field with the dummy value `"<description>"` by default, and allows the real value through for users with the `admin` role.
|
||||
|
||||
Roles are provided to EOPA via `roles/data.json`, and the SQL tables contain tickets and assignees.
|
||||
|
||||
@@ -88,7 +88,7 @@ erDiagram
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
We will be generating data filters and column masks for the `data.filters.include` rule, which marks `data.filters.masks` as its masking rule with the `custom.mask_rule` key, as shown below.
|
||||
Data filters and column masks are generated for the `data.filters.include` rule, which marks `data.filters.masks` as its masking rule with the `custom.mask_rule` key, as shown below.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="rego" label="filters.rego" default>
|
||||
@@ -201,9 +201,9 @@ Content-Type: application/json
|
||||
|
||||
## Creating a default-allow style masking policy
|
||||
|
||||
For this example, we will keep the support ticket application setup from before. We are still limiting which fields of a tickets that a user can see, based on their role.
|
||||
For this example, the support ticket application setup from before is reused. The policy still limits which fields of a ticket that a user can see, based on their role.
|
||||
|
||||
Our masking policy will allow the real value through all roles _except_ the `reader` role. The `reader` role will see the every `tickets.description` field set to `"<description>"`.
|
||||
The masking policy allows the real value through all roles _except_ the `reader` role. The `reader` role will see the every `tickets.description` field set to `"<description>"`.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="rego" label="filters.rego" default>
|
||||
|
||||
@@ -31,7 +31,7 @@ When only _known_ values are used, **you can use all of Rego.**
|
||||
|
||||
## Example Preamble
|
||||
|
||||
In our running example, we'll assume a table `fruits` with columns `name`, `colour`, and `price`. These **unknown values** are represented with `input.<TABLE>.<COLUMN>` e.g. `input.fruits.name`
|
||||
In the running example, assume a table `fruits` with columns `name`, `colour`, and `price`. These **unknown values** are represented with `input.<TABLE>.<COLUMN>` e.g. `input.fruits.name`
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
@@ -42,7 +42,7 @@ erDiagram
|
||||
}
|
||||
```
|
||||
|
||||
Our data filters also depend on user information. These **known values** are represented with `input.user`
|
||||
The data filters also depend on user information. These **known values** are represented with `input.user`
|
||||
|
||||
```json
|
||||
{
|
||||
|
||||
@@ -6,7 +6,7 @@ sidebar_position: 1
|
||||
|
||||
Data Filtering is a common use case for authorization that goes beyond "allow or deny?".
|
||||
It is often related to searching (or listing) multiple entities.
|
||||
Here, we start with a problem exposition before going into the details of data filtering with OPA in the next sections.
|
||||
This page starts with a problem exposition before going into the details of data filtering with OPA in the next sections.
|
||||
|
||||
## Evaluation vs Search
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ sidebar_position: 2
|
||||
---
|
||||
|
||||
To understand how SQL WHERE clauses can be derived from a partially evaluated Rego policy, it's beneficial to have a basic idea about how _partial evaluation_ (PE) works.
|
||||
In this walk-through of a PE run, we'll start with a basic filter policy:
|
||||
This walk-through of a PE run starts with a basic filter policy:
|
||||
|
||||
```rego title="filters.rego"
|
||||
# METADATA
|
||||
@@ -31,7 +31,7 @@ include if {
|
||||
include if input.products.price == "free"
|
||||
```
|
||||
|
||||
In this walkthrough, we'll go through the policy in the same way the evaluator does, and use the following input:
|
||||
This walkthrough follows the policy in the same way the evaluator does, using the following input:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -96,7 +96,7 @@ include if input.products.price == "free"
|
||||
</SideBySideColumn>
|
||||
<SideBySideColumn>
|
||||
The expression `input.users.name == user` uses `user`, which is _known_, "dana".
|
||||
The <abbr title="left-hand side">LHS</abbr> `input.users.name` is part of the _unknowns_ (`input.users`), so the expression contributes to our conditions:
|
||||
The <abbr title="left-hand side">LHS</abbr> `input.users.name` is part of the _unknowns_ (`input.users`), so the expression contributes to the conditions:
|
||||
|
||||
```rego
|
||||
input.users.name == "dana"
|
||||
@@ -131,9 +131,9 @@ include if input.products.price == "free"
|
||||
|
||||
</SideBySideColumn>
|
||||
<SideBySideColumn>
|
||||
Our expression's <abbr title="left-hand side">LHS</abbr> is known, "low", which is not different from "low".
|
||||
An expression with all known parts that evaluates to false makes us give up on this rule path (regardless of eventual extra expressions following),
|
||||
and we discard the set of conditions aggregated for this rule body.
|
||||
The expression's <abbr title="left-hand side">LHS</abbr> is known, "low", which is not different from "low".
|
||||
When all parts of an expression are known and it evaluates to false, this rule path is abandoned (regardless of any further expressions),
|
||||
and the set of conditions aggregated for this rule body is discarded.
|
||||
|
||||
Partial evaluation continues with the next rule body.
|
||||
</SideBySideColumn>
|
||||
@@ -165,7 +165,7 @@ include if input.products.price == "free"
|
||||
|
||||
</SideBySideColumn>
|
||||
<SideBySideColumn>
|
||||
Evaluating our second rule body, we again get a condition from the comparison with `user`, which is `input.user`, and known to be "dana":
|
||||
The second rule body is evaluated. A condition is again derived from the comparison with `user`, which is `input.user`, and known to be "dana":
|
||||
|
||||
```rego
|
||||
input.users.name == "dana"
|
||||
@@ -267,13 +267,13 @@ include if input.products.price == "free"
|
||||
<SideBySideColumn>
|
||||
As with every new rule body, the set of conditions is _reset_.
|
||||
|
||||
This expression includes one unknown and one literal, so it adds a condition to our set:
|
||||
This expression includes one unknown and one literal, so it adds a condition to the set:
|
||||
|
||||
```rego
|
||||
input.products.price == "free"
|
||||
```
|
||||
|
||||
There are no further expressions, so this condition also contributes to our PE result.
|
||||
There are no further expressions, so this condition also contributes to the PE result.
|
||||
</SideBySideColumn>
|
||||
</SideBySideContainer>
|
||||
|
||||
@@ -281,7 +281,7 @@ There are no further expressions, so this condition also contributes to our PE r
|
||||
|
||||
<SideBySideContainer>
|
||||
<SideBySideColumn>
|
||||
We're now done with the partial evaluation of our `data.filters.include` rule with the given (known) inputs.
|
||||
The partial evaluation of `data.filters.include` with the given (known) inputs is now complete.
|
||||
It has yielded two sets of conditions, **A** and **B**, which form the basis of translation into SQL queries.
|
||||
</SideBySideColumn>
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ The data filtering support makes use of the [Universal Conditions AST (UCAST)](h
|
||||
|
||||
UCAST allows 3 types of nodes in the tree:
|
||||
|
||||
- **Document-level Condition** nodes: Used to apply an operator to the entire document/table, e.g. the EXISTS operator in SQL. These types of nodes are _not used_ by any of our interpreters.
|
||||
- **Document-level Condition** nodes: Used to apply an operator to the entire document/table, e.g. the EXISTS operator in SQL. These types of nodes are _not used_ by any of the interpreters.
|
||||
- **Compound Condition** nodes: Used to apply an operator across N-many child nodes.
|
||||
- **Field Condition** nodes: Used to apply an operator to a field and an optional value.
|
||||
|
||||
|
||||
@@ -6,14 +6,13 @@ GraphQL APIs have become a popular way for clients to query the information
|
||||
they require from a range of data sources.
|
||||
Generally, services providing a [GraphQL](https://graphql.org/) API must
|
||||
authorize calls to control data access and mutations.
|
||||
OPA makes it easy to write fine-grained, context-aware policies to implement
|
||||
GraphQL query authorization.
|
||||
OPA supports writing fine-grained, context-aware policies to implement GraphQL query authorization.
|
||||
|
||||
In this tutorial, you'll use a simple GraphQL server that accepts any GraphQL query that you issue, and echoes the OPA decision back as text.
|
||||
OPA will fetch policy bundles from a simple bundle server.
|
||||
OPA, the bundle server, and the GraphQL server will run as separate containers.
|
||||
|
||||
For this tutorial, we have the following example scenario:
|
||||
For this tutorial, the following example scenario is used:
|
||||
|
||||
- Staff can see their own salaries (`query user($id: <user>) { salary }` is permitted for `<user>`)
|
||||
- A manager can see their direct reports' salaries (`query user($id: <user>) { salary }` is permitted for `<user>`'s manager)
|
||||
@@ -25,15 +24,15 @@ Using GraphQL in Rego via the
|
||||
[GraphQL built-in functions](./policy-reference/builtins/graphql)
|
||||
can involve some cumbersome code-sharing, for example when sharing custom
|
||||
`@directive` definitions between schemas used in different rules.
|
||||
We recommend you evaluate the range of available functions and review your
|
||||
Evaluate the range of available functions and review your
|
||||
GraphQL feature use to form a plan before embarking on major migrations.
|
||||
:::
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Define our GraphQL schema
|
||||
### 1. Define the GraphQL schema
|
||||
|
||||
Most modern GraphQL frameworks encourage starting with a schema, so we'll follow suit, and begin by defining the schema for this example.
|
||||
Most modern GraphQL frameworks encourage starting with a schema. Begin by defining the schema for this example.
|
||||
|
||||
```graphql title="schema.gql"
|
||||
type Employee {
|
||||
@@ -53,20 +52,20 @@ type Query {
|
||||
Every GraphQL service has a `query` type, and may or may not have a `mutation` type.
|
||||
These types are special because they define the entry points of _every_ GraphQL query for the API covered by that schema.
|
||||
|
||||
For our example above, we've defined exactly one query entry point, the parameterized query `employeeByID(id: String!)`.
|
||||
In the example above, exactly one query entry point is defined, the parameterized query `employeeByID(id: String!)`.
|
||||
|
||||
### 2. Create a policy bundle
|
||||
|
||||
GraphQL APIs allow surprising flexibility in how queries can be constructed, which makes writing policies for them a bit more challenging than for a REST API, which usually has a more fixed structure.
|
||||
|
||||
To protect a particular endpoint or field, we need to see if they are referenced in the incoming GraphQL query.
|
||||
By using `graphql.parse`, we can extract an [abstract syntax tree][wikipedia-ast] (AST) from the incoming query, and then walk down the tree to its leaves to see if our endpoint is the target of the query.
|
||||
To protect a particular endpoint or field, check whether they are referenced in the incoming GraphQL query.
|
||||
Using `graphql.parse`, an [abstract syntax tree][wikipedia-ast] (AST) can be extracted from the incoming query, and then the tree can be walked to its leaves to see if the endpoint is the target of the query.
|
||||
|
||||
We can then use separate rules to enforce conditions around the `salary` field, and who is allowed to access it.
|
||||
Separate rules can then enforce conditions around the `salary` field, and who is allowed to access it.
|
||||
|
||||
The policy below does all of the above in parts:
|
||||
|
||||
- Obtains the query AST (and validates it against our schema with `graphql.parse`).
|
||||
- Obtains the query AST (and validates it against the schema with `graphql.parse`).
|
||||
- Recursive traversal with `walk()` to obtain chunks of the AST with queries of interest present.
|
||||
- Selection of nodes of interest by name and structure.
|
||||
- Salary field selected.
|
||||
@@ -245,7 +244,7 @@ await axios
|
||||
|
||||
### 4. Check that `alice` can see her own salary
|
||||
|
||||
We'll define a quick shell function to make the following examples cleaner on the command line:
|
||||
Define a quick shell function to make the following examples cleaner on the command line:
|
||||
|
||||
```shell
|
||||
gql-query() {
|
||||
@@ -303,7 +302,7 @@ gql-query bob:password "localhost:6000/" '{"query":"query { employeeByID(id: \"c
|
||||
|
||||
Suppose the organization now includes an HR department.
|
||||
The organization wants members of HR to be able to see any salary.
|
||||
Let's extend the policy to handle this.
|
||||
Extend the policy to handle this.
|
||||
|
||||
```rego title="example-hr.rego"
|
||||
package graphqlapi.authz
|
||||
@@ -328,7 +327,7 @@ mv bundle.tar.gz ./bundles
|
||||
The updated bundle will automatically be served by the bundle server, but note that it might take up to the configured `max_delay_seconds` for the new bundle to be downloaded by OPA.
|
||||
If you plan to make frequent policy changes you might want to adjust this value in `docker-compose.yaml` accordingly.
|
||||
|
||||
For the sake of the tutorial we included `manager_of` and `hr` data directly inside the policies.
|
||||
For the sake of the tutorial, `manager_of` and `hr` data is included directly inside the policies.
|
||||
In real-world scenarios that information would be imported from external data sources.
|
||||
|
||||
### 7. Check that the new policy works
|
||||
@@ -345,7 +344,7 @@ gql-query david:password "localhost:6000/" '{"query":"query { employeeByID(id: \
|
||||
### 8. (Optional) Use JSON Web Tokens to communicate policy data
|
||||
|
||||
OPA supports the parsing of JSON Web Tokens via the builtin function `io.jwt.decode`.
|
||||
To get a sense of one way the subordinate and HR data might be communicated in the real world, let's try a similar exercise utilizing the JWT utilities of OPA.
|
||||
To get a sense of one way the subordinate and HR data might be communicated in the real world, try a similar exercise utilizing the JWT utilities of OPA.
|
||||
|
||||
```rego title="example-jwt.rego"
|
||||
package graphqlapi.authz
|
||||
@@ -460,7 +459,7 @@ opa build example-jwt.rego example-hr.rego
|
||||
mv bundle.tar.gz ./bundles
|
||||
```
|
||||
|
||||
For convenience, we'll want to store user tokens in environment variables (they're really long).
|
||||
For convenience, store user tokens in environment variables (the token strings are long).
|
||||
|
||||
```shell
|
||||
export ALICE_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWxpY2UiLCJhenAiOiJhbGljZSIsInN1Ym9yZGluYXRlcyI6W10sImhyIjpmYWxzZX0.rz3jTY033z-NrKfwrK89_dcLF7TN4gwCMj-fVBDyLoM"
|
||||
@@ -470,10 +469,10 @@ export BETTY_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYmV0dHkiLCJ
|
||||
export DAVID_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiZGF2aWQiLCJhenAiOiJkYXZpZCIsInN1Ym9yZGluYXRlcyI6W10sImhyIjp0cnVlfQ.Q6EiWzU1wx1g6sdWQ1r4bxT1JgSHUpVXpINMqMaUDMU"
|
||||
```
|
||||
|
||||
These tokens encode the same information as the policies we did before (`bob` is `alice`'s manager, `betty` is `charlie`'s, `david` is the only HR member, etc).
|
||||
These tokens encode the same information as the earlier policies (`bob` is `alice`'s manager, `betty` is `charlie`'s, `david` is the only HR member, etc).
|
||||
If you want to inspect their contents, start up the OPA REPL and execute `io.jwt.decode(<token here>, [header, payload, signature])` or open the example above in the Playground.
|
||||
|
||||
Let's try a few queries (note: you may need to escape the `?` characters in the queries for your shell):
|
||||
Try a few queries (note: you may need to escape the `?` characters in the queries for your shell):
|
||||
|
||||
Check that `charlie` can't see `bob`'s salary.
|
||||
|
||||
@@ -505,9 +504,7 @@ Check that `alice` can see her own salary.
|
||||
gql-query alice:password "localhost:5000/?token=$ALICE_TOKEN" '{"query":"query { employeeByID(id: \"alice\") { salary }}"}'
|
||||
```
|
||||
|
||||
## Wrap Up
|
||||
|
||||
Congratulations for finishing the tutorial!
|
||||
## Summary
|
||||
|
||||
You learned a number of things about API authorization with OPA:
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title: "HTTP APIs"
|
||||
---
|
||||
|
||||
Anything that exposes an HTTP API (whether an individual microservice or an application as a whole) needs to control who can run those APIs and when. OPA makes it easy to write fine-grained, context-aware policies to implement API authorization.
|
||||
Anything that exposes an HTTP API (whether an individual microservice or an application as a whole) needs to control who can run those APIs and when. OPA supports writing fine-grained, context-aware policies for API authorization.
|
||||
|
||||
## Goals
|
||||
|
||||
@@ -11,7 +11,7 @@ request that you issue and echoes the OPA decision back as text. OPA will fetch
|
||||
policy bundles from a simple bundle server. Both OPA, the bundle server and the
|
||||
web server will be run as containers.
|
||||
|
||||
For this tutorial, our desired policy is:
|
||||
For this tutorial, the desired policy is:
|
||||
|
||||
- People can see their own salaries (`GET /finance/salary/{user}` is permitted for `{user}`)
|
||||
- A manager can see their direct reports' salaries (`GET /finance/salary/{user}` is permitted for `{user}`'s manager)
|
||||
@@ -206,7 +206,7 @@ curl --user bob:password localhost:5000/finance/salary/charlie
|
||||
### 6. Change the policy
|
||||
|
||||
Suppose the organization now includes an HR department. The organization wants
|
||||
members of HR to be able to see any salary. Let's extend the policy to handle
|
||||
members of HR to be able to see any salary. Extend the policy to handle
|
||||
this.
|
||||
|
||||
```rego title="example-hr.rego"
|
||||
@@ -233,7 +233,7 @@ The updated bundle will automatically be served by the bundle server, but note t
|
||||
configured `max_delay_seconds` for the new bundle to be downloaded by OPA. If you plan to make frequent policy
|
||||
changes you might want to adjust this value in `docker-compose.yaml` accordingly.
|
||||
|
||||
For the sake of the tutorial we included `manager_of` and `hr` data directly
|
||||
For the sake of the tutorial, `manager_of` and `hr` data are included directly
|
||||
inside the policies. In real-world scenarios that information would be imported
|
||||
from external data sources.
|
||||
|
||||
@@ -252,7 +252,7 @@ curl --user david:password localhost:5000/finance/salary/david
|
||||
|
||||
OPA supports the parsing of JSON Web Tokens via the builtin function `io.jwt.decode`.
|
||||
To get a sense of one way the subordinate and HR data might be communicated in the
|
||||
real world, let's try a similar exercise utilizing the JWT utilities of OPA.
|
||||
real world, try a similar exercise utilizing the JWT utilities of OPA.
|
||||
|
||||
```rego title="example-jwt.rego"
|
||||
package httpapi.authz
|
||||
@@ -309,7 +309,7 @@ Build a new bundle for the new policy.
|
||||
opa build example-jwt.rego
|
||||
```
|
||||
|
||||
For convenience, we'll want to store user tokens in environment variables (they're really long).
|
||||
For convenience, store user tokens in environment variables (they are long).
|
||||
|
||||
```shell
|
||||
export ALICE_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWxpY2UiLCJhenAiOiJhbGljZSIsInN1Ym9yZGluYXRlcyI6W10sImhyIjpmYWxzZX0.rz3jTY033z-NrKfwrK89_dcLF7TN4gwCMj-fVBDyLoM"
|
||||
@@ -319,10 +319,10 @@ export BETTY_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYmV0dHkiLCJ
|
||||
export DAVID_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiZGF2aWQiLCJhenAiOiJkYXZpZCIsInN1Ym9yZGluYXRlcyI6W10sImhyIjp0cnVlfQ.Q6EiWzU1wx1g6sdWQ1r4bxT1JgSHUpVXpINMqMaUDMU"
|
||||
```
|
||||
|
||||
These tokens encode the same information as the policies we did before (`bob` is `alice`'s manager, `betty` is `charlie`'s, `david` is the only HR member, etc).
|
||||
These tokens encode the same information as the earlier policies (`bob` is `alice`'s manager, `betty` is `charlie`'s, `david` is the only HR member, etc).
|
||||
If you want to inspect their contents, start up the OPA REPL and execute `io.jwt.decode(<token here>, [header, payload, signature])` or open the example above in the Playground.
|
||||
|
||||
Let's try a few queries (note: you may need to escape the `?` characters in the queries for your shell):
|
||||
Try a few queries (note: you may need to escape the `?` characters in the queries for your shell):
|
||||
|
||||
Check that `charlie` can't see `bob`'s salary.
|
||||
|
||||
@@ -354,9 +354,7 @@ Check that `alice` can see her own salary.
|
||||
curl --user alice:password "localhost:5000/finance/salary/alice?token=$ALICE_TOKEN"
|
||||
```
|
||||
|
||||
## Wrap Up
|
||||
|
||||
Congratulations for finishing the tutorial!
|
||||
## Summary
|
||||
|
||||
You learned a number of things about API authorization with OPA:
|
||||
|
||||
|
||||
+18
-23
@@ -42,7 +42,7 @@ any kind of invariant in your policies. For example:
|
||||
Policy decisions are not limited to simple yes/no or allow/deny answers. Like
|
||||
query inputs, your policies can generate arbitrary structured data as output.
|
||||
|
||||
Let's look at an example. Imagine you work for an organization with a number of servers connected to managed networks via ports:
|
||||
The following example illustrates this. Imagine you work for an organization with a number of servers connected to managed networks via ports:
|
||||
|
||||
```mermaid
|
||||
graph
|
||||
@@ -94,7 +94,7 @@ The policy needs to be enforced when servers, networks, and ports are
|
||||
provisioned and the compliance team wants to periodically audit the system to
|
||||
find servers that violate the policy.
|
||||
|
||||
Let's explore how OPA can help implement this policy.
|
||||
The following section explores how OPA can help implement this policy.
|
||||
|
||||
## Writing Policy with Rego
|
||||
|
||||
@@ -116,13 +116,13 @@ They can also be run locally on your machine using the
|
||||
|
||||
:::note
|
||||
This section covers the building blocks of writing policies in Rego. You can see
|
||||
how these concepts come together to solve our network security policy in the
|
||||
how these concepts come together to solve the network security policy in the
|
||||
[Complete Example](#complete-example).
|
||||
:::
|
||||
|
||||
### Basic Syntax
|
||||
|
||||
To implement our security policy, we first need to access and examine the
|
||||
To implement the security policy, the first step is to access and examine the
|
||||
infrastructure data. When OPA evaluates policies, it binds data provided in the
|
||||
query to a global variable called `input`. You can refer to specific parts of
|
||||
the input data using the `.` (dot) operator.
|
||||
@@ -162,8 +162,8 @@ output := input.foobar
|
||||
<RunSnippet files="#input.json" command="data.servers.output"/>
|
||||
|
||||
The most simple policy decisions are made by writing expressions that perform
|
||||
logical operations on the input data. For example, we can check if a server has
|
||||
a specific ID using an equality check with `==`.
|
||||
logical operations on the input data. For example, to check if a server has
|
||||
a specific ID, use an equality check with `==`.
|
||||
|
||||
```rego
|
||||
package servers
|
||||
@@ -364,7 +364,7 @@ ssh_server if {
|
||||
|
||||
#### FOR SOME and FOR ALL
|
||||
|
||||
While plain iteration serves as a powerful building block, Rego also features ways
|
||||
While plain iteration is a fundamental building block, Rego also features ways
|
||||
to express _FOR SOME_ and _FOR ALL_ more explicitly.
|
||||
|
||||
##### FOR SOME (`some`)
|
||||
@@ -373,7 +373,7 @@ to express _FOR SOME_ and _FOR ALL_ more explicitly.
|
||||
and will bind its variables (key, value position) to the collection items.
|
||||
It introduces new bindings to the evaluation of the rest of the rule body.
|
||||
|
||||
Using `some`, we can express the rules introduced above in different ways:
|
||||
Using `some`, the rules introduced above can be expressed in different ways:
|
||||
|
||||
```rego
|
||||
package servers
|
||||
@@ -447,7 +447,7 @@ logic statements. Rules can either be "complete" or "partial".
|
||||
#### Complete Rules
|
||||
|
||||
Complete rules are if-then statements that assign a single value to a variable.
|
||||
Every rule consists of a <GlossaryTooltip term="rule-head">head</GlossaryTooltip> and a <GlossaryTooltip term="rule-body">body</GlossaryTooltip>. In Rego we say the rule head
|
||||
Every rule consists of a <GlossaryTooltip term="rule-head">head</GlossaryTooltip> and a <GlossaryTooltip term="rule-body">body</GlossaryTooltip>. In Rego, the rule head
|
||||
is true _if_ the rule body is true for some set of variable assignments.
|
||||
|
||||
```rego
|
||||
@@ -526,8 +526,7 @@ Constants defined like this can be queried just like any other values:
|
||||
count(input.servers[0].protocols) < max_allowed_protocols
|
||||
```
|
||||
|
||||
If OPA cannot find variable assignments that satisfy the rule body, we say that
|
||||
the rule is undefined. For example, if the `input` provided to OPA does not
|
||||
If OPA cannot find variable assignments that satisfy the rule body, the rule is undefined. For example, if the `input` provided to OPA does not
|
||||
include a public network then `exists_public_network` will be undefined (which is
|
||||
not the same as false.) Below, OPA is given a different set of input networks
|
||||
(none of which are public):
|
||||
@@ -574,7 +573,7 @@ public_network contains net.id if {
|
||||
|
||||
<RunSnippet id="public_network_set.rego" files="#input.json" command="data.example"/>
|
||||
|
||||
Using the `in` keyword we can use this list to test if some other value is in
|
||||
Using the `in` keyword, this list can be used to test if some other value is in
|
||||
the set defined by `public_network`:
|
||||
|
||||
```rego
|
||||
@@ -608,7 +607,7 @@ the language guide for more information.
|
||||
|
||||
When you join multiple expressions together in a query you are expressing
|
||||
logical AND. To express logical OR in Rego you define multiple rules with the
|
||||
same name. Let's look at an example.
|
||||
same name. The following example illustrates this.
|
||||
|
||||
Imagine you wanted to know if any servers expose protocols that give clients
|
||||
shell access. To determine this you could define a complete rule that declares
|
||||
@@ -689,14 +688,14 @@ express OR in idiomatic Rego for different use cases.
|
||||
|
||||
### Complete Example
|
||||
|
||||
The sections above explain the core concepts in Rego. To put it all together
|
||||
let's review the desired policy in natural language:
|
||||
The sections above explain the core concepts in Rego. To put it all together,
|
||||
review the desired policy in natural language:
|
||||
|
||||
> 1. Servers reachable from the Internet must not expose the insecure 'http' protocol.
|
||||
> 2. Servers are not allowed to expose the 'telnet' protocol.
|
||||
|
||||
At a high-level the policy needs to identify servers that violate some
|
||||
conditions. To implement this policy we could define rules called `violation`
|
||||
conditions. To implement this policy, define rules called `violation`
|
||||
that generate a set of servers that are in violation. For example:
|
||||
|
||||
```rego
|
||||
@@ -732,7 +731,7 @@ public_servers contains server if { # a server exists in the public_servers set
|
||||
|
||||
<RunSnippet files="#input.json" command="data.example.violation"/>
|
||||
|
||||
This example demonstrates how we can use Rego to create a clear list of policy
|
||||
This example demonstrates how Rego can create a clear list of policy
|
||||
violations that can be handed back to the infrastructure as code system to
|
||||
present to the user, making it easy for them to see what's gone wrong.
|
||||
|
||||
@@ -849,7 +848,7 @@ mkdir C:\Tools\OPA
|
||||
move opa.exe C:\Tools\OPA\
|
||||
```
|
||||
|
||||
Now we can add this to our `PATH`:
|
||||
Add this to your `PATH`:
|
||||
|
||||
Control Panel → System → Advanced system settings → Environment Variables
|
||||
|
||||
@@ -898,7 +897,7 @@ shasum -c $BINARY_NAME.sha256
|
||||
### 2. Try `opa eval`
|
||||
|
||||
The simplest way to interact with OPA is via the command-line using the [`opa eval` sub-command](./docs/cli#eval).
|
||||
It is a swiss-army knife that you can use to evaluate arbitrary Rego expressions and policies.
|
||||
It can be used to evaluate arbitrary Rego expressions and policies.
|
||||
`opa eval` supports a large number of options for controlling evaluation.
|
||||
Commonly used flags include:
|
||||
|
||||
@@ -1251,10 +1250,6 @@ go run main.go example.rego 'data.example.violation' < input.json
|
||||
|
||||
## Next Steps
|
||||
|
||||
Congratulations on completing the introduction to OPA. You have learned the core
|
||||
concepts behind OPA's policy language as well as how to get OPA and run it on
|
||||
your own.
|
||||
|
||||
If you have more questions about how to write policies in Rego check out:
|
||||
|
||||
- The [Policy Reference](./docs/policy-reference) page for reference documentation on built-in functions.
|
||||
|
||||
@@ -10,7 +10,7 @@ service, or tool with OPA.
|
||||
When integrating with OPA there are two interfaces to consider:
|
||||
|
||||
- **Evaluation**: OPA's interface for asking for policy decisions. Integrating OPA is primarily focused on integrating an application, service, or tool with OPA's policy evaluation interface. This integration results in policy decisions being decoupled from that application, service, or tool.
|
||||
- **Management**: OPA's interface for deploying policies, understanding status, uploading logs, and so on. This integration is typically the same across all OPA instances, regardless what software the evaluation interface is integrated with. Distributing policy, retrieving status, and storing logs in the same way across all OPAs provides a unified management plane for policy across many different software systems.
|
||||
- **Management**: OPA's interface for deploying policies, understanding status, uploading logs, and so on. This integration is typically the same across all OPA instances, regardless what software the evaluation interface is integrated with. Distributing policy, retrieving status, and storing logs in the same way across all OPA instances provides a unified management plane for policy across many different software systems.
|
||||
|
||||
This page focuses predominantly on different ways to integrate with OPA's policy evaluation interface and how they compare. For more information about the management interface:
|
||||
|
||||
@@ -37,7 +37,7 @@ OPA supports different ways to evaluate policies.
|
||||
|
||||
### Integrating with the REST API
|
||||
|
||||
To integrate with OPA outside of Go, we recommend you deploy OPA as a host-level
|
||||
To integrate with OPA outside of Go, deploy OPA as a host-level
|
||||
daemon or sidecar container. Running OPA locally on the same host as your
|
||||
application or service helps ensure policy decisions are fast and highly-available.
|
||||
|
||||
@@ -418,7 +418,7 @@ The `rego.PreparedEvalQuery#Eval` function returns a _result set_ that contains
|
||||
the query results. If the result set is empty it indicates the query could not
|
||||
be satisfied. Each element in the result set contains a set of _variable
|
||||
bindings_ and a set of expression values. The query from above includes a single
|
||||
variable `x` so we can lookup the value and interpret it to enforce the policy
|
||||
variable `x` to look up the value and interpret it to enforce the policy
|
||||
decision.
|
||||
|
||||
```go
|
||||
|
||||
+1
-1
@@ -472,7 +472,7 @@ This statement is **undefined** if `source` is a scalar value or empty collectio
|
||||
|
||||
The OPA repository contains a [test suite](https://github.com/open-policy-agent/opa/tree/main/v1/test/cases/testdata/v1)
|
||||
that is used internally to validate both the Go interpreter and the Wasm
|
||||
compiler. If you are implementing your own compiler or interpreter we highly
|
||||
compiler. If you are implementing your own compiler or interpreter, we highly
|
||||
recommend integrating the test suite into your own development environment so
|
||||
that your implementation can be verified to conform with OPA's.
|
||||
|
||||
|
||||
@@ -23,14 +23,14 @@ rules to reuse logic and improve overall readability.
|
||||
|
||||
This tutorial requires [Docker Compose](https://docs.docker.com/compose/install/) to run Kafka, ZooKeeper, and OPA.
|
||||
|
||||
Additionally, we'll use Nginx for serving policy and data bundles to OPA. This component is however easily replaceable
|
||||
Additionally, Nginx is used for serving policy and data bundles to OPA. This component is however easily replaceable
|
||||
by any other bundle server [implementation](./management-bundles/#implementations).
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Bootstrap the tutorial environment using Docker Compose
|
||||
|
||||
First, let's create some directories. We'll create one for our policy files, a second one for built bundles, and a third
|
||||
First, create some directories: one for policy files, a second for built bundles, and a third
|
||||
one or the OPA authorizer plugin.
|
||||
|
||||
```bash
|
||||
@@ -207,8 +207,8 @@ chmod +x create_cert.sh
|
||||
./create_cert.sh
|
||||
```
|
||||
|
||||
We should now find a new `cert` directory created by the script,
|
||||
containing the server and client certificates we'll need for TLS
|
||||
A new `cert` directory should now be created by the script,
|
||||
containing the server and client certificates needed for TLS
|
||||
authentication.
|
||||
|
||||
Note: Do not rely on these SSL certificates in real-world scenarios.
|
||||
@@ -226,7 +226,7 @@ you may launch the containers for this tutorial.
|
||||
docker-compose --project-name opa-kafka-tutorial up
|
||||
```
|
||||
|
||||
Now that the tutorial environment is running, we can define an authorization policy using OPA and test it.
|
||||
Now that the tutorial environment is running, define an authorization policy using OPA and test it.
|
||||
|
||||
### 2. Define a policy to restrict consumer access to topics containing Personally Identifiable Information (PII)
|
||||
|
||||
@@ -512,9 +512,7 @@ request will be denied and the producer will output an error message.
|
||||
Not authorized to access topics: [click-stream]
|
||||
```
|
||||
|
||||
## Wrap Up
|
||||
|
||||
Congratulations on finishing the tutorial!
|
||||
## Summary
|
||||
|
||||
At this point you have learned how to enforce fine-grained access control
|
||||
over Kafka topics. In addition, you have seen how to break down policies into
|
||||
|
||||
@@ -5,7 +5,7 @@ title: Debugging Tips
|
||||
If you run into problems getting OPA to enforce admission control policies in
|
||||
Kubernetes there are a few things you can check to make sure everything is
|
||||
configured correctly. If none of these tips work, feel free to join
|
||||
[our slack](https://slack.openpolicyagent.org) and ask for help.
|
||||
[our Slack](https://slack.openpolicyagent.org) and ask for help.
|
||||
|
||||
The tips below cover the OPA-Kubernetes integration that uses kube-mgmt.
|
||||
The [OPA Gatekeeper version](https://open-policy-agent.github.io/gatekeeper/)
|
||||
|
||||
@@ -51,7 +51,7 @@ If you want to kick the tires:
|
||||
[demo/agilebank](https://github.com/open-policy-agent/gatekeeper/tree/master/demo/agilebank)
|
||||
directories for examples policies and setup scripts.
|
||||
|
||||
**Recommendation**: OPA Gatekeeper is **the go-to project** for using OPA for
|
||||
**Recommendation**: OPA Gatekeeper is **the recommended project** for using OPA for
|
||||
Kubernetes admission control. Plain OPA and Kube-mgmt (see below) are alternatives
|
||||
that can be reached for if you want to use the management features of OPA, such as
|
||||
status logs, decision logs, and bundles.
|
||||
|
||||
@@ -9,7 +9,7 @@ has its own docs.
|
||||
|
||||
## Writing Policies
|
||||
|
||||
To get started, let's look at a common policy: ensure all images come from a
|
||||
To get started, consider a common policy: ensure all images come from a
|
||||
trusted registry.
|
||||
|
||||
```rego showLineNumbers=true
|
||||
@@ -31,7 +31,7 @@ In **line 1** the `package kubernetes.admission` declaration gives the (hierarch
|
||||
|
||||
### Deny Rules
|
||||
|
||||
For admission control, you write `deny` statements. Order does not matter. (OPA is far more flexible than this, but we recommend writing just `deny` statements to start.) In **line 2**, the _head_ of the rule `deny contains msg if` says that the admission control request should be rejected and the user handed the error message `msg` if the conditions in the _body_ (the statements between the `{}`) are true.
|
||||
Typically `deny` rules are used for admission control; their order does not change the result. Rego rules can implement all sorts of different logic, but for admission control starting with `deny` rules is recommended. In **line 2**, the _head_ of the rule `deny contains msg if` says that the admission control request should be rejected and the user handed the error message `msg` if the conditions in the _body_ (the statements between the `{}`) are true.
|
||||
|
||||
`deny` is the _set_ of error messages that should be returned to the user. Each rule you write adds to that set of error messages.
|
||||
|
||||
@@ -99,7 +99,7 @@ result := admission.deny
|
||||
|
||||
In OPA, `input` is a reserved, global variable whose value is the Kubernetes AdmissionReview object that the API server hands to any admission control webhook.
|
||||
|
||||
AdmissionReview objects have many fields. The rule above uses `input.request.kind`, which includes the usual group/version/kind information. The rule also uses `input.request.object`, which is the YAML that the user provided to `kubectl` (augmented with defaults, timestamps, etc.). The full `input` object is 50+ lines of YAML, so below we show just the relevant parts.
|
||||
AdmissionReview objects have many fields. The rule above uses `input.request.kind`, which includes the usual group/version/kind information. The rule also uses `input.request.object`, which is the YAML that the user provided to `kubectl` (augmented with defaults, timestamps, etc.). The full `input` object is 50+ lines of YAML, so only the relevant parts are shown below.
|
||||
|
||||
```yaml
|
||||
apiVersion: admission.k8s.io/v1
|
||||
@@ -255,13 +255,13 @@ test_image_safety if { # line 3
|
||||
|
||||
**Different Package**. On line 1 the `package` directive puts these tests in a different package than admission control policy itself. This is the recommended best practice.
|
||||
|
||||
**Import**. On line 2 `import data.kubernetes.admission` allows us to reference the admission control policy using the name `admission` everywhere in the test package. `import` is not strictly necessary--it simply sets up an alias; you could instead reference `data.kubernetes.admission` inside the rules.
|
||||
**Import**. On line 2 `import data.kubernetes.admission` allows us to reference the admission control policy using the name `admission` everywhere in the test package. `import` is not strictly necessary--it sets up an alias; you could instead reference `data.kubernetes.admission` inside the rules.
|
||||
|
||||
**Unit Test**. On line 3 `test_image_safety` defines a unittest. If the rule evaluates to true the test passes; otherwise it fails. When you use the OPA test runner, anything in any package starting with `test` is treated as a test.
|
||||
|
||||
**Assignment**. On line 4 `unsafe_image` is the input we want to use for the test. Ideally this would be a real AdmissionReview object, though those are so long that in this example we hand-rolled a partial input.
|
||||
**Assignment**. On line 4 `unsafe_image` is the input to use for the test. Ideally this would be a real AdmissionReview object, though those are so long that in this example, a hand-rolled partial input is used.
|
||||
|
||||
**Dot for packages**. On line 5 we use the Dot operator on a package. `admission.deny[expected]` runs the `deny` rule(s) in package `admission` and checks if the message is contained in the set defined by `deny`.
|
||||
**Dot for packages**. On line 5, the Dot operator is used on a package. `admission.deny[expected]` runs the `deny` rule(s) in package `admission` and checks if the message is contained in the set defined by `deny`.
|
||||
|
||||
**Test Input**. Also on line 5 the stanza `with input as unsafe_image` sets the value of `input` to be `unsafe_image` while evaluating `admission.deny[expected]`.
|
||||
|
||||
@@ -278,7 +278,7 @@ The image-repository example shows an example where you can make a policy decisi
|
||||
|
||||
For example, it’s possible to accidentally configure two Kubernetes ingresses so that one steals traffic from the other. The policy that prevents conflicting ingresses needs to compare the ingress that’s being created/updated with all of the existing ingresses. Just knowing the new/updated ingress isn't enough information to make an allow/deny decision.
|
||||
|
||||
Below is a partial example of the input OPA sees when someone creates an ingress. To avoid conflicts, we want to prevent two ingresses from having the same `request.object.spec.rules.host`. If OPA has only this one ingress configuration it doesn't have enough information to make an allow/deny decision; it also needs the configurations for all of the existing ingresses.
|
||||
Below is a partial example of the input OPA sees when someone creates an ingress. To avoid conflicts, the goal is to prevent two ingresses from having the same `request.object.spec.rules.host`. If OPA has only this one ingress configuration it doesn't have enough information to make an allow/deny decision; it also needs the configurations for all of the existing ingresses.
|
||||
|
||||
```yaml
|
||||
apiVersion: admission.k8s.io/v1
|
||||
@@ -327,7 +327,7 @@ The first part of the rule you already understand:
|
||||
- Line (1) checks if the `input` is an Ingress
|
||||
- Line (2) iterates over all the rules in the `input` ingress and looks up the `host` field for each of its rules.
|
||||
|
||||
**Existing K8s Resources** Line (3) iterates over ingresses that already exist in Kubernetes. `data` is a global variable where (among other things) OPA has a record of the current resources inside Kubernetes. The line `oldhost := data.kubernetes.ingresses[namespace][name].spec.rules[_].host` finds all ingresses in all namespaces, iterates over all the `rules` inside each of those and assigns the `host` field to the variable `oldhost`. Whenever `newhost == oldhost`, there's a conflict, and the OPA rule includes an appropriate error message into the `deny` set.
|
||||
**Existing Kubernetes Resources** Line (3) iterates over ingresses that already exist in Kubernetes. `data` is a global variable where (among other things) OPA has a record of the current resources inside Kubernetes. The line `oldhost := data.kubernetes.ingresses[namespace][name].spec.rules[_].host` finds all ingresses in all namespaces, iterates over all the `rules` inside each of those and assigns the `host` field to the variable `oldhost`. Whenever `newhost == oldhost`, there's a conflict, and the OPA rule includes an appropriate error message into the `deny` set.
|
||||
|
||||
In this case the rule uses explicit variable names `namespace` and `name` for iteration so that it can use those variables again when constructing the error message in line (7).
|
||||
|
||||
@@ -507,7 +507,7 @@ policies that have been loaded into OPA.
|
||||
|
||||
As the administrator responsible for deploying OPA, you have full control over
|
||||
the `system.main` decision (i.e., it is just another Rego policy.) A basic
|
||||
implementation of the `system.main` policy simply evaluates all deny rules that
|
||||
implementation of the `system.main` policy evaluates all deny rules that
|
||||
have been loaded into OPA and unions the results:
|
||||
|
||||
```rego
|
||||
|
||||
@@ -10,16 +10,18 @@ For the purpose of the tutorial we will deploy two policies that ensure:
|
||||
- Ingress hostnames must be on `allowlist` on the Namespace containing the Ingress.
|
||||
- Two ingresses in different namespaces must not have the same hostname.
|
||||
|
||||
> 💡 Kubernetes does not guarantee consistency across resources. If two
|
||||
> ingresses are created in parallel, there is no guarantee that OPA (or any
|
||||
> other admission controller) will observe the creation of one ingress before
|
||||
> the other. This means that it's not possible to enforce these policies during
|
||||
> admission control 100% of the time. There will be a small window of time
|
||||
> (usually on the order of milliseconds) when the eventually consistent cache
|
||||
> inside of OPA (or any other admission controller) is out-of-date. To catch
|
||||
> these violations we recommend you periodically audit the state of the cluster
|
||||
> against your policies. Offline auditing is one of the features provided by the
|
||||
> [OPA Gatekeeper](https://github.com/open-policy-agent/gatekeeper) project.
|
||||
::: tip
|
||||
Kubernetes does not guarantee consistency across resources. If two
|
||||
ingresses are created in parallel, there is no guarantee that OPA (or any
|
||||
other admission controller) will observe the creation of one ingress before
|
||||
the other. This means that it's not possible to enforce these policies during
|
||||
admission control 100% of the time. There will be a small window of time
|
||||
(usually on the order of milliseconds) when the eventually consistent cache
|
||||
inside of OPA (or any other admission controller) is out-of-date. To catch
|
||||
these violations we recommend you periodically audit the state of the cluster
|
||||
against your policies. Offline auditing is one of the features provided by the
|
||||
[OPA Gatekeeper](https://github.com/open-policy-agent/gatekeeper) project.
|
||||
:::
|
||||
|
||||
## Prerequisites
|
||||
|
||||
@@ -215,7 +217,9 @@ response := {
|
||||
else := {"allowed": true, "uid": uid}
|
||||
```
|
||||
|
||||
> ⚠️ When OPA receives a request, it executes a query against the document defined `data.system.main` by default.
|
||||
:::warning
|
||||
When OPA receives a request, it executes a query against the document defined `data.system.main` by default.
|
||||
:::
|
||||
|
||||
### 5. Build and Publish OPA Bundle
|
||||
|
||||
@@ -381,7 +385,9 @@ spec:
|
||||
```
|
||||
</EvergreenCodeBlock>
|
||||
|
||||
> ⚠️ If using `kind` to run a local Kubernetes cluster, the bundle service URL should be `http://host.docker.internal:8888`.
|
||||
:::warning
|
||||
If using `kind` to run a local Kubernetes cluster, the bundle service URL should be `http://host.docker.internal:8888`.
|
||||
:::
|
||||
|
||||
```bash
|
||||
kubectl apply -f admission-controller.yaml
|
||||
@@ -560,9 +566,7 @@ Error from server (BadRequest): error when creating "ingress-ok.yaml": admission
|
||||
path "/" is already defined in ingress production/ingress-ok
|
||||
```
|
||||
|
||||
## Wrap Up
|
||||
|
||||
Congratulations for finishing the tutorial!
|
||||
## Summary
|
||||
|
||||
This tutorial showed how you can leverage OPA to enforce admission control
|
||||
decisions in Kubernetes clusters without modifying or recompiling any
|
||||
|
||||
@@ -245,7 +245,7 @@ http/example/authz/authz.rego
|
||||
In this example, the bundle contains one policy file (`authz.rego`) and two
|
||||
data files (`roles/bindings/data.json` and `roles/permissions/data.json`).
|
||||
A data file in the root of the bundle will be loaded into the `data` Document at
|
||||
the root. For example, here we can see that the `foo` key is inserted at the
|
||||
the root. For example, the `foo` key is inserted at the
|
||||
root of the `data` document:
|
||||
|
||||
```sh
|
||||
@@ -417,8 +417,8 @@ to generate bundles that are scoped to a subset of OPA's policy and
|
||||
data cache.
|
||||
|
||||
:::danger
|
||||
We recommend that whenever possible, you implement policy and data
|
||||
aggregation centrally, however, in some cases that's not possible
|
||||
Whenever possible, implement policy and data
|
||||
aggregation centrally. In some cases that's not possible
|
||||
(e.g., due to latency requirements.).
|
||||
When using multiple sources there are **no** ordering guarantees for which bundle loads first and
|
||||
takes over some root. If multiple bundles conflict, but are loaded at different
|
||||
@@ -478,7 +478,7 @@ digitally signed so that industry-standard cryptographic primitives can verify t
|
||||
|
||||
OPA supports digital signatures for policy bundles. Specifically, a signed bundle is a normal OPA bundle that includes
|
||||
a file named `.signatures.json` that dictates which files should be included in the bundle, what their SHA hashes are,
|
||||
and of course is cryptographically secure.
|
||||
and is cryptographically secure.
|
||||
|
||||
When OPA receives a new bundle, it checks that it has been properly signed using a (public) key that OPA has been
|
||||
configured with out-of-band. Only if that verification succeeds does OPA activate the new bundle; otherwise, OPA
|
||||
@@ -503,8 +503,8 @@ roles/bindings/data.json
|
||||
```
|
||||
|
||||
The signatures file is a JSON file with an array of JSON Web Tokens (JWTs) that encapsulate the signatures for the bundle.
|
||||
Currently, you will be limited to one signature, as shown below. In the future, we may add support to include multiple
|
||||
signatures to sign different files within the bundle.
|
||||
Currently, you will be limited to one signature, as shown below. In the future, support for multiple signatures may be added
|
||||
to sign different files within the bundle.
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -632,8 +632,8 @@ bundle.RegisterVerifier("custom", &CustomVerifier{})
|
||||
## Delta Bundles
|
||||
|
||||
A regular _snapshot_ bundle represents the entirety of OPA’s policy and data cache. When a new _snapshot_ bundle is
|
||||
downloaded, OPA will erase and overwrite all the policy and data in its cache before activating the new bundle. We can
|
||||
optionally scope the bundle to a subset of OPA’s policy and data cache by defining the `roots` in the bundle's `.manifest` file.
|
||||
downloaded, OPA will erase and overwrite all the policy and data in its cache before activating the new bundle. The bundle can
|
||||
optionally be scoped to a subset of OPA’s policy and data cache by defining the `roots` in the bundle’s `.manifest` file.
|
||||
|
||||
Although OPA [caches](#caching) snapshot bundles to avoid unnecessary retransmission,
|
||||
servers must still retransmit the entire snapshot when any change occurs. If you need
|
||||
@@ -1063,7 +1063,7 @@ If your instance of OPA runs inside GCP, you'll be able to authenticate using GC
|
||||
|
||||
##### JWT Bearer Grant Type
|
||||
|
||||
Use this for [authenticating](https://docs.cloud.google.com/storage/docs/authentication) _external_ clients, i.e. OPAs running outside the GCP environment.
|
||||
Use this for [authenticating](https://docs.cloud.google.com/storage/docs/authentication) _external_ clients, i.e. OPA instances running outside the GCP environment.
|
||||
|
||||
1. Search for "credentials" in the top search box and choose "Credentials - APIs and Services".
|
||||
2. Click "Create Credentials" followed by "Service Account."
|
||||
@@ -1075,7 +1075,7 @@ Use this for [authenticating](https://docs.cloud.google.com/storage/docs/authent
|
||||
|
||||
##### Testing Authentication
|
||||
|
||||
The easiest way of testing GCP metadata token or JWT bearer grant type authentication is simply to set up OPA with config for these and run the server.
|
||||
To test GCP metadata token or JWT bearer grant type authentication, set up OPA with the relevant config and run the server.
|
||||
|
||||
#### Upload Bundle
|
||||
|
||||
@@ -1287,14 +1287,14 @@ Also note in particular how the `thumbprint` property is required for Azure. The
|
||||
|
||||
### Nginx
|
||||
|
||||
Nginx offers a simple but competent bundle server for those who prefer to host their own. A great choice for local testing.
|
||||
Nginx offers a simple but competent bundle server for those who prefer to host their own and is also suitable for local testing.
|
||||
|
||||
| Feature | Supported |
|
||||
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| Caching headers | Yes |
|
||||
| Authentication methods | [Bearer Token](https://www.openpolicyagent.org/docs/latest/configuration/#bearer-token) <sup>1</sup><br/> [OAuth2 Client Credentials JWT authentication](https://www.openpolicyagent.org/docs/latest/configuration/#oauth2-client-credentials-jwt-authentication) <sup>2</sup> |
|
||||
|
||||
<sup>1</sup>Nginx does not support bearer token authentication, but it does support [basic auth](https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/). This can be achieved by setting `services[_].credentials.bearer.scheme` to `Basic` in the OPA configuration, and simply provide the base64 encoded credentials as the token.<br/>
|
||||
<sup>1</sup>Nginx does not support bearer token authentication, but it does support [basic auth](https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/). This can be achieved by setting `services[_].credentials.bearer.scheme` to `Basic` in the OPA configuration, and providing the base64 encoded credentials as the token.<br/>
|
||||
<sup>2</sup>Only available with Nginx Plus.
|
||||
|
||||
#### Upload Bundle
|
||||
@@ -1330,7 +1330,7 @@ The bundle container is composed of 3 layers:
|
||||
- the bundle tarball layer - the actual bundle tarball
|
||||
- the configuration layer - currently empty
|
||||
|
||||
For OCI compatible registries an _**oci**_ folder is created in the [persistence directory](./configuration/#miscellaneous). If this value is not set, because the OCI downloader plugin requires a storage path, the system's temporary folder location will be used instead. This folder should be maintained by the user. We recommend backing-up or cleaning up this folder periodically as this acts as a local cache for the OCI downloader.
|
||||
For OCI compatible registries an _**oci**_ folder is created in the [persistence directory](./configuration/#miscellaneous). If this value is not set, because the OCI downloader plugin requires a storage path, the system's temporary folder location will be used instead. This folder should be maintained by the user. Back up or clean up this folder periodically as this acts as a local cache for the OCI downloader.
|
||||
|
||||
**Current Limitations**
|
||||
The OCI Downloader plugin used by OPA has a couple of limitation:
|
||||
@@ -1354,7 +1354,7 @@ commands:
|
||||
|
||||
- `opa build <path_to_src>` will allow you to build a bundle tarball from your OPA policy and data files
|
||||
|
||||
Now that we have the tarball we will need to provide a config manifest to the ORAS CLI and the tarball itself:
|
||||
Provide a config manifest to the ORAS CLI and the tarball itself:
|
||||
|
||||
- `oras push <registry>/<org>/<repo>:<tag> --manifest-config <you_config_json>:application/vnd.oci.image.config.v1+json <the_tarball_obtained_from_opa_build>:application/vnd.oci.image.layer.v1.tar+gzip`
|
||||
|
||||
@@ -1362,15 +1362,15 @@ Using an empty(`{}`) `manifest-config` json file should be sufficient to be able
|
||||
|
||||
#### Maintaining a policy-as-code repository
|
||||
|
||||
One of the easiest method of managing your policy bundles is to store your code base in a hosted repository service like Github or Gitlab and set up an automated way to build and publish your code as a container to the desired registry using a CI(ex. Github Action).
|
||||
One of the easiest method of managing your policy bundles is to store your code base in a hosted repository service like GitHub or GitLab and set up an automated way to build and publish your code as a container to the desired registry using a CI(ex. GitHub Action).
|
||||
|
||||
#### Example
|
||||
|
||||
In this example we are using the [ghcr.io](https://ghcr.io) OCI registry as the upstream repository and the OPA and ORAS CLI as our build and publishing tool.
|
||||
In this example, the [ghcr.io](https://ghcr.io) OCI registry is used as the upstream repository and the OPA and ORAS CLI as the build and publishing tool.
|
||||
|
||||
##### Starting from scratch
|
||||
|
||||
Let's set up a basic policy example structured as:
|
||||
Set up a basic policy example structured as:
|
||||
|
||||
```
|
||||
└── src
|
||||
@@ -1380,7 +1380,7 @@ Let's set up a basic policy example structured as:
|
||||
└── hello.rego
|
||||
```
|
||||
|
||||
Here our _hello.rego_ file contains a very simple example:
|
||||
The _hello.rego_ file contains a very simple example:
|
||||
|
||||
```rego
|
||||
package policies.play
|
||||
@@ -1414,7 +1414,7 @@ And the _data.json_ file is empty json:
|
||||
|
||||
###### Building your policy
|
||||
|
||||
To build my bundle tarball I'm going to use the OPA CLI and run the following command:
|
||||
To build the bundle tarball, use the OPA CLI and run the following command:
|
||||
|
||||
```bash
|
||||
opa build .src/
|
||||
@@ -1422,19 +1422,19 @@ opa build .src/
|
||||
|
||||
###### Pushing the container to a remote registry
|
||||
|
||||
I'll prepare an empty config.json file that contains:
|
||||
Prepare an empty config.json file that contains:
|
||||
|
||||
```
|
||||
{}
|
||||
```
|
||||
|
||||
To push the build image to an upstream registry we first need to login using:
|
||||
To push the build image to an upstream registry, first log in using:
|
||||
|
||||
```bash
|
||||
oras login ghcr.io
|
||||
```
|
||||
|
||||
And now we can push our policy using:
|
||||
Push the policy using:
|
||||
|
||||
```bash
|
||||
oras push ghcr.io/someorg/policy-hello:1.0.0 --config config.json:application/vnd.oci.image.config.v1+json bundle.tar.gz:application/vnd.oci.image.layer.v1.tar+gzip
|
||||
@@ -1442,9 +1442,9 @@ oras push ghcr.io/someorg/policy-hello:1.0.0 --config config.json:application/vn
|
||||
|
||||
###### Spin up the policy with OPA CLI
|
||||
|
||||
Now that our image is pushed we prepare the OPA configuration.
|
||||
With the image pushed, prepare the OPA configuration.
|
||||
|
||||
In this example the configuration.yaml looks like this as the pushed image is private we need credentials for OPA to download it:
|
||||
In this example the configuration.yaml looks like this. The pushed image is private, so credentials are needed for OPA to download it:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
@@ -1466,9 +1466,9 @@ bundles:
|
||||
max_delay_seconds: 120
|
||||
```
|
||||
|
||||
In the above configuration we pinned the configuration to use the 1.0.0 tag of the image. OPA will identify this image by the tag and the descriptor SHA. If the SHA of the image is changed upstream, OPA will redownload and activate the changes.
|
||||
In the above configuration, the 1.0.0 tag of the image is pinned. OPA will identify this image by the tag and the descriptor SHA. If the SHA of the image is changed upstream, OPA will redownload and activate the changes.
|
||||
|
||||
If we run the _opa CLI_ with this configuration using the command it will open an interactive terminal (REPL) where we can see the loaded bundle:
|
||||
Running the _opa CLI_ with this configuration opens an interactive terminal (REPL) showing the loaded bundle:
|
||||
|
||||
```bash
|
||||
opa run -c configuration.yaml
|
||||
@@ -1489,7 +1489,7 @@ The terminal should show that the bundle has been loaded and activated:
|
||||
> exit
|
||||
```
|
||||
|
||||
We can now start OPA as a server using:
|
||||
Start OPA as a server using:
|
||||
|
||||
```bash
|
||||
opa run --server --set default_decision=policies -c configuration.yaml
|
||||
@@ -1521,7 +1521,7 @@ Content-Length: 24
|
||||
## Ecosystem Projects
|
||||
|
||||
<EcosystemEmbed feature="opa-bundles">
|
||||
The Bundle API is great way to manage your policies and data. The following
|
||||
The Bundle API supports managing policies and data. The following
|
||||
projects all make use of this API if you're looking for inspiration or examples
|
||||
of how to use it.
|
||||
</EcosystemEmbed>
|
||||
|
||||
@@ -160,9 +160,9 @@ default them to the value from the boot configuration.
|
||||
|
||||
### Example
|
||||
|
||||
Let's see an example of how the discovery feature can be used to dynamically
|
||||
The following example shows how the discovery feature can be used to dynamically
|
||||
configure an OPA to download one of two bundles based on a label in the boot
|
||||
configuration. Let's say the label `region` indicates the region in which the
|
||||
configuration. Assume the label `region` indicates the region in which the
|
||||
OPA is running and it's value will decide the bundle to download.
|
||||
|
||||
Below is a policy file which generates an OPA configuration.
|
||||
@@ -280,9 +280,11 @@ bundle before activating it. The format of the `.signatures.json` file and the v
|
||||
regular bundles. Since the discovered configuration ignores changes to the `discovery` section, any key used for
|
||||
signature verification of a discovery bundle **CANNOT** be modified via discovery.
|
||||
|
||||
> 🚨 We recommend that if you are using discovery you should be signing the discovery bundles because those bundles
|
||||
> include the keys used to verify the non-discovery bundles. However, OPA does not enforce that recommendation. You may use
|
||||
> unsigned discovery bundles that themselves require non-discovery bundles to be signed.
|
||||
:::warning
|
||||
It is recommended to sign discovery bundles because those bundles
|
||||
include the keys used to verify the non-discovery bundles. However, OPA does not enforce that recommendation. You may use
|
||||
unsigned discovery bundles that themselves require non-discovery bundles to be signed.
|
||||
:::
|
||||
|
||||
To enable signature verification for discovery bundles, add the `signing` field to your discovery configuration and define the verification key:
|
||||
|
||||
@@ -328,8 +330,8 @@ configuration will always be used.
|
||||
|
||||
## Ecosystem Projects
|
||||
|
||||
Configuring OPA using Discovery Bundles is a powerful production feature.
|
||||
Configuring OPA using Discovery Bundles is a production-grade feature.
|
||||
|
||||
<EcosystemEmbed feature="wasm-integration">
|
||||
Wasm is a great way to integrate OPA into applications where the Go SDK is unavailable.
|
||||
Wasm can be used to integrate OPA into applications where the Go SDK is unavailable.
|
||||
</EcosystemEmbed>
|
||||
|
||||
@@ -26,7 +26,7 @@ import DistributedDiagram from './assets/DistributedDiagram';
|
||||
|
||||
<DistributedDiagram/>
|
||||
|
||||
To control and observe a set of OPAs, each OPA can be configured to connect to
|
||||
To control and observe a set of OPA instances, each OPA can be configured to connect to
|
||||
management APIs that enable:
|
||||
|
||||
- Policy distribution ([Bundles](./management-bundles))
|
||||
@@ -35,7 +35,7 @@ management APIs that enable:
|
||||
- Dynamic agent configuration ([Discovery](./management-discovery))
|
||||
|
||||
By configuring and implementing these management APIs you can unify control and
|
||||
visibility over OPAs in your environments. OPA does not provide a control plane
|
||||
visibility over OPA instances in your environments. OPA does not provide a control plane
|
||||
service out-of-the-box.
|
||||
|
||||
import ControlPlaneDiagram from './assets/ControlPlaneDiagram';
|
||||
|
||||
@@ -220,12 +220,12 @@ This takes only the `data.utils.validation` subtree and mounts it at `data.app.v
|
||||
|
||||
## Stacks
|
||||
|
||||
Stacks enforce that certain policies are distributed to OPAs managed by OCP. When OCP builds bundles it identifies the applicable stacks (via [Selectors](#selectors)) and then adds the required sources (declared via `requirements`) to the bundle. Consider using stacks if:
|
||||
Stacks enforce that certain policies are distributed to OPA instances managed by OCP. When OCP builds bundles it identifies the applicable stacks (via [Selectors](#selectors)) and then adds the required sources (declared via `requirements`) to the bundle. Consider using stacks if:
|
||||
|
||||
- You have ephemeral OPA deployments that need to have a consistent set of policies applied (e.g., CI/CD pipelines, Kubernetes clusters, etc.)
|
||||
- You have global or hierarchical rules implementing organization-wide policies that you want to enforce automatically in many OPA deployments.
|
||||
|
||||
Let's look at an example:
|
||||
Consider the following example:
|
||||
|
||||
- Your organization deploys microservices that use OPA to enforce API authorization rules
|
||||
- Each microservice and bundle is owned by a separate team
|
||||
@@ -267,7 +267,7 @@ A selector value matches the label value if:
|
||||
|
||||
### Conflict Resolution
|
||||
|
||||
If a stack policy and a bundle policy generate different decisions we refer to this as a _conflict_. Similarly, when multiple stacks are included in a bundle they may also generate conflicting decisions. Before returning the final decision to the application, the overall policy should resolve any potential conflicts by combining the different decisions. Below we provide examples of how to implement common conflict resolution patterns for different use cases. In general, conflict resolution involves:
|
||||
If a stack policy and a bundle policy generate different decisions, this is referred to as a _conflict_. Similarly, when multiple stacks are included in a bundle they may also generate conflicting decisions. Before returning the final decision to the application, the overall policy should resolve any potential conflicts by combining the different decisions. The following examples show how to implement common conflict resolution patterns for different use cases. In general, conflict resolution involves:
|
||||
|
||||
- the bundle policy that produces a decision
|
||||
- one or more stack policies that each produce a separate decision
|
||||
@@ -283,7 +283,7 @@ The following example shows how to implement a common pattern where:
|
||||
- the bundle policy generates an allow (i.e., allow is true) AND
|
||||
- the stack policy does not generate a deny (i.e., deny is undefined or false)
|
||||
|
||||
To illustrate this pattern we will use a simple example with two bundle policies and a stack policy. The bundle policies allow access to microservice APIs (for a "petshop" service and a "notifications" service) and the stack policy will deny access based on a blocklist. Finally, there is an entrypoint policy that composes the bundle and stack policies to produce the final decision.
|
||||
To illustrate this pattern, consider a simple example with two bundle policies and a stack policy. The bundle policies allow access to microservice APIs (for a "petshop" service and a "notifications" service) and the stack policy will deny access based on a blocklist. Finally, there is an entrypoint policy that composes the bundle and stack policies to produce the final decision.
|
||||
|
||||
The petshop service will define a policy that allows:
|
||||
|
||||
@@ -378,7 +378,7 @@ The following example shows how to implement a common pattern where:
|
||||
- stack owners also define policies that generate sets of deny reasons
|
||||
- the final decision returned to the application should be the union of all the deny reasons
|
||||
|
||||
To illustrate this pattern we will use a simple example with a single bundle policy and two stack policies. The final decision will be generated by the entrypoint policy by unioning the bundle and stack decisions. For this example, we will assume that application querying OPA is a job running in a CI/CD pipeline that provides a set of build artifacts to deploy.
|
||||
To illustrate this pattern, consider a simple example with a single bundle policy and two stack policies. The final decision will be generated by the entrypoint policy by unioning the bundle and stack decisions. For this example, assume that the application querying OPA is a job running in a CI/CD pipeline that provides a set of build artifacts to deploy.
|
||||
|
||||
The bundle policy will deny deployments that contain artifacts that do not contain a "qa" attestation.
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ sidebar_label: "Tutorial: Deploy as a Service"
|
||||
|
||||
# Deploy as a Service to Kubernetes, AWS…
|
||||
|
||||
This example expands on [Kick the tires](./#kick-the-tires),
|
||||
This example expands on [Quick Start](./#quick-start),
|
||||
illustrating a more comprehensive and realistic configuration. This will
|
||||
showcase a practical, more complete server configuration and demonstrate its
|
||||
operational aspects.
|
||||
@@ -171,7 +171,7 @@ env:
|
||||
|
||||
## Shared Datasource
|
||||
|
||||
One of the powerful concepts in OCP is the ability to share policies and data across multiple bundles. To do this we create another [source](./concepts.md#sources) for this and require it in the bundle. We will set up an HTTP datasource to share, but you could just as easily do this for rego. Full datasource configuration can be found [in the Concepts documentation](./concepts.md#sources), but for our purposes we will call out to httpbin using a bearer token (other authn can be found [in the Secrets section](./concepts.md#secrets)):
|
||||
One notable concept in OCP is the ability to share policies and data across multiple bundles. To do this we create another [source](./concepts.md#sources) for this and require it in the bundle. We will set up an HTTP datasource to share, but you could just as easily do this for rego. Full datasource configuration can be found [in the Concepts documentation](./concepts.md#sources), but for our purposes we will call out to httpbin using a bearer token (other authn can be found [in the Secrets section](./concepts.md#secrets)):
|
||||
|
||||
```yaml
|
||||
sources:
|
||||
|
||||
@@ -6,7 +6,7 @@ sidebar_label: Overview
|
||||
# OPA Control Plane: Overview
|
||||
|
||||
OPA Control Plane (OCP) simplifies how you manage policies for your OPA
|
||||
deployments. It provides a centralized management system to control how OPAs
|
||||
deployments. It provides a centralized management system to control how OPA instances
|
||||
receive the policies and data they need to make decisions. OCP provides:
|
||||
|
||||
- **Git-based Policy Management.** Build bundles based on Rego from multiple Git
|
||||
@@ -15,7 +15,7 @@ receive the policies and data they need to make decisions. OCP provides:
|
||||
policies build-time using HTTP push and pull datasources.
|
||||
- **Highly-Available & Scalable Bundle Serving.** Distribute bundles to cloud
|
||||
object storage like AWS S3, Google Cloud Storage, or Azure Blob Storage and
|
||||
ensure your OPAs can quickly and reliably serve policy decisions.
|
||||
ensure your OPA instances can quickly and reliably serve policy decisions.
|
||||
- **Global and hierarchical policies.** Enforce organization-wide rules by
|
||||
defining global policies that get injected into bundles at build-time based on
|
||||
label selectors. Global policies can override other policies based on custom
|
||||
@@ -31,7 +31,7 @@ receive the policies and data they need to make decisions. OCP provides:
|
||||
- [OCP on GitHub](https://github.com/open-policy-agent/opa-control-plane) -
|
||||
explore OCP the code, contribute and file issues.
|
||||
|
||||
## Kick the tires
|
||||
## Quick Start
|
||||
|
||||
Follow this section to get a quick example running on your laptop. By following
|
||||
these instructions, you will be able to:
|
||||
@@ -42,7 +42,7 @@ these instructions, you will be able to:
|
||||
- Configure OPA to use the OCP build bundle
|
||||
- Test the policy's enforcement and observe its effects.
|
||||
|
||||
This example is designed for rapid iteration and learning, making it ideal for new users who want to understand OCP's fundamental concepts and operational flow in a controlled, personal setting. We'll focus on simplicity and clarity, ensuring that each step is easy to follow and the outcomes are immediately visible.
|
||||
This example is designed for rapid iteration and learning, making it ideal for new users who want to understand OCP's fundamental concepts and operational flow in a controlled, personal setting. The focus is on simplicity and clarity, ensuring that each step is easy to follow and the outcomes are immediately visible.
|
||||
|
||||
## 1. Install binary
|
||||
|
||||
@@ -67,7 +67,7 @@ sources:
|
||||
- rules/rules.rego
|
||||
```
|
||||
|
||||
We also will want to define a simple policy for this bundle. Add the following
|
||||
Define a simple policy for this bundle. Add the following
|
||||
to `./files/sources/hello-world/rules/rules.rego`
|
||||
|
||||
```rego title="files/sources/hello-world/rules/rules.rego"
|
||||
@@ -92,7 +92,7 @@ opactl build
|
||||
|
||||
## 4. Configure OPA to use the bundle
|
||||
|
||||
You could set up a simple server to serve up the bundle, but for now we can just use OPA to watch the bundle. Run this in your working directory:
|
||||
You could set up a simple server to serve up the bundle, but for now, use OPA to watch the bundle. Run this in your working directory:
|
||||
|
||||
```shell
|
||||
opa run -s -w ./bundles/hello-world/bundle.tar.gz
|
||||
|
||||
@@ -66,7 +66,7 @@ The mechanisms discussed above ensure that OPA is not asked to answer policy que
|
||||
- fail-open: if OPA does not provide a decision, then treat the decision as allowed.
|
||||
- fail-closed: if OPA does not provide a decision, then treat the decision as denied.
|
||||
|
||||
The choices are more varied if the policy is not making an allow/deny decision, but often there is some analog to fail-open and fail-closed. The key observation is that this logic is entirely the responsibility of the software asking OPA for a policy decision. Despite the fact that what to do when OPA is unavailable is technically a policy question, it is one that we cannot rely on OPA to answer. The right logic can depend on many factors including the likelihood of OPA not making a decision and the cost of allowing or denying a request incorrectly.
|
||||
The choices are more varied if the policy is not making an allow/deny decision, but often there is some analog to fail-open and fail-closed. The key observation is that this logic is entirely the responsibility of the software asking OPA for a policy decision. Despite the fact that what to do when OPA is unavailable is technically a policy question, it is one that OPA cannot answer. The right logic can depend on many factors including the likelihood of OPA not making a decision and the cost of allowing or denying a request incorrectly.
|
||||
|
||||
In Kubernetes admission control, for example, the Kubernetes admin can choose whether to fail-open or fail-closed, leaving the decision up to the user. And often this is the correct way to build an integration because it is unlikely that there is a universal solution. For example, running an OPA-integration in a development environment might require fail-open, but running exactly the same integration in a production environment might require fail-closed.
|
||||
|
||||
@@ -88,7 +88,7 @@ valid_semantic_version_tag if {
|
||||
}
|
||||
```
|
||||
|
||||
We can check whether it is compatible with different versions of OPA:
|
||||
The policy's compatibility with different versions of OPA can be checked as follows:
|
||||
|
||||
```bash
|
||||
# OK!
|
||||
|
||||
@@ -15,8 +15,7 @@ they are. Authorization and more generally policy often utilize the results of
|
||||
authentication (the username, user attributes, groups, claims), but makes
|
||||
decisions based on far more information than just who the user is. Generalizing
|
||||
away from authorization back to policy makes the distinction even clearer
|
||||
because some policy decisions have nothing to do with users, e.g. policy simply
|
||||
describes invariants that must hold in a software system (e.g. all binaries must
|
||||
because some policy decisions have nothing to do with users, e.g. policy describes invariants that must hold in a software system (e.g. all binaries must
|
||||
come from a trusted source).
|
||||
|
||||
Today policy is often a hard-coded feature of the software service it actually
|
||||
@@ -92,19 +91,19 @@ See the [Introduction](..) for an overview of how OPA works and how to get start
|
||||
## The OPA Document Model
|
||||
|
||||
OPA policies (written in Rego) make decisions based on hierarchical structured data.
|
||||
Sometimes we refer to this data as a document, set of attributes, piece of context,
|
||||
This data is sometimes referred to as a document, set of attributes, piece of context,
|
||||
or even just "JSON" [1]. Importantly, OPA policies can make decisions based on _arbitrary_
|
||||
structured data. OPA itself is not tied to any particular domain model. Similarly,
|
||||
OPA policies can represent decisions as arbitrary structured data (e.g., booleans,
|
||||
strings, maps, maps of lists of maps, etc.)
|
||||
|
||||
Data can be loaded into OPA from outside world using push or pull interfaces that operate
|
||||
synchronously or asynchronously with respect to policy evaluation. We refer to all data
|
||||
loaded into OPA from the outside world as <GlossaryTooltip term="base-documents">**base documents**</GlossaryTooltip> [2]. These base documents
|
||||
synchronously or asynchronously with respect to policy evaluation. All data loaded into OPA
|
||||
from the outside world is referred to as <GlossaryTooltip term="base-documents">**base documents**</GlossaryTooltip> [2]. These base documents
|
||||
almost always contribute to your policy decision-making logic. However, your policies can
|
||||
also make decisions based on each other. Policies almost always consist of multiple rules
|
||||
that refer to other rules (possibly authored by different groups). In OPA, we refer
|
||||
to the values generated by rules (a.k.a., decisions) as <GlossaryTooltip term="virtual-documents">**virtual documents**</GlossaryTooltip>. The term
|
||||
that refer to other rules (possibly authored by different groups). In OPA, the
|
||||
values generated by rules (a.k.a., decisions) are referred to as <GlossaryTooltip term="virtual-documents">**virtual documents**</GlossaryTooltip>. The term
|
||||
"virtual" in this case just means the document is _computed_ by the policy, i.e.,
|
||||
it's not loaded into OPA from the outside world.
|
||||
|
||||
@@ -130,7 +129,7 @@ into OPA when the state of the world changes. This can happen periodically or wh
|
||||
event (like a database change notification) occurs. Base documents loaded asynchronously
|
||||
are always accessed under the `data` global variable. On the other hand, base documents can
|
||||
also be pushed or pulled into OPA _synchronously_ when your software queries OPA for policy
|
||||
decisions. We refer to base documents pushed synchronously as "input". Policies can
|
||||
decisions. Base documents pushed synchronously are called "input". Policies can
|
||||
access these inputs under the `input` global variable. To pull base documents during
|
||||
policy evaluation, OPA exposes (and can be extended with custom) built-in functions like
|
||||
`http.send`. Built-in function return values can be assigned to local variables and
|
||||
|
||||
@@ -17,7 +17,7 @@ document models such as JSON.
|
||||
|
||||
Use Rego for defining policy that is easy to read and write.
|
||||
|
||||
Rego focuses on providing powerful support for referencing nested documents and
|
||||
Rego focuses on providing support for referencing nested documents and
|
||||
ensuring that queries are correct and unambiguous.
|
||||
|
||||
Rego is declarative so policy authors can focus on what queries should return
|
||||
@@ -77,7 +77,7 @@ result := rect == {"width": 2, "height": 4}
|
||||
|
||||
You can define a new concept using a rule. For example, `v` below is true if the
|
||||
equality expression is true.
|
||||
If we evaluate `v`, the result is `undefined` because the body of the rule never
|
||||
Evaluating `v` returns `undefined` because the body of the rule never
|
||||
evaluates to `true`. As a result, the document generated by the rule is not
|
||||
defined.
|
||||
|
||||
@@ -102,7 +102,7 @@ w if v != true
|
||||
|
||||
<RunSnippet command="data.example.w"/>
|
||||
|
||||
We can define rules in terms of [variables](#variables) as well:
|
||||
Rules can also be defined in terms of [variables](#variables):
|
||||
|
||||
```rego
|
||||
package example
|
||||
@@ -149,7 +149,7 @@ prod_exists if {
|
||||
|
||||
<RunSnippet id="sites.rego" command="data.sites.prod_exists"/>
|
||||
|
||||
We can generalize the example above with a rule that defines a set document
|
||||
The example above can be generalized with a rule that defines a set document
|
||||
instead of a boolean value. Here `site_names` is a set of all the site's name
|
||||
values.
|
||||
|
||||
@@ -421,7 +421,7 @@ q contains name if {
|
||||
|
||||
<RunSnippet id="vars.rego" command="data.variables.q"/>
|
||||
|
||||
In this case, we evaluate `q` with a variable `x` (which is not bound to a value). As a result, the query returns all of the values for `x` and all of the values for `q[x]`, which are always the same because `q` is a set.
|
||||
In this case, evaluating `q` with a variable `x` (which is not bound to a value) returns all of the values for `x` and all of the values for `q[x]`, which are always the same because `q` is a set.
|
||||
|
||||
```rego
|
||||
package variables
|
||||
@@ -431,7 +431,7 @@ result := { x | q[x] }
|
||||
|
||||
<RunSnippet files="#vars.rego" command="data.variables.result"/>
|
||||
|
||||
On the other hand, if we evaluate `q` with an input value for `name` we can determine whether `name` exists in the document defined by `q`:
|
||||
On the other hand, evaluating `q` with an input value for `name` determines whether `name` exists in the document defined by `q`:
|
||||
|
||||
```rego
|
||||
package variables
|
||||
@@ -540,7 +540,7 @@ containers := [
|
||||
|
||||
</details>
|
||||
|
||||
The simplest reference contains no variables. For example, the following reference returns the hostname of the second server in the first site document from our example data:
|
||||
The simplest reference contains no variables. For example, the following reference returns the hostname of the second server in the first site document from the example data:
|
||||
|
||||
```rego
|
||||
package references
|
||||
@@ -586,7 +586,7 @@ the example above this is `sites`. The root document may be:
|
||||
|
||||
References can include variables as keys. References written this way are used to select a value from every element in a collection.
|
||||
|
||||
The following reference will select the hostnames of all the servers in our
|
||||
The following reference will select the hostnames of all the servers in the
|
||||
example data:
|
||||
|
||||
```rego
|
||||
@@ -612,7 +612,7 @@ def hostnames(sites):
|
||||
return result
|
||||
```
|
||||
|
||||
In the reference above, we effectively used variables named `i` and `j` to iterate the collections. If the variables are unused outside the reference, we prefer to replace them with an underscore (`_`) character. The reference above can be rewritten as:
|
||||
In the reference above, variables named `i` and `j` were used to iterate the collections. If the variables are unused outside the reference, the convention is to replace them with an underscore (`_`) character. The reference above can be rewritten as:
|
||||
|
||||
```rego
|
||||
sites[_].servers[_].hostname
|
||||
@@ -721,7 +721,7 @@ In the above query, the second expression contains an [array comprehension](#arr
|
||||
|
||||
> When a comprehension refers to a variable in an outer body, OPA will reorder expressions in the outer body so that variables referred to in the comprehension are bound by the time the comprehension is evaluated.
|
||||
|
||||
Comprehensions are similar to the same constructs found in other languages like Python. For example, we could write the above comprehension in Python as follows:
|
||||
Comprehensions are similar to the same constructs found in other languages like Python. For example, the above comprehension in Python would be:
|
||||
|
||||
```python
|
||||
# Python equivalent of Rego comprehension shown above.
|
||||
@@ -766,7 +766,7 @@ Object comprehensions build object values out of sub-queries. Object comprehensi
|
||||
{ <key>: <term> | <body> }
|
||||
```
|
||||
|
||||
We can use object comprehensions to write the rule from above as a comprehension instead:
|
||||
Object comprehensions can rewrite the rule above as a comprehension instead:
|
||||
|
||||
```rego
|
||||
package comprehensions
|
||||
@@ -807,7 +807,7 @@ the following form, where terms are selected from the body to be set members:
|
||||
{ <term> | <body> }
|
||||
```
|
||||
|
||||
For example, to construct a set from an array, we can use `e` where `e` is an
|
||||
For example, to construct a set from an array, use `e` where `e` is an
|
||||
element in the array:
|
||||
|
||||
```rego
|
||||
@@ -822,7 +822,7 @@ my_set := {e | some e in my_array}
|
||||
## Rules
|
||||
|
||||
Rules define the content of [virtual documents](./philosophy#how-does-opa-work) in
|
||||
OPA. When OPA evaluates a rule, we say OPA _generates_ the content of the
|
||||
OPA. When OPA evaluates a rule, OPA _generates_ the content of the
|
||||
document that is defined by the rule.
|
||||
|
||||
The sample code in this section make use of the data defined in [References](#references).
|
||||
@@ -844,14 +844,14 @@ hostnames contains name if {
|
||||
|
||||
<RunSnippet files="#example_data.rego" command="data.sets.hostnames"/>
|
||||
|
||||
When we query for the content of our new `hostnames` rule we see the same data
|
||||
as we would if we queried using the `sites[_].servers[_].hostname` reference
|
||||
Querying the content of the new `hostnames` rule returns the same data
|
||||
as querying using the `sites[_].servers[_].hostname` reference
|
||||
directly.
|
||||
|
||||
This example introduces a few important aspects of Rego.
|
||||
|
||||
First, the rule defines a set document where the contents are defined by the
|
||||
variable `name`. We know this rule defines a set document because the head only
|
||||
variable `name`. This rule defines a set document because the head only
|
||||
includes a key. All rules have the following form (where key, value, and body
|
||||
are all optional):
|
||||
|
||||
@@ -867,8 +867,8 @@ For a more formal definition of the rule syntax, see the [Policy Reference](./po
|
||||
|
||||
Second, the `sites[_].servers[_].hostname` fragment selects the `hostname`
|
||||
attribute from all the objects in the `servers` collection. From reading the
|
||||
fragment in isolation we cannot tell whether the fragment refers to arrays or
|
||||
objects. We only know that it refers to a collections of values.
|
||||
fragment in isolation, it is not possible to tell whether the fragment refers to arrays or
|
||||
objects. It only indicates a collection of values.
|
||||
|
||||
Third, the `name := sites[_].servers[_].hostname` expression binds the value of the `hostname` attribute to the variable `name`, which is also declared in the head of the rule.
|
||||
|
||||
@@ -899,13 +899,13 @@ The rule above defines an object that maps hostnames to app names. The main diff
|
||||
### Incremental Definitions
|
||||
|
||||
A rule may be defined multiple times with the same name. When a rule is defined
|
||||
this way, we refer to the rule definition as _incremental_ because each
|
||||
this way, the rule definition is called _incremental_ because each
|
||||
definition is additive. The document produced by incrementally defined rules is
|
||||
the union of the documents produced by each individual rule.
|
||||
|
||||
An incrementally defined rule can be intuitively understood as `<rule-1> OR <rule-2> OR ... OR <rule-N>`.
|
||||
|
||||
For example, we can write a rule that abstracts over our `servers` and
|
||||
For example, a rule can abstract over the `servers` and
|
||||
`containers` data as `instances`:
|
||||
|
||||
```rego
|
||||
@@ -1138,7 +1138,7 @@ cannot be detected at compile-time. However, at evaluation-time `R2` will
|
||||
attempt to inject a value under key `t` in an object value defined by `R1`. This
|
||||
is a conflict, as rules are not allowed to modify or replace values defined by
|
||||
other rules.
|
||||
We won't get a conflict if we update the policy to the following:
|
||||
There is no conflict when the policy is updated to the following:
|
||||
|
||||
```rego
|
||||
package example
|
||||
@@ -1175,7 +1175,7 @@ result := trim_and_split(" foo.bar ")
|
||||
|
||||
<RunSnippet command="data.functions.result"/>
|
||||
|
||||
Functions may have an arbitrary number of inputs, but exactly one output. Function arguments may be any kind of term. For example, suppose we have the following function:
|
||||
Functions may have an arbitrary number of inputs, but exactly one output. Function arguments may be any kind of term. For example, consider the following function:
|
||||
|
||||
```rego
|
||||
package functions
|
||||
@@ -1362,7 +1362,7 @@ t if {
|
||||
Negation is required to check whether some value _does not_ exist in a collection: `not p["foo"]`. That is not the same as complementing the `==` operator in an expression `p[_] == "foo"` which yields `p[_] != "foo"`
|
||||
which means for any item in `p`, return true if the item is not `"foo"`. See more details [in the Regal documentation](/projects/regal/rules/bugs/not-equals-in-loop).
|
||||
|
||||
For example, we can write a rule that defines a document containing names of
|
||||
For example, a rule can define a document containing names of
|
||||
apps not deployed on the `"prod"` site:
|
||||
|
||||
```rego
|
||||
@@ -1486,8 +1486,8 @@ result := true if {
|
||||
<RunSnippet files="#bitcoin.rego" command="data.negation.result"/>
|
||||
|
||||
:::info
|
||||
The `undefined` result above is expected because we did not define a default
|
||||
value for `no_bitcoin_miners_using_negation`. Since the body of the rule fails
|
||||
The `undefined` result above is expected because no default value was defined
|
||||
for `no_bitcoin_miners_using_negation`. Since the body of the rule fails
|
||||
to match, there is no value generated.
|
||||
:::
|
||||
|
||||
@@ -1518,7 +1518,7 @@ ALL. To express FOR ALL in Rego complement the logic in the rule body (e.g.,
|
||||
`!=` becomes `==`) and then complement the check using negation (e.g.,
|
||||
`no_bitcoin_miners` becomes `not any_bitcoin_miners`).
|
||||
|
||||
Alternatively, we can implement the same kind of logic inside a single rule
|
||||
Alternatively, the same kind of logic can be implemented inside a single rule
|
||||
using [comprehensions](#comprehensions).
|
||||
|
||||
```rego
|
||||
@@ -1764,8 +1764,8 @@ tuples contains [i, j] if {
|
||||
|
||||
<RunSnippet files="#example_data.rego" command="data.tuples.tuples"/>
|
||||
|
||||
If we query for the tuples we get two results.
|
||||
Since we have declared `i`, `j`, and `server` to be local, we can introduce
|
||||
Querying for the tuples returns two results.
|
||||
Since `i`, `j`, and `server` are declared as local, it is possible to introduce
|
||||
rules in the same package without affecting the result above:
|
||||
|
||||
```rego
|
||||
@@ -1773,7 +1773,7 @@ rules in the same package without affecting the result above:
|
||||
i := 1
|
||||
```
|
||||
|
||||
If we had not declared `i` with the `some` keyword, introducing the `i` rule
|
||||
Without declaring `i` with the `some` keyword, introducing the `i` rule
|
||||
above would have changed the result of `tuples` because the `i` symbol in the
|
||||
body would capture the global value. Try removing `some i, j` and see what happens!
|
||||
|
||||
@@ -1850,7 +1850,7 @@ under the [input document](./philosophy/#the-opa-document-model) or the
|
||||
[data document](./philosophy/#the-opa-document-model), or [built-in functions](#built-in-functions).
|
||||
|
||||
For example, given the simple authorization policy in the [imports](#imports)
|
||||
section, we can write a query that checks whether a particular request would be
|
||||
section, a query can check whether a particular request would be
|
||||
allowed:
|
||||
|
||||
```rego
|
||||
@@ -1890,7 +1890,7 @@ result := true if {
|
||||
<RunSnippet files="#imports.rego" command="data.authz.result"/>
|
||||
|
||||
It's also possible to use `with` multiple times in the same query. `dev` role
|
||||
allows `GET`, even for an unknown user in our policy.
|
||||
allows `GET`, even for an unknown user in the policy.
|
||||
|
||||
```rego
|
||||
package authz
|
||||
@@ -2025,7 +2025,7 @@ allow if {
|
||||
|
||||
<RunSnippet command="data.example.allow"/>
|
||||
|
||||
But if we run this with the following input:
|
||||
If this is run with the following input:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -2049,7 +2049,7 @@ allow if {
|
||||
|
||||
<RunSnippet files="#input.bob.json" command="data.example.allow"/>
|
||||
|
||||
Without the default definition, the `allow` document would simply be undefined for the same input.
|
||||
Without the default definition, the `allow` document would be undefined for the same input.
|
||||
|
||||
When the `default` keyword is used, the rule syntax is restricted to:
|
||||
|
||||
@@ -3136,7 +3136,7 @@ Consider the following input document:
|
||||
}
|
||||
```
|
||||
|
||||
Clearly there are 2 image names that are in violation of the policy. However, when we evaluate the erroneous Rego code against this input we obtain:
|
||||
Clearly there are 2 image names that are in violation of the policy. However, evaluating the erroneous Rego code against this input produces:
|
||||
|
||||
```shell
|
||||
$ opa eval data.kubernetes.admission --format pretty -i opa-schema-examples/kubernetes/input.json -d opa-schema-examples/kubernetes/policy.rego
|
||||
@@ -3145,7 +3145,7 @@ $ opa eval data.kubernetes.admission --format pretty -i opa-schema-examples/kube
|
||||
|
||||
The empty value returned is indistinguishable from a situation where the input did not violate the policy. This error is therefore causing the policy not to catch violating inputs appropriately.
|
||||
|
||||
If we fix the Rego code and change `input.request.kind.kinds` to `input.request.kind.kind`, then we obtain the expected result:
|
||||
Fixing the Rego code and changing `input.request.kind.kinds` to `input.request.kind.kind` produces the expected result:
|
||||
|
||||
```json
|
||||
[
|
||||
@@ -3157,13 +3157,13 @@ If we fix the Rego code and change `input.request.kind.kinds` to `input.request.
|
||||
With this feature, it is possible to pass a schema to `opa eval`, written in JSON Schema. Consider the admission review schema provided at
|
||||
[`schemas/input.json`](https://github.com/aavarghese/opa-schema-examples/blob/main/kubernetes/schemas/input.json).
|
||||
|
||||
We can pass this schema to the evaluator as follows:
|
||||
Pass this schema to the evaluator as follows:
|
||||
|
||||
```
|
||||
% opa eval data.kubernetes.admission --format pretty -i opa-schema-examples/kubernetes/input.json -d opa-schema-examples/kubernetes/policy.rego -s opa-schema-examples/kubernetes/schemas/input.json
|
||||
```
|
||||
|
||||
With the erroneous Rego code, we now obtain the following type error:
|
||||
With the erroneous Rego code, the evaluator produces the following type error:
|
||||
|
||||
```shell
|
||||
1 error occurred: ../../aavarghese/opa-schema-examples/kubernetes/policy.rego:5: rego_type_error: undefined ref: input.request.kind.kinds
|
||||
@@ -3233,12 +3233,12 @@ mySchemasDir/
|
||||
|
||||
See here for [code samples](https://github.com/aavarghese/opa-schema-examples/tree/main/acl).
|
||||
|
||||
In the first `allow` rule above, the input document has the schema `input.json`, and `data.acl` has the schema `acl-schema.json`. Note that we use the relative path inside the `mySchemasDir` directory to identify a schema, omit the `.json` suffix, and use the global variable `schema` to stand for the top-level of the directory.
|
||||
In the first `allow` rule above, the input document has the schema `input.json`, and `data.acl` has the schema `acl-schema.json`. Note that the relative path inside the `mySchemasDir` directory identifies a schema, omitting the `.json` suffix, and uses the global variable `schema` to stand for the top-level of the directory.
|
||||
Schemas in annotations are proper Rego references. So `schema.input` is also valid, but `schema.acl-schema` is not.
|
||||
|
||||
If we had the expression `data.acl.foo` in this rule, it would result in a type error because the schema contained in `acl-schema.json` only defines object properties `"alice"` and `"bob"` in the ACL data document.
|
||||
The expression `data.acl.foo` in this rule would result in a type error because the schema contained in `acl-schema.json` only defines object properties `"alice"` and `"bob"` in the ACL data document.
|
||||
|
||||
On the other hand, this annotation does not constrain other paths under `data`. What it says is that we know the type of `data.acl` statically, but not that of other paths. So for example, `data.foo` is not a type error and gets assigned the type `Any`.
|
||||
On the other hand, this annotation does not constrain other paths under `data`. What it says is that the type of `data.acl` is known statically, but not that of other paths. So for example, `data.foo` is not a type error and gets assigned the type `Any`.
|
||||
|
||||
Note that the second `allow` rule doesn't have a METADATA comment block attached to it, and hence will not be type checked with any schemas.
|
||||
|
||||
@@ -3259,7 +3259,7 @@ overriding for type checking.
|
||||
|
||||
In the example above, the second rule does not include an annotation so type
|
||||
checking of the second rule would not take schemas into account. To enable type
|
||||
checking on the second (or other rules in the same file) we could specify the
|
||||
checking on the second (or other rules in the same file), specify the
|
||||
annotation multiple times:
|
||||
|
||||
```rego
|
||||
@@ -3284,7 +3284,7 @@ allow if {
|
||||
}
|
||||
```
|
||||
|
||||
This is obviously redundant and error-prone. To avoid this problem, we can
|
||||
This is redundant and error-prone. To avoid this problem,
|
||||
define the annotation once on a rule with scope `document`:
|
||||
|
||||
```rego
|
||||
@@ -3351,7 +3351,7 @@ they would be able to pick up that one schema declaration.
|
||||
|
||||
### Overriding
|
||||
|
||||
JSON Schemas are often incomplete specifications of the format of data. For example, a Kubernetes Admission Review resource has a field `object` which can contain any other Kubernetes resource. A schema for Admission Review has a generic type `object` for that field that has no further specification. To allow more precise type checking in such cases, we support overriding existing schemas.
|
||||
JSON Schemas are often incomplete specifications of the format of data. For example, a Kubernetes Admission Review resource has a field `object` which can contain any other Kubernetes resource. A schema for Admission Review has a generic type `object` for that field that has no further specification. To allow more precise type checking in such cases, schema overriding is supported.
|
||||
|
||||
Consider the following example:
|
||||
|
||||
@@ -3371,7 +3371,7 @@ deny contains msg if {
|
||||
}
|
||||
```
|
||||
|
||||
In this example, the `input` is associated with an Admission Review schema, and furthermore `input.request.object` is set to have the schema of a Kubernetes Pod. In effect, the second schema annotation overrides the first one. Overriding is a schema transformation feature and combines existing schemas. In this case, we are combining the Admission Review schema with that of a Pod.
|
||||
In this example, the `input` is associated with an Admission Review schema, and furthermore `input.request.object` is set to have the schema of a Kubernetes Pod. In effect, the second schema annotation overrides the first one. Overriding is a schema transformation feature and combines existing schemas. In this case, the Admission Review schema is combined with that of a Pod.
|
||||
|
||||
Notice that the order of schema annotations matter for overriding to work correctly.
|
||||
|
||||
@@ -3383,7 +3383,7 @@ In general, consider the existing Rego type:
|
||||
object{a: object{b: object{c: C, d: D, e: E}}}
|
||||
```
|
||||
|
||||
If we override this type with the following type (derived from a schema annotation of the form `a.b.e: schema-for-E1`):
|
||||
If this type is overridden with the following type (derived from a schema annotation of the form `a.b.e: schema-for-E1`):
|
||||
|
||||
```
|
||||
object{a: object{b: object{e: E1}}}
|
||||
@@ -3397,19 +3397,19 @@ object{a: object{b: object{c: C, d: D, e: E1}}}
|
||||
|
||||
Notice that `b` still has its fields `c` and `d`, so overriding has a merging effect as well. Moreover, the type of expression `a.b.e` is now `E1` instead of `E`.
|
||||
|
||||
We can also use overriding to add new paths to an existing type, so if we override the initial type with the following:
|
||||
Overriding can also add new paths to an existing type. If the initial type is overridden with the following:
|
||||
|
||||
```
|
||||
object{a: object{b: object{f: F}}}
|
||||
```
|
||||
|
||||
We obtain the following type:
|
||||
The result is the following type:
|
||||
|
||||
```
|
||||
object{a: object{b: object{c: C, d: D, e: E, f: F}}}
|
||||
```
|
||||
|
||||
We use schemas to enhance the type checking capability of OPA, and not to validate the input and data documents against desired schemas. This burden is still on the user and care must be taken when using overriding to ensure that the input and data provided are sensible and validated against the transformed schemas.
|
||||
Schemas enhance the type checking capability of OPA, and are not used to validate the input and data documents against desired schemas. This burden is still on the user and care must be taken when using overriding to ensure that the input and data provided are sensible and validated against the transformed schemas.
|
||||
|
||||
### Multiple input schemas
|
||||
|
||||
@@ -3453,14 +3453,14 @@ mySchemasDir/
|
||||
└── whocan-input-schema.json
|
||||
```
|
||||
|
||||
In this example, we associate the schema `input.json` with the input document in the rule `allow`, and the schema `whocan-input-schema.json`
|
||||
In this example, the schema `input.json` is associated with the input document in the rule `allow`, and the schema `whocan-input-schema.json`
|
||||
with the input document for the rule `whocan`.
|
||||
|
||||
### Translating schemas to Rego types and dynamicity
|
||||
|
||||
Rego has a gradual type system meaning that types can be partially known statically. For example, an object could have certain fields whose types are known and others that are unknown statically. OPA type checks what it knows statically and leaves the unknown parts to be type checked at runtime. An OPA object type has two parts: the static part with the type information known statically, and a dynamic part, which can be nil (meaning everything is known statically) or non-nil and indicating what is unknown.
|
||||
|
||||
When we derive a type from a schema, we try to match what is known and unknown in the schema. For example, an `object` that has no specified fields becomes the Rego type `Object{Any: Any}`. However, currently `additionalProperties` and `additionalItems` are ignored. When a schema is fully specified, we derive a type with its dynamic part set to nil, meaning that we take a strict interpretation in order to get the most out of static type checking. This is the case even if `additionalProperties` is set to `true` in the schema. In the future, we will take this feature into account when deriving Rego types.
|
||||
When deriving a type from a schema, the compiler tries to match what is known and unknown in the schema. For example, an `object` that has no specified fields becomes the Rego type `Object{Any: Any}`. However, currently `additionalProperties` and `additionalItems` are ignored. When a schema is fully specified, the dynamic part is set to nil, meaning that a strict interpretation is used in order to get the most out of static type checking. This is the case even if `additionalProperties` is set to `true` in the schema. In the future, this feature will be taken into account when deriving Rego types.
|
||||
|
||||
When overriding existing types, the dynamicity of the overridden prefix is preserved.
|
||||
|
||||
@@ -3521,7 +3521,7 @@ deny if {
|
||||
}
|
||||
```
|
||||
|
||||
We can see that `request` is an object with two options as indicated by the choices under `anyOf`:
|
||||
The output shows that `request` is an object with two options as indicated by the choices under `anyOf`:
|
||||
|
||||
- contains property `kind`, which has properties `kind` and `version`
|
||||
- contains property `server`, which has properties `accessNum` and `version`
|
||||
@@ -3597,7 +3597,7 @@ deny if {
|
||||
}
|
||||
```
|
||||
|
||||
We can see that `request` is an object with properties as indicated by the elements listed under `allOf`:
|
||||
The output shows that `request` is an object with properties as indicated by the elements listed under `allOf`:
|
||||
|
||||
- contains property `kind`, which has properties `kind` and `version`
|
||||
- contains property `server`, which has properties `accessNum` and `version`
|
||||
@@ -3680,7 +3680,7 @@ In particular the following features are not yet supported:
|
||||
- enum
|
||||
- if/then/else
|
||||
|
||||
A note of caution: overriding is a powerful capability that must be used carefully. For example, the user is allowed to write:
|
||||
A note of caution: overriding is a flexible capability that must be used carefully. For example, the user is allowed to write:
|
||||
|
||||
```
|
||||
# METADATA
|
||||
@@ -3689,7 +3689,7 @@ A note of caution: overriding is a powerful capability that must be used careful
|
||||
# - data: schema["some-schema"]
|
||||
```
|
||||
|
||||
In this case, we are overriding the root of all documents to have some schema. Since all Rego code lives under `data` as virtual documents, this in practice renders all of them inaccessible (resulting in type errors). Similarly, assigning a schema to a package name is not a good idea and can cause problems. Care must also be taken when defining overrides so that the transformation of schemas is sensible and data can be validated against the transformed schema.
|
||||
In this case, the root of all documents is being overridden to have some schema. Since all Rego code lives under `data` as virtual documents, this in practice renders all of them inaccessible (resulting in type errors). Similarly, assigning a schema to a package name is not a good idea and can cause problems. Care must also be taken when defining overrides so that the transformation of schemas is sensible and data can be validated against the transformed schema.
|
||||
|
||||
### References
|
||||
|
||||
|
||||
@@ -304,7 +304,7 @@ the policy should generate a document like this:
|
||||
```
|
||||
|
||||
Since multiple ports could be exposed on a single interface, the policy must use a <GlossaryTooltip term="comprehensions">comprehension</GlossaryTooltip> to
|
||||
aggregate the port values by the interface names. To implement this logic in Rego, we would write:
|
||||
aggregate the port values by the interface names. To implement this logic in Rego:
|
||||
|
||||
```rego
|
||||
some i
|
||||
@@ -317,7 +317,7 @@ However, with comprehension indexing, the query remains O(n) because OPA only co
|
||||
_once_. In this case, the comprehension is evaluated and all possible values of `ports` are computed
|
||||
at once. These values are indexed by the assignments of `intf`.
|
||||
|
||||
To implement the policy above we could write:
|
||||
To implement the policy above:
|
||||
|
||||
```rego
|
||||
package example
|
||||
@@ -428,7 +428,7 @@ When the sort criteria is not provided `total_time_ns` has the highest sort prio
|
||||
while `line` has the lowest.
|
||||
|
||||
The `num_gen_expr` represents the number of expressions generated for a given statement on a particular line. For example,
|
||||
let's take the following policy:
|
||||
take the following policy:
|
||||
|
||||
```rego
|
||||
package test
|
||||
@@ -441,7 +441,7 @@ p if {
|
||||
}
|
||||
```
|
||||
|
||||
If we profile the above policy we would get something like the following output:
|
||||
Profiling the above policy produces something like the following output:
|
||||
|
||||
```
|
||||
+----------+----------+----------+--------------+-------------+
|
||||
@@ -455,8 +455,7 @@ If we profile the above policy we would get something like the following output:
|
||||
+----------+----------+----------+--------------+-------------+
|
||||
```
|
||||
|
||||
The first entry indicates that line `test.rego:8` has a `EVAL/REDO` count of `3`. If we look at the expression on line `test.rego:8`
|
||||
i.e. `x = a + b * c` it's not immediately clear why this line has a `EVAL/REDO` count of `3`. But we also notice that there
|
||||
The first entry indicates that line `test.rego:8` has a `EVAL/REDO` count of `3`. Looking at the expression on line `test.rego:8`, i.e. `x = a + b * c`, it is not immediately clear why this line has a `EVAL/REDO` count of `3`. Note also that there
|
||||
are `3` generated expressions (i.e. `NUM GEN EXPR`) at line `test.rego:8`. This is because the compiler rewrites the above policy to
|
||||
something like below:
|
||||
|
||||
@@ -797,7 +796,7 @@ PASS: 2/2
|
||||
#### Example: Benchmark RBAC unit tests and compare with `benchstat`
|
||||
|
||||
The benchmark output formats default to `pretty`, but support a `gobench` format which complies with the
|
||||
[Golang Benchmark Data Format](https://go.googlesource.com/proposal/+/master/design/14313-benchmark-format.md).
|
||||
[Go Benchmark Data Format](https://go.googlesource.com/proposal/+/master/design/14313-benchmark-format.md).
|
||||
This allows for usage of tools like [benchstat](https://pkg.go.dev/golang.org/x/perf/cmd/benchstat?utm_source=godoc) to gain additional
|
||||
insight into the benchmark results and to diff between benchmark results.
|
||||
|
||||
@@ -844,7 +843,7 @@ DataRbacTestUserHasRoleDev 229 ± 0%
|
||||
DataRbacTestUserHasRoleNegative 235 ± 0%
|
||||
```
|
||||
|
||||
If later on a change was introduced that altered the performance we can run again:
|
||||
If a change is later introduced that alters performance, run again:
|
||||
|
||||
```bash
|
||||
opa test -v --bench --count 10 --format gobench ./rbac.rego ./rbac_test.rego | tee ./new.txt
|
||||
@@ -859,7 +858,7 @@ PASS: 2/2
|
||||
|
||||
(Repeated 10 times)
|
||||
|
||||
Then we can compare the results via:
|
||||
Compare the results via:
|
||||
|
||||
```bash
|
||||
benchstat ./old.txt ./new.txt
|
||||
|
||||
@@ -10,7 +10,7 @@ useful to decode and verify JWT tokens signed with a symmetric key just to
|
||||
see what the output of `io.jwt.decode_verify()` looks like.
|
||||
|
||||
The not-so-secret symmetric key `password` was used to sign the token
|
||||
provided in the input for the policy below. We can see that the claims in this
|
||||
example contains `secret` and `iss` only. This means that the validity period
|
||||
provided in the input for the policy below. The claims in this
|
||||
example contain `secret` and `iss` only. This means that the validity period
|
||||
of the token is not checked, nor is the audience or the algorithm used to sign
|
||||
it.
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
<!-- markdownlint-disable MD041 -->
|
||||
|
||||
In the example that follows, we show a policy that uses the
|
||||
The following example shows a policy that uses the
|
||||
`regex.find_all_string_submatch_n` built-in to extract the 'plus suffix', if
|
||||
present, from an email address.
|
||||
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
<!-- markdownlint-disable MD041 -->
|
||||
|
||||
Here we see how `regex.find_all_string_submatch_n` can be used to create
|
||||
structured data from unstructured text. In this example, we parse a list of
|
||||
scopes from a string and use that to create an object we can use in policies to
|
||||
The following example shows how `regex.find_all_string_submatch_n` can be used to create
|
||||
structured data from unstructured text. In this example, a list of
|
||||
scopes is parsed from a string to create an object that can be used in policies to
|
||||
look up permissions.
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
Sometimes data can be supplied in a variety of cases, and matches need to be
|
||||
the same regardless of case. One example of this when matching GitHub usernames.
|
||||
|
||||
This is where the `(?i)` modifier comes in. In the following example we can see
|
||||
This is where the `(?i)` modifier comes in. The following example shows
|
||||
how repositories with different cases are matched.
|
||||
|
||||
@@ -5,4 +5,4 @@ is more complicated than just checking an email matches a pattern, but since a R
|
||||
policy is often a first point of contact, doing a pattern based test on emails is
|
||||
still a good idea as it can help surface issues to users early if they make a mistake.
|
||||
|
||||
`regex.match` is the best way to validate emails in Rego.
|
||||
Use `regex.match` to validate emails in Rego.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!-- markdownlint-disable MD041 -->
|
||||
|
||||
In the example that follows, we have a complex path which represents an AWS ARN
|
||||
The following example uses a complex path representing an AWS ARN
|
||||
owned by a project with a UUID v4 identifier. The path is validated in two
|
||||
parts using two separate patterns, each contained to particular segments of the
|
||||
path.
|
||||
|
||||
@@ -4,5 +4,5 @@ A common attribute-based access control (ABAC) requirement is to grant
|
||||
access based on time. This is typically done by determining the user's
|
||||
local time and ensuring it falls within a given period.
|
||||
|
||||
In this example we show how to allow requests when made by a user
|
||||
This example shows how to allow requests when made by a user
|
||||
in their local business hours.
|
||||
|
||||
@@ -6,6 +6,6 @@ and local times can be useful when debugging or troubleshooting
|
||||
and so in many cases returning them from policy decisions can
|
||||
be helpful.
|
||||
|
||||
In this example we see a user is not an admin and is denied access,
|
||||
In this example, a user is not an admin and is denied access,
|
||||
the policy response is a message that includes the current time
|
||||
and an error code to help them debug.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!-- markdownlint-disable MD041 -->
|
||||
|
||||
In this example, we see compare an
|
||||
This example compares an
|
||||
[RFC3339](https://datatracker.ietf.org/doc/html/rfc3339) timestamp
|
||||
with the current time to determine if the timestamp is in the past.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!-- markdownlint-disable MD041 -->
|
||||
|
||||
In order to check if a time is in a period of time, we need to know the
|
||||
start and the end of the period first. This policy defines two dates in
|
||||
To check if a time is in a period of time, the start and end of the period
|
||||
must be known first. This policy defines two dates in
|
||||
time to mark the start and the end of the period, before testing if the
|
||||
supplied time is in the period.
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<!-- markdownlint-disable MD041 -->
|
||||
|
||||
In OPA, we can parse a simple YYYY-MM-DD timestamp as follows:
|
||||
In OPA, a simple YYYY-MM-DD timestamp can be parsed as follows:
|
||||
|
||||
@@ -9,7 +9,7 @@ import BuiltinLegacyRedirect from "@site/src/components/BuiltinLegacyRedirect";
|
||||
<BuiltinLegacyRedirect/>
|
||||
|
||||
This page is a reference for details of the Rego language and its syntax. See
|
||||
our guided [Policy Language](./policy-language) page for a walked introduction.
|
||||
the guided [Policy Language](./policy-language) page for a walked introduction.
|
||||
There are also detailed sections for
|
||||
[built-in functions](./policy-reference/builtins) as well as examples for
|
||||
specific keywords such as
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<!-- markdownlint-disable MD041 -->
|
||||
|
||||
While this first example is trivially simple and unlikely to be useful when building real
|
||||
While this first example is simple and unlikely to be useful when building real
|
||||
policies, it illustrates the fundamental reason for using the `contains` keyword: building sets.
|
||||
Sets are unordered collections, and they form an important building block for many policies.
|
||||
|
||||
In this example, we use a multi-value rule defined using the `contains` keyword to create a simple
|
||||
In this example, a multi-value rule defined using the `contains` keyword creates a simple
|
||||
list of todos. Just remember that sets are unordered and so you should not depend on the order of
|
||||
the result.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<!-- markdownlint-disable MD041 -->
|
||||
|
||||
As we saw in the previous example, `default` is helpful for handling undefined
|
||||
As shown in the previous example, `default` is helpful for handling undefined
|
||||
values. Handling undefined values is not just important for callers, but also
|
||||
within policies themselves.
|
||||
|
||||
Using the `default` keyword with functions, we can quickly build in
|
||||
functionality to set a base case that's overridden when conditions are met.
|
||||
The `default` keyword with functions provides a convenient way to set a base
|
||||
case that is overridden when conditions are met.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!-- markdownlint-disable MD041 -->
|
||||
|
||||
Here we use the `every` keyword to validate that an example session has all the
|
||||
The `every` keyword is used here to validate that an example session has all the
|
||||
required feature flags for a request.
|
||||
|
||||
`test_speedy_checkout` is false in the `input.json`, this will need to be true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!-- markdownlint-disable MD041 -->
|
||||
|
||||
Every can also be used to check an object's keys and values. Here we do just
|
||||
`every` can also be used to check an object's keys and values. The following example does just
|
||||
that to validate attendees of a meeting invite.
|
||||
|
||||
In this example, all attendees must have the staff role and the correct email
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
or more heads. The head and body of a function are also separated by the `if`
|
||||
keyword for consistency and readability.
|
||||
|
||||
In this example, we can see that the `is_sudo` function is incrementally defined
|
||||
In this example, the `is_sudo` function is incrementally defined
|
||||
where each head adds new cases to the functionality. In this case, each head
|
||||
defines scenarios where the user is a 'sudoer' - both when the user is an admin
|
||||
or when the user has the sudo field set.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!-- markdownlint-disable MD041 -->
|
||||
|
||||
`if` is _everywhere_ in Rego, but there are some cases where we don't use it.
|
||||
`if` is _everywhere_ in Rego, but there are some cases where it is not used.
|
||||
In this example, using `if` for a rule name is a parse error.
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<!-- markdownlint-disable MD041 -->
|
||||
|
||||
Similar to arrays, `some` can also be used on key->value pairs in
|
||||
objects. Here, we create two variables, one for the key and another
|
||||
objects. Here, two variables are created, one for the key and another
|
||||
for the value. The Rego rule is then evaluated for each pair.
|
||||
|
||||
We can use the key and value however we like. Here, we use the
|
||||
The key and value can be used in any way. Here, the rule uses the
|
||||
name of the permission to create a list of permissions that are
|
||||
toggled on in the `example_object`.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!-- markdownlint-disable MD041 -->
|
||||
|
||||
In this example, we use `some` to select each item in an array,
|
||||
In this example, `some` is used to select each item in an array,
|
||||
perform a check on it, and return matching items as a new array.
|
||||
Processing lists of values like this is one of the most common use
|
||||
cases for `some`.
|
||||
|
||||
@@ -10,7 +10,7 @@ keyword makes this
|
||||
[universal quantification](/docs/policy-language#universal-quantification-for-all)
|
||||
easier.
|
||||
|
||||
Here we show two equivalent rules achieve universal quantification, note how
|
||||
The following two equivalent rules achieve universal quantification. Note how
|
||||
much easier to read the one using `every` is.
|
||||
|
||||
```rego
|
||||
@@ -31,7 +31,7 @@ allow2 if {
|
||||
<sub>
|
||||
`allow2` works by generating a set of 'results' testing elements from the
|
||||
array `[1,2,3]`. The resulting set is tested against `{true}` to verify all
|
||||
elements are `true`. As we can see `every` is a much better option!
|
||||
elements are `true`. `every` is a much better option!
|
||||
</sub>
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -5,15 +5,14 @@ title: 'Rego Keyword Examples: import'
|
||||
|
||||
In Rego, the `import` keyword is used to include references in the current file
|
||||
from other places, namely other Rego packages. However, the `import` keyword is
|
||||
also used to change the Rego syntax available in the current file. Let's cover
|
||||
this case first.
|
||||
also used to change the Rego syntax available in the current file. This case is covered first.
|
||||
|
||||
## Importing packages
|
||||
|
||||
Most importantly, the `import` keyword is used to make the rules defined in one
|
||||
package, available in another.
|
||||
|
||||
Imagine we have a package, `package1`, that defines a rule `name` like this:
|
||||
Consider a package, `package1`, that defines a rule `name` like this:
|
||||
|
||||
```rego
|
||||
package package1
|
||||
@@ -25,8 +24,7 @@ name := "World"
|
||||
|
||||
<RunSnippet id="package1.rego"/>
|
||||
|
||||
Now, if we'd like to use the `name` rule in another package, `package2`, we
|
||||
could do something like this:
|
||||
To use the `name` rule in another package, `package2`, write something like this:
|
||||
|
||||
```rego
|
||||
package package2
|
||||
@@ -43,7 +41,7 @@ output := sprintf("Hello, %v", [data.package1.name])
|
||||
|
||||
While this will work, it's better to use an import at the top of the file to
|
||||
save repetition and declare the dependency upfront for readers of the policy.
|
||||
We can achieve the same result like this:
|
||||
The same result can be achieved like this:
|
||||
|
||||
```rego
|
||||
package package2
|
||||
|
||||
@@ -89,7 +89,7 @@ admin(group) if group in ["admin", "sudo"]
|
||||
<RunSnippet files="#input.negation1.json" command="data.negation.restricted"/>
|
||||
|
||||
:::important
|
||||
Notice how if we remove the `future.keywords.not` import in the above policy, the `restricted` rule starts failing.
|
||||
Notice that removing the `future.keywords.not` import in the above policy causes the `restricted` rule to start failing.
|
||||
This is a consequence of the `lookup()` function failing with an `undefined` value.
|
||||
:::
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ and reduce the amount of time it takes to modify rules as requirements evolve.
|
||||
|
||||
## Getting Started
|
||||
|
||||
Let's use an example to get started. The file below implements a simple
|
||||
The following example demonstrates getting started. The file below implements a simple
|
||||
policy that allows new users to be created and users to access their own
|
||||
profile.
|
||||
|
||||
@@ -32,7 +32,7 @@ allow if {
|
||||
}
|
||||
```
|
||||
|
||||
To test this policy, we will create a separate Rego file that contains test cases.
|
||||
To test this policy, create a separate Rego file that contains test cases.
|
||||
|
||||
```rego title="example_test.rego"
|
||||
package authz_test
|
||||
@@ -159,7 +159,7 @@ FAIL: 1/1
|
||||
```
|
||||
|
||||
The test failed because it expected users with **write** permission to implicitly also have the **read** permission, an expectation the function under test didn't meet.
|
||||
By including the failing expression and its local variable assignments in the test report, we make troubleshooting easier for the developer, as it's immediately apparent what assertion and combination of parameters caused the test to fail.
|
||||
The test report includes the failing expression and its local variable assignments, making it immediately apparent what assertion and combination of parameters caused the failure.
|
||||
|
||||
## Test Format
|
||||
|
||||
@@ -603,7 +603,7 @@ It is also possible that [rule indexing](./policy-performance/#use-indexed-state
|
||||
has determined some path unnecessary for evaluation, thereby affecting the lines
|
||||
reported as covered.
|
||||
|
||||
If we run the coverage report on the original **example.rego** file without
|
||||
If the coverage report is run on the original **example.rego** file without
|
||||
`test_get_user_allowed` from **example_test**.rego the report will indicate
|
||||
that line 8 is not covered.
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ Host: api.github.com
|
||||
User-Agent: OPA-Version-Checker
|
||||
```
|
||||
|
||||
No data about your OPA instance is sent in the request. OPA simply retrieves
|
||||
No data about your OPA instance is sent in the request. OPA retrieves
|
||||
information about the latest release. The GitHub API responds with release
|
||||
information including the tag name and release notes URL. OPA uses this
|
||||
information to determine if a newer version is available and constructs a
|
||||
|
||||
@@ -1657,7 +1657,7 @@ exceptions contains "bob"
|
||||
exceptions contains "alice"
|
||||
```
|
||||
|
||||
In this case, if we execute query on behalf of a user that does not
|
||||
In this case, if a query is executed on behalf of a user that does not
|
||||
have an exception (e.g., `"eve"`), the OPA response will not contain a
|
||||
`queries` field at all. This indicates there are NO conditions that
|
||||
could make the query true.
|
||||
@@ -1729,7 +1729,7 @@ OPA uses the `Accept` header to denote the target response format.
|
||||
|
||||
#### Example Request
|
||||
|
||||
With OPA running with this policy, we'll compile the query `data.filters.include` into SQL filters:
|
||||
With OPA running with this policy, the following request compiles the query `data.filters.include` into SQL filters:
|
||||
|
||||
```rego
|
||||
package filters
|
||||
@@ -1797,7 +1797,7 @@ An empty string `query` indicates **unconditional include**:
|
||||
|
||||
#### Example: Mapping Table and Column Names
|
||||
|
||||
For this example, let's assume OPA is running with this policy:
|
||||
For this example, assume OPA is running with this policy:
|
||||
|
||||
```rego
|
||||
package filters
|
||||
@@ -1812,7 +1812,7 @@ include if input.price == "free"
|
||||
```
|
||||
|
||||
If there is no one-to-one correspondence between unknowns and table/column names, the target mapping comes into play.
|
||||
With this mapping, we would translate the policy into `WHERE fruit.display_name = E'pineapple' AND fruit.price_tag = E'free'`:
|
||||
With this mapping, the policy translates into `WHERE fruit.display_name = E'pineapple' AND fruit.price_tag = E'free'`:
|
||||
|
||||
```http
|
||||
POST /v1/compile/filters/include
|
||||
@@ -2398,7 +2398,7 @@ faster to evaluate since OPA will not have to re-parse or compile it. Hence, whe
|
||||
OPA also supports query instrumentation. To enable query instrumentation,
|
||||
specify the `instrument=true` query parameter when executing the API call.
|
||||
Query instrumentation can help diagnose performance problems, however, it can
|
||||
add significant overhead to query evaluation. We recommend leaving query
|
||||
add significant overhead to query evaluation. It is recommended to leave query
|
||||
instrumentation off unless you are debugging a performance problem.
|
||||
|
||||
When instrumentation is enabled there are several additional performance metrics
|
||||
|
||||
+17
-17
@@ -58,7 +58,7 @@ openssl genrsa -out private.key 2048
|
||||
openssl req -new -x509 -sha256 -key private.key -out public.crt -days 1
|
||||
```
|
||||
|
||||
> We have generated a self-signed certificate for example purposes here. DO NOT
|
||||
> A self-signed certificate has been generated for example purposes here. DO NOT
|
||||
> rely on self-signed certificates outside of development without understanding
|
||||
> the risks.
|
||||
|
||||
@@ -83,7 +83,7 @@ curl -k https://localhost:8181/v1/data
|
||||
```
|
||||
|
||||
:::info
|
||||
We have to use cURL's `-k/--insecure` flag because we are using a self-signed certificate.
|
||||
cURL's `-k/--insecure` flag is required because a self-signed certificate is used.
|
||||
:::
|
||||
|
||||
## Interface Binding
|
||||
@@ -411,17 +411,17 @@ identity_rights contains right if { # Right is in the identity_rights set if...
|
||||
|
||||
### TLS-based Authentication Example
|
||||
|
||||
To set up authentication based on mutual TLS, we will need three certificates:
|
||||
To set up authentication based on mutual TLS, three certificates are needed:
|
||||
|
||||
1. the CA cert (self-signed),
|
||||
2. the server cert (signed by the CA), and
|
||||
3. the client cert (signed by the CA).
|
||||
|
||||
We use `openssl` to create the example certificates and keys used in this demo. In production, creation of certificates
|
||||
`openssl` is used to create the example certificates and keys in this demo. In production, creation of certificates
|
||||
and keys should be handled by an automated process out of scope for this tutorial.
|
||||
|
||||
Note that we also create an extra client cert (client-2). While this certificate is signed by the same CA, it's identity
|
||||
is different. We'll use this to show our authorization policy in action.
|
||||
Note that an extra client cert (client-2) is also created. While this certificate is signed by the same CA, its identity
|
||||
is different. This cert demonstrates the authorization policy in action.
|
||||
|
||||
```bash
|
||||
# CA
|
||||
@@ -489,7 +489,7 @@ openssl req -new -key server-key.pem -out csr.pem -subj "/CN=server" -config req
|
||||
openssl x509 -req -in csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -days 10 -extensions v3_req -extfile req.cnf -sha256
|
||||
```
|
||||
|
||||
We also create an example authorization policy file, called `check.rego`. This example `system.authz` policy will check
|
||||
An example authorization policy file, `check.rego`, is also created. This example `system.authz` policy will check
|
||||
the certificate ID against a list of allowed paths as defined in a simple Access Control List.
|
||||
|
||||
:::danger
|
||||
@@ -522,7 +522,7 @@ allow := {"allowed": true} if {
|
||||
}
|
||||
```
|
||||
|
||||
Now, we're ready to starting the server with `-authentication=tls` and the
|
||||
Start the server with `-authentication=tls` and the
|
||||
certificate-related parameters:
|
||||
|
||||
```console
|
||||
@@ -537,10 +537,10 @@ $ opa run -s \
|
||||
{"addrs":["https://127.0.0.1:8181"],"diagnostic-addrs":[],"level":"info","msg":"Initializing server.","time":"2023-01-04T10:31:12Z"}
|
||||
```
|
||||
|
||||
We can use `curl` to validate our TLS-based authentication setup:
|
||||
Use `curl` to validate the TLS-based authentication setup:
|
||||
|
||||
First, we use the client certificate that was signed by the CA, and has a subject
|
||||
matching our authorization policy:
|
||||
First, use the client certificate that was signed by the CA, with a subject
|
||||
matching the authorization policy:
|
||||
|
||||
```console
|
||||
$ curl --key client-key-1.pem \
|
||||
@@ -551,13 +551,13 @@ $ curl --key client-key-1.pem \
|
||||
{"result":{}}
|
||||
```
|
||||
|
||||
Note that we're passing the CA cert to curl -- this is done to have curl accept
|
||||
the server's certificate, which has been signed by our CA cert.
|
||||
Note that the CA cert is passed to curl -- this is done to have curl accept
|
||||
the server's certificate, which has been signed by the CA cert.
|
||||
|
||||
Since we've set up an IP SAN, we may also `curl https://127.0.0.1:8181/v1/data`
|
||||
directly. (To keep our examples focused, we'll do that from here on.)
|
||||
Since an IP SAN is configured, `curl https://127.0.0.1:8181/v1/data`
|
||||
also works directly. The remaining examples use this shorter form to stay focused.
|
||||
|
||||
Using a valid certificate whose subject will be declined by our authorization
|
||||
Using a valid certificate whose subject will be declined by the authorization
|
||||
policy:
|
||||
|
||||
```console
|
||||
@@ -571,7 +571,7 @@ $ curl --key client-key-2.pem \
|
||||
}
|
||||
```
|
||||
|
||||
Finally, we'll attempt to query without a client certificate:
|
||||
Finally, query without a client certificate:
|
||||
|
||||
```console
|
||||
$ curl --cacert ca.pem https://127.0.0.1:8181/v1/data
|
||||
|
||||
@@ -3,8 +3,8 @@ title: SSH and sudo
|
||||
---
|
||||
|
||||
Host-level access controls are an important part of every organization's
|
||||
security strategy. Using [Linux-PAM](https://tldp.org/HOWTO/User-Authentication-HOWTO/x115.html) and OPA
|
||||
we can extend policy-based access control to SSH and sudo.
|
||||
security strategy. With [Linux-PAM](https://tldp.org/HOWTO/User-Authentication-HOWTO/x115.html) and OPA,
|
||||
policy-based access control can be extended to SSH and sudo.
|
||||
|
||||
## Goals
|
||||
|
||||
@@ -12,16 +12,16 @@ This tutorial shows how you can use OPA and Linux-PAM to enforce fine-grained,
|
||||
host-level access controls over SSH and sudo.
|
||||
|
||||
Linux-PAM can be configured to delegate authorization decisions to plugins
|
||||
(shared libraries). In this case, we have created an OPA-based plugin that can
|
||||
(shared libraries). An OPA-based plugin has been created that can
|
||||
be configured to authorize SSH and sudo access. The OPA-based Linux-PAM plugin
|
||||
used in this tutorial can be found at [open-policy-agent/contrib](https://github.com/open-policy-agent/contrib/tree/main/pam_opa).
|
||||
|
||||
For this tutorial, our desired policy is:
|
||||
For this tutorial, the desired policy is:
|
||||
|
||||
- Admins can SSH into any host and run sudo commands.
|
||||
- Normal users can SSH into hosts that they have _contributed_ to and run sudo commands.
|
||||
|
||||
Furthermore, we'll assume we have the following set of users and hosts:
|
||||
The following set of users and hosts is assumed:
|
||||
|
||||
- `frontend-dev` is a developer who contributes to the app running on the `frontend` host.
|
||||
- `backend-dev` is a developer who contributes to the app running on the `backend` host.
|
||||
@@ -32,8 +32,6 @@ responsibility so this tutorial relies on identities being statically
|
||||
defined. In real-world scenarios authentication can be delegated to SSH itself
|
||||
(`authorized_keys`) or other identity management systems.
|
||||
|
||||
Let's get started.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
This tutorial requires [Docker Compose](https://docs.docker.com/compose/install/) to run dummy SSH hosts along
|
||||
@@ -44,7 +42,7 @@ with OPA. The dummy SSH hosts are just containers running sshd inside.
|
||||
### 1. Bootstrap the tutorial environment using Docker Compose
|
||||
|
||||
First, create a `tutorial-docker-compose.yaml` file that runs OPA and the containers that
|
||||
represent our backend and frontend hosts.
|
||||
represent the backend and frontend hosts.
|
||||
|
||||
<EvergreenCodeBlock>
|
||||
```yaml title="tutorial-docker-compose.yaml"
|
||||
@@ -92,7 +90,7 @@ services:
|
||||
|
||||
The `tutorial-docker-compose.yaml` file requires two other local files:
|
||||
`frontend_host_id.json` and `backend_host_id.json`. These files are mounted
|
||||
into the containers representing our hosts. The content of the file provides
|
||||
into the containers representing the hosts. The content of the file provides
|
||||
_context_ that the PAM module provides as input when executing queries
|
||||
against OPA.
|
||||
|
||||
@@ -103,7 +101,7 @@ echo '{"host_id": "frontend"}' > frontend_host_id.json
|
||||
echo '{"host_id": "backend"}' > backend_host_id.json
|
||||
```
|
||||
|
||||
> In real-world scenarios, these files could contain arbitrary information that we want to expose to the policy.
|
||||
> In real-world scenarios, these files could contain arbitrary information to expose to the policy.
|
||||
|
||||
Finally, run `docker-compose` to pull and run the containers.
|
||||
|
||||
@@ -112,7 +110,7 @@ docker-compose -f tutorial-docker-compose.yaml up
|
||||
```
|
||||
|
||||
This tutorial uses a special Docker image named `openpolicyagent/demo-pam` to simulate an SSH server.
|
||||
This image contains pre-created Linux accounts for our users, and the required PAM module is
|
||||
This image contains pre-created Linux accounts for the users, and the required PAM module is
|
||||
pre-configured inside the `sudo` and `sshd` files in `/etc/pam.d/`.
|
||||
|
||||
### 2. Create a Bundle for the policies and data
|
||||
@@ -147,8 +145,7 @@ include some default values, such as the username making the request. See
|
||||
[this documentation](https://github.com/open-policy-agent/contrib/tree/main/pam_opa/pam#authz)
|
||||
to get a better understanding of what the `input` to the authorization policy will look like.
|
||||
|
||||
Unlike the _pull_ policy, we'll create separate _authz_ policies
|
||||
for SSH and `sudo` for more fine-grained control.
|
||||
Unlike the _pull_ policy, the _authz_ policies for SSH and `sudo` are kept separate for more fine-grained control.
|
||||
In production, it makes more sense to have this separation for _display_ and _pull_ as well.
|
||||
|
||||
Create the SSH authorization policy. It should allow admins to SSH into all hosts,
|
||||
@@ -211,7 +208,7 @@ errors contains "Request denied by administrative policy" if {
|
||||
}
|
||||
```
|
||||
|
||||
Now we need to create the data that represents our roles, hots, and contributors into OPA.
|
||||
Create the data that represents the roles, hosts, and contributors in OPA.
|
||||
|
||||
Create a folder called roles, and the following data file.
|
||||
|
||||
@@ -270,7 +267,7 @@ Now you should have the following file structure setup.
|
||||
|
||||
### 3. SSH and sudo as a user with the `admin` role
|
||||
|
||||
First, let's try to access the hosts as the `ops` user. Recall, the `ops` user
|
||||
First, try to access the hosts as the `ops` user. Recall, the `ops` user
|
||||
has been granted the `admin` role (via the `PUT /data/roles` request above) and
|
||||
users with the `admin` role can login to any host and perform sudo commands.
|
||||
|
||||
@@ -292,7 +289,7 @@ configuration. For more details see
|
||||
|
||||
### 4. SSH as a user without the `admin` role
|
||||
|
||||
Let's try a user without the admin role. Recall, that a non-admin user can SSH
|
||||
Try a user without the admin role. Recall, that a non-admin user can SSH
|
||||
into any host that they have _contributed to_.
|
||||
|
||||
The `frontend-dev` user contributed code to the `frontend` host so they should be
|
||||
@@ -320,7 +317,7 @@ Suppose you have a ticketing system for elevation, where you generate tickets fo
|
||||
that need elevated rights, send the ticket to the user, and expire those tickets when
|
||||
their rights should be removed.
|
||||
|
||||
Let's mock the current state of this simple ticketing system's API with some data.
|
||||
Mock the current state of this simple ticketing system's API with some data.
|
||||
|
||||
```shell
|
||||
mkdir elevate
|
||||
@@ -336,9 +333,9 @@ EOF
|
||||
This means that for now, if the `frontend-dev` user can provide ticket number `1234`,
|
||||
they should be able to SSH into all servers.
|
||||
|
||||
Let's write policy to ensure that this happens.
|
||||
Write policy to ensure that this happens.
|
||||
|
||||
First, we need to make the PAM module take input from the user.
|
||||
First, make the PAM module take input from the user.
|
||||
|
||||
**display.rego**:
|
||||
|
||||
@@ -355,7 +352,7 @@ display_spec := [
|
||||
]
|
||||
```
|
||||
|
||||
Then we need to make sure that the authorization takes this input into account.
|
||||
Then make sure that the authorization takes this input into account.
|
||||
|
||||
**sudo_authz_elevated.rego**:
|
||||
|
||||
@@ -374,7 +371,7 @@ allow if {
|
||||
}
|
||||
```
|
||||
|
||||
Now we need to build a new bundle for OPA to use.
|
||||
Now build a new bundle for OPA to use.
|
||||
|
||||
```shell
|
||||
opa build -b .
|
||||
@@ -389,12 +386,12 @@ ssh -p 2222 frontend-dev@localhost \
|
||||
sudo ls /
|
||||
```
|
||||
|
||||
You should be prompted with the message that we defined in our _display_ policy
|
||||
You should be prompted with the message defined in the _display_ policy
|
||||
for both the SSH and `sudo` authorization cycles.
|
||||
This happens because the _display_ policy is shared by the PAM configurations of SSH and `sudo`.
|
||||
In production, it is more practical to use separate policy packages for each PAM configuration.
|
||||
|
||||
We have not defined the SSH _authz_ policy to work with elevation, so you can enter any value
|
||||
The SSH _authz_ policy has not been defined to work with elevation, so you can enter any value
|
||||
into the prompt that comes up for SSH.
|
||||
|
||||
For `sudo`, enter the ticket number `1234` to get access.
|
||||
@@ -418,13 +415,11 @@ opa build -b .
|
||||
You will find that running `sudo ls /` as the `frontend-dev` user is disallowed again.
|
||||
|
||||
It is possible to configure the _display_ policy to only make the PAM module prompt for the
|
||||
elevation ticket when our mock API has a non-empty `tickets` object. So when there are no
|
||||
elevation ticket when the mock API has a non-empty `tickets` object. So when there are no
|
||||
elevated users, there will be no prompt for a ticket. This can be done using the Rego
|
||||
[`count` aggregate](./policy-reference/builtins/aggregates).
|
||||
|
||||
## Wrap Up
|
||||
|
||||
Congratulations for finishing the tutorial!
|
||||
## Summary
|
||||
|
||||
You learned a number of things about SSH with OPA:
|
||||
|
||||
|
||||
+10
-11
@@ -10,10 +10,10 @@ The purpose of this style guide is to provide a collection of recommendations an
|
||||
[Rego](https://www.openpolicyagent.org/docs/policy-language).
|
||||
From the maintainers of [Open Policy Agent](https://www.openpolicyagent.org) (OPA),
|
||||
and some of the most experienced members of the community,
|
||||
we hope to share lessons learnt from authoring and reviewing hundreds of thousands of lines of Rego over the years.
|
||||
this guide shares lessons learnt from authoring and reviewing hundreds of thousands of lines of Rego over the years.
|
||||
|
||||
With new features, language constructs, and other improvements continuously finding their way into OPA, we aim to keep
|
||||
this style guide a reflection of what we consider current best practices. Make sure to check back every once in a while,
|
||||
With new features, language constructs, and other improvements continuously finding their way into OPA, the goal is to keep
|
||||
this style guide a reflection of current best practices. Make sure to check back every once in a while,
|
||||
and see the changelog for updates since your last visit.
|
||||
|
||||
## Regal
|
||||
@@ -186,7 +186,7 @@ Regal rule. Get started with [Regal, the Rego linter](/projects/regal).
|
||||
|
||||
### Optionally, use leading underscore for rules intended for internal use
|
||||
|
||||
While OPA doesn't have "private" rules or functions, a pretty common convention that we've seen in the community is to
|
||||
While OPA doesn't have "private" rules or functions, a pretty common convention in the community is to
|
||||
use a leading underscore for rules and functions that are intended to be internal to the package that they are in:
|
||||
|
||||
```rego
|
||||
@@ -304,14 +304,14 @@ deny contains "User is anonymous" if input.user_id == "anonymous"
|
||||
At first glance, it might seem obvious that evaluating the rule should add a violation to the set of messages if the
|
||||
`user_id` provided in `input` is equal to "anonymous". But what happens if there is no `user_id` provided _at all_?
|
||||
Evaluation will stop when encountering undefined, and the comparison will never be invoked, leading to **nothing**
|
||||
being added to the `deny` set — the rule allows someone without a `user_id`. We could of course add another
|
||||
being added to the `deny` set — the rule allows someone without a `user_id`. One option is to add another
|
||||
rule, checking only for its presence:
|
||||
|
||||
```rego
|
||||
deny contains "User ID missing from input" if not input.user_id
|
||||
```
|
||||
|
||||
This is nice in that we'll get an even more granular message returned to the caller, but quickly becomes tedious when
|
||||
This provides an even more granular message returned to the caller, but quickly becomes tedious when
|
||||
working with a large set of input data. To deal with this, a helper rule using _negation_ may be used.
|
||||
|
||||
**Prefer**
|
||||
@@ -324,8 +324,8 @@ authenticated_user if input.user_id != "anonymous"
|
||||
```
|
||||
|
||||
In the above case, the `authenticated_user` rule will fail **both** in the the undefined case, and if defined
|
||||
but equal to "anonymous". Since we negate the result of the helper rule in the `deny` rule, we'll have both
|
||||
cases covered.
|
||||
but equal to "anonymous". Since the result of the helper rule is negated in the `deny` rule, both
|
||||
cases are covered.
|
||||
|
||||
#### Related Resources
|
||||
|
||||
@@ -502,8 +502,7 @@ my_rule if {
|
||||
}
|
||||
```
|
||||
|
||||
While this could be alleviated by declaring `some user` before the iteration, we can't take that consideration for
|
||||
granted when reading code from someone else.
|
||||
While this could be alleviated by declaring `some user` before the iteration, that assumption cannot be taken for granted when reading code from someone else.
|
||||
|
||||
**Avoid**
|
||||
```rego
|
||||
@@ -1074,7 +1073,7 @@ Regal rule. Get started with [Regal, the Rego linter](/projects/regal).
|
||||
## Contributing
|
||||
|
||||
This document is meant to reflect the style preferences and best practices as compiled by the OPA community. As such,
|
||||
we welcome contributions from any of its members. Since most of the topics in a guide like this are likely subject to
|
||||
contributions from community members are welcome. Since most of the topics in a guide like this are likely subject to
|
||||
discussion, please open an issue, and allow some time for people to comment, before opening a PR.
|
||||
|
||||
If you'd like to add or remove items for your own company, team or project, forking this repo is highly encouraged!
|
||||
|
||||
+6
-10
@@ -446,7 +446,7 @@ Here is the expected contents of `tfplan.json`.
|
||||
}
|
||||
```
|
||||
|
||||
The JSON plan output produced by terraform contains a lot of information. For this tutorial, we will be interested by:
|
||||
The JSON plan output produced by terraform contains a lot of information. For this tutorial, the relevant fields are:
|
||||
|
||||
- `.resource_changes`: array containing all the actions that terraform will apply on the infrastructure.
|
||||
- `.resource_changes[].type`: the type of resource (e.g. `aws_instance` , `aws_iam` ...)
|
||||
@@ -595,7 +595,7 @@ opa exec --decision terraform/analysis/authz --bundle policy/ tfplan.json
|
||||
```
|
||||
|
||||
If you're curious, you can ask for the score that the policy used to make the authorization decision.
|
||||
In our example, it is 11 (10 for the creation of the auto-scaling group and 1 for the creation of the server).
|
||||
In this example, it is 11 (10 for the creation of the auto-scaling group and 1 for the creation of the server).
|
||||
|
||||
```shell
|
||||
opa exec --decision terraform/analysis/score --bundle policy/ tfplan.json
|
||||
@@ -696,7 +696,7 @@ In addition to loading policies from the local filesystem, `opa exec` can fetch
|
||||
opa build policy/
|
||||
```
|
||||
|
||||
Next, serve the bundle via NGINX:
|
||||
Next, serve the bundle via Nginx:
|
||||
|
||||
```bash
|
||||
docker run --rm --name bundle_server -d -p 8888:80 -v ${PWD}:/usr/share/nginx/html:ro nginx:latest
|
||||
@@ -711,9 +711,7 @@ opa exec --decision terraform/analysis/authz \
|
||||
tfplan_large.json
|
||||
```
|
||||
|
||||
## Wrap Up
|
||||
|
||||
Congratulations for finishing the tutorial!
|
||||
## Summary
|
||||
|
||||
You learned a number of things about Terraform Testing with OPA:
|
||||
|
||||
@@ -805,7 +803,7 @@ terraform show -json tfplan.binary > tfplan2.json
|
||||
The policy evaluates if a security group is valid based on the contents of it's description:
|
||||
|
||||
- Resources can be specified under the root module or in child modules
|
||||
- We want to evaluate against the combined group of these resources
|
||||
- The policy evaluates against the combined group of these resources
|
||||
- This example is scoped to the planned changes section of the JSON representation
|
||||
|
||||
The policy uses the walk keyword to explore the json structure, and uses conditions to filter for the specific paths where resources would be found.
|
||||
@@ -904,9 +902,7 @@ This should return one of the two resources. The security group created by the m
|
||||
}
|
||||
```
|
||||
|
||||
## Module Wrap Up
|
||||
|
||||
Congratulations on finishing the tutorial!
|
||||
## Summary
|
||||
|
||||
You learned OPA can be used to determine if a proposed configuration is authorized.
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ feature set. Examples of when this applies:
|
||||
|
||||
- You run a service for customers who supply their own Rego.
|
||||
- You use OPA as part of a managed platform and need to run a mix of v0.x and
|
||||
v1.0 OPAs based on customer demands.
|
||||
v1.0 OPA instances based on customer demands.
|
||||
|
||||
Users with control over their Rego and OPA deployments are instead encouraged
|
||||
to migrate their Rego to be compatible with OPA v1.0 using the below tooling options:
|
||||
|
||||
@@ -55,7 +55,7 @@ working with in different parts of their systems. Select the scenario that best
|
||||
matches your setup to find the recommended upgrade path.
|
||||
|
||||
If you are in doubt, [Scenario 1](#scenario-1-v0x-producer-v0x-consumer) is the most common starting
|
||||
point and we recommended you start there.
|
||||
point and is the recommended place to start.
|
||||
|
||||
| | v0.x Consumer | Mix Consumer | v1.0 Consumer |
|
||||
| ----------------- | -------------------------------------------------------------- | --------------------------------------------------- | -------------------------------------------------------------- |
|
||||
@@ -174,7 +174,7 @@ bundles. This upgrade path cannot continue until this is possible.
|
||||
### Next
|
||||
|
||||
Upgrade producers to v1.0 and continue the upgrade from that point. Generally, it's recommended to
|
||||
upgrade producers first, however depending on your existing OPAs v1.0 consumers deployments, you may prefer to
|
||||
upgrade producers first, however depending on your existing v1.0 OPA consumer deployments, you may prefer to
|
||||
upgrade all your producers to v1.0 rather than to downgrade consumers.
|
||||
|
||||
### Scenario 5: Mix Producer, Mix Consumer
|
||||
@@ -201,7 +201,7 @@ Please gradually upgrade producers to v1.0 until all producers are v1.0 ([Scenar
|
||||
### Scenario 6: v1.0 Producer, Mix Consumer
|
||||
|
||||
All consumers can be run without flags, as the bundle will contain
|
||||
attributes to inform v1.0 OPAs to accept v0.x modules.
|
||||
attributes to inform v1.0 OPA instances to accept v0.x modules.
|
||||
|
||||
#### How to Run
|
||||
|
||||
@@ -222,7 +222,7 @@ the next and final step.
|
||||
### Scenario 7: v0.x Producer, v1.0 Consumer
|
||||
|
||||
All consumers are v1.0, but producers are v0.x. This scenario might occur when
|
||||
OPAs used for evaluation are upgraded before the policy bundling system.
|
||||
OPA instances used for evaluation are upgraded before the policy bundling system.
|
||||
|
||||
#### Pre-requisites
|
||||
|
||||
@@ -268,10 +268,10 @@ Upgrade producers to v1.0 ([Scenario 9](#scenario-9-v10-producer-v10-consumer)),
|
||||
|
||||
Once you have all consumers and producers running at v1.0 then you have
|
||||
completed the upgrade to OPA v1.0. If you are using `--v0-compatible`
|
||||
functionality, the next task is to upgrade the Rego loaded into OPAs to Rego v1.
|
||||
functionality, the next task is to upgrade the Rego loaded into OPA instances to Rego v1.
|
||||
|
||||
Regardless of whether you are now upgrading your Rego, we encourage users to
|
||||
use `opa check`, `opa check --strict` and to lint their Rego projects if you
|
||||
Regardless of whether you are now upgrading your Rego, run `opa check`,
|
||||
`opa check --strict` and lint your Rego projects if you
|
||||
have not already done so to identify issues.
|
||||
|
||||
## Changes to Rego in OPA v1.0
|
||||
|
||||
+6
-6
@@ -16,7 +16,7 @@ As described on [https://webassembly.org/](https://webassembly.org/)
|
||||
|
||||
OPA is able to compile Rego policies into executable Wasm modules that can be
|
||||
evaluated with different inputs and external data. This is _not_ running the OPA
|
||||
server in Wasm, nor is this just cross-compiled Golang code. The compiled Wasm
|
||||
server in Wasm, nor is this just cross-compiled Go code. The compiled Wasm
|
||||
module is a planned evaluation path for the source policy and query.
|
||||
|
||||
## Current Status
|
||||
@@ -79,7 +79,7 @@ API that produces OPA bundle files. The compile API is recommended.
|
||||
|
||||
There is a JavaScript SDK available that simplifies the process of loading and
|
||||
evaluating compiled policies. If you want to evaluate Rego policies inside
|
||||
JavaScript we recommend you use the
|
||||
JavaScript, it is recommended to use the
|
||||
[Javascript SDK](https://github.com/open-policy-agent/npm-opa-wasm).
|
||||
There is also an
|
||||
[example NodeJS application](https://github.com/open-policy-agent/npm-opa-wasm/tree/main/examples/nodejs-app)
|
||||
@@ -312,7 +312,7 @@ heap pointer. The calling convention is as follows:
|
||||
and clear any memory left from `eval` or `opa_eval` calls.
|
||||
2. Call `opa_heap_blocks_restore` to reinstate the heap stashed heap memory.
|
||||
3. Call `opa_malloc`/`opa_json_parse`/`opa_free` to create the "path" and
|
||||
"value" arguments (in WASM value form) as usual.
|
||||
"value" arguments (in Wasm value form) as usual.
|
||||
4. Call `opa_value_add_path` or `opa_value_remove_path` as usual.
|
||||
5. Call `opa_value_free` on the "path" argument to release it as usual.
|
||||
6. Call `opa_heap_blocks_stash` to stash any free heap blocks to
|
||||
@@ -334,9 +334,9 @@ to `eval` or `opa_eval`. But that memory was never truly available for
|
||||
queries in the first place. The very first call to `opa_heap_ptr_set`
|
||||
(either before `eval` or which `opa_eval` calls internally) resets the heap
|
||||
and leaks any free blocks on the heap. In versions of the ABI prior to
|
||||
1.3 this memory was simply lost. Note, however, that multiple queries
|
||||
1.3 this memory was lost. Note, however, that multiple queries
|
||||
would not continue to leak memory since they would always reset the heap
|
||||
pointer to the same value. The WASM engine would only leak further
|
||||
pointer to the same value. The Wasm engine would only leak further
|
||||
memory if there were subsequent calls to `opa_value_add_path` or
|
||||
`opa_value_remove_path` followed by more queries. ABI 1.3 introduced the
|
||||
calling convention using `opa_heap_blocks_stash` and
|
||||
@@ -378,5 +378,5 @@ Sets are represented as JSON arrays.
|
||||
## Ecosystem Projects
|
||||
|
||||
<EcosystemEmbed feature="wasm-integration">
|
||||
Wasm is a great way to integrate OPA into applications where the Go SDK is unavailable.
|
||||
Wasm can be used to integrate OPA into applications where the Go SDK is unavailable.
|
||||
</EcosystemEmbed>
|
||||
|
||||
@@ -286,10 +286,11 @@ Documentation: https://www.openpolicyagent.org/projects/regal/rules/style/prefer
|
||||
<!-- markdownlint-restore -->
|
||||
<br />
|
||||
|
||||
> **Note**
|
||||
> If you're running Regal on an existing policy library, you may want to disable the `style` category initially, as it
|
||||
> will likely generate a lot of violations. You can do this by passing the `--disable-category style` flag to
|
||||
> `regal lint`.
|
||||
:::note
|
||||
If you're running Regal on an existing policy library, you may want to disable the `style` category initially, as it
|
||||
will likely generate a lot of violations. You can do this by passing the `--disable-category style` flag to
|
||||
`regal lint`.
|
||||
:::
|
||||
|
||||
### Using Regal in Your Editor
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ Runs Regal against all staged `.rego` files, aborting the commit if any fail.
|
||||
|
||||
Runs Regal against all staged `.rego` files, aborting the commit if any fail.
|
||||
|
||||
- Downloads the latest `regal` binary from Github.
|
||||
- Downloads the latest `regal` binary from GitHub.
|
||||
|
||||
### `regal-fix`
|
||||
|
||||
@@ -77,4 +77,4 @@ Same as `regal-fix`, but uses the `regal` binary already on `$PATH`.
|
||||
|
||||
Same as `regal-fix`, but downloads the latest `regal` binary from GitHub instead of building or relying on `$PATH`.
|
||||
|
||||
- Downloads the latest `regal` binary from Github.
|
||||
- Downloads the latest `regal` binary from GitHub.
|
||||
|
||||
@@ -20,4 +20,4 @@ docs_features:
|
||||
'
|
||||
---
|
||||
|
||||
A crate to use OPA policies compiled to WASM.
|
||||
A crate to use OPA policies compiled to Wasm.
|
||||
|
||||
@@ -19,4 +19,4 @@ docs_features:
|
||||
'
|
||||
---
|
||||
|
||||
A Zig library to use OPA policies compiled to WASM.
|
||||
A Zig library to use OPA policies compiled to Wasm.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Discovery Bundles
|
||||
description: Distribute flexible configuration to OPAs
|
||||
description: Distribute flexible configuration to OPA instances
|
||||
category: production
|
||||
---
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user