k8s-security.pro
kubernetes security eks aws devops

EKS Security Best Practices: A Practical Hardening Checklist

EKS security best practices that matter in production: IRSA, endpoint access, IMDSv2, control plane logging, KMS encryption, and the checks auditors ask about.

K8s Security Pro Team | | 11 min read

EKS Security Best Practices: A Practical Hardening Checklist

There’s a comfortable assumption people make about EKS: it’s managed, it’s AWS, so security is mostly handled. Then the first audit or pentest report arrives and the findings list looks the same as any self-managed cluster, plus a few AWS-specific ones on top.

Here’s the thing. AWS runs the control plane well. Patched, replicated, encrypted at rest on their side. What they don’t do is configure your half of the shared responsibility model. And the defaults on your half are permissive in ways that surprise people.

This is the checklist I work through on EKS clusters, roughly in order of how badly things go wrong when they’re missed.

1. Stop pods from stealing the node’s IAM role (IMDSv2)

This is the big EKS-specific one. Every EC2 node has an instance metadata endpoint at 169.254.169.254, and by default any pod on that node can query it and pull the node role’s credentials. One SSRF bug in one app and an attacker holds IAM credentials, not just a shell in a container.

Two fixes, do both:

Enforce IMDSv2 with a hop limit of 1 so containers in their own network namespace can’t get responses back (hostNetwork pods still can, one more reason to keep the node role minimal):

aws ec2 modify-instance-metadata-options \
  --instance-id i-0abcd1234 \
  --http-tokens required \
  --http-put-response-hop-limit 1

In managed node groups, set this in the launch template so new nodes come up correct. Then check what the node role actually carries. It should have the three EKS worker policies and almost nothing else. If someone bolted S3 or DynamoDB access onto the node role “temporarily”, every pod on that node has it too.

2. Use IRSA (or Pod Identity), and keep the roles boring

Pods that need AWS access should get it through IAM Roles for Service Accounts or the newer EKS Pod Identity, never through the node role. Most teams know this. The part that slips is the permissions inside those roles.

I keep seeing IRSA roles with s3:* on * because the deadline was Friday. Scope them like you’d scope any IAM role: specific actions, specific resources. The whole point of per-workload identity is lost if every workload’s identity is an admin.

Quick way to find pods using service accounts with AWS annotations:

kubectl get sa --all-namespaces -o json | jq -r '.items[] | select(.metadata.annotations["eks.amazonaws.com/role-arn"] != null) | "\(.metadata.namespace)/\(.metadata.name): \(.metadata.annotations["eks.amazonaws.com/role-arn"])"'

Then go read those roles in IAM and ask whether each one would pass a review.

3. Make the API endpoint private (or at least narrow)

New EKS clusters come with a public API endpoint, open to the internet, protected only by authentication. That’s a bigger exposed surface than most teams intend.

aws eks update-cluster-config \
  --name my-cluster \
  --resources-vpc-config endpointPublicAccess=false,endpointPrivateAccess=true

If your team genuinely needs public access (remote engineers, no VPN), restrict it with publicAccessCidrs to office and VPN ranges. “Authenticated but open to the whole internet” is what you’re trying to avoid, because it makes every credential leak immediately exploitable from anywhere.

4. Turn on control plane logging, all five types

EKS ships with control plane logging off. All of it. No audit log, no authenticator log, nothing. If something happens in the cluster, you cannot answer who did what.

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

Logs land in CloudWatch. The audit and authenticator types are the ones your security team and any SOC2 auditor will actually ask for. Set retention to at least 90 days, the CloudWatch default of never-expire gets expensive and one-day retention is useless in an incident.

5. Put secrets encryption on your own KMS key

EKS now envelope-encrypts API data by default with an AWS-owned KMS key, but you can’t see, audit, or revoke that key. For anything an auditor will look at, associate your own customer-managed key so key usage shows up in your CloudTrail and you control rotation and revocation:

