From 81894846eba03f58387a70eb387a3e400a63d872 Mon Sep 17 00:00:00 2001 From: Christoph Hamsen Date: Sun, 4 Feb 2024 19:12:36 +0100 Subject: [PATCH] ci: add initial integration tests --- .../workflows/.reusable-integration-test.yml | 203 +++++++++++++++++- .github/workflows/pr.yml | 2 +- .github/workflows/push.yml | 2 +- Makefile | 6 +- README.md | 22 +- charts/semgr8s/templates/webhook.yaml | 2 +- tests/{ => demo}/failing_deployment.yaml | 4 +- tests/{ => demo}/passing_deployment.yaml | 0 tests/integration/README.md | 14 ++ tests/integration/data/00_namespaces.yaml | 15 ++ tests/integration/data/20_compliant_pod.yaml | 23 ++ tests/integration/data/40_nosc_pod.yaml | 13 ++ tests/integration/data/41_privileged_pod.yaml | 21 ++ .../integration/data/42_hostnetwork_pod.yaml | 21 ++ tests/integration/main.sh | 55 +++++ tests/integration/scripts/basic.sh | 10 + tests/integration/scripts/common.sh | 168 +++++++++++++++ tests/integration/scripts/rules.sh | 10 + tests/integration/test_cases/basic.yaml | 19 ++ tests/integration/test_cases/rules.yaml | 31 +++ 20 files changed, 619 insertions(+), 22 deletions(-) rename tests/{ => demo}/failing_deployment.yaml (93%) rename tests/{ => demo}/passing_deployment.yaml (100%) create mode 100644 tests/integration/README.md create mode 100644 tests/integration/data/00_namespaces.yaml create mode 100644 tests/integration/data/20_compliant_pod.yaml create mode 100644 tests/integration/data/40_nosc_pod.yaml create mode 100644 tests/integration/data/41_privileged_pod.yaml create mode 100644 tests/integration/data/42_hostnetwork_pod.yaml create mode 100755 tests/integration/main.sh create mode 100644 tests/integration/scripts/basic.sh create mode 100644 tests/integration/scripts/common.sh create mode 100644 tests/integration/scripts/rules.sh create mode 100644 tests/integration/test_cases/basic.yaml create mode 100644 tests/integration/test_cases/rules.yaml diff --git a/.github/workflows/.reusable-integration-test.yml b/.github/workflows/.reusable-integration-test.yml index 43a8708..649fa82 100644 --- a/.github/workflows/.reusable-integration-test.yml +++ b/.github/workflows/.reusable-integration-test.yml @@ -29,13 +29,210 @@ env: IMAGEPULLSECRET: dockerconfigjson-ghcr jobs: - do-nothing: + integration-test: name: functional runs-on: ubuntu-latest if: inputs.skip != 'all' # permissions: #TODO: reactivate for non-private # packages: read + env: + IMAGE: ${{ inputs.build_image_repository }} + TAG: ${{ inputs.build_tag }} + strategy: + fail-fast: false + matrix: + integration-test-arg: + [ + "basic", + ] steps: - - name: Do nothing + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Login with registry + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + with: + registry: ${{ inputs.build_registry }} + username: ${{ inputs.repo_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Install yq run: | - sleep 1 + sudo snap install yq + - uses: ./.github/actions/k8s-version-config + name: Setup k8s cluster + with: + k8s-version: v1.25 + - name: Run test + run: | + bash tests/integration/main.sh "${{ matrix.integration-test-arg }}" + - name: Display semgr8s configuration + if: always() + run: | + echo "::group::values.yaml" + yq e '... comments=""' charts/semgr8s/values.yaml + echo "::endgroup::" + - name: Display k8s state if integration test failed + if: failure() + run: | + kubectl describe deployments.apps -n semgr8ns -lapp.kubernetes.io/name=semgr8s + kubectl describe pods -n semgr8ns -lapp.kubernetes.io/name=semgr8s + - name: Display logs if integration test failed + if: failure() + run: | + kubectl logs -n semgr8ns -lapp.kubernetes.io/name=semgr8s --prefix=true --tail=-1 + + optional-integration-test: + name: optional + runs-on: ubuntu-latest + if: | + inputs.skip != 'non-required' && + inputs.skip != 'all' + # permissions: #TODO: reactivate for non-private + # packages: read + env: + IMAGE: ${{ inputs.build_image_repository }} + TAG: ${{ inputs.build_tag }} + strategy: + fail-fast: false + matrix: + integration-test-arg: + [ + "rules", + ] + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Login with registry + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + with: + registry: ${{ inputs.build_registry }} + username: ${{ inputs.repo_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Install yq + run: | + sudo snap install yq + - uses: ./.github/actions/k8s-version-config + name: Setup k8s cluster + with: + k8s-version: v1.25 + - name: Run test + run: | + bash tests/integration/main.sh "${{ matrix.integration-test-arg }}" + - name: Display semgr8s configuration + if: always() + run: | + echo "::group::values.yaml" + yq e '... comments=""' charts/semgr8s/values.yaml + echo "::endgroup::" + - name: Display k8s state if integration test failed + if: failure() + run: | + kubectl describe deployments.apps -n semgr8ns -lapp.kubernetes.io/name=semgr8s + kubectl describe pods -n semgr8ns -lapp.kubernetes.io/name=semgr8s + - name: Display logs if integration test failed + if: failure() + run: | + kubectl logs -n semgr8ns -lapp.kubernetes.io/name=semgr8s --prefix=true --tail=-1 + + k8s-versions: + name: k8s versions + runs-on: ubuntu-latest + if: inputs.skip != 'all' + # permissions: #TODO: reactivate for non-private + # packages: read + env: + IMAGE: ${{ inputs.build_image_repository }} + TAG: ${{ inputs.build_tag }} + strategy: + fail-fast: false + matrix: + k8s-version: [ + "v1.25", + "v1.26", + "v1.27", + "v1.28", + ] + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Login with registry + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + with: + registry: ${{ inputs.build_registry }} + username: ${{ inputs.repo_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Install yq + run: | + sudo snap install yq + - uses: ./.github/actions/k8s-version-config + name: Setup k8s cluster + with: + k8s-version: ${{ matrix.k8s-version }} + - name: Run pre-config and workload integration tests + run: | + bash tests/integration/main.sh "basic" + - name: Display k8s state and logs if integration test failed + if: failure() + run: | + kubectl describe deployments.apps -n semgr8ns -lapp.kubernetes.io/name=semgr8s + kubectl describe pods -n semgr8ns -lapp.kubernetes.io/name=semgr8s + kubectl logs -n semgr8ns -lapp.kubernetes.io/name=semgr8s --prefix=true --tail=-1 + - name: Display semgr8s configuration + if: always() + run: | + echo "::group::values.yaml" + yq e '... comments=""' charts/semgr8s/values.yaml + echo "::endgroup::" + + optional-k8s-versions: + name: optional k8s versions + runs-on: ubuntu-latest + if: | + inputs.skip != 'non-required' && + inputs.skip != 'all' + # permissions: #TODO: reactivate for non-private + # packages: read + env: + IMAGE: ${{ inputs.build_image_repository }} + TAG: ${{ inputs.build_tag }} + strategy: + fail-fast: false + matrix: + k8s-version: [ + "v1.20", + "v1.21", + "v1.22", + "v1.23", + "v1.24", + ] + steps: + - name: Checkout code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Login with registry + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + with: + registry: ${{ inputs.build_registry }} + username: ${{ inputs.repo_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Install yq + run: | + sudo snap install yq + - uses: ./.github/actions/k8s-version-config + name: Setup k8s cluster + with: + k8s-version: ${{ matrix.k8s-version }} + - name: Run pre-config and workload integration tests + run: | + bash tests/integration/main.sh "basic" + - name: Display k8s state and logs if integration test failed + if: failure() + run: | + kubectl describe deployments.apps -n semgr8ns -lapp.kubernetes.io/name=semgr8s + kubectl describe pods -n semgr8ns -lapp.kubernetes.io/name=semgr8s + kubectl logs -n semgr8ns -lapp.kubernetes.io/name=semgr8s --prefix=true --tail=-1 + - name: Display semgr8s configuration + if: always() + run: | + echo "::group::values.yaml" + yq e '... comments=""' charts/semgr8s/values.yaml + echo "::endgroup::" + diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 4c39e0c..b5d5611 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -25,5 +25,5 @@ jobs: skip_sast: 'none' skip_sca: 'none' skip_docs: 'non-required' - skip_integration_tests: 'none' + skip_integration_tests: 'non-required' output_type: 'sarif' diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 1c5516b..ccde0a8 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -25,5 +25,5 @@ jobs: skip_sast: 'non-required' skip_sca: 'non-required' skip_docs: 'none' - skip_integration_tests: 'non-required' + skip_integration_tests: 'none' output_type: 'sarif' diff --git a/Makefile b/Makefile index d06e2cb..a281738 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,7 @@ install: @echo "####################" @echo "## $(@)" @echo "####################" - helm install semgr8s charts/semgr8s --create-namespace --namespace $(ns) + helm install semgr8s charts/semgr8s --atomic --create-namespace --namespace $(ns) .PHONY:uninstall uninstall: @@ -48,10 +48,10 @@ test: @echo "####################" @echo "## $(@)" @echo "####################" - -kubectl create -f tests/ + -kubectl create -f tests/demo @echo -kubectl get pods -n test-semgr8s-passing @echo -kubectl get pods -n test-semgr8s-failing @echo - -kubectl delete -f tests/ + -kubectl delete -f tests/demo diff --git a/README.md b/README.md index 8dff3f0..422cd5b 100644 --- a/README.md +++ b/README.md @@ -99,22 +99,22 @@ Once all resources are in `READY` state, you have successfully installed semgr8s ### Testing -Several test resources are provided under `tests/`. -Semgr8s denies creating pods with insecure configuration according to the rules in `charts/semgr8s/rules`: +Several test resources are provided under `tests/demo/`. +For namespaces with label `semgr8s/validation=enabled`, Semgr8s denies creating pods with insecure configuration according to the rules in `charts/semgr8s/rules`: ```bash -kubectl create -f tests/failing_deployment.yaml +kubectl create -f tests/demo/failing_deployment.yaml ```
output ```bash namespace/test-semgr8s-failing created - Error from server: error when creating "tests/failing_deployment.yaml": admission webhook "semgr8s-svc.semgr8ns.svc" denied the request: Found 1 violation(s) of the following policies: + Error from server: error when creating "tests/demo/failing_deployment.yaml": admission webhook "semgr8s-svc.semgr8ns.svc" denied the request: Found 1 violation(s) of the following policies: * rules.allow-privilege-escalation-no-securitycontext - Error from server: error when creating "tests/failing_deployment.yaml": admission webhook "semgr8s-svc.semgr8ns.svc" denied the request: Found 1 violation(s) of the following policies: + Error from server: error when creating "tests/demo/failing_deployment.yaml": admission webhook "semgr8s-svc.semgr8ns.svc" denied the request: Found 1 violation(s) of the following policies: * rules.privileged-container - Error from server: error when creating "tests/failing_deployment.yaml": admission webhook "semgr8s-svc.semgr8ns.svc" denied the request: Found 1 violation(s) of the following policies: + Error from server: error when creating "tests/demo/failing_deployment.yaml": admission webhook "semgr8s-svc.semgr8ns.svc" denied the request: Found 1 violation(s) of the following policies: * rules.hostnetwork-pod ```
@@ -122,7 +122,7 @@ kubectl create -f tests/failing_deployment.yaml Securely configured resources on the other hand are permitted to the cluster: ```bash -kubectl create -f tests/passing_deployment.yaml +kubectl create -f tests/demo/passing_deployment.yaml ```
output @@ -153,7 +153,7 @@ kubectl delete ns semgr8ns Test resources are deleted via: ```bash -kubectl delete -f tests/ +kubectl delete -f tests/demo/ ```
output @@ -162,9 +162,9 @@ kubectl delete -f tests/ namespace "test-semgr8s-failing" deleted namespace "test-semgr8s-passing" deleted pod "passing-testpod-1" deleted - Error from server (NotFound): error when deleting "tests/failing_deployment.yaml": pods "failing-testpod-1" not found - Error from server (NotFound): error when deleting "tests/failing_deployment.yaml": pods "failing-testpod-2" not found - Error from server (NotFound): error when deleting "tests/failing_deployment.yaml": pods "failing-testpod-3" not found + Error from server (NotFound): error when deleting "tests/demo/failing_deployment.yaml": pods "failing-testpod-1" not found + Error from server (NotFound): error when deleting "tests/demo/failing_deployment.yaml": pods "failing-testpod-2" not found + Error from server (NotFound): error when deleting "tests/demo/failing_deployment.yaml": pods "failing-testpod-3" not found ```
diff --git a/charts/semgr8s/templates/webhook.yaml b/charts/semgr8s/templates/webhook.yaml index f7183fa..657e5de 100644 --- a/charts/semgr8s/templates/webhook.yaml +++ b/charts/semgr8s/templates/webhook.yaml @@ -38,7 +38,7 @@ webhooks: - "*" operations: - CREATE - resources: ["pods"] + - UPDATE clientConfig: service: name: {{ include "semgr8s.serviceName" . }} diff --git a/tests/failing_deployment.yaml b/tests/demo/failing_deployment.yaml similarity index 93% rename from tests/failing_deployment.yaml rename to tests/demo/failing_deployment.yaml index c61bdc7..b4e6695 100644 --- a/tests/failing_deployment.yaml +++ b/tests/demo/failing_deployment.yaml @@ -33,7 +33,7 @@ spec: drop: - ALL privileged: true - readOnlyRootFilesystem: false + readOnlyRootFilesystem: true runAsNonRoot: true --- apiVersion: v1 @@ -52,5 +52,5 @@ spec: drop: - ALL privileged: false - readOnlyRootFilesystem: false + readOnlyRootFilesystem: true hostNetwork: true diff --git a/tests/passing_deployment.yaml b/tests/demo/passing_deployment.yaml similarity index 100% rename from tests/passing_deployment.yaml rename to tests/demo/passing_deployment.yaml diff --git a/tests/integration/README.md b/tests/integration/README.md new file mode 100644 index 0000000..0da5f01 --- /dev/null +++ b/tests/integration/README.md @@ -0,0 +1,14 @@ +# Run integration tests + +Use the cluster of your choice, e.g. [kind](https://kind.sigs.k8s.io/). +Specify which semgr8s image is to be used as environment variable, e.g.: + +```bash +export IMAGE=ghcr.io/sse-secure-systems/semgr8s +export TAG=v0.1.0 +``` + +Run the desired integration test via: +```bash +tests/integration/main.sh "basic" +``` diff --git a/tests/integration/data/00_namespaces.yaml b/tests/integration/data/00_namespaces.yaml new file mode 100644 index 0000000..651aa0b --- /dev/null +++ b/tests/integration/data/00_namespaces.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: validatedns + labels: + semgr8s/validation: enabled + use: semgr8s-integration-test +--- +apiVersion: v1 +kind: Namespace +metadata: + name: ignoredns + labels: + use: semgr8s-integration-test diff --git a/tests/integration/data/20_compliant_pod.yaml b/tests/integration/data/20_compliant_pod.yaml new file mode 100644 index 0000000..1a3b303 --- /dev/null +++ b/tests/integration/data/20_compliant_pod.yaml @@ -0,0 +1,23 @@ +--- +apiVersion: v1 +kind: Pod +metadata: + name: compliant-pod + namespace: validatedns +spec: + containers: + - image: busybox + name: compliant-pod + command: ["/bin/sh", "-ec", "sleep 1000"] + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 10001 # remove when using openshift or OKD 4 + runAsGroup: 20001 # remove when using openshift or OKD 4 + seccompProfile: # remove when using Kubernetes prior v1.19, openshift or OKD 4 + type: RuntimeDefault # remove when using Kubernetes prior v1.19, openshift or OKD 4 diff --git a/tests/integration/data/40_nosc_pod.yaml b/tests/integration/data/40_nosc_pod.yaml new file mode 100644 index 0000000..8ade569 --- /dev/null +++ b/tests/integration/data/40_nosc_pod.yaml @@ -0,0 +1,13 @@ +--- +apiVersion: v1 +kind: Pod +metadata: + name: nosc-pod + namespace: validatedns + labels: + use: semgr8s-integration-test +spec: + containers: + - image: busybox + name: nosc-pod + command: ["/bin/sh", "-ec", "sleep 1000"] diff --git a/tests/integration/data/41_privileged_pod.yaml b/tests/integration/data/41_privileged_pod.yaml new file mode 100644 index 0000000..143f94a --- /dev/null +++ b/tests/integration/data/41_privileged_pod.yaml @@ -0,0 +1,21 @@ +--- +apiVersion: v1 +kind: Pod +metadata: + name: privileged-pod + namespace: validatedns + labels: + use: semgr8s-integration-test +spec: + containers: + - image: busybox + name: privileged-pod + command: ["/bin/sh", "-ec", "sleep 1000"] + securityContext: + allowPrivilegeEscalation: true + capabilities: + drop: + - ALL + privileged: true + readOnlyRootFilesystem: true + runAsNonRoot: true diff --git a/tests/integration/data/42_hostnetwork_pod.yaml b/tests/integration/data/42_hostnetwork_pod.yaml new file mode 100644 index 0000000..11be564 --- /dev/null +++ b/tests/integration/data/42_hostnetwork_pod.yaml @@ -0,0 +1,21 @@ +--- +apiVersion: v1 +kind: Pod +metadata: + name: hostnetwork-pod + namespace: validatedns + labels: + use: semgr8s-integration-test +spec: + containers: + - image: busybox + name: hostnetwork-pod + command: ["/bin/sh", "-ec", "sleep 1000"] + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + privileged: false + readOnlyRootFilesystem: true + hostNetwork: true diff --git a/tests/integration/main.sh b/tests/integration/main.sh new file mode 100755 index 0000000..3f1ac21 --- /dev/null +++ b/tests/integration/main.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +set -euo pipefail + +declare -A DEPLOYMENT_RES=(["VALID"]="0" ["INVALID"]="0") +SCRIPT_PATH=$(tmp=$(realpath "$0") && dirname "${tmp}") +RED="\033[0;31m" +GREEN="\033[0;32m" +NC="\033[0m" +SUCCESS="${GREEN}SUCCESS${NC}" +FAILED="${RED}FAILED${NC}" +EXIT="0" +RETRY=3 + +# install/uninstall/upgrade, utility stuff +source ${SCRIPT_PATH}/scripts/common.sh + +# integration test specific functions +source ${SCRIPT_PATH}/scripts/basic.sh +source ${SCRIPT_PATH}/scripts/rules.sh + +# backup values.yaml +cp charts/semgr8s/values.yaml charts/semgr8s/values.yaml.bak + +case $1 in +"basic") + # testing basic functionality + basic_integration_test + ;; +"rules") + # testing multiple pre-built rules + rules_integration_test + ;; +"restore") + restore + ;; +*) + echo "Unknown test type: $1" + EXIT="1" + ;; +esac + +if [[ "${EXIT}" != "0" ]]; then + echo -e "${FAILED} Failed integration test." +else + echo -e "${SUCCESS} Passed integration test." +fi + +if [[ "${CI-}" == "true" ]]; then + exit $((${EXIT})) +fi + +echo 'Cleaning up ...' +restore +make uninstall >/dev/null 2>&1 || true +kubectl delete all,cronjobs,daemonsets,jobs,replicationcontrollers,statefulsets,namespaces -luse="semgr8s-integration-test" -A >/dev/null diff --git a/tests/integration/scripts/basic.sh b/tests/integration/scripts/basic.sh new file mode 100644 index 0000000..a32e0ac --- /dev/null +++ b/tests/integration/scripts/basic.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail + +basic_integration_test() { + create_namespaces + update_with_file "basic" + install "make" + multi_test "basic" + uninstall "make" +} diff --git a/tests/integration/scripts/common.sh b/tests/integration/scripts/common.sh new file mode 100644 index 0000000..3fa0cb2 --- /dev/null +++ b/tests/integration/scripts/common.sh @@ -0,0 +1,168 @@ +#!/usr/bin/env bash +set -euo pipefail + +## UTILS ------------------------------------------------------- ## +fail() { + echo -e "${FAILED}" + exit 1 +} + +success() { + echo -e "${SUCCESS}" +} + +restore() { + cp charts/semgr8s/values.yaml.bak charts/semgr8s/values.yaml + rm charts/semgr8s/values.yaml.bak +} + +null_to_empty() { + read in + + if [[ "$in" == "null" ]]; then + echo "" + else + echo "$in" + fi +} + +## INSTALLATIONS ---------------------------------------------- ## +install() { # $1: helm or make, $2: namespace (helm), $3: additional args (helm) + echo -n 'Installing semgr8s ... ' + case $1 in + "helm") + helm install semgr8s charts/semgr8s --atomic --namespace "${2}" \ + ${3} >/dev/null || fail + ;; + "make") + make install >/dev/null || fail + ;; + *) + fail + ;; + esac + success +} + +uninstall() { # $1: helm or make, $2: namespace (helm) + echo -n 'Uninstalling semgr8s ...' + case $1 in + "helm") + helm uninstall semgr8s --namespace "${2}" >/dev/null || fail + ;; + "make") + make uninstall >/dev/null || fail + ;; + "force") + kubectl delete all,secrets,serviceaccounts,mutatingwebhookconfigurations,configmaps,namespaces \ + -lapp.kubernetes.io/instance=semgr8s -A --force --grace-period=0 >/dev/null 2>&1 + ;; + *) + fail + ;; + esac + success +} + +upgrade() { # $1: helm or make, $2: namespace (helm) + echo -n 'Upgrading semgr8s ...' + case $1 in + "helm") + helm upgrade semgr8s charts/semgr8s --wait \ + --namespace "${2}" >/dev/null || fail + ;; + "make") + make upgrade >/dev/null || fail + ;; + *) + fail + ;; + esac + success +} + +## UPDATES ----------------------------------------------------- ## + +update() { # $@: update expressions + for update in "$@"; do + yq e -i "${update}" charts/semgr8s/values.yaml + done +} + +update_with_file() { # $1: file name + envsubst $1 + yq eval-all --inplace 'select(fileIndex == 0) * select(fileIndex == 1).values' charts/semgr8s/values.yaml $1 + rm $1 +} + +## TEST NAMESPACES --------------------------------------------- ## + +create_namespaces() { + echo -n "Creating test namespaces..." + kubectl create namespace ignoredns >/dev/null + kubectl label ns ignoredns semgr8s/validation=disabled use=semgr8s-integration-test >/dev/null + kubectl create namespace validatedns >/dev/null + kubectl label ns validatedns semgr8s/validation=enabled use=semgr8s-integration-test >/dev/null + success +} + +## TESTS ------------------------------------------------------- ## +single_test() { # ID TXT TYP REF NS MSG RES + echo -n "[$1] $2" + i=0 # intialize iterator + export RAND=$(head -c 5 /dev/urandom | hexdump -ve '1/1 "%.2x"') # creating a random index to label the pods and avoid name collision for repeated runs + + if [[ "$6" == "" ]]; then + MSG="pod/pod-$1-${RAND} created" + else + MSG=$(envsubst <<<"$6") # in case RAND is to be used, it needs to be added as ${RAND} to cases.yaml (and maybe deployment file) + fi + + while :; do + i=$((i + 1)) + if [[ "$3" == "deploy" ]]; then + kubectl run pod-$1-${RAND} --image="$4" --namespace="$5" -luse="semgr8s-integration-test" >output.log 2>&1 || true + else + kubectl apply -f "${SCRIPT_PATH}/data/$4.yaml" >output.log 2>&1 || true + fi + # if the webhook couldn't be called, try again. + [[ ("$(cat output.log)" =~ "failed calling webhook") && $i -lt ${RETRY} ]] || break + done + if [[ ! "$(cat output.log)" =~ "${MSG}" ]]; then + echo -e ${FAILED} + echo "::group::Output" + cat output.log + kubectl logs -n semgr8ns -lapp.kubernetes.io/instance=semgr8s + echo "::endgroup::" + EXIT="1" + else + echo -e "${SUCCESS}" + fi + rm output.log + + if [[ "$7" != "null" ]]; then + DEPLOYMENT_RES[$7]=$((${DEPLOYMENT_RES[$7]} + 1)) + fi + + # 3 tries on first test, 2 tries on second, 1 try for all subsequential + RETRY=$((RETRY - 1)) +} + +multi_test() { # $1: file name, $2: key to find the testcases (default: testCases) + + # converting to json, as yq processing is pretty slow + test_cases=$(yq e -o=json ".${2:-testCases}" ${SCRIPT_PATH}/test_cases/$1.yaml) + len=$(echo ${test_cases} | jq 'length') + for i in $(seq 0 $(($len - 1))); do + test_case=$(echo ${test_cases} | jq ".[$i]") + ID=$(echo ${test_case} | jq -r ".id" | null_to_empty) + TEST_CASE_TXT=$(echo ${test_case} | jq -r ".txt" | null_to_empty) + TYPE=$(echo ${test_case} | jq -r ".type" | null_to_empty) + REF=$(echo ${test_case} | jq -r ".ref" | null_to_empty) + NAMESPACE=$(echo ${test_case} | jq -r ".namespace" | null_to_empty) + EXP_MSG=$(echo ${test_case} | jq -r ".expected_msg" | null_to_empty) + EXP_RES=$(echo ${test_case} | jq -r ".expected_result" | null_to_empty) + single_test "${ID}" "${TEST_CASE_TXT}" "${TYPE:=deploy}" "${REF}" "${NAMESPACE:=default}" "${EXP_MSG}" "${EXP_RES:=null}" + done +} + diff --git a/tests/integration/scripts/rules.sh b/tests/integration/scripts/rules.sh new file mode 100644 index 0000000..bf1f7dd --- /dev/null +++ b/tests/integration/scripts/rules.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail + +rules_integration_test() { + create_namespaces + update_with_file "basic" + install "make" + multi_test "rules" + uninstall "make" +} diff --git a/tests/integration/test_cases/basic.yaml b/tests/integration/test_cases/basic.yaml new file mode 100644 index 0000000..8dccf3c --- /dev/null +++ b/tests/integration/test_cases/basic.yaml @@ -0,0 +1,19 @@ +testCases: +- id: b-01 + txt: Testing compliant pod... + type: k8s-yaml + ref: 20_compliant_pod + namespace: validatedns + expected_msg: pod/compliant-pod created +- id: b-02 + txt: Testing non-compliant pod w/o securityContext... + type: k8s-yaml + ref: 40_nosc_pod + namespace: validatedns + expected_msg: rules.allow-privilege-escalation-no-securitycontext + +values: + deployment: + image: + repository: "${IMAGE}" + tag: "${TAG}" diff --git a/tests/integration/test_cases/rules.yaml b/tests/integration/test_cases/rules.yaml new file mode 100644 index 0000000..fafd7a0 --- /dev/null +++ b/tests/integration/test_cases/rules.yaml @@ -0,0 +1,31 @@ +testCases: +- id: r-01 + txt: Testing compliant pod... + type: k8s-yaml + ref: 20_compliant_pod + namespace: validatedns + expected_msg: pod/compliant-pod created +- id: r-02 + txt: Testing non-compliant pod w/o securityContext... + type: k8s-yaml + ref: 40_nosc_pod + namespace: validatedns + expected_msg: rules.allow-privilege-escalation-no-securitycontext +- id: r-03 + txt: Testing non-compliant privileged pod... + type: k8s-yaml + ref: 41_privileged_pod + namespace: validatedns + expected_msg: rules.privileged-container +- id: r-04 + txt: Testing non-compliant pod w/ access to host network... + type: k8s-yaml + ref: 42_hostnetwork_pod + namespace: validatedns + expected_msg: rules.hostnetwork-pod + +values: + deployment: + image: + repository: "${IMAGE}" + tag: "${TAG}"