Safeguard
Container Security

Hardening Amazon EKS clusters against common attack paths

A step-by-step guide to EKS security best practices: lock down IAM, enforce pod security standards, segment networks, and verify every control.

Karan Patel
Cloud Security Engineer
8 min read

Amazon EKS removes a lot of the undifferentiated heavy lifting of running Kubernetes, but it does not remove your responsibility for securing what runs inside the cluster. Most EKS breaches we see in incident reviews do not start with a Kubernetes CVE — they start with an over-permissioned IAM role, a public API endpoint with no network restrictions, a workload running as root, or a supply-chain compromise that lands a malicious image in a namespace with too much blast radius. Following EKS security best practices closes off these paths before an attacker can chain them together.

This guide walks through a practical, sequential hardening process: locking down cluster access, fixing IAM and pod identity, enforcing EKS pod security standards, segmenting the network, and controlling what images and packages can run. By the end, you'll have a checklist you can apply to any EKS cluster, plus a verification section to confirm each control is actually working. This is EKS cluster hardening you can do in an afternoon, not a quarter-long project.

Step 1: Restrict and Audit API Server Access

The EKS control plane's API server is your highest-value target. By default, many clusters are created with a public endpoint that accepts connections from any IP address. That's the first thing to fix.

Check your current endpoint configuration:

aws eks describe-cluster --name my-cluster \
  --query "cluster.resourcesVpcConfig"

If endpointPublicAccess is true with no CIDR restrictions, tighten it to your office and VPN ranges, or move to private-only access if your CI/CD runs inside the VPC:

aws eks update-cluster-config \
  --name my-cluster \
  --resources-vpc-config \
  endpointPublicAccess=true,\
publicAccessCidrs="203.0.113.0/24",\
endpointPrivateAccess=true

Next, turn on control plane audit logging so you have a record of every API call against the cluster:

aws eks update-cluster-config \
  --name my-cluster \
  --logging '{"clusterLogging":[{"types":["api","audit","authenticator"],"enabled":true}]}'

Ship these logs to CloudWatch and retain them for at least 90 days — audit logs are what let you reconstruct an attack path after the fact, and they're one of the first things auditors ask for during a SOC 2 review.

Step 2: Fix IAM Roles and Move to Pod Identity

Overly broad IAM roles attached to nodes or pods are the single most common escalation path in EKS. If every pod on a node can assume the node's instance role, a compromised container can reach any AWS resource that role can touch.

First, audit which IAM roles are attached to your node groups and remove anything beyond what's strictly needed for the kubelet and CNI (ECR pull, CloudWatch logs, VPC CNI permissions). Then move workload-level permissions off the node role entirely using IAM Roles for Service Accounts (IRSA) or, on newer clusters, EKS Pod Identity:

aws eks create-pod-identity-association \
  --cluster-name my-cluster \
  --namespace payments \
  --service-account payments-sa \
  --role-arn arn:aws:iam::111122223333:role/payments-app-role

Scope that role's trust policy and permissions to exactly what the payments-sa service account needs — nothing else. Run this for every workload that touches AWS APIs, and confirm no pod is silently inheriting the node's IAM role by checking aws sts get-caller-identity from inside a shell in each namespace.

Step 3: Enforce EKS Pod Security Standards

Kubernetes' built-in Pod Security Standards (PSS) replace the deprecated PodSecurityPolicy and are enforced natively via the pod-security.kubernetes.io labels on namespaces. Applying the restricted profile blocks privileged containers, host namespace sharing, and root execution outright.

Label your namespaces to enforce the restricted EKS pod security standards baseline:

kubectl label namespace payments \
  pod-security.kubernetes.io/enforce=restricted \
  pod-security.kubernetes.io/enforce-version=latest \
  pod-security.kubernetes.io/audit=restricted \
  pod-security.kubernetes.io/warn=restricted

Pair this with explicit securityContext settings in your deployment manifests so workloads pass admission on the first try:

securityContext:
  runAsNonRoot: true
  runAsUser: 1000
  allowPrivilegeEscalation: false
  readOnlyRootFilesystem: true
  capabilities:
    drop: ["ALL"]
seccompProfile:
  type: RuntimeDefault

Start with warn and audit modes in production namespaces to see what would break, then flip to enforce once your workloads are compliant.

Step 4: Segment the Network with Security Groups and NetworkPolicies

EKS clusters often ship with flat networking where every pod can talk to every other pod and to the internet. Two layers fix this: AWS security groups for node/ENI-level control, and Kubernetes NetworkPolicies for pod-to-pod control.

