Skip to content

Commit

Permalink
Add OPA documentation
Browse files Browse the repository at this point in the history
This documentation covers the general Rego information and also two
specific sections about Open Policy Agent and Gatekeeper.
  • Loading branch information
ereslibre committed Sep 14, 2021
1 parent b957f8d commit 686fb86
Show file tree
Hide file tree
Showing 11 changed files with 833 additions and 0 deletions.
10 changes: 10 additions & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@
- [Write a mutation policy](./writing-policies/rust/05-mutation-policy.md)
- [Logging](./writing-policies/rust/06-logging.md)
- [Build and distribute](./writing-policies/rust/07-build-and-distribute.md)
- [Rego](./writing-policies/rego/01-intro.md)
- [Open Policy Agent](./writing-policies/rego/open-policy-agent/01-intro.md)
- [Create a new policy](./writing-policies/rego/open-policy-agent/02-create-policy.md)
- [Build and run](./writing-policies/rego/open-policy-agent/03-build-and-run.md)
- [Distribute](./writing-policies/rego/open-policy-agent/04-distribute.md)
- [Gatekeeper](./writing-policies/rego/gatekeeper/01-intro.md)
- [Create a new policy](./writing-policies/rego/gatekeeper/02-create-policy.md)
- [Build and run](./writing-policies/rego/gatekeeper/03-build-and-run.md)
- [Distribute](./writing-policies/rego/gatekeeper/04-distribute.md)
- [Builtin support](./writing-policies/rego/02-builtin-support.md)
- [Go](./writing-policies/go/01-intro.md)
- [Create a new policy](./writing-policies/go/02-scaffold.md)
- [Define policy settings](./writing-policies/go/03-policy-settings.md)
Expand Down
86 changes: 86 additions & 0 deletions src/writing-policies/rego/01-intro.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Rego

The Rego language is a tailor made language designed to embrace
policies as
code. [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/)
is a language inspired by Datalog.

There are two ways of writing Rego policies as of today in order to
implement policies as code in Kubernetes: Open Policy Agent and
Gatekeeper.

## One language. Two frameworks

### Open Policy Agent

Open Policy Agent is a project that allows you to implement policies
as code in any project. You can rely on Open Policy Agent for any
policy based check that you might require in your own application,
that will in turn execute the required Rego policies.

In this context, writing policies for Kubernetes is just another way
of exercising Open Policy Agent. By using Kubernetes admission
webhooks, it's possible to leverage Kubernetes' admission webhooks to
evaluate requests using Open Policy Agent, that will in turn execute
the policies written in Rego.

