|
1 |
| -# opa-k8s-development |
2 |
| -Contains a valid OPA unit testing environment |
| 1 | +# OPA Kubernetes validation and mutation testing environment |
| 2 | + |
| 3 | + |
| 4 | + |
| 5 | +This project makes easy to develop `deny` rules and `patches` mutations. In this repository you will find a `main.rego` file wich is in charge of generate the expected Kubernetes API `json` response in the Validation and mutation stage a long with a set of basic `deny` rules and it's tests + mocks. |
3 | 6 |
|
4 | 7 | ## TL;DR
|
5 | 8 |
|
6 | 9 | ```bash
|
7 | 10 | $ opa test -v .
|
| 11 | +[angel@elitebook opa-k8s-development]$ opa test -v . |
| 12 | +data.kubernetes.admission.test_create_ingress_existing_in_other_namespace: PASS (719.34µs) |
| 13 | +data.kubernetes.admission.test_create_client_valid_ingress: PASS (503.277µs) |
| 14 | +data.system.test_default_response: PASS (495.764µs) |
| 15 | +data.kubernetes.admission.test_invalid_client_pod_priorityclass: PASS (527.651µs) |
| 16 | +data.kubernetes.admission.test_valid_client_pod_priorityclass: PASS (472.089µs) |
| 17 | +-------------------------------------------------------------------------------- |
| 18 | +PASS: 5/5 |
| 19 | + |
| 20 | +``` |
| 21 | + |
| 22 | +## OPA Webhooks |
| 23 | + |
| 24 | +OPA and Kubernetes has to be configured registering a validation and mutation webhook into Kubernetes API. This topic is out of the scope of this project but you can read more about it in the [official OPA documentation](https://www.openpolicyagent.org/docs/latest/kubernetes-admission-control). |
| 25 | + |
| 26 | +But, you have to keep in your mind the following diagram: |
| 27 | + |
| 28 | + |
| 29 | + |
| 30 | +The important thing here is to know that the mutation phase is triggered before the validation one. |
| 31 | + |
| 32 | +## Project structure |
| 33 | + |
| 34 | +This repository contains three different tpyes of rego files to make it easy to extend this environment: |
| 35 | + |
| 36 | +- `[KIND].rego`: This files contains the `deny` or `pathes` rules to validate or mutate a request. |
| 37 | +- `[KIND]-test.rego`: This ones contains a set of test related to the testing kind specified in it's name. |
| 38 | +- `[KIND]-mocks.rego`: This files contains a series of Kubernetes requests to use as mocked requests in tests. |
| 39 | + |
| 40 | +There are also a couple of special rego files: |
| 41 | + |
| 42 | +- `main.rego` and `main-test.rego`: Contains the structures to build the kubernetes api responses used to validate and mutate the incoming requests. |
| 43 | + |
| 44 | +### main.rego |
| 45 | + |
| 46 | +```rego |
| 47 | +package system |
| 48 | +
|
| 49 | +import data.kubernetes.admission |
| 50 | +
|
| 51 | +main = { |
| 52 | + "apiVersion": "admission.k8s.io/v1beta1", |
| 53 | + "kind": "AdmissionReview", |
| 54 | + "response": response, |
| 55 | +} |
| 56 | +
|
| 57 | +default response = {"allowed": true} |
| 58 | +
|
| 59 | +response = { |
| 60 | + "allowed": false, |
| 61 | + "status": {"reason": reason}, |
| 62 | +} { |
| 63 | + reason := concat(", ", admission.deny) |
| 64 | + reason != "" |
| 65 | +} |
| 66 | +
|
| 67 | +response = { |
| 68 | + "allowed": true, |
| 69 | + "patchType": "JSONPatch", |
| 70 | + "patch": patch_bytes, |
| 71 | +} { |
| 72 | + reason := concat(", ", admission.deny) |
| 73 | + reason == "" |
| 74 | + patch := {xw | xw := admission.patches[_][_]} |
| 75 | + patch_json := json.marshal(patch) |
| 76 | + patch_bytes := base64.encode(patch_json) |
| 77 | +} |
| 78 | +``` |
| 79 | + |
| 80 | +Let's review this **important** file. The `main` structure contains the common part of a validate and mutate response. In the `main` structure it's present a `response` attribute. This attribute will get three different values depending of the rules (deny and patches) applied to the incoming requests: |
| 81 | + |
| 82 | +- If no deny and no patches applied to the incoming request, it will use the `default response`. |
| 83 | +- If not deny rules applied, `reason == ""`, the response will be `"patchType": "JSONPatch",` with the patches applyed to the request *(Can be empty)*. |
| 84 | +- If some deny rules returned a reason, `reason != ""`, the response will be `"allowed": false, "status": {"reason": reason},`. The client will get the reason message in the response. |
| 85 | + |
| 86 | +It's really important to know that the same input has to generate only one output. OPA Allows a function to return two different outputs, but kubernetes only accepts one. This is the reason why both (non default) response structure are exclusive using the `reason empty comparation`. |
| 87 | + |
| 88 | + |
| 89 | +## Using it |
| 90 | + |
| 91 | +To use this testing environment you will need to clone this repository and download the latest [OPA](https://www.openpolicyagent.org/) release *(actual version: [v0.13.5](https://github.com/open-policy-agent/opa/releases/tag/v0.13.5))*. It's not needed to have a kubernetes cluster. Remember, this project is useful in the [OPA](https://www.openpolicyagent.org/) rules/mutations development stage. |
| 92 | + |
| 93 | + |
| 94 | +The following commands has been executed in a [`centos:7` container image](https://hub.docker.com/_/centos?tab=tags) to make it repeatable and cleaner: |
| 95 | +```bash |
| 96 | +$ docker run -it --rm centos:7 /bin/bash |
| 97 | +[root@d8997b63a370 /]# yum install -y wget git # Output hidden |
| 98 | +[root@d8997b63a370 /]# git clone https://github.com/k8spin/opa-k8s-development.git |
| 99 | +Cloning into 'opa-k8s-development'... |
| 100 | +remote: Enumerating objects: 26, done. |
| 101 | +remote: Counting objects: 100% (26/26), done. |
| 102 | +remote: Compressing objects: 100% (20/20), done. |
| 103 | +remote: Total 26 (delta 10), reused 19 (delta 5), pack-reused 0 |
| 104 | +Unpacking objects: 100% (26/26), done. |
| 105 | +[root@d8997b63a370 /]# wget -q https://github.com/open-policy-agent/opa/releases/download/v0.13.5/opa_linux_amd64 -O opa |
| 106 | +[root@d8997b63a370 /]# chmod +x opa |
| 107 | +[root@d8997b63a370 /]# ./opa test -v opa-k8s-development/ |
| 108 | +data.kubernetes.admission.test_create_ingress_existing_in_other_namespace: PASS (748.309µs) |
| 109 | +data.kubernetes.admission.test_create_client_valid_ingress: PASS (522.186µs) |
| 110 | +data.system.test_default_response: PASS (550.857µs) |
| 111 | +data.kubernetes.admission.test_invalid_client_pod_priorityclass: PASS (520.333µs) |
| 112 | +data.kubernetes.admission.test_valid_client_pod_priorityclass: PASS (609.106µs) |
| 113 | +-------------------------------------------------------------------------------- |
| 114 | +PASS: 5/5 |
8 | 115 | ```
|
0 commit comments