If you're using the Amazon VPC CNI, enable security groups for pods so sensitive workloads (databases, payment processors) can have tighter AWS-native network rules than the rest of the cluster. Then add default-deny NetworkPolicies as a baseline and layer specific allow rules on top:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: payments
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-api-to-db
  namespace: payments
spec:
  podSelector:
    matchLabels:
      app: payments-db
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: payments-api
      ports:
        - port: 5432
  policyTypes:
    - Ingress

This kind of Kubernetes security AWS teams often skip is worth the setup time — lateral movement inside a flat cluster network is how a single compromised pod becomes a full-cluster incident.

Step 5: Control the Software Supply Chain into Your Cluster

Hardening the runtime means nothing if an attacker can get a malicious image deployed through your CI/CD pipeline or a compromised dependency. Restrict which registries the cluster can pull from and require signed, scanned images.

Use an admission controller (Kyverno, OPA Gatekeeper, or the built-in ECR pull-through with policy) to enforce that images only come from your approved registry and carry a valid signature:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: restrict-image-registries
spec:
  validationFailureAction: Enforce
  rules:
    - name: allowed-registries
      match:
        any:
          - resources:
              kinds: ["Pod"]
      validate:
        message: "Images must come from the approved ECR registry"
        pattern:
          spec:
            containers:
              - image: "111122223333.dkr.ecr.us-east-1.amazonaws.com/*"

Scan every image for known CVEs and embedded secrets before it reaches ECR, and verify SBOM attestations at admission time rather than trusting a tag. This is the point where container hardening meets software supply chain security — the two disciplines converge on the same control point.

Step 6: Rotate Secrets and Eliminate Long-Lived Credentials

Kubernetes Secrets stored as base64-encoded objects in etcd are not encrypted by default at the application layer, and long-lived AWS access keys baked into ConfigMaps or environment variables are a recurring finding in every audit. Enable envelope encryption for Secrets using a KMS key:

aws eks associate-encryption-config \
  --cluster-name my-cluster \
  --encryption-config '[{"resources":["secrets"],"provider":{"keyArn":"arn:aws:kms:us-east-1:111122223333:key/abcd-1234"}}]'

Then migrate application secrets to AWS Secrets Manager or Parameter Store with the External Secrets Operator, so nothing sensitive lives in a manifest or Helm values file that could be checked into source control by mistake.

Verifying Your EKS Security Best Practices: Troubleshooting Checks

Once these controls are in place, validate them rather than assuming they work:

  • API access: Run aws eks describe-cluster and confirm endpointPublicAccess reflects the CIDR restrictions you set. Test from an IP outside the allowed range and confirm the connection is refused.
  • IAM/Pod Identity: Exec into a pod and run aws sts get-caller-identity. If it returns the node role instead of the workload-specific role, the pod identity association or IRSA annotation is misconfigured — check the service account annotation matches the role trust policy exactly.
  • Pod Security Standards: Deploy a deliberately non-compliant pod (runAsUser: 0, no dropped capabilities) into an enforcing namespace and confirm the API server rejects it with a clear admission error. If it's silently accepted, check that the namespace label wasn't overwritten by a GitOps sync or Helm release.
  • NetworkPolicies: Use kubectl exec from a pod without an explicit allow rule and confirm connections to protected services time out. A common gotcha is forgetting that NetworkPolicies require a CNI that enforces them — confirm your VPC CNI or Cilium install actually supports policy enforcement.
  • Image policy: Attempt to deploy a pod referencing an image outside the approved registry and confirm the admission controller blocks it with a readable error message, not a silent failure.
  • Audit logging: Query CloudWatch Logs Insights for recent audit log entries to confirm control plane logs are actually flowing, not just enabled in configuration.

If any of these checks fail silently, the most common root cause is drift — a Terraform apply, Helm upgrade, or manual kubectl edit that reverted a label or policy. Treat cluster configuration as code and diff it against your baseline regularly.

How Safeguard Helps

Manually verifying every one of these controls across every cluster, every namespace, and every deployment is exactly the kind of drift-prone work that security teams fall behind on. Safeguard continuously maps your software supply chain from source to running workload, so misconfigurations like an unscoped IAM role, an unlabeled namespace, or an image pulled from an unapproved registry surface automatically instead of waiting for the next manual audit.

Because Safeguard tracks provenance and SBOM data alongside runtime configuration, it can flag the exact moment a non-compliant image reaches an EKS cluster or a pod identity association grants more access than its workload needs — connecting the supply-chain risk to the runtime exposure in one view. For teams building out EKS security best practices as an ongoing program rather than a one-time project, that continuous correlation is what turns a hardening checklist into a durable control, and what gives auditors evidence they can trust without another point-in-time scramble.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.