Skip to content

Kubernetes admission, network, and image-policy lab

Exercises pod hardening, network-policy, and image-decision cases using local fixtures and pinned policy schemas.

Cloud Security 3 min read

Implementation: Partially tested

Implementation

hardened-pods.yaml YAML · 78 lines
labs/kubernetes-security/policies/hardened-pods.yaml
1apiVersion: policies.kyverno.io/v1
2kind: ValidatingPolicy
3metadata:
4  name: hardened-pods
5  annotations:
6    policies.kyverno.io/title: Hardened Pod baseline
7    policies.kyverno.io/category: Pod Security
8    policies.kyverno.io/severity: high
9spec:
10  validationActions:
11    - Deny
12  failurePolicy: Fail
13  evaluation:
14    admission:
15      enabled: true
16    background:
17      enabled: true
18    mode: Kubernetes
19  webhookConfiguration:
20    timeoutSeconds: 10
21  matchConstraints:
22    resourceRules:
23      - apiGroups:
24          - ""
25        apiVersions:
26          - v1
27        operations:
28          - CREATE
29          - UPDATE
30        resources:
31          - pods
32  variables:
33    - name: allContainers
34      expression: >-
35        object.spec.containers
36        + object.spec.?initContainers.orValue([])
37        + object.spec.?ephemeralContainers.orValue([])
38  validations:
39    - message: Host PID, IPC, and network namespaces are not allowed.
40      expression: >-
41        !object.spec.?hostPID.orValue(false)
42        && !object.spec.?hostIPC.orValue(false)
43        && !object.spec.?hostNetwork.orValue(false)
44    - message: hostPath volumes are not allowed by this workload baseline.
45      expression: >-
46        !has(object.spec.volumes)
47        || object.spec.volumes.all(volume, !has(volume.hostPath))
48    - message: Service-account token automount must be explicitly disabled.
49      expression: object.spec.?automountServiceAccountToken.orValue(true) == false
50    - message: Containers must run as non-root without privileged mode or privilege escalation.
51      expression: >-
52        variables.allContainers.all(container,
53          has(container.securityContext)
54          && container.securityContext.?runAsNonRoot.orValue(false) == true
55          && container.securityContext.?privileged.orValue(false) == false
56          && container.securityContext.?allowPrivilegeEscalation.orValue(true) == false)
57    - message: Containers must use a read-only root filesystem and drop all capabilities.
58      expression: >-
59        variables.allContainers.all(container,
60          container.securityContext.?readOnlyRootFilesystem.orValue(false) == true
61          && has(container.securityContext.capabilities)
62          && container.securityContext.capabilities.?drop.orValue([]).exists(capability, capability == "ALL")
63          && size(container.securityContext.capabilities.?add.orValue([])) == 0)
64    - message: A RuntimeDefault or Localhost seccomp profile is required.
65      expression: >-
66        has(object.spec.securityContext)
67        && has(object.spec.securityContext.seccompProfile)
68        && object.spec.securityContext.seccompProfile.type in ["RuntimeDefault", "Localhost"]
69    - message: CPU and memory requests and limits are required for every container.
70      expression: >-
71        variables.allContainers.all(container,
72          has(container.resources)
73          && has(container.resources.requests)
74          && has(container.resources.requests.cpu)
75          && has(container.resources.requests.memory)
76          && has(container.resources.limits)
77          && has(container.resources.limits.cpu)
78          && has(container.resources.limits.memory))

Run it

  • kyverno test labs/kubernetes-security
  • node labs/kubernetes-security/tests/run-tests.js

Evidence status: partially tested lab. Native Kyverno v1.18.2 testing exercised the hardened-pod policy. The image policy is a schema-validated example; signature, certificate, registry, transparency, mutation, and live admission were not tested.

This lab validates a narrow Kubernetes security baseline with positive and negative fixtures. It does not claim that a namespace, admission policy, or NetworkPolicy is a hard hostile-tenant boundary.

Pinned validation scope

  • Kubernetes API shapes reviewed for Kubernetes 1.34.
  • Kyverno CLI 1.18.2.
  • Kyverno policies.kyverno.io/v1 ValidatingPolicy and ImageValidatingPolicy, stable in Kyverno 1.18.

Run the dependency-free structural and identity-policy tests:

node labs/kubernetes-security/tests/run-tests.js

Run the native Kyverno tests:

kyverno test labs/kubernetes-security --remove-color

On 2026-07-23, the official Kyverno v1.18.2 Windows CLI asset (SHA-256 b5c9d1cb75587a312dc8334537a5773bdedb1a985deae9d89a5251385afb831f) ran the native hardened-pod suite: 7 tests passed and 0 tests failed. This native run does not include verify-release-images.yaml.

Enforcement demonstrated

policies/hardened-pods.yaml denies:

  • privileged containers and privilege escalation;
  • host PID, IPC, or network namespaces;
  • hostPath volumes;
  • added Linux capabilities or failure to drop ALL;
  • writable root filesystems;
  • missing non-root and seccomp configuration;
  • missing CPU/memory requests or limits; and
  • service-account token automount unless explicitly disabled.

The accepted Pod is tested alongside privileged, host-namespace, hostPath, capability, missing-resource, and token-automount negative fixtures.

policies/verify-release-images.yaml uses the stable Kyverno 1.18 policies.kyverno.io/v1 ImageValidatingPolicy. It constrains repository, keyless issuer, full workflow-and-branch subject, SLSA provenance predicate type, required verification, digest verification, and failurePolicy: Fail. Separate :* and @sha256:* globs make tagged and digest references to the exact repository eligible for evaluation while excluding lookalike repositories.

On 2026-07-23, the manifest conformed to Kyverno v1.18.2's official ImageValidatingPolicy CRD v1 schema. The downloaded CRD asset SHA-256 was 3528151f3717c9946ee56d60866f2cf6c29a4b1a7e759c72af60451147b995c2. The Node harness separately evaluates eleven synthetic, already-resolved evidence claim sets. It returns false for unsigned, wrong repository/workflow/branch/issuer/predicate, missing or malformed provenance, verifier/transparency failure, and digest mismatch.

The twelfth case proves only that a tag such as :latest matches the reviewed tag selector. Its admission result is deliberately null: the harness does not model registry resolution or Kyverno's mutateDigest behavior and therefore makes no accept/deny claim for tag-only input. This is a schema-validated example; no live enforcement test. Signature, certificate, registry, transparency-log, mutation, webhook failure, and controller behavior were not executed.

Network boundary

fixtures/network-policies.yaml contains namespace-wide default-deny ingress and egress plus an explicit DNS exception. NetworkPolicy has an effect only when the selected CNI implements it. DNS labels, ports, node-local DNS paths, dual-stack behavior, and required application egress must be verified in each cluster. NetworkPolicy does not control all host-network, node, service-mesh, or cloud-network paths.

Operational rollout

Use observe → audit → warn → enforce → measure bypasses. Track policy evaluation errors, denials, exceptions and expiry, unsigned-image attempts, registry/rekor availability, admission latency, and workloads that require a separate runtime class or cluster. Keep a reviewed break-glass path outside tenant administrator control.

References