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..4a51e7b09c --- /dev/null +++ b/src/writing-policies/rego/01-intro.md @@ -0,0 +1,47 @@ +# Rego + +The Rego language is a tailor made language designed to embrace +policies as code. Rego 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 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 aalso +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..4c91393123 --- /dev/null +++ b/src/writing-policies/rego/02-builtin-support.md @@ -0,0 +1,102 @@ +# 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 built on top of other +functions and 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 may use +them in order to build the rest of the built-ins. In any case, this +built-ins are exposed to the policy and any new or existing policy +could depend on them. + +This is the built-ins implemented up until now in Kubewarden: + + +| Category | Built-in | Status | +|--------------------|---------------------------------------------|-------------| +| Numbers | `rand.intn` | - | +|
| | | +| Objects | `json.patch` | - | +|
| | | +| Strings | `sprintf` | Implemented | +|
| | | +| Regex | `regex.split` | - | +| | `regex.globs_match` | - | +| | `regex.template_match` | - | +| | `regex.find_n` | - | +|
| | | +| Glob | `glob.quote_meta` | - | +|
| | | +| Units | `units.parse_bytes` | - | +|
| | | +| Encoding | `base64url.encode_no_pad` | - | +| | `urlquery.encode` | - | +| | `urlquery.encode_object` | - | +| | `urlquery.decode` | - | +| | `urlquery.decode_object` | - | +| | `json.is_valid` | - | +| | `yaml.marshal` | - | +| | `yaml.unmarshal` | - | +| | `yaml.is_valid` | - | +| | `hex.encode` | - | +| | `hex.decode` | - | +|
| | | +| Token Signing | `io.jwt.encode_sign_raw` | - | +| | `io.jwt.encode_sign` | - | +|
| | | +| Token Verification | `io.jwt.verify_rs256` | - | +| | `io.jwt.verify_rs384` | - | +| | `io.jwt.verify_rs512` | - | +| | `io.jwt.verify_ps256` | - | +| | `io.jwt.verify_ps384` | - | +| | `io.jwt.verify_ps512` | - | +| | `io.jwt.verify_es256` | - | +| | `io.jwt.verify_es384` | - | +| | `io.jwt.verify_es512` | - | +| | `io.jwt.verify_hs256` | - | +| | `io.jwt.verify_hs384` | - | +| | `io.jwt.verify_hs512` | - | +| | `io.jwt.decode` | - | +| | `io.jwt.decode_verify` | - | +|
| | | +| Time | `time.now_ns` | - | +| | `time.parse_ns` | - | +| | `time.parse_rfc3339_ns` | - | +| | `time.parse_duration_ns` | - | +| | `time.date` | - | +| | `time.clock` | - | +| | `time.weekday` | - | +| | `time.add_date` | - | +| | `time.diff` | - | +|
| | | +| Cryptography | `crypto.x509.parse_certificates` | - | +| | `crypto.x509.parse_and_verify_certificates` | - | +| | `crypto.x509.parse_certificate_request` | - | +| | `crypto.md5` | - | +| | `crypto.sha1` | - | +| | `crypto.sha256` | - | +|
| | | +| HTTP | `http.send` | - | +|
| | | +| Net | `net.cidr_contains_matches` | - | +| | `net.cidr_expand` | - | +| | `net.cidr_merge` | - | +|
| | | +| UUID | `uuid.rfc4122` | - | +|
| | | +| Semantic Versions | `semver.is_valid` | - | +| | `semver.compare` | - | +|
| | | +| Rego | `rego.parse_module` | - | +|
| | | +| OPA | `opa.runtime` | - | +|
| | | +| Debugging | `trace` | - | 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..3db2b4dd0f --- /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 in +> Kubewarden. + +They have to be recompiled 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..d53ee92379 --- /dev/null +++ b/src/writing-policies/rego/gatekeeper/02-create-policy.md @@ -0,0 +1,48 @@ +# 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 none 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`. 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..4c9a4ab879 --- /dev/null +++ b/src/writing-policies/rego/gatekeeper/03-build-and-run.md @@ -0,0 +1,82 @@ +# Build and run + +Building and running the policy is done exactly the same 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/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 +gatekeeper-policy » tar -xf bundle.tar.gz /policy.wasm +``` + +The tree looks like: + +``` +. +├── 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..36b909fbcd --- /dev/null +++ b/src/writing-policies/rego/gatekeeper/04-distribute.md @@ -0,0 +1,99 @@ +# Distribute + +Policies have to be annotated for them to be pushed, and eventually +executed in the `policy-server` in a Kubernetes cluster. + +Annotating and distributing our gatekeeper policy is very similar to +distribute 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 +``` + +## Creating a scaffold manifest + +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... +``` + +Now, we can check that the policy is listed as expected: + +```console +» kwctl policies ++--------------------------------------------------------------------------------------+----------+---------------+--------------+-----------+ +| Policy | Mutating | Context aware | SHA-256 | Size | ++--------------------------------------------------------------------------------------+----------+---------------+--------------+-----------+ +| registry://registry.my-company.com/kubewarden/no-default-namespace-gatekeeper:v0.0.1 | no | no | 0e34c374db2e | 112.99 kB | ++--------------------------------------------------------------------------------------+----------+---------------+--------------+-----------+ +``` + +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..9bc3995f84 --- /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 it has 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..b82d08b0d9 --- /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 is going to evaluate 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 in purpose for the sake of simplicity for the exercise, +so we can focus on the bits that matter. + +I first create a `default-ns.json` file with 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 and y reading its documentation. + +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..339f4f0ffe --- /dev/null +++ b/src/writing-policies/rego/open-policy-agent/04-distribute.md @@ -0,0 +1,139 @@ +# 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 Kubernets 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... +``` + +Now, we can check that the policy is listed as expected: + +```console +» kwctl policies ++---------------------------------------------------------------------------+----------+---------------+--------------+-----------+ +| Policy | Mutating | Context aware | SHA-256 | Size | ++---------------------------------------------------------------------------+----------+---------------+--------------+-----------+ +| registry://registry.my-company.com/kubewarden/no-default-namespace:v0.0.1 | no | no | 55290dc1ba87 | 114.66 kB | ++---------------------------------------------------------------------------+----------+---------------+--------------+-----------+ +``` + +Now, 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.