From 670f1b7423b348a063573f4472a3523baa264eb7 Mon Sep 17 00:00:00 2001 From: Fabrizio Sestito Date: Wed, 25 Oct 2023 12:03:06 +0200 Subject: [PATCH 1/2] feat: policy Signed-off-by: Fabrizio Sestito --- .github/workflows/release.yml | 28 ++++++++++++++++++++++ .github/workflows/test.yml | 8 +++++++ .gitignore | 1 + Makefile | 25 +++++++++++++++++++ e2e.bats | 36 ++++++++++++++++++++++++++++ metadata.yml | 23 ++++++++++++++++++ policy.rego | 6 +++++ policy_test.rego | 15 ++++++++++++ renovate.json | 8 +++++++ test_data/invalid.json | 5 ++++ test_data/invalid_no_user_field.json | 4 ++++ test_data/valid.json | 5 ++++ utility/README.md | 12 ++++++++++ utility/policy.rego | 25 +++++++++++++++++++ 14 files changed, 201 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/test.yml create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 e2e.bats create mode 100644 metadata.yml create mode 100644 policy.rego create mode 100644 policy_test.rego create mode 100644 renovate.json create mode 100644 test_data/invalid.json create mode 100644 test_data/invalid_no_user_field.json create mode 100644 test_data/valid.json create mode 100644 utility/README.md create mode 100644 utility/policy.rego diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..fd4f2e5 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,28 @@ +on: + push: + branches: + - main + tags: + - "v*" + +name: Release policy + +jobs: + test: + name: run tests and linters + uses: kubewarden/github-actions/.github/workflows/reusable-test-policy-rego.yml@v3.1.10 + + release: + needs: test + permissions: + # Required to create GH releases + contents: write + # Required to push to GHCR + packages: write + # Required by cosign keyless signing + id-token: write + + uses: kubewarden/github-actions/.github/workflows/reusable-release-policy-rego.yml@v3.1.10 + with: + oci-target: ghcr.io/${{ github.repository_owner }}/tests/raw-validation-opa-policy + artifacthub: false diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..aef8e78 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,8 @@ +on: [push, pull_request] +name: Continuous integration +jobs: + test: + name: run tests and linters + uses: kubewarden/github-actions/.github/workflows/reusable-test-policy-rego.yml@v3.1.10 + with: + artifacthub: false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..19e1bce --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.wasm diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f04a82f --- /dev/null +++ b/Makefile @@ -0,0 +1,25 @@ +SOURCE_FILES := $(shell find . -type f -name '*.rego') +# It's necessary to call cut because kwctl command does not handle version +# starting with v. +VERSION ?= $(shell git describe | cut -c2-) + +policy.wasm: $(SOURCE_FILES) + opa build -t wasm -e policy/main utility/policy.rego -o bundle.tar.gz policy.rego + tar xvf bundle.tar.gz /policy.wasm + rm bundle.tar.gz + touch policy.wasm # opa creates the bundle with unix epoch timestamp, fix it + +.PHONY: test +test: + opa test *.rego + +annotated-policy.wasm: policy.wasm metadata.yml + kwctl annotate -m metadata.yml -u README.md -o annotated-policy.wasm policy.wasm + +.PHONY: e2e-tests +e2e-tests: annotated-policy.wasm + bats e2e.bats + +.PHONY: clean +clean: + rm -f *.wasm *.tar.gz diff --git a/e2e.bats b/e2e.bats new file mode 100644 index 0000000..fe98f1d --- /dev/null +++ b/e2e.bats @@ -0,0 +1,36 @@ +#!/usr/bin/env bats + +@test "accept if the request is valid" { + run kwctl run --raw -e opa annotated-policy.wasm -r test_data/valid.json + + # this prints the output when one the checks below fails + echo "output = ${output}" + + # request accepted + [ "$status" -eq 0 ] + [ $(expr "$output" : '.*allowed.*true') -ne 0 ] +} + +@test "reject if the request is invalid" { + run kwctl run --raw -e opa annotated-policy.wasm -r test_data/invalid.json + + # this prints the output when one the checks below fails + echo "output = ${output}" + + # request rejected + [ "$status" -eq 0 ] + [ $(expr "$output" : '.*allowed.*false') -ne 0 ] + [ $(expr "$output" : '.*User not allowed.*') -ne 0 ] +} + +@test "reject if the request has no user field" { + run kwctl run --raw -e opa annotated-policy.wasm -r test_data/invalid_no_user_field.json + + # this prints the output when one the checks below fails + echo "output = ${output}" + + # request rejected + [ "$status" -eq 0 ] + [ $(expr "$output" : '.*allowed.*false') -ne 0 ] + [ $(expr "$output" : '.*User not allowed.*') -ne 0 ] +} diff --git a/metadata.yml b/metadata.yml new file mode 100644 index 0000000..c353f71 --- /dev/null +++ b/metadata.yml @@ -0,0 +1,23 @@ +rules: +mutating: false +contextAware: false +executionMode: opa +# Consider the policy for the background audit scans. Default is true. Note the +# intrinsic limitations of the background audit feature on docs.kubewarden.io; +# If your policy hits any limitations, set to false for the audit feature to +# skip this policy and not generate false positives. +backgroundAudit: true +annotations: + # kubewarden specific: + io.kubewarden.policy.ociUrl: ghcr.io/kubewarden/tests/raw-validation-opa-policy # must match release workflow oci-target + io.kubewarden.policy.title: raw-validation-opa-policy + io.kubewarden.policy.description: An OPA policy that validates a raw request + io.kubewarden.policy.author: "Kubewarden developers " + io.kubewarden.policy.url: https://github.com/kubewarden/raw-validation-opa-policy + io.kubewarden.policy.source: https://github.com/kubewarden/raw-validation-opa-policy + io.kubewarden.policy.license: Apache-2.0 + # The next two annotations are used in the policy report generated by the + # Audit scanner. Severity indicates policy check result criticality and + # Category indicates policy category. See more here at docs.kubewarden.io + io.kubewarden.policy.severity: medium # one of info, low, medium, high, critical. See docs. + io.kubewarden.policy.category: Resource validation diff --git a/policy.rego b/policy.rego new file mode 100644 index 0000000..b24f50d --- /dev/null +++ b/policy.rego @@ -0,0 +1,6 @@ +package validation + +deny[msg] { + not input.request.user == "tonio" + msg := "User not allowed" +} diff --git a/policy_test.rego b/policy_test.rego new file mode 100644 index 0000000..5ddd081 --- /dev/null +++ b/policy_test.rego @@ -0,0 +1,15 @@ +package validation + +valid_request = {"request": {"user": "tonio", "action": "eats", "resource": "hay"}} + +invalid_request = {"request": {"user": "wanda", "action": "eats", "resource": "banana"}} + +test_accept { + res = deny with input as valid_request + count(res) = 0 +} + +test_reject { + res = deny with input as invalid_request + count(res) = 1 +} diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..16613b2 --- /dev/null +++ b/renovate.json @@ -0,0 +1,8 @@ +{ + "extends": [ + "config:base", + "group:allNonMajor", + "schedule:earlyMondays" + ], + "labels": ["dependencies"] +} diff --git a/test_data/invalid.json b/test_data/invalid.json new file mode 100644 index 0000000..b91b853 --- /dev/null +++ b/test_data/invalid.json @@ -0,0 +1,5 @@ +{ + "user": "wanda", + "action": "eats", + "eats": "banana" +} diff --git a/test_data/invalid_no_user_field.json b/test_data/invalid_no_user_field.json new file mode 100644 index 0000000..63595f4 --- /dev/null +++ b/test_data/invalid_no_user_field.json @@ -0,0 +1,4 @@ +{ + "action": "eats", + "eats": "banana" +} diff --git a/test_data/valid.json b/test_data/valid.json new file mode 100644 index 0000000..98cd3c0 --- /dev/null +++ b/test_data/valid.json @@ -0,0 +1,5 @@ +{ + "user": "tonio", + "action": "eats", + "eats": "hay" +} diff --git a/utility/README.md b/utility/README.md new file mode 100644 index 0000000..5445ccc --- /dev/null +++ b/utility/README.md @@ -0,0 +1,12 @@ +# Open Policy Agent utility + +This folder contains the entry point for Open Policy Agent policies. + +Since Open Policy Agent policies have to produce a `RawReviewResponse` +object, this utility library contains the Rego entry point that +generates such `RawReviewResponse`, based on whether the `deny` query +inside the package `validation` (defined by the policy +itself) is evaluated to `true`. + +If `deny` evaluates to true, the produced `RawReviewResponse` will +reject the request. Otherwise, it will be accepted. diff --git a/utility/policy.rego b/utility/policy.rego new file mode 100644 index 0000000..70834fa --- /dev/null +++ b/utility/policy.rego @@ -0,0 +1,25 @@ +package policy + +import data.validation + +main = { + "response": response, +} + +default uid = "" + +uid = input.request.uid + +response = { + "uid": uid, + "allowed": false, + "status": {"message": reason}, +} { + reason = concat(", ", validation.deny) + reason != "" +} else = { + "uid": uid, + "allowed": true, +} { + true +} From ed7874fabe61aeccdaac6249d734986ede68a489 Mon Sep 17 00:00:00 2001 From: Fabrizio Sestito Date: Wed, 25 Oct 2023 12:52:01 +0200 Subject: [PATCH 2/2] fix: add policyType to metadata Signed-off-by: Fabrizio Sestito --- metadata.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/metadata.yml b/metadata.yml index c353f71..94dd7af 100644 --- a/metadata.yml +++ b/metadata.yml @@ -1,6 +1,7 @@ rules: mutating: false contextAware: false +policyType: raw executionMode: opa # Consider the policy for the background audit scans. Default is true. Note the # intrinsic limitations of the background audit feature on docs.kubewarden.io;