Command Palette

Search for a command to run...

Mission 0.4 · Stage 0 — Platform Foundations: The Paved Road on Kubernetes

Guardrails as Code: Kyverno Policies

Goal: The cluster rejects unsafe workloads, fills in sensible defaults, and reports violations, so standards are enforced automatically instead of in review meetings.

50 min Free 6 steps 2 break-it drills

By the end of this mission

  • Compare Kyverno with OPA Gatekeeper and Pod Security Admission
  • Write validate, mutate, and generate policies
  • Roll out policies in Audit mode first, then Enforce
  • Test policies in CI with the Kyverno CLI

Part 1

Understand it first

Why policy engines

A platform can't review every manifest by hand. Admission controllers (Kubernetes course, admission controllers) intercept every create/update in the API server. A POLICY ENGINE plugs in there and evaluates rules: block privileged containers, require resource requests, require an owner label, allow images only from our registry, and require signed images (DevSecOps course, image signing).

KYVERNO writes policies as Kubernetes YAML: easy for platform teams, with no new language. OPA GATEKEEPER uses Rego, a powerful general-purpose policy language also used outside Kubernetes (Terraform plans, APIs). POD SECURITY ADMISSION (built in) covers the basics of pod security per namespace (restricted, baseline) with no extra install. Many platforms use PSA for the baseline plus Kyverno or Gatekeeper for organisation-specific rules.

Three kinds of rule

VALIDATE: allow or deny (or just report) a resource. MUTATE: change it on the way in (add default labels, set imagePullPolicy, inject a securityContext). GENERATE: create other resources when something happens (every new team namespace gets a default NetworkPolicy, ResourceQuota, and LimitRange). Kyverno also has VERIFYIMAGES rules for Cosign/Notary signatures, and background scans that report violations in existing resources as PolicyReports.

Roll out safely

A new enforce-mode policy can instantly block every deploy of a team that doesn't comply yet. Start with validationFailureAction: Audit (violations are reported, not blocked), publish the report, give teams time (and a golden path that already complies), then switch to Enforce. Exclude system namespaces explicitly. Test policies in CI against sample good and bad manifests.

Part 2

Your project after this mission · 6 files change

shoplite-platform/
  • shoplite-platform/
    • addons/
      • kyverno/
        • policies/
          • add-default-labels.yamlnew
          • namespace-defaults.yamlnew
          • require-requests-limits.yamlnew
          • restrict-registries.yamlnew
        • tests/
          • kyverno-test.yamlnew
        • kyverno-app.yamlnew

Part 3

