From e9585046eb8eb1a608c80e2d97b3af1466c091a8 Mon Sep 17 00:00:00 2001 From: Christoph Hamsen Date: Fri, 14 Jun 2024 00:02:45 +0200 Subject: [PATCH] feat: audit mode --- .../workflows/.reusable-integration-test.yml | 1 + charts/semgr8s/templates/_helpers.tpl | 2 +- charts/semgr8s/templates/env.yaml | 5 ++-- charts/semgr8s/values.yaml | 5 ++-- docs/usage.md | 8 ++++++ semgr8s/app.py | 19 ++++++++----- tests/integration/main.sh | 5 ++++ tests/integration/scripts/audit.sh | 10 +++++++ tests/integration/test_cases/audit.yaml | 27 +++++++++++++++++++ 9 files changed, 70 insertions(+), 12 deletions(-) create mode 100644 tests/integration/scripts/audit.sh create mode 100644 tests/integration/test_cases/audit.yaml diff --git a/.github/workflows/.reusable-integration-test.yml b/.github/workflows/.reusable-integration-test.yml index 309d876..18ccfc1 100644 --- a/.github/workflows/.reusable-integration-test.yml +++ b/.github/workflows/.reusable-integration-test.yml @@ -43,6 +43,7 @@ jobs: "basic", "autofix", "semgrep_login", + "audit", ] steps: - name: Checkout code diff --git a/charts/semgr8s/templates/_helpers.tpl b/charts/semgr8s/templates/_helpers.tpl index 31d9e6a..1b0c4cb 100644 --- a/charts/semgr8s/templates/_helpers.tpl +++ b/charts/semgr8s/templates/_helpers.tpl @@ -83,4 +83,4 @@ Create the name of the environments to use {{- define "semgr8s.getFileName" -}} {{- . | trimPrefix "rules/" | replace "/" "_" }} -{{- end }} \ No newline at end of file +{{- end }} diff --git a/charts/semgr8s/templates/env.yaml b/charts/semgr8s/templates/env.yaml index ffc08cc..8bc24ab 100644 --- a/charts/semgr8s/templates/env.yaml +++ b/charts/semgr8s/templates/env.yaml @@ -5,5 +5,6 @@ metadata: labels: {{- include "semgr8s.labels" . | nindent 4 }} data: - SEMGREP_RULES: {{ join " " .Values.application.remoteRules | quote }} - NAMESPACE: {{ .Release.Namespace }} + ENFORCE: {{ .Values.application.enforce | quote }} + SEMGREP_RULES: {{ join " " .Values.application.remoteRules | quote }} + NAMESPACE: {{ .Release.Namespace }} diff --git a/charts/semgr8s/values.yaml b/charts/semgr8s/values.yaml index c97d559..cd75f80 100644 --- a/charts/semgr8s/values.yaml +++ b/charts/semgr8s/values.yaml @@ -63,10 +63,11 @@ webhooks: # configuration options for webhooks described under https://kubernet operations: ["CREATE", "UPDATE"] application: - # Apply remote rules from e.g. + enforce: true # fail on rule violation (true/false) + # remoteRules: Apply remote rules from e.g. # * semgrep registry: https://semgrep.dev/r # * semgrep-rules github repo: https://github.com/semgrep/semgrep-rules # common choices: p/kubernetes, r/yaml.kubernetes remoteRules: ["p/kubernetes"] - autofix: false + autofix: false # apply semgrep fixes before validation (see https://semgrep.dev/docs/writing-rules/autofix) semgrepLogin: false # requires generic secret with name 'semgrep-app-token' and key 'token' in semgr8ns namespace diff --git a/docs/usage.md b/docs/usage.md index 7ebe24a..5f5d708 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -214,6 +214,14 @@ kubectl delete -f tests/demo/ ## Features +### Audit mode + +It is possible to run Semgr8s in *audit* mode by which it will not block but only warn on non-compliant resources. +As the logs contain an explicit error code, it is possible to alert on admission responses with warnings via typical monitoring solutions such as [Prometheus](https://prometheus.io/) or [DataDog](https://www.datadoghq.com/). +This is particularly useful during rollout of Semgr8s and for enforcement of new policies. + +To activate audit mode, set `.application.enforce=false` in the `charts/semgr8s/values.yaml`. + ### Semgrep login With the *Semgrep login* feature, you can connect Semgr8s to your [Semgrep AppSec Platform](https://semgrep.dev/login) account and use platform features such as [private remote rules](https://semgrep.dev/docs/writing-rules/private-rules). diff --git a/semgr8s/app.py b/semgr8s/app.py index e54e270..0c01185 100644 --- a/semgr8s/app.py +++ b/semgr8s/app.py @@ -15,6 +15,7 @@ from semgr8s.files import S8sFiles APP = Flask(__name__) +AUDIT = os.environ.get("ENFORCE", "true").lower() == "false" @APP.route("/health", methods=["GET", "POST"]) @@ -70,7 +71,7 @@ def perform_review( 'allowed': , 'status': { 'code': , - 'message': + 'msg': }, 'uid': } @@ -181,26 +182,30 @@ def perform_review( return send_response(True, uid, 201, "Compliant resource admitted") -def send_response(allowed, uid, code, message, patch=None): +def send_response(allowed, uid, code, msg, patch=None): """ Prepare json response in expected format based on validation result. """ APP.logger.info( - "> response:(allowed=%s, uid=%s, status_code=%s message=%s)", - allowed, + "> response:(allowed=%s, uid=%s, status_code=%s msg=%s)", + allowed | AUDIT, uid, code, - message, + msg, ) review = { "apiVersion": "admission.k8s.io/v1", "kind": "AdmissionReview", "response": { - "allowed": allowed, + "allowed": allowed | AUDIT, "uid": uid, - "status": {"code": code, "message": message}, + "status": {"code": code, "message": msg}, }, } + + if AUDIT and not allowed: + review["response"]["warnings"] = ["[Semgr8s] " + msg.replace("\n", "")] + if patch: review["response"]["patchType"] = "JSONPatch" review["response"]["patch"] = base64.b64encode( diff --git a/tests/integration/main.sh b/tests/integration/main.sh index 0dea54f..58e5343 100755 --- a/tests/integration/main.sh +++ b/tests/integration/main.sh @@ -19,6 +19,7 @@ source "${SCRIPT_PATH}/scripts/basic.sh" source "${SCRIPT_PATH}/scripts/remote_rules.sh" source "${SCRIPT_PATH}/scripts/autofix.sh" source "${SCRIPT_PATH}/scripts/semgrep_login.sh" +source "${SCRIPT_PATH}/scripts/audit.sh" # backup values.yaml cp charts/semgr8s/values.yaml charts/semgr8s/values.yaml.bak @@ -40,6 +41,10 @@ case $1 in # testing semgrep appsec platform login with private rules semgrep_login_integration_test ;; +"audit") + # testing autofixing with mutating webhook + audit_integration_test + ;; "restore") restore ;; diff --git a/tests/integration/scripts/audit.sh b/tests/integration/scripts/audit.sh new file mode 100644 index 0000000..5104183 --- /dev/null +++ b/tests/integration/scripts/audit.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail + +audit_integration_test() { + create_test_namespaces + update_with_file "audit" + install "make" + multi_test "audit" + uninstall "make" +} diff --git a/tests/integration/test_cases/audit.yaml b/tests/integration/test_cases/audit.yaml new file mode 100644 index 0000000..318c3e9 --- /dev/null +++ b/tests/integration/test_cases/audit.yaml @@ -0,0 +1,27 @@ +testCases: +- id: a-01 + txt: Testing compliant pod... + type: k8s-yaml + ref: 20_compliant_pod + namespace: validatedns + expected_msg: pod/compliant-pod created +- id: a-02 + txt: Testing non-compliant pod against local rule for forbidden test label... + type: k8s-yaml + ref: 40_testlabel_pod + namespace: validatedns + expected_msg: "Warning: [Semgr8s] Found 1 violation" +- id: a-03 + txt: Testing non-compliant pod against local rule for forbidden test label... + type: k8s-yaml + ref: 41_nosc_pod + namespace: validatedns + expected_msg: pod/nosc-pod created + +values: + deployment: + image: + repository: "${IMAGE}" + tag: "${TAG}" + application: + enforce: false