aws eks associate-encryption-config \
  --cluster-name my-cluster \
  --encryption-config '[{"resources":["secrets"],"provider":{"keyArn":"arn:aws:kms:...:key/..."}}]'

Better still, keep the important secrets out of the cluster entirely. External Secrets Operator pulling from AWS Secrets Manager means secrets live in a service built for them, with rotation and audit trails, and Git never sees a plaintext value.

6. NetworkPolicies still apply to you

Security groups don’t isolate pod-to-pod traffic. Inside the cluster it’s a flat network, and a compromised pod can reach your database pod, other namespaces, everything. It catches out teams who assume the VPC handles it.

The Amazon VPC CNI has supported NetworkPolicy enforcement since v1.14, you just have to turn it on (or run Calico/Cilium). Then start with default-deny per namespace and allow what each workload needs. One caveat that bites everyone: a default-deny egress policy also blocks DNS, so pair it with an allow rule for UDP/TCP 53 to kube-system.

Fastest way to see where you stand:

kubectl get networkpolicy --all-namespaces

Empty output means flat network.

7. The boring baseline still matters

Everything from the general Kubernetes hardening playbook applies on EKS, and in my experience these exact items are what an auditor flags first: privileged containers, containers running as root, no resource limits, :latest image tags, service account tokens automounted into pods that never call the API, and RBAC wildcards nobody remembers writing.

None of these are EKS-specific, which is exactly why they get skipped on EKS. Managed control plane, unmanaged habits.

For a fast first pass over this category, I use k8s-audit, a small open-source script that runs 16 read-only checks with kubectl and jq and takes about 30 seconds. It won’t replace a CIS scan, it just tells you which fires to look at first.

8. Keep the cluster and add-ons current

EKS versions leave standard support roughly fourteen months after release, and then you’re paying for extended support whether you noticed or not. Same story for the VPC CNI, CoreDNS, and kube-proxy add-ons, which don’t update themselves.

aws eks describe-cluster --name my-cluster --query 'cluster.version'
aws eks describe-addon-versions --kubernetes-version 1.34 --query 'addons[].addonName'

Old versions aren’t just a compliance checkbox. Unpatched CVEs in the CNI or kubelet are exactly the kind of thing that turns a small foothold into node access.

Where this fits in an audit

If you’re doing this because SOC2 or a customer security review is coming: items 1 through 6 map to specific CIS EKS Benchmark controls, and the evidence is mostly the CLI output above plus your policy YAML in Git. Auditors care less about a clean point-in-time scan and more about proof that the settings are enforced and stay enforced.

That’s the workflow our 50-point checklist is built around, each item with the check command, the fix, and the CIS/SOC2 mapping, so the evidence pack more or less assembles itself. The EKS-specific items above plus that list covers what the last few audits I’ve seen asked for.

FAQ

Is EKS secure by default?

The AWS-managed half is. Your half isn’t: public endpoint, no logging, no customer-managed encryption key, and IMDSv1 open to pods are all defaults you have to close yourself.

What should I fix first on an existing EKS cluster?

IMDSv2 enforcement and node role permissions, in that order. It’s the shortest path from “compromised pod” to “compromised AWS account”, which makes it the highest-impact fix on the list.

Do security groups replace NetworkPolicies?

No. Security groups govern instance-level traffic, NetworkPolicies govern pod-to-pod. You need both, and NetworkPolicy enforcement needs a CNI that supports it, enabled explicitly.

How do I check all of this quickly?

The AWS-side items: run the CLI commands in this post against your cluster, they’re all read-only describes. The in-cluster items: run k8s-audit for the 30-second pass, then kube-bench with the EKS benchmark for depth.

Get the Complete 50-Point Security Checklist

5 production-ready templates + audit checklist highlights, free for K8s engineers.

Get Free Kit

Get the Free K8s Security Quick-Start Kit

Get 5 essential templates + audit checklist highlights delivered to your inbox.

No spam. Unsubscribe anytime.

Secure Your Kubernetes Clusters

Get the complete 50-point audit checklist and 20+ production-ready YAML templates.

View Pricing Plans