Open Policy Agent has some optional integration with Kubernetes
through its `kube-mgmt` sidecar. When deployed on top of Kubernetes
and next to the Open Policy Agent server evaluating the Rego policies,
it is able to replicate the configured Kubernetes resources into Rego
-- so those Kubernetes resources are visible to all policies. It also
lets you define policies inside Kubernetes' configmaps. You can read
more about it on [its project
page](https://github.com/open-policy-agent/kube-mgmt).

### Gatekeeper

Gatekeeper is very different from Open Policy Agent in this regard. It
is focused exclusively to be used in Kubernetes, and takes advantage
of that as much as it can, making some Kubernetes workflows easier
than Open Policy Agent in many cases.

## Looking at the differences

Both Open Policy Agent and Gatekeeper policies use Rego to describe
their policies as code. However, this is only one part of the
puzzle. Each solution has differences when it comes to writing real
policies in Rego, and we are going to look at those differences in the
next sections.

## Common concepts

### Entry point

The entry point is the name of the rule within a package, and is
therule to be invoked by the runtime when the policy is
instantiated.

## Current limitations

### Context-aware policies

Context-aware policies are policies that don't evaluate the input
request in isolation. They take other factors into account in order to
take a decision. For example, a policy that evaluates namespaced
resources and uses an annotation on the parent namespace to configure
something on the policy. Another example would be a policy that
evaluates `Ingress` resources, but that in order to take a decision
has the list of the already existing `Ingress` resources.

The concept of context-aware policies can also extend to custom
resources, so your policy might want to evaluate a request based on
currently persisted custom resources as well.

Policies written for Open Policy Agent with the `kube-mgmt` sidecar
integration, or Gatekeeper support context-aware policies. Kubewarden
has not yet implemented this functionality, but it is in our plans to
do so.

### Mutating policies

Gatekeeper has support for mutating policies, but Kubewarden has not
yet implemented mutating policies with Gatekeeper compatibility. You
can use policies that use the Kubewarden SDK to write mutating
policies, but at the time of writing, you cannot run Gatekeeper
mutating policies in Kubewarden yet.
28 changes: 28 additions & 0 deletions src/writing-policies/rego/02-builtin-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Builtin support

Building a policy for the `wasm` target is only half of the problem,
it needs to be executed.

The Open Policy Agent team has a dedicated page you can check in order
to [find out the built-in support
level](https://www.openpolicyagent.org/docs/latest/policy-reference/#built-in-functions).

Every green check in this table means that those built-ins are
implemented regardless of the runtime: they are implemented already on
the policy you have built.

The built-ins marked as `SDK-dependent` are the ones that the host has
to implement -- in this case, Kubewarden. Open Policy Agent and
Gatekeeper may use them depending on the needs of the policy. In any
case, this built-ins are exposed to the policy and any new or existing
policy could depend on them.

These are the [built-ins implemented up until now in
Kubewarden](https://github.com/kubewarden/policy-evaluator/issues/56#issue-983937559).

## Executing policies with missing built-ins

When a policy is instantiated with `kwctl` or the `policy-server`, the
list of built-ins used by the policy will be inspected, and if any of
the used built-ins is missing, the program will abort execution
logging a fatal error reporting what are the missing built-ins.
19 changes: 19 additions & 0 deletions src/writing-policies/rego/gatekeeper/01-intro.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Gatekeeper

Gatekeeper is a project targeting Kubernetes, and as such, has some
features that are thought out of the box for being integrated with it.

## Compatibility with existing policies

All Gatekeeper policies that you have written already should be
compatible with Kubewarden as we will explain during this chapter.

> **Note**: if this is not the case, please report it to us and we
> will do our best to make sure your policy runs flawlessly with
> Kubewarden.
Policies have to be compiled with the `opa` CLI to the `wasm` target.

In terms of policy execution, you can read more about the [Open Policy
Agent built-in support that is implemented in
Kubewarden](../02-builtin-support.md).
101 changes: 101 additions & 0 deletions src/writing-policies/rego/gatekeeper/02-create-policy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Create a new policy

Let's implement the same policy that [we wrote with Open Policy
Agent](../open-policy-agent/02-create-policy.md): a policy that
rejects a resource if it's targeting the `default` namespace.

## Requirements

As in the previous section, we will require the following tools:

- `opa`
- `kwctl`

## The policy

Since Gatekeeper is targeting Kubernetes, it has the freedom to be
more handy in what the policy has to return.

With Open Policy Agent we had to construct a whole `AdmissionReview`
object as the response of our policy. With Gatekeeper, we only have to
return none or more violations from our policy entrypoint.

If no violations were reported, the request will be accepted. If one,
or more violations were reported, the request will be rejected.

We create a new folder, named `rego-policy`. Inside of it, we create a
`policy.rego` file with contents:

```rego
package policy
violation[{"msg": msg}] {
input.review.object.metadata.namespace == "default"
msg := "it is forbidden to use the default namespace"
}
```

In this case, our entrypoint is `policy/violation`, and because of how
Rego works, it can either have 1 violation: if the object to be
reviewed is targeting the `default` namespace, or 0 violations
otherwise.

Take a moment to compare this policy with the one we wrote in the Open
Policy Agent section. That one had to build the whole
`AdmissionReview` response, and the inputs were slightly
different. In the Gatekeeper mode, the `AdmissionRequest` object is
provided at the `input.review` attribute. All attributes of the
`AdmissionRequest` are readable along with `object`.

Now, let's create the requests that we are going to evaluate in the
next section.

Let us first create a `default-ns.json` file with the following
contents inside the `data` directory:

```json
{
"apiVersion": "admission.k8s.io/v1",
"kind": "AdmissionReview",
"request": {
"uid": "1299d386-525b-4032-98ae-1949f69f9cfc",
"operation": "CREATE",
"object": {
"kind": "Pod",
"apiVersion": "v1",
"metadata": {
"name": "nginx",
"namespace": "default",
"uid": "04dc7a5e-e1f1-4e34-8d65-2c9337a43e64"
}
}
}
}
```

Now, let's create another `AdmissionReview` object that this time is
targeting a namespace different than the `default` one. Let us name
this file `other-ns.json`. It has the following contents:

```json
{
"apiVersion": "admission.k8s.io/v1",
"kind": "AdmissionReview",
"request": {
"uid": "1299d386-525b-4032-98ae-1949f69f9cfc",
"operation": "CREATE",
"object": {
"kind": "Pod",
"apiVersion": "v1",
"metadata": {
"name": "nginx",
"namespace": "other",
"uid": "04dc7a5e-e1f1-4e34-8d65-2c9337a43e64"
}
}
}
}
```

As you can see, this simulates another pod creation request, this time
under a namespace called `other`.
81 changes: 81 additions & 0 deletions src/writing-policies/rego/gatekeeper/03-build-and-run.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Build and run

Building and running the policy is done exactly the same way as a Rego
policy targeting Open Policy Agent. The structure is like:

```
.
├── data
│   ├── default-ns.json
│   └── other-ns.json
└── policy.rego
1 directory, 3 files
```

## Build

Let's build our policy by running the following `opa` command:

```shell
~/gatekeeper-policy » opa build -t wasm -e policy/violation policy.rego
```

What this does is build the rego policy, with:

- `target`: `wasm`. We want to build the policy for the `wasm` target.
- `entrypoint`: `policy/violation`. The entry point is the `violation`
rule inside the `policy` package.
- `policy.rego`: build and include the `policy.rego` file.

After the build is complete, `opa build` will have generated a
`bundle.tar.gz` file. You can extract the wasm file from it:

```shell
gatekeeper-policy » tar -xf bundle.tar.gz /policy.wasm
```

The tree looks like the following:

```
.
├── bundle.tar.gz
├── data
│   ├── default-ns.json
│   └── other-ns.json
├── policy.rego
└── policy.wasm
1 directory, 5 files
```

We can now execute our policy!

## Run

Let's use `kwctl` to run our policy as follows:

```
gatekeeper-policy » kwctl run -e gatekeeper --request-path data/other-ns.json policy.wasm | jq
{
"uid": "1299d386-525b-4032-98ae-1949f69f9cfc",
"allowed": true
}
```

Given that this is our resource created in the namespace called
`other`, this resource is accepted, as expected. Now let's execute a
request that will be rejected by the policy:

```
gatekeeper-policy » kwctl run -e gatekeeper --request-path data/default-ns.json policy.wasm | jq
{
"uid": "1299d386-525b-4032-98ae-1949f69f9cfc",
"allowed": false,
"status": {
"message": "it is forbidden to use the default namespace"
}
}
```

As you can see, our Gatekeeper policy rejected this resource as expected.
Loading

0 comments on commit 686fb86

Please sign in to comment.