Add Kyverno admission policies and CIS mappings
Kube-bench CIS scan / Scan ephemeral K3s cluster (push) Failing after 3m11s

This commit is contained in:
2026-08-10 17:34:45 -04:00
parent fc786ec63c
commit ff033f2fcb
10 changed files with 253 additions and 12 deletions
+1 -1
View File
@@ -34,7 +34,7 @@ It is not needed for the portfolio demo and is not run in CI.
- [ ] Phase 2: ArgoCD app-of-apps deployment (scaffolded; runtime verification pending) - [ ] Phase 2: ArgoCD app-of-apps deployment (scaffolded; runtime verification pending)
- [x] Phase 3: kube-bench CI scan - [x] Phase 3: kube-bench CI scan
- [ ] Architecture revision: Flatcar hosts and Cilium kube-proxy replacement (configured; runtime verification pending) - [ ] Architecture revision: Flatcar hosts and Cilium kube-proxy replacement (configured; runtime verification pending)
- [ ] Phase 4: Kyverno policy set and CIS mapping - [ ] Phase 4: Kyverno policy set and CIS mapping (configured; runtime verification pending)
- [ ] Phase 5: Falco rules and webhook alerting - [ ] Phase 5: Falco rules and webhook alerting
- [ ] Phase 6: test workloads and evidence capture - [ ] Phase 6: test workloads and evidence capture
- [ ] Phase 7: architecture/design documentation - [ ] Phase 7: architecture/design documentation
+19
View File
@@ -0,0 +1,19 @@
# CIS control to Kyverno mapping
The policy set is scoped to namespaces carrying the
`security-baseline/open: "true"` label. This makes the local demo predictable
and avoids applying workload admission rules to platform namespaces.
| CIS control | Policy | What the demo proves | Boundary |
| --- | --- | --- | --- |
| 5.2.2 | `disallow-privileged` | A Pod with `privileged: true` is rejected. | Does not replace host hardening or Linux capability review. |
| 5.2.3 | `disallow-host-network` | A Pod with `hostPID: true` is rejected. | Does not cover every host namespace or hostPath risk. |
| 5.2.5 | `disallow-host-network` | A Pod with `hostNetwork: true` is rejected. | Does not replace network policy. |
| 5.2.7 | `require-non-root` | A Pod without explicit non-root execution is rejected. | Does not prove the image cannot switch users internally. |
| 5.5.1 | `restrict-image-registries` | Images outside the approved registry set are rejected. | Full provenance needs signing, verification, and trusted build controls. |
| 5.7.3 supplemental | `require-resource-limits` | Pods must declare CPU and memory limits. | Resource limits are a defense-in-depth control, not a direct CIS test. |
The Kubernetes CIS Benchmark is broader than admission policy. kube-bench
covers node, control-plane, RBAC, audit, network policy, and manual review
areas that these Kyverno policies do not implement.
+24
View File
@@ -0,0 +1,24 @@
# Kyverno policy baseline
These policies are `ClusterPolicy` resources, but they intentionally select
only namespaces labeled `security-baseline/open: "true"`. That keeps the
portfolio demo enforceable without blocking Kyverno, Falco, ArgoCD, or other
platform components. Expand the label scope deliberately for a real cluster.
All five policies use `validationFailureAction: Enforce`, so a matching Pod is
rejected at admission time. The policies use `background: false` because the
goal is to demonstrate admission enforcement, not retroactively evaluate
existing objects.
| Policy | CIS alignment | Enforcement |
| --- | --- | --- |
| `disallow-privileged` | 5.2.2 | Rejects privileged containers and init containers. |
| `require-non-root` | 5.2.7 | Requires `runAsNonRoot: true` on containers and init containers. |
| `require-resource-limits` | 5.7.3 supplemental | Requires CPU and memory limits. This is a supplemental control, not a one-to-one CIS test. |
| `restrict-image-registries` | 5.5.1 supplemental | Allows only approved registries. Full image provenance requires signing and verification as well. |
| `disallow-host-network` | 5.2.3 and 5.2.5 | Rejects host PID, host network, and host IPC access. |
The CIS benchmark includes manual checks and platform-specific exceptions.
The mapping above describes the control objective each policy supports, not a
claim that one Kyverno rule implements the entire benchmark control.
@@ -0,0 +1,32 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-host-network
annotations:
policies.kyverno.io/title: Disallow host network and host PID
policies.kyverno.io/category: Pod Security Standards
policies.kyverno.io/severity: high
# CIS 5.2.3: minimize hostPID; CIS 5.2.5: minimize hostNetwork.
policies.kyverno.io/cis-control: "5.2.3, 5.2.5"
spec:
validationFailureAction: Enforce
background: false
failurePolicy: Fail
rules:
- name: no-host-network-or-pid
match:
any:
- resources:
kinds:
- Pod
namespaceSelector:
matchLabels:
security-baseline/open: "true"
validate:
message: hostNetwork, hostPID, and hostIPC must be false or omitted.
pattern:
spec:
=(hostNetwork): false
=(hostPID): false
=(hostIPC): false
+42
View File
@@ -0,0 +1,42 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-privileged
annotations:
policies.kyverno.io/title: Disallow privileged containers
policies.kyverno.io/category: Pod Security Standards
policies.kyverno.io/severity: high
# CIS 5.2.2: minimize admission of privileged containers.
policies.kyverno.io/cis-control: "5.2.2"
spec:
validationFailureAction: Enforce
background: false
failurePolicy: Fail
rules:
- name: privileged-containers
match:
any:
- resources:
kinds:
- Pod
namespaceSelector:
matchLabels:
security-baseline/open: "true"
validate:
message: Privileged containers are not allowed in security-baseline namespaces.
foreach:
- list: request.object.spec.containers[]
deny:
conditions:
any:
- key: "{{ element.securityContext.privileged || false }}"
operator: Equals
value: true
- list: request.object.spec.initContainers[]
deny:
conditions:
any:
- key: "{{ element.securityContext.privileged || false }}"
operator: Equals
value: true
+5 -2
View File
@@ -2,5 +2,8 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization kind: Kustomization
resources: resources:
- namespace.yaml - namespace.yaml
- phase-2-placeholder.yaml - disallow-privileged.yaml
- require-non-root.yaml
- require-resource-limits.yaml
- restrict-image-registries.yaml
- disallow-host-network.yaml
@@ -1,9 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: policy-baseline-status
namespace: security-baseline
data:
phase: "2"
status: "GitOps target established; Kyverno policies arrive in phase 4"
+42
View File
@@ -0,0 +1,42 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-non-root
annotations:
policies.kyverno.io/title: Require non-root containers
policies.kyverno.io/category: Pod Security Standards
policies.kyverno.io/severity: high
# CIS 5.2.7: minimize admission of root containers.
policies.kyverno.io/cis-control: "5.2.7"
spec:
validationFailureAction: Enforce
background: false
failurePolicy: Fail
rules:
- name: containers-run-as-non-root
match:
any:
- resources:
kinds:
- Pod
namespaceSelector:
matchLabels:
security-baseline/open: "true"
validate:
message: Containers must explicitly set runAsNonRoot=true.
foreach:
- list: request.object.spec.containers[]
deny:
conditions:
any:
- key: "{{ element.securityContext.runAsNonRoot || false }}"
operator: Equals
value: false
- list: request.object.spec.initContainers[]
deny:
conditions:
any:
- key: "{{ element.securityContext.runAsNonRoot || false }}"
operator: Equals
value: false
@@ -0,0 +1,49 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-resource-limits
annotations:
policies.kyverno.io/title: Require CPU and memory limits
policies.kyverno.io/category: Resource governance
policies.kyverno.io/severity: medium
# Supplemental hardening aligned with CIS 5.7.3 workload controls. The
# CIS benchmark does not define a one-to-one resource-limit test.
policies.kyverno.io/cis-control: "5.7.3 supplemental"
spec:
validationFailureAction: Enforce
background: false
failurePolicy: Fail
rules:
- name: containers-have-resource-limits
match:
any:
- resources:
kinds:
- Pod
namespaceSelector:
matchLabels:
security-baseline/open: "true"
validate:
message: Every container must define CPU and memory limits.
foreach:
- list: request.object.spec.containers[]
deny:
conditions:
any:
- key: "{{ element.resources.limits.cpu || '' }}"
operator: Equals
value: ""
- key: "{{ element.resources.limits.memory || '' }}"
operator: Equals
value: ""
- list: request.object.spec.initContainers[]
deny:
conditions:
any:
- key: "{{ element.resources.limits.cpu || '' }}"
operator: Equals
value: ""
- key: "{{ element.resources.limits.memory || '' }}"
operator: Equals
value: ""
@@ -0,0 +1,39 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: restrict-image-registries
annotations:
policies.kyverno.io/title: Restrict image registries
policies.kyverno.io/category: Supply chain
policies.kyverno.io/severity: high
# CIS 5.5.1: image provenance. This is a concrete admission control that
# supports the broader provenance objective but is not the full CIS test.
policies.kyverno.io/cis-control: "5.5.1 supplemental"
spec:
validationFailureAction: Enforce
background: false
failurePolicy: Fail
rules:
- name: images-from-approved-registries
match:
any:
- resources:
kinds:
- Pod
namespaceSelector:
matchLabels:
security-baseline/open: "true"
validate:
message: Images must come from ghcr.io, quay.io, or docker.io/library.
foreach:
- list: request.object.spec.containers[]
anyPattern:
- image: ghcr.io/*
- image: quay.io/*
- image: docker.io/library/*
- list: request.object.spec.initContainers[]
anyPattern:
- image: ghcr.io/*
- image: quay.io/*
- image: docker.io/library/*