Rejecting Insecure Workloads Twice: Pipeline Scanning, Kyverno Admission Policies, and Pod-Level IAM

  • Kubernetes
  • EKS
  • AKS
  • Kyverno
  • RBAC
  • Trivy
  • Aqua

Before

Without admission controls, a cluster runs any workload it's given, including privileged pods and images no one has scanned.

What I built

Enforced Kubernetes guardrails at admission and in the pipeline: Kyverno admission policies that block privileged pods, RBAC and namespace isolation, and Trivy and Aqua image scanning built into CI/CD, so insecure workloads are rejected before they run.

01BuildCI/CD02Image scanTrivy · Aqua03Admission policyKyverno04NamespaceRBAC05RunningworkloadA failed scan or a privileged pod is rejectedbefore it runs.

Production work described at the level I can share publicly. Code samples are illustrative patterns, not production code; identifiers are documentation placeholders.

The problem: a cluster trusts whatever it's given

  • Without admission controls, a cluster runs any workload it's given, including:
    • privileged pods, which can escape the container and take over the node
    • images no one has scanned, carrying known vulnerabilities into production
  • Without workload identity, every pod on a node shares that node's cloud permissions. Compromise one pod and you get everything the node can do in AWS.
  • Pipelines and clusters are often secured separately, so a workload that slips past one check meets nothing else.

The approach: two checkpoints and a scoped identity

Build → Image scan (Trivy / Aqua) → Admission policy (Kyverno) → Namespace + RBAC
      → Running pod with its own IAM role (IRSA)
  • In the pipeline: catch vulnerable images before they're pushed or deployed.
  • At admission: reject insecure pod specs no matter how they reach the cluster.
  • At runtime: each pod gets only its own scoped cloud permissions.

What I built

1. Scanning in the pipeline

Trivy and Aqua image scanning built into CI/CD, alongside SAST/DAST, so vulnerable images fail the build.

Illustrative: fail the pipeline on high or critical vulnerabilities.

trivy image --exit-code 1 --severity HIGH,CRITICAL --ignore-unfixed \
  registry.example.com/payments-api:${BUILD_TAG}

2. Kyverno admission policies

Kyverno policies that block privileged pods, alongside Kubernetes RBAC and namespace isolation.

Typical additions to a policy library like this are running as non-root, blocking host networking and host paths, and allowing images only from approved registries.

Illustrative: Kyverno's standard "disallow privileged containers" policy.

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-privileged-containers
spec:
  validationFailureAction: Enforce   # start with Audit, switch to Enforce once clean
  background: true
  rules:
    - name: privileged-containers
      match:
        any:
          - resources:
              kinds: [Pod]
      validate:
        message: "Privileged containers are not allowed."
        pattern:
          spec:
            =(ephemeralContainers):
              - =(securityContext):
                  =(privileged): "false"
            =(initContainers):
              - =(securityContext):
                  =(privileged): "false"
            containers:
              - =(securityContext):
                  =(privileged): "false"

3. Pod-level IAM with IRSA

EKS OIDC / IRSA gives each workload its own scoped IAM role across 8+ clusters, instead of shared node-level credentials.

Illustrative: an IRSA trust policy that only one service account can use.

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {
      "Federated": "arn:aws:iam::111122223333:oidc-provider/oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE"
    },
    "Action": "sts:AssumeRoleWithWebIdentity",
    "Condition": {
      "StringEquals": {
        "oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:sub": "system:serviceaccount:payments:payments-api",
        "oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:aud": "sts.amazonaws.com"
      }
    }
  }]
}

4. Posture across clusters

Wiz CSPM with EKS scanning enabled through cross-account roles, for visibility into what's actually running.

How it works

  • Kyverno is a Kubernetes admission controller with policies written in YAML. It can validate, mutate and generate resources, and verify image signatures.
  • Pod Security Policies were removed in Kubernetes 1.25. The built-in replacement is Pod Security Admission, with three levels (privileged / baseline / restricted). Kyverno adds finer-grained, custom control on top.
  • IRSA links a Kubernetes service account to an IAM role through the cluster's OIDC provider. The pod receives short-lived credentials for its own role only. EKS Pod Identity is the newer alternative with simpler setup.

Things to watch

  • Roll out in Audit, then Enforce. Switching straight to Enforce on a busy cluster blocks existing workloads on their next restart. Fix the violations Audit reports first.
  • Handle exceptions explicitly. Use PolicyException resources for genuine exceptions (some system components need privileges) rather than weakening the policy for everyone.
  • Newer Kyverno versions move the failure action to the rule level (failureAction). Check the syntax for your version.
  • Pin the IRSA sub condition exactly. A wildcard or missing sub condition lets any service account in the cluster assume the role.
  • Scanning thresholds need an owner. --ignore-unfixed and severity filters keep builds moving, but someone must track the vulnerabilities they let through.

What it solves

  • Insecure workloads are rejected before they run, at two independent checkpoints.
  • A compromised pod gets only its own permissions, not the node's.
  • Consistent rules across clusters, applied by policy instead of by reviewer memory.

What it doesn't solve

  • Vulnerabilities disclosed after deployment. A clean scan at build time can go stale. Running images need rescanning.
  • Runtime behaviour. Admission control checks the pod spec, not what the container does once running. That's runtime detection, a separate layer.
  • Over-permissioned roles. IRSA scopes credentials to the pod; the role itself still needs least-privilege design.
  • Workloads admitted before the policies existed. Background scans report them, but they keep running until they're fixed or redeployed.

Checklist

  • [ ] Image scanning fails the build on high and critical vulnerabilities
  • [ ] Admission policies block privileged pods (and other agreed risks)
  • [ ] Policies rolled out in Audit before Enforce
  • [ ] Exceptions handled with explicit, reviewed exception resources
  • [ ] RBAC and namespace isolation per workload or team
  • [ ] Each workload has its own IAM role, with an exact sub condition
  • [ ] Running images rescanned for newly disclosed vulnerabilities

Further reading

Get in touch