From 6359f9d13d82764ccf9c3197518a7eadbc72ee65 Mon Sep 17 00:00:00 2001 From: sachin chauhan <110888148+sachinchauhan23@users.noreply.github.com> Date: Thu, 14 Nov 2024 12:15:29 +0100 Subject: [PATCH] Argo diff (#44) --- docker-compose/Dockerfile | 2 + kyverno-policies/_helpers.tpl | 84 ++++++++++++++++ kyverno-policies/disallow-capabilities.yaml | 99 +++++++++++++++++++ .../disallow-host-namespaces.yaml | 44 +++++++++ kyverno-policies/disallow-host-path.yaml | 54 ++++++++++ kyverno-policies/disallow-host-ports.yaml | 50 ++++++++++ kyverno-policies/disallow-host-process.yaml | 55 +++++++++++ .../disallow-privileged-containers.yaml | 55 +++++++++++ kyverno-policies/disallow-proc-mount.yaml | 52 ++++++++++ kyverno-policies/disallow-selinux.yaml | 91 +++++++++++++++++ .../restrict-apparmor-profiles.yaml | 52 ++++++++++ kyverno-policies/restrict-seccomp.yaml | 59 +++++++++++ kyverno-policies/restrict-sysctls.yaml | 49 +++++++++ targets.go | 69 +++++++++++++ targets_test.go | 18 ++++ .../validate-fail/deployment-fail.yaml | 34 +++++++ tests/templates/validate/deployment-ok.yaml | 30 ++++++ 17 files changed, 897 insertions(+) create mode 100644 kyverno-policies/_helpers.tpl create mode 100644 kyverno-policies/disallow-capabilities.yaml create mode 100644 kyverno-policies/disallow-host-namespaces.yaml create mode 100644 kyverno-policies/disallow-host-path.yaml create mode 100644 kyverno-policies/disallow-host-ports.yaml create mode 100644 kyverno-policies/disallow-host-process.yaml create mode 100644 kyverno-policies/disallow-privileged-containers.yaml create mode 100644 kyverno-policies/disallow-proc-mount.yaml create mode 100644 kyverno-policies/disallow-selinux.yaml create mode 100644 kyverno-policies/restrict-apparmor-profiles.yaml create mode 100644 kyverno-policies/restrict-seccomp.yaml create mode 100644 kyverno-policies/restrict-sysctls.yaml create mode 100644 tests/templates/validate-fail/deployment-fail.yaml create mode 100644 tests/templates/validate/deployment-ok.yaml diff --git a/docker-compose/Dockerfile b/docker-compose/Dockerfile index 6d7683e..272e8ba 100644 --- a/docker-compose/Dockerfile +++ b/docker-compose/Dockerfile @@ -2,6 +2,7 @@ FROM docker.io/argoproj/argocd:v2.6.15@sha256:58ebb4ed23c8db4cd4cc3f954f8d94c4b4e3d9669c0751c484108d22b86d52de as argocd FROM zegl/kube-score:v1.19.0@sha256:94137f32ce139dc9fbdbbd380249025e4d378c282ff151a100b981cdeeb923b6 as kube-score FROM ghcr.io/yannh/kubeconform:v0.6.7@sha256:0925177fb05b44ce18574076141b5c3d83235e1904d3f952182ac99ddc45762c as kubeconform +FROM ghcr.io/kyverno/kyverno-cli:v1.12@sha256:229154f8f42326c5a7568b3787887b81ca94f0824400574ffab1dd9d30931f01 AS kyverno FROM ghcr.io/coopnorge/engineering-docker-images/e0/devtools-golang-v1beta1:latest@sha256:026875885070fa56db08fe23c4a495a82cc86523cc6540a32a360b572a53c011 AS golang-devtools @@ -10,3 +11,4 @@ COPY --from=argocd /usr/local/bin/argocd /usr/local/bin/argocd COPY --from=argocd /usr/local/bin/helm /usr/local/bin/helm COPY --from=argocd /usr/local/bin/kustomize /usr/local/bin/kustomize COPY --from=kubeconform /kubeconform /usr/local/bin/kubeconform +COPY --from=kyverno /ko-app/kubectl-kyverno /usr/local/bin/kyverno \ No newline at end of file diff --git a/kyverno-policies/_helpers.tpl b/kyverno-policies/_helpers.tpl new file mode 100644 index 0000000..ef0b68f --- /dev/null +++ b/kyverno-policies/_helpers.tpl @@ -0,0 +1,84 @@ +{{/* vim: set filetype=mustache: */}} +{{/* Expand the name of the chart. */}} +{{- define "kyverno-policies.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* Create chart name and version as used by the chart label. */}} +{{- define "kyverno-policies.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* Helm required labels */}} +{{- define "kyverno-policies.labels" -}} +app.kubernetes.io/component: kyverno +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +app.kubernetes.io/name: {{ template "kyverno-policies.name" . }} +app.kubernetes.io/part-of: {{ template "kyverno-policies.name" . }} +app.kubernetes.io/version: "{{ .Chart.Version | replace "+" "_" }}" +helm.sh/chart: {{ template "kyverno-policies.chart" . }} +{{- if .Values.customLabels }} +{{ toYaml .Values.customLabels }} +{{- end }} +{{- end -}} + +{{/* Set if a baseline policy is managed */}} +{{- define "kyverno-policies.podSecurityBaseline" -}} +{{- if or (eq .Values.podSecurityStandard "baseline") (eq .Values.podSecurityStandard "restricted") }} +{{- true }} +{{- else if and (eq .Values.podSecurityStandard "custom") (has .name .Values.podSecurityPolicies) }} +{{- true }} +{{- else -}} +{{- false }} +{{- end -}} +{{- end -}} + +{{/* Set if a restricted policy is managed */}} +{{- define "kyverno-policies.podSecurityRestricted" -}} +{{- if eq .Values.podSecurityStandard "restricted" }} +{{- true }} +{{- else if and (eq .Values.podSecurityStandard "custom") (has .name .Values.podSecurityPolicies) }} +{{- true }} +{{- else if has .name .Values.includeRestrictedPolicies }} +{{- true }} +{{- else -}} +{{- false }} +{{- end -}} +{{- end -}} + +{{/* Set if a other policies are managed */}} +{{- define "kyverno-policies.podSecurityOther" -}} +{{- if has .name .Values.includeOtherPolicies }} +{{- true }} +{{- else -}} +{{- false }} +{{- end -}} +{{- end -}} + +{{/* Get deployed Kyverno version from Kubernetes */}} +{{- define "kyverno-policies.kyvernoVersion" -}} +{{- $version := "" -}} +{{- if eq .Values.kyvernoVersion "autodetect" }} +{{- with (lookup "apps/v1" "Deployment" .Release.Namespace "kyverno") -}} + {{- with (first .spec.template.spec.containers) -}} + {{- $imageTag := (last (splitList ":" .image)) -}} + {{- $version = trimPrefix "v" $imageTag -}} + {{- end -}} +{{- end -}} +{{ $version }} +{{- else -}} +{{ .Values.kyvernoVersion }} +{{- end -}} +{{- end -}} + +{{/* Fail if deployed Kyverno does not match */}} +{{- define "kyverno-policies.supportedKyvernoCheck" -}} +{{- $supportedKyverno := index . "ver" -}} +{{- $top := index . "top" }} +{{- if (include "kyverno-policies.kyvernoVersion" $top) -}} + {{- if not ( semverCompare $supportedKyverno (include "kyverno-policies.kyvernoVersion" $top) ) -}} + {{- fail (printf "Kyverno version is too low, expected %s" $supportedKyverno) -}} + {{- end -}} +{{- end -}} +{{- end -}} diff --git a/kyverno-policies/disallow-capabilities.yaml b/kyverno-policies/disallow-capabilities.yaml new file mode 100644 index 0000000..fd77891 --- /dev/null +++ b/kyverno-policies/disallow-capabilities.yaml @@ -0,0 +1,99 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: disallow-capabilities + annotations: + pod-policies.kyverno.io/autogen-controllers: DaemonSet,Deployment,StatefulSet,ReplicaSet,ReplicationController + policies.kyverno.io/title: Disallow Capabilities + policies.kyverno.io/category: Pod Security Standards (Baseline) + policies.kyverno.io/severity: medium + kyverno.io/kyverno-version: 1.6.0 + policies.kyverno.io/minversion: 1.6.0 + kyverno.io/kubernetes-version: "1.22-1.23" + policies.kyverno.io/subject: Pod + policies.kyverno.io/description: >- + Adding capabilities beyond those listed in the policy must be disallowed. + labels: + app.kubernetes.io/component: kyverno + app.kubernetes.io/instance: release-name + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: kyverno-policies + app.kubernetes.io/part-of: kyverno-policies + app.kubernetes.io/version: "3.1.3" + helm.sh/chart: kyverno-policies-3.1.3 +spec: + # validationFailureAction: Audit + validationFailureAction: audit + background: true + failurePolicy: Ignore + rules: + - name: adding-capabilities + match: + any: + - resources: + kinds: + - Pod + exclude: + any: + - resources: + kinds: + - Pod + namespaces: + - datadog + preconditions: + all: + - key: "{{ request.operation || 'BACKGROUND' }}" + operator: NotEquals + value: DELETE + context: + - name: capabilities + variable: + value: ["AUDIT_WRITE","CHOWN","DAC_OVERRIDE","FOWNER","FSETID","KILL","MKNOD","NET_BIND_SERVICE","SETFCAP","SETGID","SETPCAP","SETUID","SYS_CHROOT"] + validate: + message: >- + Any capabilities added beyond the allowed list (AUDIT_WRITE, CHOWN, DAC_OVERRIDE, FOWNER, + FSETID, KILL, MKNOD, NET_BIND_SERVICE, SETFCAP, SETGID, SETPCAP, SETUID, SYS_CHROOT) + are disallowed. Service mesh initContainers may additionally add NET_ADMIN and NET_RAW. + foreach: + - list: request.object.spec.initContainers[] + preconditions: + all: + - key: "{{ element.image || '' }}" + operator: AnyIn + value: + - "*/istio/proxyv2*" + - key: "{{ element.securityContext.capabilities.add[] || `[]` }}" + operator: AnyNotIn + value: + - NET_ADMIN + - NET_RAW + - "{{ capabilities || '' }}" + deny: + conditions: + all: + - key: "{{ element.securityContext.capabilities.add[] || `[]` }}" + operator: AnyNotIn + value: "{{ capabilities || '' }}" + message: The service mesh initContainer {{ element.name }} is attempting to add forbidden capabilities. + - list: request.object.spec.initContainers[] + preconditions: + all: + - key: "{{ element.image || '' }}" + operator: AnyNotIn + value: + - "*/istio/proxyv2*" + deny: + conditions: + all: + - key: "{{ element.securityContext.capabilities.add[] || `[]` }}" + operator: AnyNotIn + value: "{{ capabilities || '' }}" + message: The initContainer {{ element.name }} is attempting to add forbidden capabilities. + - list: request.object.spec.[ephemeralContainers, containers][] + deny: + conditions: + all: + - key: "{{ element.securityContext.capabilities.add[] || `[]` }}" + operator: AnyNotIn + value: "{{ capabilities || '' }}" + message: The container {{ element.name }} is attempting to add forbidden capabilities. \ No newline at end of file diff --git a/kyverno-policies/disallow-host-namespaces.yaml b/kyverno-policies/disallow-host-namespaces.yaml new file mode 100644 index 0000000..438fbc0 --- /dev/null +++ b/kyverno-policies/disallow-host-namespaces.yaml @@ -0,0 +1,44 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: disallow-host-namespaces + annotations: + policies.kyverno.io/title: Disallow Host Namespaces + policies.kyverno.io/category: Pod Security Standards (Baseline) + policies.kyverno.io/severity: medium + kyverno.io/kyverno-version: 1.6.0 + kyverno.io/kubernetes-version: "1.22-1.23" + policies.kyverno.io/subject: Pod + policies.kyverno.io/description: >- + Host namespaces (Process ID namespace, Inter-Process Communication namespace, and + network namespace) allow access to shared information and can be used to elevate + privileges. Pods should not be allowed access to host namespaces. This policy ensures + fields which make use of these host namespaces are unset or set to `false`. + labels: + app.kubernetes.io/component: kyverno + app.kubernetes.io/instance: release-name + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: kyverno-policies + app.kubernetes.io/part-of: kyverno-policies + app.kubernetes.io/version: "3.1.3" + helm.sh/chart: kyverno-policies-3.1.3 +spec: + validationFailureAction: Audit + background: true + failurePolicy: Ignore + rules: + - name: host-namespaces + match: + any: + - resources: + kinds: + - Pod + validate: + message: >- + Sharing the host namespaces is disallowed. The fields spec.hostNetwork, + spec.hostIPC, and spec.hostPID must be unset or set to `false`. + pattern: + spec: + =(hostPID): "false" + =(hostIPC): "false" + =(hostNetwork): "false" \ No newline at end of file diff --git a/kyverno-policies/disallow-host-path.yaml b/kyverno-policies/disallow-host-path.yaml new file mode 100644 index 0000000..83d6a9b --- /dev/null +++ b/kyverno-policies/disallow-host-path.yaml @@ -0,0 +1,54 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: disallow-host-path + annotations: + policies.kyverno.io/title: Disallow hostPath + policies.kyverno.io/category: Pod Security Standards (Baseline) + policies.kyverno.io/severity: medium + policies.kyverno.io/subject: Pod,Volume + kyverno.io/kyverno-version: 1.6.0 + kyverno.io/kubernetes-version: "1.22-1.23" + policies.kyverno.io/description: >- + HostPath volumes let Pods use host directories and volumes in containers. + Using host resources can be used to access shared data or escalate privileges + and should not be allowed. This policy ensures no hostPath volumes are in use. + labels: + app.kubernetes.io/component: kyverno + app.kubernetes.io/instance: release-name + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: kyverno-policies + app.kubernetes.io/part-of: kyverno-policies + app.kubernetes.io/version: "3.1.3" + helm.sh/chart: kyverno-policies-3.1.3 +spec: + validationFailureAction: Audit + validationFailureActionOverrides: + - action: audit + namespaces: + - '{{ request.object.metadata.name }}' + background: true + failurePolicy: Ignore + rules: + - name: host-path + match: + any: + - resources: + kinds: + - Pod + exclude: + any: + - resources: + kinds: + - Pod + namespaces: + - datadog + - kube-system + validate: + message: >- + HostPath volumes are forbidden. The field spec.volumes[*].hostPath must be unset. + pattern: + =(spec): + =(volumes): + - =(hostPath): + path: "/var/run/datadog/" diff --git a/kyverno-policies/disallow-host-ports.yaml b/kyverno-policies/disallow-host-ports.yaml new file mode 100644 index 0000000..a7ce8cf --- /dev/null +++ b/kyverno-policies/disallow-host-ports.yaml @@ -0,0 +1,50 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: disallow-host-ports + annotations: + policies.kyverno.io/title: Disallow hostPorts + policies.kyverno.io/category: Pod Security Standards (Baseline) + policies.kyverno.io/severity: medium + policies.kyverno.io/subject: Pod + kyverno.io/kyverno-version: 1.6.0 + kyverno.io/kubernetes-version: "1.22-1.23" + policies.kyverno.io/description: >- + Access to host ports allows potential snooping of network traffic and should not be + allowed, or at minimum restricted to a known list. This policy ensures the `hostPort` + field is unset or set to `0`. + labels: + app.kubernetes.io/component: kyverno + app.kubernetes.io/instance: release-name + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: kyverno-policies + app.kubernetes.io/part-of: kyverno-policies + app.kubernetes.io/version: "3.1.3" + helm.sh/chart: kyverno-policies-3.1.3 +spec: + validationFailureAction: Audit + background: true + failurePolicy: Ignore + rules: + - name: host-ports-none + match: + any: + - resources: + kinds: + - Pod + validate: + message: >- + Use of host ports is disallowed. The fields spec.containers[*].ports[*].hostPort + , spec.initContainers[*].ports[*].hostPort, and spec.ephemeralContainers[*].ports[*].hostPort + must either be unset or set to `0`. + pattern: + spec: + =(ephemeralContainers): + - =(ports): + - =(hostPort): 0 + =(initContainers): + - =(ports): + - =(hostPort): 0 + containers: + - =(ports): + - =(hostPort): 0 \ No newline at end of file diff --git a/kyverno-policies/disallow-host-process.yaml b/kyverno-policies/disallow-host-process.yaml new file mode 100644 index 0000000..a35d9bb --- /dev/null +++ b/kyverno-policies/disallow-host-process.yaml @@ -0,0 +1,55 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: disallow-host-process + annotations: + policies.kyverno.io/title: Disallow hostProcess + policies.kyverno.io/category: Pod Security Standards (Baseline) + policies.kyverno.io/severity: medium + policies.kyverno.io/subject: Pod + kyverno.io/kyverno-version: 1.6.0 + kyverno.io/kubernetes-version: "1.22-1.23" + policies.kyverno.io/description: >- + Windows pods offer the ability to run HostProcess containers which enables privileged + access to the Windows node. Privileged access to the host is disallowed in the baseline + policy. HostProcess pods are an alpha feature as of Kubernetes v1.22. This policy ensures + the `hostProcess` field, if present, is set to `false`. + labels: + app.kubernetes.io/component: kyverno + app.kubernetes.io/instance: release-name + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: kyverno-policies + app.kubernetes.io/part-of: kyverno-policies + app.kubernetes.io/version: "3.1.3" + helm.sh/chart: kyverno-policies-3.1.3 +spec: + validationFailureAction: Audit + background: true + failurePolicy: Ignore + rules: + - name: host-process-containers + match: + any: + - resources: + kinds: + - Pod + validate: + message: >- + HostProcess containers are disallowed. The fields spec.securityContext.windowsOptions.hostProcess, + spec.containers[*].securityContext.windowsOptions.hostProcess, spec.initContainers[*].securityContext.windowsOptions.hostProcess, + and spec.ephemeralContainers[*].securityContext.windowsOptions.hostProcess must either be undefined + or set to `false`. + pattern: + spec: + =(ephemeralContainers): + - =(securityContext): + =(windowsOptions): + =(hostProcess): "false" + =(initContainers): + - =(securityContext): + =(windowsOptions): + =(hostProcess): "false" + containers: + - =(securityContext): + =(windowsOptions): + =(hostProcess): "false" diff --git a/kyverno-policies/disallow-privileged-containers.yaml b/kyverno-policies/disallow-privileged-containers.yaml new file mode 100644 index 0000000..d0d98ba --- /dev/null +++ b/kyverno-policies/disallow-privileged-containers.yaml @@ -0,0 +1,55 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: disallow-privileged-containers + annotations: + policies.kyverno.io/title: Disallow Privileged Containers + policies.kyverno.io/category: Pod Security Standards (Baseline) + policies.kyverno.io/severity: medium + policies.kyverno.io/subject: Pod + kyverno.io/kyverno-version: 1.6.0 + kyverno.io/kubernetes-version: "1.22-1.23" + policies.kyverno.io/description: >- + Privileged mode disables most security mechanisms and must not be allowed. This policy + ensures Pods do not call for privileged mode. + labels: + app.kubernetes.io/component: kyverno + app.kubernetes.io/instance: release-name + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: kyverno-policies + app.kubernetes.io/part-of: kyverno-policies + app.kubernetes.io/version: "3.1.3" + helm.sh/chart: kyverno-policies-3.1.3 +spec: + validationFailureAction: Audit + background: true + failurePolicy: Ignore + rules: + - name: privileged-containers + match: + any: + - resources: + kinds: + - Pod + exclude: + any: + - resources: + kinds: + - Pod + namespaces: + - spacelift-workers + validate: + message: >- + Privileged mode is disallowed. The fields spec.containers[*].securityContext.privileged + and spec.initContainers[*].securityContext.privileged must be unset or set to `false`. + pattern: + spec: + =(ephemeralContainers): + - =(securityContext): + =(privileged): "false" + =(initContainers): + - =(securityContext): + =(privileged): "false" + containers: + - =(securityContext): + =(privileged): "false" diff --git a/kyverno-policies/disallow-proc-mount.yaml b/kyverno-policies/disallow-proc-mount.yaml new file mode 100644 index 0000000..f93216e --- /dev/null +++ b/kyverno-policies/disallow-proc-mount.yaml @@ -0,0 +1,52 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: disallow-proc-mount + annotations: + policies.kyverno.io/title: Disallow procMount + policies.kyverno.io/category: Pod Security Standards (Baseline) + policies.kyverno.io/severity: medium + policies.kyverno.io/subject: Pod + kyverno.io/kyverno-version: 1.6.0 + kyverno.io/kubernetes-version: "1.22-1.23" + policies.kyverno.io/description: >- + The default /proc masks are set up to reduce attack surface and should be required. This policy + ensures nothing but the default procMount can be specified. Note that in order for users + to deviate from the `Default` procMount requires setting a feature gate at the API + server. + labels: + app.kubernetes.io/component: kyverno + app.kubernetes.io/instance: release-name + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: kyverno-policies + app.kubernetes.io/part-of: kyverno-policies + app.kubernetes.io/version: "3.1.3" + helm.sh/chart: kyverno-policies-3.1.3 +spec: + validationFailureAction: Audit + background: true + failurePolicy: Ignore + rules: + - name: check-proc-mount + match: + any: + - resources: + kinds: + - Pod + validate: + message: >- + Changing the proc mount from the default is not allowed. The fields + spec.containers[*].securityContext.procMount, spec.initContainers[*].securityContext.procMount, + and spec.ephemeralContainers[*].securityContext.procMount must be unset or + set to `Default`. + pattern: + spec: + =(ephemeralContainers): + - =(securityContext): + =(procMount): "Default" + =(initContainers): + - =(securityContext): + =(procMount): "Default" + containers: + - =(securityContext): + =(procMount): "Default" diff --git a/kyverno-policies/disallow-selinux.yaml b/kyverno-policies/disallow-selinux.yaml new file mode 100644 index 0000000..83d3e92 --- /dev/null +++ b/kyverno-policies/disallow-selinux.yaml @@ -0,0 +1,91 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: disallow-selinux + annotations: + policies.kyverno.io/title: Disallow SELinux + policies.kyverno.io/category: Pod Security Standards (Baseline) + policies.kyverno.io/severity: medium + policies.kyverno.io/subject: Pod + kyverno.io/kyverno-version: 1.6.0 + kyverno.io/kubernetes-version: "1.22-1.23" + policies.kyverno.io/description: >- + SELinux options can be used to escalate privileges and should not be allowed. This policy + ensures that the `seLinuxOptions` field is undefined. + labels: + app.kubernetes.io/component: kyverno + app.kubernetes.io/instance: release-name + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: kyverno-policies + app.kubernetes.io/part-of: kyverno-policies + app.kubernetes.io/version: "3.1.3" + helm.sh/chart: kyverno-policies-3.1.3 +spec: + validationFailureAction: Audit + background: true + failurePolicy: Ignore + rules: + - name: selinux-type + match: + any: + - resources: + kinds: + - Pod + validate: + message: >- + Setting the SELinux type is restricted. The fields + spec.securityContext.seLinuxOptions.type, spec.containers[*].securityContext.seLinuxOptions.type, + , spec.initContainers[*].securityContext.seLinuxOptions, and spec.ephemeralContainers[*].securityContext.seLinuxOptions.type + must either be unset or set to one of the allowed values (container_t, container_init_t, or container_kvm_t). + pattern: + spec: + =(securityContext): + =(seLinuxOptions): + =(type): "container_t | container_init_t | container_kvm_t" + =(ephemeralContainers): + - =(securityContext): + =(seLinuxOptions): + =(type): "container_t | container_init_t | container_kvm_t" + =(initContainers): + - =(securityContext): + =(seLinuxOptions): + =(type): "container_t | container_init_t | container_kvm_t" + containers: + - =(securityContext): + =(seLinuxOptions): + =(type): "container_t | container_init_t | container_kvm_t" + - name: selinux-user-role + match: + any: + - resources: + kinds: + - Pod + validate: + message: >- + Setting the SELinux user or role is forbidden. The fields + spec.securityContext.seLinuxOptions.user, spec.securityContext.seLinuxOptions.role, + spec.containers[*].securityContext.seLinuxOptions.user, spec.containers[*].securityContext.seLinuxOptions.role, + spec.initContainers[*].securityContext.seLinuxOptions.user, spec.initContainers[*].securityContext.seLinuxOptions.role, + spec.ephemeralContainers[*].securityContext.seLinuxOptions.user, and spec.ephemeralContainers[*].securityContext.seLinuxOptions.role + must be unset. + pattern: + spec: + =(securityContext): + =(seLinuxOptions): + X(user): "null" + X(role): "null" + =(ephemeralContainers): + - =(securityContext): + =(seLinuxOptions): + X(user): "null" + X(role): "null" + =(initContainers): + - =(securityContext): + =(seLinuxOptions): + X(user): "null" + X(role): "null" + containers: + - =(securityContext): + =(seLinuxOptions): + X(user): "null" + X(role): "null" diff --git a/kyverno-policies/restrict-apparmor-profiles.yaml b/kyverno-policies/restrict-apparmor-profiles.yaml new file mode 100644 index 0000000..c82fe15 --- /dev/null +++ b/kyverno-policies/restrict-apparmor-profiles.yaml @@ -0,0 +1,52 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: restrict-apparmor-profiles + annotations: + policies.kyverno.io/title: Restrict AppArmor + policies.kyverno.io/category: Pod Security Standards (Baseline) + policies.kyverno.io/severity: medium + policies.kyverno.io/subject: Pod, Annotation + policies.kyverno.io/minversion: 1.3.0 + kyverno.io/kyverno-version: 1.6.0 + kyverno.io/kubernetes-version: "1.22-1.23" + policies.kyverno.io/description: >- + On supported hosts, the 'runtime/default' AppArmor profile is applied by default. + The default policy should prevent overriding or disabling the policy, or restrict + overrides to an allowed set of profiles. This policy ensures Pods do not + specify any other AppArmor profiles than `runtime/default` or `localhost/*`. + labels: + app.kubernetes.io/component: kyverno + app.kubernetes.io/instance: release-name + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: kyverno-policies + app.kubernetes.io/part-of: kyverno-policies + app.kubernetes.io/version: "3.1.3" + helm.sh/chart: kyverno-policies-3.1.3 +spec: + validationFailureAction: Audit + background: true + failurePolicy: Ignore + rules: + - name: app-armor + match: + any: + - resources: + kinds: + - Pod + exclude: + any: + - resources: + kinds: + - Pod + namespaces: + - datadog + validate: + message: >- + Specifying other AppArmor profiles is disallowed. The annotation + `container.apparmor.security.beta.kubernetes.io` if defined + must not be set to anything other than `runtime/default` or `localhost/*`. + pattern: + =(metadata): + =(annotations): + =(container.apparmor.security.beta.kubernetes.io/*): "runtime/default | localhost/*" diff --git a/kyverno-policies/restrict-seccomp.yaml b/kyverno-policies/restrict-seccomp.yaml new file mode 100644 index 0000000..4153810 --- /dev/null +++ b/kyverno-policies/restrict-seccomp.yaml @@ -0,0 +1,59 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: restrict-seccomp + annotations: + policies.kyverno.io/title: Restrict Seccomp + policies.kyverno.io/category: Pod Security Standards (Baseline) + policies.kyverno.io/severity: medium + policies.kyverno.io/subject: Pod + kyverno.io/kyverno-version: 1.6.0 + kyverno.io/kubernetes-version: "1.22-1.23" + policies.kyverno.io/description: >- + The seccomp profile must not be explicitly set to Unconfined. This policy, + requiring Kubernetes v1.19 or later, ensures that seccomp is unset or + set to `RuntimeDefault` or `Localhost`. + labels: + app.kubernetes.io/component: kyverno + app.kubernetes.io/instance: release-name + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: kyverno-policies + app.kubernetes.io/part-of: kyverno-policies + app.kubernetes.io/version: "3.1.3" + helm.sh/chart: kyverno-policies-3.1.3 +spec: + background: true + validationFailureAction: Audit + failurePolicy: Ignore + rules: + - name: check-seccomp + match: + any: + - resources: + kinds: + - Pod + validate: + message: >- + Use of custom Seccomp profiles is disallowed. The fields + spec.securityContext.seccompProfile.type, + spec.containers[*].securityContext.seccompProfile.type, + spec.initContainers[*].securityContext.seccompProfile.type, and + spec.ephemeralContainers[*].securityContext.seccompProfile.type + must be unset or set to `RuntimeDefault` or `Localhost`. + pattern: + spec: + =(securityContext): + =(seccompProfile): + =(type): "RuntimeDefault | Localhost" + =(ephemeralContainers): + - =(securityContext): + =(seccompProfile): + =(type): "RuntimeDefault | Localhost" + =(initContainers): + - =(securityContext): + =(seccompProfile): + =(type): "RuntimeDefault | Localhost" + containers: + - =(securityContext): + =(seccompProfile): + =(type): "RuntimeDefault | Localhost" diff --git a/kyverno-policies/restrict-sysctls.yaml b/kyverno-policies/restrict-sysctls.yaml new file mode 100644 index 0000000..7aeaf8b --- /dev/null +++ b/kyverno-policies/restrict-sysctls.yaml @@ -0,0 +1,49 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: restrict-sysctls + annotations: + policies.kyverno.io/title: Restrict sysctls + policies.kyverno.io/category: Pod Security Standards (Baseline) + policies.kyverno.io/severity: medium + policies.kyverno.io/subject: Pod + kyverno.io/kyverno-version: 1.6.0 + kyverno.io/kubernetes-version: "1.22-1.23" + policies.kyverno.io/description: >- + Sysctls can disable security mechanisms or affect all containers on a + host, and should be disallowed except for an allowed "safe" subset. A + sysctl is considered safe if it is namespaced in the container or the + Pod, and it is isolated from other Pods or processes on the same Node. + This policy ensures that only those "safe" subsets can be specified in + a Pod. + labels: + app.kubernetes.io/component: kyverno + app.kubernetes.io/instance: release-name + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: kyverno-policies + app.kubernetes.io/part-of: kyverno-policies + app.kubernetes.io/version: "3.1.3" + helm.sh/chart: kyverno-policies-3.1.3 +spec: + validationFailureAction: Audit + background: true + failurePolicy: Ignore + rules: + - name: check-sysctls + match: + any: + - resources: + kinds: + - Pod + validate: + message: >- + Setting additional sysctls above the allowed type is disallowed. + The field spec.securityContext.sysctls must be unset or not use any other names + than kernel.shm_rmid_forced, net.ipv4.ip_local_port_range, + net.ipv4.ip_unprivileged_port_start, net.ipv4.tcp_syncookies and + net.ipv4.ping_group_range. + pattern: + spec: + =(securityContext): + =(sysctls): + - =(name): "kernel.shm_rmid_forced | net.ipv4.ip_local_port_range | net.ipv4.ip_unprivileged_port_start | net.ipv4.tcp_syncookies | net.ipv4.ping_group_range" \ No newline at end of file diff --git a/targets.go b/targets.go index 22078a0..71d0019 100644 --- a/targets.go +++ b/targets.go @@ -2,6 +2,7 @@ package magekubernetes import ( "fmt" + "os" "strings" "github.com/magefile/mage/mg" // mg contains helpful utility functions, like Deps @@ -75,6 +76,74 @@ func ArgoCDDiff() error { return nil } +// validateKyvernoPolicies runs Kyverno validation on rendered Kubernetes manifests. +func validateKyvernoPolicies(renderedTemplatePaths string) error { + policyDir := "kyverno-policies" // Directory where policies are stored + + templateFiles, err := os.ReadDir(renderedTemplatePaths) + if err != nil { + return fmt.Errorf("failed to read rendered templates: %w", err) + } + for _, templateFile := range templateFiles { + // Skip if it’s a directory + + if templateFile.IsDir() { + continue + } + // Construct the full path for the current template file + templatePath := fmt.Sprintf("%s/%s", renderedTemplatePaths, templateFile.Name()) + + policyFiles, err := os.ReadDir(policyDir) + if err != nil { + return fmt.Errorf("failed to read Kyverno policies: %w", err) + } + + for _, policyFile := range policyFiles { + if !strings.HasSuffix(policyFile.Name(), ".yaml") { + continue + } + + policyFilePath := fmt.Sprintf("%s/%s", policyDir, policyFile.Name()) + cmdOptions := []string{ + "apply", policyFilePath, + "--resource", templatePath, + "--policy-report", + "--output", "yaml", + } + + output, err := sh.Output("kyverno", cmdOptions...) + if err != nil { + return fmt.Errorf("Kyverno validation failed for template '%s' with policy '%s': %w", templatePath, policyFilePath, err) + } + + fmt.Printf("Kyverno validation for template '%s' with policy '%s' completed.\n", templatePath, policyFilePath) + + if strings.Contains(output, "violation") || strings.Contains(output, "failed") { + return fmt.Errorf("Kyverno validation issues found in template '%s' with policy '%s': %s", templatePath, policyFilePath, output) + } + } + } + return nil +} + +// ValidateKyverno runs render Kubernetes manifests and invokes validateKyvernoPolicies. +func ValidateKyverno() error { + // Render templates and obtain paths + renderedTemplates, err := renderTemplates() + if err != nil { + return fmt.Errorf("failed to render templates: %w", err) + } + + // Validate rendered templates with Kyverno + err = validateKyvernoPolicies(renderedTemplates) + if err != nil { + return fmt.Errorf("Kyverno validation failed: %w", err) + } + + fmt.Println("All templates passed Kyverno validation.") + return nil +} + func kubeScore(paths string) error { if paths == "" { fmt.Printf("No files presented to kube-score, skipping") diff --git a/targets_test.go b/targets_test.go index b452fe4..3bbed5b 100644 --- a/targets_test.go +++ b/targets_test.go @@ -36,3 +36,21 @@ func TestOKKubeConform(t *testing.T) { t.Fatalf(`kubeConform(paths,"api-platform) should pass but failed with error %v`, err) } } + +// Test for manifest files expected to fail Kyverno policy validation +func TestFailedValidateKyverno(t *testing.T) { + path := "tests/templates/validate-fail/" + err := validateKyvernoPolicies(path) + if err == nil { + t.Fatalf("Expected validation to fail for manifest %s, but it passed", path) + } +} + +// Test for manifest files expected to pass Kyverno policy validation +func TestOKValidateKyverno(t *testing.T) { + path := "tests/templates/validate/" + err := validateKyvernoPolicies(path) + if err != nil { + t.Fatalf("Expected validation to pass for manifest %s, but it failed with error: %v", path, err) + } +} diff --git a/tests/templates/validate-fail/deployment-fail.yaml b/tests/templates/validate-fail/deployment-fail.yaml new file mode 100644 index 0000000..e0e0430 --- /dev/null +++ b/tests/templates/validate-fail/deployment-fail.yaml @@ -0,0 +1,34 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: insecure-app + labels: + app: insecure-app +spec: + replicas: 1 + selector: + matchLabels: + app: insecure-app + template: + metadata: + labels: + app: insecure-app + spec: + containers: + - name: insecure-container + image: nginx:latest # Violates "no latest tag" policy + securityContext: + capabilities: # Violates "disallow capabilities" policy + add: + - SETGID + - SYS_ADMIN + privileged: true # Violates "no privileged containers" policy + runAsNonRoot: false # Violates "must run as non-root" policy + readOnlyRootFilesystem: false # Violates "read-only root filesystem" policy + resources: + requests: + memory: "0" # Violates "resource requests and limits required" policy + cpu: "0" # Violates "resource requests and limits required" policy + limits: + memory: "0" + cpu: "0" diff --git a/tests/templates/validate/deployment-ok.yaml b/tests/templates/validate/deployment-ok.yaml new file mode 100644 index 0000000..00cc209 --- /dev/null +++ b/tests/templates/validate/deployment-ok.yaml @@ -0,0 +1,30 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: secure-app + labels: + app: secure-app +spec: + replicas: 1 + selector: + matchLabels: + app: secure-app + template: + metadata: + labels: + app: secure-app + spec: + containers: + - name: secure-container + image: nginx:1.21.6 # Uses a specific tag instead of 'latest' + securityContext: + runAsNonRoot: true # Enforces running as a non-root user + allowPrivilegeEscalation: false # Prevents privilege escalation + readOnlyRootFilesystem: true # Enforces read-only root filesystem + resources: + requests: # Defines minimum resource requests + memory: "64Mi" + cpu: "250m" + limits: # Defines maximum resource limits + memory: "128Mi" + cpu: "500m"