From eb1c29797eee5f258361dc3ff8262459de40ed5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Fern=C3=A1ndez=20L=C3=B3pez?= Date: Thu, 26 Aug 2021 14:31:16 +0200 Subject: [PATCH] Add OPA documentation This documentation covers the general Rego information and also two specific sections about Open Policy Agent and Gatekeeper. --- src/SUMMARY.md | 10 ++ src/writing-policies/rego/01-intro.md | 49 ++++++ .../rego/02-builtin-support.md | 28 +++ .../rego/gatekeeper/01-intro.md | 19 ++ .../rego/gatekeeper/02-create-policy.md | 101 +++++++++++ .../rego/gatekeeper/03-build-and-run.md | 81 +++++++++ .../rego/gatekeeper/04-distribute.md | 88 ++++++++++ .../rego/open-policy-agent/01-intro.md | 28 +++ .../open-policy-agent/02-create-policy.md | 162 ++++++++++++++++++ .../open-policy-agent/03-build-and-run.md | 102 +++++++++++ .../rego/open-policy-agent/04-distribute.md | 128 ++++++++++++++ 11 files changed, 796 insertions(+) create mode 100644 src/writing-policies/rego/01-intro.md create mode 100644 src/writing-policies/rego/02-builtin-support.md create mode 100644 src/writing-policies/rego/gatekeeper/01-intro.md create mode 100644 src/writing-policies/rego/gatekeeper/02-create-policy.md create mode 100644 src/writing-policies/rego/gatekeeper/03-build-and-run.md create mode 100644 src/writing-policies/rego/gatekeeper/04-distribute.md create mode 100644 src/writing-policies/rego/open-policy-agent/01-intro.md create mode 100644 src/writing-policies/rego/open-policy-agent/02-create-policy.md create mode 100644 src/writing-policies/rego/open-policy-agent/03-build-and-run.md create mode 100644 src/writing-policies/rego/open-policy-agent/04-distribute.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index b42e1cb275..38978b602f 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -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) diff --git a/src/writing-policies/rego/01-intro.md b/src/writing-policies/rego/01-intro.md new file mode 100644 index 0000000000..d86ffee9c8 --- /dev/null +++ b/src/writing-policies/rego/01-intro.md @@ -0,0 +1,49 @@ +# 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. diff --git a/src/writing-policies/rego/02-builtin-support.md b/src/writing-policies/rego/02-builtin-support.md new file mode 100644 index 0000000000..b4e5da8184 --- /dev/null +++ b/src/writing-policies/rego/02-builtin-support.md @@ -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. diff --git a/src/writing-policies/rego/gatekeeper/01-intro.md b/src/writing-policies/rego/gatekeeper/01-intro.md new file mode 100644 index 0000000000..2136ae6c56 --- /dev/null +++ b/src/writing-policies/rego/gatekeeper/01-intro.md @@ -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). diff --git a/src/writing-policies/rego/gatekeeper/02-create-policy.md b/src/writing-policies/rego/gatekeeper/02-create-policy.md new file mode 100644 index 0000000000..28f026a116 --- /dev/null +++ b/src/writing-policies/rego/gatekeeper/02-create-policy.md @@ -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`. diff --git a/src/writing-policies/rego/gatekeeper/03-build-and-run.md b/src/writing-policies/rego/gatekeeper/03-build-and-run.md new file mode 100644 index 0000000000..9bdd9b5048 --- /dev/null +++ b/src/writing-policies/rego/gatekeeper/03-build-and-run.md @@ -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. diff --git a/src/writing-policies/rego/gatekeeper/04-distribute.md b/src/writing-policies/rego/gatekeeper/04-distribute.md new file mode 100644 index 0000000000..2a913e0599 --- /dev/null +++ b/src/writing-policies/rego/gatekeeper/04-distribute.md @@ -0,0 +1,88 @@ +# Distribute + +Policies have to be annotated for them to be pushed, and eventually +executed by the Kubewarden `policy-server` in a Kubernetes cluster. + +Annotating and distributing our gatekeeper policy is very similar to +distributing an Open Policy Agent one. Let's go through it. + +## Annotating the policy + +We are going to write a `metadata.yaml` file in our policy directory +with contents: + +```yaml +rules: +- apiGroups: [""] + apiVersions: ["*"] + resources: ["*"] + operations: ["CREATE"] +mutating: false +contextAware: false +executionMode: gatekeeper +annotations: + io.kubewarden.policy.title: no-default-namespace + io.kubewarden.policy.description: This policy will reject any resource created inside the default namespace + io.kubewarden.policy.author: The Kubewarden Authors + io.kubewarden.policy.url: https://github.com/kubewarden/some-policy + io.kubewarden.policy.source: https://github.com/kubewarden/some-policy + io.kubewarden.policy.license: Apache-2.0 + io.kubewarden.policy.usage: | + This policy is just an example. + + You can write interesting descriptions about the policy here. +``` + +As you can see, everything is the same as the Open Policy Agent +version metadata, except for the `executionMode: gatekeeper` bit. + +Let's go ahead and annotate the policy: + +```console +gatekeeper-policy » kwctl annotate policy.wasm --metadata-path metadata.yaml --output-path annotated-policy.wasm +``` + +## Pushing the policy + +Let's push our policy to an OCI registry: + +```console +gatekeeper-policy » kwctl push annotated-policy.wasm registry.my-company.com/kubewarden/no-default-namespace-gatekeeper:v0.0.1 +Policy successfully pushed +``` + +## Deploying on Kubernetes + +We have to pull our policy to our `kwctl` local store first: + +```console +» kwctl pull registry://registry.my-company.com/kubewarden/no-default-namespace-gatekeeper:v0.0.1 +pulling policy... +``` + +We can now create a scaffold `ClusterAdmissionPolicy` resource: + +```console +» kwctl manifest registry://registry.my-company.com/kubewarden/no-default-namespace-gatekeeper:v0.0.1 --type ClusterAdmissionPolicy +--- +apiVersion: policies.kubewarden.io/v1alpha2 +kind: ClusterAdmissionPolicy +metadata: + name: generated-policy +spec: + module: "registry://registry.my-company.com/kubewarden/no-default-namespace-gatekeeper:v0.0.1" + settings: {} + rules: + - apiGroups: + - "" + apiVersions: + - "*" + resources: + - "*" + operations: + - CREATE + mutating: false +``` + +We could now use this `ClusterAdmissionPolicy` resource to deploy our +policy to a Kubernetes cluster. diff --git a/src/writing-policies/rego/open-policy-agent/01-intro.md b/src/writing-policies/rego/open-policy-agent/01-intro.md new file mode 100644 index 0000000000..64aec3d183 --- /dev/null +++ b/src/writing-policies/rego/open-policy-agent/01-intro.md @@ -0,0 +1,28 @@ +# Open Policy Agent + +Open Policy Agent is a general purpose policy framework. This is the +reason why we can use regular Rego policies with Open Policy Agent +when targeting Kubernetes as if it was any other application taking +advantage of it. + +## Introduction + +Rego policies work by receiving an input to evaluate, and produce an +output as a response. In this sense, Open Policy Agent has no specific +tooling for targeting writing policies for Kubernetes. + +Specifically, policies in Open Policy Agent receive a JSON input and +produce a JSON output. When the Open Policy Agent server is set up to +receive admission review requests from Kubernetes, policies will +receive a Kubernetes `AdmissionReview` object in JSON format with the +object to evaluate, and they have to produce a valid `AdmissionReview` +object in return with the evaluation results. + +## Compatibility with existing policies + +All policies can be compiled to the `wasm` target (WebAssembly) with +the official `opa` CLI tool. + +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). diff --git a/src/writing-policies/rego/open-policy-agent/02-create-policy.md b/src/writing-policies/rego/open-policy-agent/02-create-policy.md new file mode 100644 index 0000000000..bf50da4d78 --- /dev/null +++ b/src/writing-policies/rego/open-policy-agent/02-create-policy.md @@ -0,0 +1,162 @@ +# Create a new policy + +Let's create a sample policy that will help us go through some +important concepts. Let's start! + +## Requirements + +We will write, compile and execute the policy on this section. You +need some tools in order to complete this tutorial: + +- [`opa`](https://github.com/open-policy-agent/opa/releases): we +will use the `opa` CLI to build our policy to a `wasm` target. + +- [`kwctl`](https://github.com/kubewarden/kwctl/releases): we will use +`kwctl` to execute our built policy. + +## The policy + +We are going to create a policy that evaluates any kind of namespaced +resource. Its goal is to forbid the creation of any resource if the +target namespace is `default`. Otherwise, the request will be +accepted. Let's start by creating a folder called `opa-policy`. + +We are going to create a folder named `data` inside of the +`opa-policy` folder. This folder will contain the recorded +`AdmissionReview` objects from the Kubernetes API server. I reduced +them greatly for the sake of simplicity for the exercise, so we can +focus on the bits that matter. + +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" + } + } + } +} +``` + +This simulates a pod operation creation inside the `default` +namespace. Now, let's create another request example in +`other-ns.json` 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": "other", + "uid": "04dc7a5e-e1f1-4e34-8d65-2c9337a43e64" + } + } + } +} +``` + +As you can see, this simulates another pod creation request, this time +under a namespace called `other`. + +Let's go back to our `opa-policy` folder and start writing our Rego policy. + +Inside this folder, we create a file named `request.rego` inside the +`opa-policy` folder. The name can be anything, but we'll use that one +for this exercise. As the name suggests, this is a Rego file that has +some utility code regarding the request/response itself: in +particular, it allows us to simplify our policy code itself and reuse +this common bit across different policies if desired. The contents +are: + +```rego +package policy + +import data.kubernetes.admission + +main = { + "apiVersion": "admission.k8s.io/v1", + "kind": "AdmissionReview", + "response": response, +} + +response = { + "uid": input.request.uid, + "allowed": false, + "status": {"message": reason}, +} { + reason = concat(", ", admission.deny) + reason != "" +} else = { + "uid": input.request.uid, + "allowed": true, +} { + true +} +``` + +We will not go too deep into the Rego code itself. You can learn about +it in depth in [its +website](https://www.openpolicyagent.org/docs/latest/policy-language/). + +Suffice to say that in this case, it will return either `allowed: +true` or `allowed: false` depending on whether other package +(`data.kubernetes.admission`) has any `deny` statement that evaluates +to `true`. + +If any `data.kubernetes.admission.deny` evaluates to `true`, the +`response` here will evaluate to the first block. Otherwise, it will +evaluate to the second block -- leading to acceptance, because no +`deny` block evaluated to `true`, this means we are accepting the +request. + +Now, this is just the shell of the policy, the utility. Now, we create +another file, called, for example `policy.rego` inside our +`opa-policy` folder with the following contents: + +```rego +package kubernetes.admission + +deny[msg] { + input.request.object.metadata.namespace == "default" + msg := "it is forbidden to use the default namespace" +} +``` + +This is our policy. The important part. `deny` will evaluate to true +if all statements within it evaluate to true. In this case, is only +one statement: checking if the namespace is `default`. + +By Open Policy Agent design, `input` contains the queriable object +with the `AdmissionReview` object, so we can inspect it quite easily. + +If everything went well, our tree should look like the following: + +``` +. +├── data +│   ├── default-ns.json +│   └── other-ns.json +├── policy.rego +└── request.rego + +1 directory, 4 files +``` diff --git a/src/writing-policies/rego/open-policy-agent/03-build-and-run.md b/src/writing-policies/rego/open-policy-agent/03-build-and-run.md new file mode 100644 index 0000000000..9c40459512 --- /dev/null +++ b/src/writing-policies/rego/open-policy-agent/03-build-and-run.md @@ -0,0 +1,102 @@ +# Build and run + +In the previous section we have written our Rego policy. The structure +looks as the following: + +``` +. +├── data +│   ├── default-ns.json +│   └── other-ns.json +├── policy.rego +└── request.rego + +1 directory, 4 files +``` + +## Build + +We have our policy, now let's go ahead and build it. We do: + +```shell +~/opa-policy » opa build -t wasm -e policy/main policy.rego request.rego +``` + +What this does is build the rego policy, with: + +- `target`: `wasm`. We want to build the policy for the `wasm` target. +- `entrypoint`: `policy/main`. The entry point is the `main` rule +inside the `policy` package. +- `policy.rego`: build and include the `policy.rego` file. +- `request.rego`: build and include the `request.rego` file. + +After the build is complete, `opa build` will have generated a +`bundle.tar.gz` file. You can extract it: + +```shell +opa-policy » tar -xf bundle.tar.gz /policy.wasm +``` + +Now the tree looks like the following: + +```shell +. +├── bundle.tar.gz +├── data +│   ├── default-ns.json +│   └── other-ns.json +├── policy.rego +├── policy.wasm +└── request.rego + +1 directory, 6 file +``` + +We have our precious `policy.wasm` file: + +```shell +opa-policy » file policy.wasm +policy.wasm: WebAssembly (wasm) binary module version 0x1 (MVP) +``` + +Now it's time to execute it! Let's go on. + +## Run + +We are going to use `kwctl` in order to run the policy: + +``` +opa-policy » kwctl run -e opa --request-path data/other-ns.json policy.wasm | jq +{ + "uid": "1299d386-525b-4032-98ae-1949f69f9cfc", + "allowed": true +} +``` + +This request is accepted by the policy, since this is the request +pointing to the `other` namespace. + +- `execution-mode`: `opa`. Rego policies can be targeting Open Policy + Agent or Gatekeeper: we must tell `kwctl` what kind of policy we are + running. + + +- `request-path`: the location of the recorded request `kwctl` will + send to the policy to evaluate. + +Now let's try to evaluate the request that creates the pod inside the +`default` namespace: + +``` +opa-policy » kwctl run -e opa --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" + } +} +``` + +In this case, the policy is rejecting the request, and giving a reason +back to the API server that will be returned to the user or API consumer. diff --git a/src/writing-policies/rego/open-policy-agent/04-distribute.md b/src/writing-policies/rego/open-policy-agent/04-distribute.md new file mode 100644 index 0000000000..262303570a --- /dev/null +++ b/src/writing-policies/rego/open-policy-agent/04-distribute.md @@ -0,0 +1,128 @@ +# Distribute + +We have written, built and run our Rego policy. Now it's time to +distribute the policy. + +Policies have to be annotated in order for them to be executed in the +`policy-server`, the component that executes the policies when running +in a Kubernetes cluster. + +Also, the command we used to execute from the previous section also +signals a limitation: + +``` +opa-policy » kwctl run -e opa --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, we had to provide the execution mode. When executing +an unannotated policy written in Rego is not trivial to determine +whether the target framework is Open Policy Agent or Gatekeeper. This +is why when executing an unannotated policy built with `opa build` +`kwctl` cannot know what is the target. + +## Annotating the policy + +In order to get rid of this limitations, we will annotate the +policy. This is not only useful for what we have already described, +but also because the annotation of the policy contains valuable +information that always travels with the policy. + +Let's write a simple `metadata.yaml` file: + +```yaml +rules: +- apiGroups: [""] + apiVersions: ["*"] + resources: ["*"] + operations: ["CREATE"] +mutating: false +contextAware: false +executionMode: opa +annotations: + io.kubewarden.policy.title: no-default-namespace + io.kubewarden.policy.description: This policy will reject any resource created inside the default namespace + io.kubewarden.policy.author: The Kubewarden Authors + io.kubewarden.policy.url: https://github.com/kubewarden/some-policy + io.kubewarden.policy.source: https://github.com/kubewarden/some-policy + io.kubewarden.policy.license: Apache-2.0 + io.kubewarden.policy.usage: | + This policy is just an example. + + You can write interesting descriptions about the policy here. +``` + +In this case, you can see several details: + +- Rules: what resources this policy is targeting +- Mutating: whether this policy is mutating. In this case, is just +validating. +- Context aware: whether this policy requires context from the +cluster in order to evaluate the request. +- Execution mode: since this is a Rego policy it is mandatory to +specify what execution mode it expects: `opa` or `gatekeeper`. This +policy is written in the `opa` style: returning a whole +`AdmissionReview` object. +- Annotations: metadata stored into the policy itself. + +Let's go ahead and annotate our policy: + +```console +opa-policy » kwctl annotate policy.wasm --metadata-path metadata.yaml --output-path annotated-policy.wasm +``` + +Now you can `inspect` the policy if you will by running `kwctl inspect annotated-policy.wasm`. + +## Pushing the policy + +Now that the policy is annotated we can push it to an OCI +registry. Let's do that: + +```console +opa-policy » kwctl push annotated-policy.wasm registry.my-company.com/kubewarden/no-default-namespace:v0.0.1 +Policy successfully pushed +``` + +Now our Rego policy targeting the OPA framework has everything it +needs to be deployed in production by creating a +`ClusterAdmissionPolicy`. Let's prepare that too. First, we have to +pull the policy into the `kwctl` local store: + +```console +» kwctl pull registry://registry.my-company.com/kubewarden/no-default-namespace:v0.0.1 +pulling policy... +``` + +Let's create a `ClusterAdmissionPolicy` out of it. This operation will +take into account the metadata it has about the policy: + +```console +» kwctl manifest registry://registry.my-company.com/kubewarden/no-default-namespace:v0.0.1 --type ClusterAdmissionPolicy +--- +apiVersion: policies.kubewarden.io/v1alpha2 +kind: ClusterAdmissionPolicy +metadata: + name: generated-policy +spec: + module: "registry://registry.my-company.com/kubewarden/no-default-namespace:v0.0.1" + settings: {} + rules: + - apiGroups: + - "" + apiVersions: + - "*" + resources: + - "*" + operations: + - CREATE + mutating: false +``` + +You can now use this `ClusterAdmissionPolicy` as a base to target the +resources that you want, or deploy to Kubernetes as is.