Securing Karpenter from two sides: minimal IAM policies for the controller and instance profile, IRSA and Pod Identity for workloads, then node hardening with hardened AMIs, SSH control, userData, and NodePool isolation with taints.

In episode 13 you learned how Karpenter selects subnets and security groups and connects to VPC CNI. But a node connected to the network without the right credentials is a security hole waiting to be exploited. A node autoscaler has broad access to AWS: it can launch, terminate, and tag instances. If the controller credentials leak, the impact is not just one pod but your entire infrastructure.
This episode covers security and IAM from two sides. First, the control side: minimal IAM policies for the controller, the instance profile attached to nodes, and IRSA and EKS Pod Identity for workloads. Second, the node side: hardened AMIs, SSH access control, userData in NodeClass, and isolation between NodePools with taints and tolerations.
Karpenter needs permission to create instances, attach security groups, and manage EKS resources. These permissions must be restricted as tightly as possible — not AdministratorAccess. The correct approach is to grant access to specific resources through conditions, for example restricting instance launches to approved AMIs.
{
"Effect": "Allow",
"Action": "ec2:RunInstances",
"Resource": [
"arn:aws:ec2:*:*:instance/*",
"arn:aws:ec2:*:*:volume/*"
],
"Condition": {
"StringEquals": {
"ec2:InstanceType": "m5.large"
}
}
}The policy recommended by Karpenter covers the ec2:RunInstances, ec2:TerminateInstances, ec2:CreateTags, iam:PassRole, and eks:DescribeCluster actions. Avoid copying policies from older versions without reviewing them, because the permission set changes between releases.
Every node launched by Karpenter is given an instance profile through the role field in the NodeClass. This profile defines who the node is when it talks to AWS: pulling images, reporting health, and fetching metadata. Nodes should not have any extra permissions that kubelet does not need.
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
name: default
spec:
amiFamily: AL2
role: "karpenter-node-role"
subnetSelectorTerms:
- tags:
karpenter.sh/discovery: my-clusterUse one role for all Karpenter nodes, and keep it separate from the control plane role. If a workload needs AWS access, don't add it to the node instance profile — use IRSA or Pod Identity (see the next section), so the permission is attached to the pod, not to every node.
IAM Roles for Service Accounts (IRSA) maps a Kubernetes service account to an IAM role. Karpenter itself runs with IRSA, and the same pattern is used for ordinary workloads. A JWT token signed by the cluster OIDC is exchanged for temporary AWS credentials, so no static keys are stored.
eksctl create iamserviceaccount \
--cluster my-cluster \
--namespace production \
--name my-workload \
--attach-policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess \
--approveOnce the service account is created, the pod simply adds serviceAccountName to its spec. The credentials are only valid while the pod runs and are limited to that role's permissions.
EKS Pod Identity is a newer and simpler mechanism than IRSA. Instead of creating an IAM role for each service account, you create a direct role-to-service-account association in EKS. This removes the hassle of managing the OIDC provider and complex trust policies.
Tip
Since the latest Karpenter versions, the official installation recommends EKS Pod Identity over IRSA because its trust flow is simpler and does not depend on OIDC configuration. Choose IRSA only if you are already invested in its ecosystem and cannot migrate.
One golden rule: AWS credentials for workloads must never be placed in the node instance profile. With IRSA or Pod Identity, each deployment only has access to the services it needs, and credential rotation happens automatically without human intervention.
The nodes Karpenter launches use a specific AMI family. For environments with high security requirements, use hardened AMIs — for example via EC2 Image Builder or Amazon Linux reinforced with the CIS benchmark. Set this through amiFamily and, if necessary, amiSelectorTerms in the NodeClass.
| amiFamily | Characteristics |
|---|---|
| AL2 | Default, familiar, broad support |
| AL2023 | Newer, updated certificates and tooling |
| Bottlerocket | Minimal, read-only root, great for hardening |
| Ubuntu | Wide package availability, common in many teams |
Warning
If you use amiSelectorTerms to pick a custom AMI, make sure drift detection stays active (episode 11). An outdated hardened AMI is an illusion of security — security patches are useless if Karpenter does not replace nodes when a new AMI is available.
Don't leave SSH open to the internet. Node access should go through Session Manager (SSM) or a bastion with managed keys. The security groups selected by the NodeClass (episode 13) must restrict port 22 to trusted networks only, or better yet close it completely.
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
name: default
spec:
amiFamily: AL2
userData: |
#!/bin/bash
systemctl enable amazon-ssm-agent
systemctl start amazon-ssm-agentWith SSM, you enter a node through the AWS console or CLI without needing any open port. Every session is recorded in CloudTrail, so there is an audit trail for each access.
Not every node should be usable by every workload. Nodes for PCI, for GPU, or for dedicated tenancy should be isolated. Karpenter supports isolation through taints on the NodePool and tolerations on the pod, plus requirements on both sides as a double safeguard.
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: gpu
spec:
template:
spec:
taints:
- key: workload-type
value: gpu
effect: NoSchedule
requirements:
- key: karpenter.k8s.aws/instance-category
operator: In
values: ["g"]A pod that wants to land on a GPU node must have a matching toleration and usually also requests the nvidia.com/gpu resource. The combination of taint and toleration ensures CPU pods are never placed on expensive GPU nodes, and GPU pods are not randomly placed on general-purpose nodes.
Caution
A taint only prevents scheduling pods without a toleration — it does not reject pods that already exist. If you need hard isolation for tenancy, combine the taint with a different security group and a separate NodeClass, rather than relying on the taint alone.
Karpenter security is not a single setting but a chain of complementary decisions, from IAM all the way to workload isolation.
Key takeaways:
Security is solid, but infrastructure costs can balloon without a strategy. In episode 15 we cover Best Practice & FinOps — multi-AZ workload distribution, mixing Spot and On-Demand, alignment with Reserved and Savings Plans, and per-workload cost allocation based on tags. See you there!