Build it, step by step

  1. 1

    Install Kyverno

    Chart kyverno from https://kyverno.github.io/kyverno/, 3 admission controller replicas for HA (if Kyverno is down and its webhook fails closed, nothing can be created). Policies go in a separate Application synced after Kyverno is healthy (sync waves).

    shoplite-platform/addons/kyverno/kyverno-app.yamlwhole fileyaml
    apiVersion: argoproj.io/v1alpha1
    kind: Application
    metadata:
      name: kyverno
      namespace: argocd
      annotations: { argocd.argoproj.io/sync-wave: "-5" }
    spec:
      project: default
      source:
        repoURL: https://kyverno.github.io/kyverno/
        chart: kyverno
        targetRevision: 3.5.1
        helm:
          valuesObject:
            admissionController: { replicas: 3 }
      destination:
        server: https://kubernetes.default.svc
        namespace: kyverno
      syncPolicy:
        automated: { prune: true, selfHeal: true }
        syncOptions: [CreateNamespace=true, ServerSideApply=true]
  2. 2

    Validate: require requests and limits

    Every container needs CPU/memory requests and a memory limit, the input Karpenter and the scheduler rely on. Audit mode first.

    shoplite-platform/addons/kyverno/policies/require-requests-limits.yamlwhole fileyaml
    apiVersion: kyverno.io/v1
    kind: ClusterPolicy
    metadata:
      name: require-requests-limits
      annotations:
        policies.kyverno.io/description: Containers must set CPU/memory requests and a memory limit.
    spec:
      validationFailureAction: Audit      # → Enforce after teams comply
      background: true
      rules:
        - name: check-resources
          match:
            any: [{ resources: { kinds: [Pod] } }]
          exclude:
            any: [{ resources: { namespaces: [kube-system, kyverno, argocd] } }]
          validate:
            message: "Set resources.requests.cpu, requests.memory and limits.memory on every container."
            pattern:
              spec:
                containers:
                  - resources:
                      requests: { cpu: "?*", memory: "?*" }
                      limits: { memory: "?*" }
  3. 3

    Validate: only our registries

    Images must come from the company registry (ECR) or a small allow-list. This blocks typo-squatted and unreviewed public images.

    shoplite-platform/addons/kyverno/policies/restrict-registries.yamlwhole fileyaml
    apiVersion: kyverno.io/v1
    kind: ClusterPolicy
    metadata: { name: restrict-image-registries }
    spec:
      validationFailureAction: Enforce
      background: true
      rules:
        - name: allowed-registries
          match:
            any: [{ resources: { kinds: [Pod], namespaceSelector: { matchLabels: { shoplite.dev/team: "?*" } } } }]
          validate:
            message: "Images must come from 123456789012.dkr.ecr.ap-south-1.amazonaws.com or public.ecr.aws/shoplite."
            pattern:
              spec:
                containers:
                  - image: "123456789012.dkr.ecr.ap-south-1.amazonaws.com/* | public.ecr.aws/shoplite/*"
  4. 4

    Mutate: add defaults

    Add a restrictive pod securityContext (runAsNonRoot, the default seccomp profile) when a team hasn't set one. The +( ) anchors only add a field if it's missing, so teams can still override. Mutations make compliance free for teams.

    shoplite-platform/addons/kyverno/policies/add-default-labels.yamlwhole fileyaml
    apiVersion: kyverno.io/v1
    kind: ClusterPolicy
    metadata: { name: add-defaults }
    spec:
      rules:
        - name: default-security-context
          match:
            any: [{ resources: { kinds: [Pod] } }]
          exclude:
            any: [{ resources: { namespaces: [kube-system] } }]
          mutate:
            patchStrategicMerge:
              spec:
                securityContext:
                  +(runAsNonRoot): true
                  +(seccompProfile): { type: RuntimeDefault }
  5. 5

    Generate: every team namespace gets defaults

    When a namespace labelled shoplite.dev/team is created, Kyverno generates a default-deny NetworkPolicy (Kubernetes course, NetworkPolicy) and a ResourceQuota, and keeps them in sync.

    shoplite-platform/addons/kyverno/policies/namespace-defaults.yamlwhole fileyaml
    apiVersion: kyverno.io/v1
    kind: ClusterPolicy
    metadata: { name: namespace-defaults }
    spec:
      rules:
        - name: default-deny-ingress
          match:
            any: [{ resources: { kinds: [Namespace], selector: { matchLabels: { shoplite.dev/team: "?*" } } } }]
          generate:
            apiVersion: networking.k8s.io/v1
            kind: NetworkPolicy
            name: default-deny-ingress
            namespace: "{{request.object.metadata.name}}"
            synchronize: true
            data:
              spec:
                podSelector: {}
                policyTypes: [Ingress]
        - name: quota
          match:
            any: [{ resources: { kinds: [Namespace], selector: { matchLabels: { shoplite.dev/team: "?*" } } } }]
          generate:
            apiVersion: v1
            kind: ResourceQuota
            name: team-quota
            namespace: "{{request.object.metadata.name}}"
            synchronize: true
            data:
              spec:
                hard: { requests.cpu: "20", requests.memory: 40Gi, pods: "100" }
  6. 6

    Test policies in CI

    The Kyverno CLI applies policies to sample manifests offline. Put this in the platform repo's pipeline so a policy change can't silently block everything.

    shoplite-platform/addons/kyverno/tests/kyverno-test.yamlwhole fileyaml
    apiVersion: cli.kyverno.io/v1alpha1
    kind: Test
    metadata: { name: platform-policies }
    policies: [../policies/require-requests-limits.yaml, ../policies/restrict-registries.yaml]
    resources: [good-pod.yaml, bad-pod.yaml]
    results:
      - { policy: require-requests-limits, rule: check-resources, resources: [good-pod], result: pass }
      - { policy: require-requests-limits, rule: check-resources, resources: [bad-pod], result: fail }
    terminal
    $ kyverno test shoplite-platform/addons/kyverno/tests/
    kubectl get policyreport -A -o wide | head -4
    ── expected output ──
    Test Summary: 2 tests passed and 0 tests failed
    NAMESPACE NAME KIND NAME PASS FAIL WARN ERROR SKIP
    catalog 3f1c... Deployment catalog 4 1 0 0 0
    checkout 9b2e... Deployment checkout 5 0 0 0 0

Checkpoint — you should now have

  • ✓Kyverno runs with 3 replicas and all policies are synced from Git.
  • ✓Pods from unapproved registries are rejected with a clear message.
  • ✓New team namespaces automatically receive a default-deny NetworkPolicy and a quota.
  • ✓Policy tests run in CI.

Part 4

Break it on purpose

Make each change, run the command, and read the error before revealing the diagnosis. Recognising these messages on sight is what makes you fast on a real team. Undo the change afterwards.

Break #1

Enforce on day one

Switch require-requests-limits to Enforce without looking at the audit report first, then deploy.

terminal
$ argocd app get catalog-prod | tail -2
── what you'll see ──
apps Deployment catalog catalog Synced Degraded ...
admission webhook "validate.kyverno.svc-fail" denied the request: resource Pod/catalog/catalog-7d9c... was blocked due to the following policies: require-requests-limits: check-resources: 'validation error: Set resources.requests.cpu, ... rule check-resources failed at path /spec/containers/0/resources/limits/'

Break #2

Kyverno down, cluster frozen

Kyverno is running a single replica, and its node is drained.

terminal
$ kubectl -n checkout scale deploy checkout --replicas=5
── what you'll see ──
Error from server (InternalError): Internal error occurred: failed calling webhook "validate.kyverno.svc-fail": failed to call webhook: Post "https://kyverno-svc.kyverno.svc:443/validate/fail?timeout=10s": no endpoints available for service "kyverno-svc"

Part 5

Interview questions from this mission

01

Kyverno vs OPA Gatekeeper?

02

How do you roll out a new enforcement policy without breaking teams?

0/4 · 0%