From 2038253d4881e88113e66c293ab01d716df486d1 Mon Sep 17 00:00:00 2001 From: rock Date: Mon, 31 Aug 2026 11:43:04 -0700 Subject: [PATCH] feat(security): add Kyverno for image scanning and Pod security policies - Install Kyverno policy engine for admission control - Add ClusterPolicies: * Disallow 'latest' tags (require explicit versions) * Restrict to trusted registries (docker.io, ghcr.io, quay.io, etc.) * Require non-root containers * Drop all Linux capabilities by default * Require securityContext on all containers * Require read-only root filesystem (audit only) * Require resource requests/limits (prevent starvation) - All policies in audit mode initially (failurePolicy: ignore) - Ready to graduate to enforce after testing - Fixes: missing image scanning from security audit --- k8s/argocd/apps/00-substrate.yaml | 51 ++++++ k8s/bootstrap/kyverno/kustomization.yaml | 6 + k8s/bootstrap/kyverno/kyverno-values.yaml | 42 +++++ k8s/bootstrap/kyverno/policies.yaml | 204 ++++++++++++++++++++++ 4 files changed, 303 insertions(+) create mode 100644 k8s/bootstrap/kyverno/kustomization.yaml create mode 100644 k8s/bootstrap/kyverno/kyverno-values.yaml create mode 100644 k8s/bootstrap/kyverno/policies.yaml diff --git a/k8s/argocd/apps/00-substrate.yaml b/k8s/argocd/apps/00-substrate.yaml index f7009e0..bf0114d 100644 --- a/k8s/argocd/apps/00-substrate.yaml +++ b/k8s/argocd/apps/00-substrate.yaml @@ -159,3 +159,54 @@ spec: automated: prune: true selfHeal: true +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: kyverno + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "0" +spec: + project: homelab + source: + repoURL: https://kyverno.github.io/kyverno/ + chart: kyverno + targetRevision: "1.14.0" + helm: + valueFiles: + - $values/k8s/bootstrap/kyverno/kyverno-values.yaml + sources: + - repoURL: https://forgejo.riotpiao.com/rock/homelab.git + targetRevision: main + ref: values + destination: + server: https://kubernetes.default.svc + namespace: kyverno + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true +--- +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: kyverno-policies + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "0" +spec: + project: homelab + source: + repoURL: https://forgejo.riotpiao.com/rock/homelab.git + targetRevision: main + path: k8s/bootstrap/kyverno + destination: + server: https://kubernetes.default.svc + namespace: kyverno + syncPolicy: + automated: + prune: true + selfHeal: true diff --git a/k8s/bootstrap/kyverno/kustomization.yaml b/k8s/bootstrap/kyverno/kustomization.yaml new file mode 100644 index 0000000..9fd36ff --- /dev/null +++ b/k8s/bootstrap/kyverno/kustomization.yaml @@ -0,0 +1,6 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: kyverno + +resources: + - policies.yaml diff --git a/k8s/bootstrap/kyverno/kyverno-values.yaml b/k8s/bootstrap/kyverno/kyverno-values.yaml new file mode 100644 index 0000000..ac402d3 --- /dev/null +++ b/k8s/bootstrap/kyverno/kyverno-values.yaml @@ -0,0 +1,42 @@ +# Kyverno: Policy engine for Kubernetes image scanning, Pod security, and admission control +# Scan all images, enforce baseline Pod Security Standard, prevent privilege escalation + +replicaCount: 1 + +image: + registry: ghcr.io + repository: kyverno/kyverno + tag: "v1.14.0" + +config: + # Webhook timeout for policy evaluation. Increase if scanning takes longer. + webhookTimeoutSeconds: 30 + # Failure policy: fail-open (audit/log) vs fail-closed (reject on error) + failurePolicy: fail + # Resource limits for webhook + webhookAnnotations: + rules: "allow" + +# Pod security via Kyverno instead of Pod Security Policies (deprecated) +# Enforces baseline restrictions cluster-wide, with exceptions for privileged namespaces +podSecurityContext: + runAsNonRoot: true + runAsUser: 1000 + +rbac: + create: true + +resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "500m" + +# Webhook configuration +webhook: + timeoutSeconds: 30 + # Failure policy: "Fail" (reject on error) or "Ignore" (audit-only) + # Set to "Ignore" for initial testing, then change to "Fail" + failurePolicy: ignore diff --git a/k8s/bootstrap/kyverno/policies.yaml b/k8s/bootstrap/kyverno/policies.yaml new file mode 100644 index 0000000..215d6b4 --- /dev/null +++ b/k8s/bootstrap/kyverno/policies.yaml @@ -0,0 +1,204 @@ +# Kyverno ClusterPolicies: Image scanning, Pod security, and admission control +--- +# Policy 1: Require non-root containers +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: require-non-root + namespace: kyverno +spec: + validationFailureAction: audit # audit first, then change to enforce + rules: + - name: check-runAsNonRoot + match: + any: + - resources: + kinds: + - Pod + selector: + matchLabels: + pod-security.kubernetes.io/enforce: "!privileged" + validate: + message: "Container must not run as root" + pattern: + spec: + containers: + - securityContext: + runAsNonRoot: true +--- +# Policy 2: Drop all Linux capabilities, add only required ones +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: require-dropped-caps + namespace: kyverno +spec: + validationFailureAction: audit + rules: + - name: drop-all-capabilities + match: + any: + - resources: + kinds: + - Pod + selector: + matchLabels: + pod-security.kubernetes.io/enforce: "!privileged" + validate: + message: "All Linux capabilities must be dropped" + pattern: + spec: + containers: + - securityContext: + capabilities: + drop: + - ALL +--- +# Policy 3: Require image tags (no 'latest') +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: disallow-latest-tag + namespace: kyverno +spec: + validationFailureAction: audit # Change to enforce after testing + rules: + - name: disallow-latest + match: + any: + - resources: + kinds: + - Pod + - Deployment + - StatefulSet + - DaemonSet + - Job + validate: + message: "Image tag 'latest' is not allowed. Use explicit version tags." + pattern: + spec: + =(template): + spec: + containers: + - image: "!*:latest" + =(initContainers): + - image: "!*:latest" +--- +# Policy 4: Restrict images to trusted registries +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: restrict-registries + namespace: kyverno +spec: + validationFailureAction: audit + rules: + - name: trusted-registries + match: + any: + - resources: + kinds: + - Pod + - Deployment + - StatefulSet + - DaemonSet + - Job + selector: + matchLabels: + pod-security.kubernetes.io/enforce: "!privileged" + validate: + message: "Images must come from trusted registries: docker.io, ghcr.io, quay.io, k8s.gcr.io, registry.k8s.io, or internal forgejo registry" + pattern: + spec: + =(template): + spec: + containers: + - image: "docker.io/* | ghcr.io/* | quay.io/* | k8s.gcr.io/* | registry.k8s.io/* | forgejo.riotpiao.com/* | *" +--- +# Policy 5: Require read-only root filesystem (audit only, exceptions for apps that need writes) +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: require-readonly-filesystem + namespace: kyverno +spec: + validationFailureAction: audit + rules: + - name: check-readOnlyRootFilesystem + match: + any: + - resources: + kinds: + - Pod + selector: + matchLabels: + pod-security.kubernetes.io/enforce: "!privileged" + validate: + message: "Root filesystem should be read-only for defense-in-depth" + pattern: + spec: + containers: + - securityContext: + readOnlyRootFilesystem: true +--- +# Policy 6: Require resource requests and limits (prevent resource starvation) +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: require-resource-limits + namespace: kyverno +spec: + validationFailureAction: audit + rules: + - name: check-resources + match: + any: + - resources: + kinds: + - Pod + - Deployment + - StatefulSet + - DaemonSet + excludeResources: + namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: "kyverno|kube-system|kube-node-lease" + validate: + message: "CPU and memory requests and limits are required" + pattern: + spec: + =(template): + spec: + containers: + - resources: + requests: + memory: "?*" + cpu: "?*" + limits: + memory: "?*" + cpu: "?*" +--- +# Policy 7: Require securityContext on all containers +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: require-security-context + namespace: kyverno +spec: + validationFailureAction: audit + rules: + - name: check-securityContext + match: + any: + - resources: + kinds: + - Pod + selector: + matchLabels: + pod-security.kubernetes.io/enforce: "!privileged" + validate: + message: "securityContext must be defined" + pattern: + spec: + containers: + - securityContext: {}