From 3752fd03861f6db938c13a97f41b72b9376a1057 Mon Sep 17 00:00:00 2001 From: Greg Hendrickson Date: Mon, 9 Feb 2026 18:02:21 +0000 Subject: [PATCH] feat(kyverno): add policy engine with security baseline - Kyverno 3.3.4 via Helm (HA config: 3 admission, 2 background replicas) - Validation policies: - disallow-privileged-containers (Enforce) - require-resource-limits (Enforce) - require-labels (Audit - standard k8s labels) - require-run-as-non-root (Audit) - disallow-latest-tag (Enforce - GitOps reproducibility) - Mutating policy: - add-default-securitycontext (seccomp, drop caps, read-only fs) - System namespaces excluded (kube-system, kyverno, istio-system) - Auto-discovered by ArgoCD ApplicationSet Reference: CIS Kubernetes Benchmark, Pod Security Standards --- README.md | 22 ++++++ infrastructure/kyverno/kustomization.yaml | 62 +++++++++++++++++ infrastructure/kyverno/namespace.yaml | 10 +++ .../policies/add-default-securitycontext.yaml | 68 ++++++++++++++++++ .../kyverno/policies/disallow-latest-tag.yaml | 60 ++++++++++++++++ .../kyverno/policies/disallow-privileged.yaml | 47 +++++++++++++ .../kyverno/policies/kustomization.yaml | 12 ++++ .../kyverno/policies/require-labels.yaml | 69 +++++++++++++++++++ .../kyverno/policies/require-non-root.yaml | 54 +++++++++++++++ .../policies/require-resource-limits.yaml | 41 +++++++++++ 10 files changed, 445 insertions(+) create mode 100644 infrastructure/kyverno/kustomization.yaml create mode 100644 infrastructure/kyverno/namespace.yaml create mode 100644 infrastructure/kyverno/policies/add-default-securitycontext.yaml create mode 100644 infrastructure/kyverno/policies/disallow-latest-tag.yaml create mode 100644 infrastructure/kyverno/policies/disallow-privileged.yaml create mode 100644 infrastructure/kyverno/policies/kustomization.yaml create mode 100644 infrastructure/kyverno/policies/require-labels.yaml create mode 100644 infrastructure/kyverno/policies/require-non-root.yaml create mode 100644 infrastructure/kyverno/policies/require-resource-limits.yaml diff --git a/README.md b/README.md index cfea3cb..3b1b6f9 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ See [docs/BOOTSTRAP.md](docs/BOOTSTRAP.md) for full setup guide. │ └── dev/ ├── infrastructure/ # Cluster infrastructure │ ├── cert-manager/ # ✅ TLS with Let's Encrypt +│ ├── kyverno/ # ✅ Policy engine (security + best practices) │ ├── networking/ # Istio gateway, NetworkPolicies │ ├── storage/ # NFS StorageClass │ └── monitoring/ # Prometheus, Grafana, Loki @@ -69,6 +70,27 @@ Uses **ArgoCD ApplicationSets** with Git Directory Generator: - 🏠 Home Assistant - 📊 Homepage, Uptime Kuma +## Policy Engine (Kyverno) + +Kyverno enforces security and best practices across the cluster. Policies include: + +| Policy | Mode | Description | +|--------|------|-------------| +| `disallow-privileged` | Enforce | Blocks privileged containers | +| `require-resource-limits` | Enforce | Requires CPU/memory limits | +| `require-labels` | Audit | Standard labeling for workloads | +| `require-non-root` | Audit | Non-root container requirement | +| `disallow-latest-tag` | Enforce | Requires explicit image tags | +| `add-default-securitycontext` | Mutate | Adds secure defaults automatically | + +Policies in **Audit** mode generate reports without blocking. Promote to **Enforce** after validating existing workloads. + +```bash +# Check policy reports +kubectl get policyreports -A +kubectl get clusterpolicyreports +``` + ## Secrets Management Encrypted with **SOPS + age**. Configuration in `.sops.yaml`. diff --git a/infrastructure/kyverno/kustomization.yaml b/infrastructure/kyverno/kustomization.yaml new file mode 100644 index 0000000..3067b7a --- /dev/null +++ b/infrastructure/kyverno/kustomization.yaml @@ -0,0 +1,62 @@ +# infrastructure/kyverno/kustomization.yaml +# Kyverno Policy Engine - GitOps-native Kubernetes policy enforcement +# CNCF Graduated project, integrates seamlessly with ArgoCD +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: kyverno + +resources: + - namespace.yaml + - policies/ + +# Kyverno deployment via Helm +helmCharts: + - name: kyverno + repo: https://kyverno.github.io/kyverno/ + version: "3.3.4" + releaseName: kyverno + namespace: kyverno + valuesInline: + # Admission controller replicas for HA + admissionController: + replicas: 3 + resources: + limits: + memory: 512Mi + requests: + cpu: 100m + memory: 256Mi + # Background controller for generate/mutate policies + backgroundController: + replicas: 2 + resources: + limits: + memory: 256Mi + requests: + cpu: 50m + memory: 128Mi + # Reports controller for policy reports + reportsController: + replicas: 2 + # Cleanup controller + cleanupController: + replicas: 2 + # Enable policy exception support + features: + policyExceptions: + enabled: true + namespace: "kyverno" + # Webhooks config + config: + webhooks: + # Exclude system namespaces from validation + - namespaceSelector: + matchExpressions: + - key: kubernetes.io/metadata.name + operator: NotIn + values: + - kube-system + - kube-public + - kube-node-lease + - kyverno diff --git a/infrastructure/kyverno/namespace.yaml b/infrastructure/kyverno/namespace.yaml new file mode 100644 index 0000000..aef4744 --- /dev/null +++ b/infrastructure/kyverno/namespace.yaml @@ -0,0 +1,10 @@ +# infrastructure/kyverno/namespace.yaml +apiVersion: v1 +kind: Namespace +metadata: + name: kyverno + labels: + app.kubernetes.io/name: kyverno + app.kubernetes.io/component: policy-engine + # Exempt from Pod Security Standards (Kyverno needs privileges) + pod-security.kubernetes.io/enforce: privileged diff --git a/infrastructure/kyverno/policies/add-default-securitycontext.yaml b/infrastructure/kyverno/policies/add-default-securitycontext.yaml new file mode 100644 index 0000000..6bfb155 --- /dev/null +++ b/infrastructure/kyverno/policies/add-default-securitycontext.yaml @@ -0,0 +1,68 @@ +# infrastructure/kyverno/policies/add-default-securitycontext.yaml +# Mutating policy: adds secure defaults to pods missing securityContext +# Implements defense-in-depth by setting secure defaults +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: add-default-securitycontext + annotations: + policies.kyverno.io/title: Add Default Security Context + policies.kyverno.io/category: Best Practices + policies.kyverno.io/severity: low + policies.kyverno.io/subject: Pod + policies.kyverno.io/description: >- + Mutating policy that adds secure default securityContext to pods + that don't specify one. Reduces attack surface by dropping + capabilities and making filesystem read-only where possible. + pod-policies.kyverno.io/autogen-controllers: DaemonSet,Deployment,Job,StatefulSet,ReplicaSet +spec: + # Mutate rules apply during admission + rules: + - name: add-pod-security-context + match: + any: + - resources: + kinds: + - Pod + exclude: + any: + - resources: + namespaces: + - kube-system + - kyverno + - istio-system + mutate: + patchStrategicMerge: + spec: + # Add pod-level securityContext if missing + +(securityContext): + seccompProfile: + type: RuntimeDefault + # Don't allow privilege escalation by default + runAsNonRoot: true + - name: add-container-security-context + match: + any: + - resources: + kinds: + - Pod + exclude: + any: + - resources: + namespaces: + - kube-system + - kyverno + - istio-system + mutate: + foreach: + - list: "request.object.spec.containers" + patchStrategicMerge: + spec: + containers: + - name: "{{ element.name }}" + +(securityContext): + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true diff --git a/infrastructure/kyverno/policies/disallow-latest-tag.yaml b/infrastructure/kyverno/policies/disallow-latest-tag.yaml new file mode 100644 index 0000000..ce710bf --- /dev/null +++ b/infrastructure/kyverno/policies/disallow-latest-tag.yaml @@ -0,0 +1,60 @@ +# infrastructure/kyverno/policies/disallow-latest-tag.yaml +# Prevents use of 'latest' image tag for reproducibility +# GitOps best practice: always use explicit image versions +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: disallow-latest-tag + annotations: + policies.kyverno.io/title: Disallow Latest Tag + policies.kyverno.io/category: Best Practices + policies.kyverno.io/severity: medium + policies.kyverno.io/subject: Pod + policies.kyverno.io/description: >- + The 'latest' tag is mutable and can change unexpectedly, making + deployments non-reproducible. This policy requires explicit + image tags for GitOps traceability. +spec: + validationFailureAction: Enforce + background: true + rules: + - name: require-image-tag + match: + any: + - resources: + kinds: + - Pod + exclude: + any: + - resources: + namespaces: + - kube-system + - kyverno + validate: + message: "Images must use an explicit tag (not 'latest'). Specify a version tag like ':v1.2.3' or SHA digest." + pattern: + spec: + containers: + - image: "*:*" + =(initContainers): + - image: "*:*" + - name: validate-not-latest + match: + any: + - resources: + kinds: + - Pod + exclude: + any: + - resources: + namespaces: + - kube-system + - kyverno + validate: + message: "The 'latest' tag is not allowed. Use a specific version tag." + pattern: + spec: + containers: + - image: "!*:latest" + =(initContainers): + - image: "!*:latest" diff --git a/infrastructure/kyverno/policies/disallow-privileged.yaml b/infrastructure/kyverno/policies/disallow-privileged.yaml new file mode 100644 index 0000000..57dec71 --- /dev/null +++ b/infrastructure/kyverno/policies/disallow-privileged.yaml @@ -0,0 +1,47 @@ +# infrastructure/kyverno/policies/disallow-privileged.yaml +# Prevents pods from running as privileged containers +# Security baseline: CIS Benchmark 5.2.1 +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: high + policies.kyverno.io/subject: Pod + policies.kyverno.io/description: >- + Privileged containers have all Linux capabilities and can access + host resources. This policy prevents privileged containers from + being created except in system namespaces. +spec: + validationFailureAction: Enforce + background: true + rules: + - name: privileged-containers + match: + any: + - resources: + kinds: + - Pod + exclude: + any: + - resources: + namespaces: + - kube-system + - kyverno + - istio-system + - cert-manager + validate: + message: "Privileged containers are not allowed. Set securityContext.privileged to false." + pattern: + spec: + containers: + - securityContext: + privileged: "false" + =(initContainers): + - securityContext: + privileged: "false" + =(ephemeralContainers): + - securityContext: + privileged: "false" diff --git a/infrastructure/kyverno/policies/kustomization.yaml b/infrastructure/kyverno/policies/kustomization.yaml new file mode 100644 index 0000000..af62224 --- /dev/null +++ b/infrastructure/kyverno/policies/kustomization.yaml @@ -0,0 +1,12 @@ +# infrastructure/kyverno/policies/kustomization.yaml +# Security policies for cluster-wide enforcement +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - disallow-privileged.yaml + - require-resource-limits.yaml + - require-labels.yaml + - require-non-root.yaml + - disallow-latest-tag.yaml + - add-default-securitycontext.yaml diff --git a/infrastructure/kyverno/policies/require-labels.yaml b/infrastructure/kyverno/policies/require-labels.yaml new file mode 100644 index 0000000..56fcd04 --- /dev/null +++ b/infrastructure/kyverno/policies/require-labels.yaml @@ -0,0 +1,69 @@ +# infrastructure/kyverno/policies/require-labels.yaml +# Enforces standard labeling for all workloads +# Enables proper resource organization and cost tracking +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: require-labels + annotations: + policies.kyverno.io/title: Require Labels + policies.kyverno.io/category: Best Practices + policies.kyverno.io/severity: medium + policies.kyverno.io/subject: Pod, Deployment, StatefulSet, DaemonSet + policies.kyverno.io/description: >- + Labels are essential for organizing, filtering, and managing + Kubernetes resources. This policy requires standard labels on + all workloads. +spec: + validationFailureAction: Audit # Start in audit mode + background: true + rules: + - name: check-deployment-labels + match: + any: + - resources: + kinds: + - Deployment + - StatefulSet + - DaemonSet + exclude: + any: + - resources: + namespaces: + - kube-system + - kyverno + validate: + message: "Workloads must have 'app.kubernetes.io/name' and 'app.kubernetes.io/part-of' labels." + pattern: + metadata: + labels: + app.kubernetes.io/name: "?*" + app.kubernetes.io/part-of: "?*" + - name: check-pod-labels + match: + any: + - resources: + kinds: + - Pod + exclude: + any: + - resources: + namespaces: + - kube-system + - kyverno + # Exclude pods created by controllers (they inherit parent labels) + selector: + matchLabels: + app.kubernetes.io/managed-by: "*" + preconditions: + all: + # Only check standalone pods (not owned by ReplicaSet, etc.) + - key: "{{ request.object.metadata.ownerReferences[] || `[]` | length(@) }}" + operator: Equals + value: 0 + validate: + message: "Pods must have 'app.kubernetes.io/name' label." + pattern: + metadata: + labels: + app.kubernetes.io/name: "?*" diff --git a/infrastructure/kyverno/policies/require-non-root.yaml b/infrastructure/kyverno/policies/require-non-root.yaml new file mode 100644 index 0000000..9b0f646 --- /dev/null +++ b/infrastructure/kyverno/policies/require-non-root.yaml @@ -0,0 +1,54 @@ +# infrastructure/kyverno/policies/require-non-root.yaml +# Requires containers to run as non-root user +# Security baseline: CIS Benchmark 5.2.6 +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: require-run-as-non-root + annotations: + policies.kyverno.io/title: Require Run As Non-Root + policies.kyverno.io/category: Pod Security Standards (Restricted) + policies.kyverno.io/severity: high + policies.kyverno.io/subject: Pod + policies.kyverno.io/description: >- + Running as root inside a container is a security risk. If a + container breakout occurs, root in the container could become + root on the host. This policy requires containers to run as + a non-root user. +spec: + validationFailureAction: Audit # Audit first, Enforce after baseline + background: true + rules: + - name: run-as-non-root + match: + any: + - resources: + kinds: + - Pod + exclude: + any: + - resources: + namespaces: + - kube-system + - kyverno + - istio-system + validate: + message: "Containers must run as non-root. Set runAsNonRoot: true or specify a non-root runAsUser." + anyPattern: + # Pattern 1: Pod-level runAsNonRoot + - spec: + securityContext: + runAsNonRoot: true + # Pattern 2: Container-level runAsNonRoot + - spec: + containers: + - securityContext: + runAsNonRoot: true + # Pattern 3: Explicit non-root UID (>= 1000) + - spec: + securityContext: + runAsUser: ">999" + - spec: + containers: + - securityContext: + runAsUser: ">999" diff --git a/infrastructure/kyverno/policies/require-resource-limits.yaml b/infrastructure/kyverno/policies/require-resource-limits.yaml new file mode 100644 index 0000000..f452d1d --- /dev/null +++ b/infrastructure/kyverno/policies/require-resource-limits.yaml @@ -0,0 +1,41 @@ +# infrastructure/kyverno/policies/require-resource-limits.yaml +# Ensures all pods have resource limits defined +# Prevents resource exhaustion and enables proper scheduling +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: require-resource-limits + annotations: + policies.kyverno.io/title: Require Resource Limits + policies.kyverno.io/category: Best Practices + policies.kyverno.io/severity: medium + policies.kyverno.io/subject: Pod + policies.kyverno.io/description: >- + Resource limits prevent a single workload from consuming excessive + cluster resources. This policy requires all containers to define + CPU and memory limits. +spec: + validationFailureAction: Enforce + background: true + rules: + - name: validate-resources + match: + any: + - resources: + kinds: + - Pod + exclude: + any: + - resources: + namespaces: + - kube-system + - kyverno + validate: + message: "CPU and memory limits are required. Add resources.limits.cpu and resources.limits.memory." + pattern: + spec: + containers: + - resources: + limits: + memory: "?*" + cpu: "?*"