.claude/skills/eks-irsa/SKILL.md
IAM Roles for Service Accounts (IRSA) for EKS pod-level AWS permissions. Use when configuring pod IAM access, setting up AWS service integrations, implementing least-privilege security, troubleshooting OIDC trust relationships, or deploying AWS controllers.
npx skillsauth add adaptationio/skrillz eks-irsaInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Comprehensive guide for implementing IAM Roles for Service Accounts (IRSA) in Amazon EKS. IRSA enables fine-grained IAM permissions at the pod level using OpenID Connect (OIDC) federation, eliminating the need for node-level IAM credentials and enabling least-privilege security.
Keywords: IRSA, IAM Roles for Service Accounts, EKS security, OIDC provider, pod IAM permissions, service account annotations, least privilege, AWS integration, trust policy, cross-account access
Status: Production-ready (2025 best practices)
IAM Roles for Service Accounts (IRSA) allows Kubernetes workloads to assume IAM roles securely without relying on node-level credentials.
1. Pod starts with annotated ServiceAccount
2. EKS mutating webhook injects AWS_WEB_IDENTITY_TOKEN_FILE
3. AWS SDK reads JWT token from injected file
4. SDK calls STS::AssumeRoleWithWebIdentity
5. OIDC provider validates token against trust policy
6. Temporary credentials issued (automatically rotated)
7. Pod uses scoped IAM permissions
Security:
Compliance:
Operational:
# 1. Enable IRSA in EKS module (automatic OIDC setup)
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 20.0"
cluster_name = "production"
enable_irsa = true # Creates OIDC provider automatically
}
# 2. Create IAM role for S3 access
module "s3_access_irsa" {
source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
version = "~> 5.0"
role_name = "my-app-s3-access"
role_policy_arns = {
s3_read = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
}
oidc_providers = {
main = {
provider_arn = module.eks.oidc_provider_arn
namespace_service_accounts = ["production:my-app-sa"]
}
}
}
# 3. Create Kubernetes ServiceAccount
resource "kubernetes_service_account" "my_app" {
metadata {
name = "my-app-sa"
namespace = "production"
annotations = {
"eks.amazonaws.com/role-arn" = module.s3_access_irsa.iam_role_arn
}
}
}
# 4. Use ServiceAccount in pod
resource "kubernetes_deployment" "my_app" {
spec {
template {
spec {
service_account_name = "my-app-sa" # ✅ IRSA enabled!
containers {
name = "app"
image = "my-app:latest"
# AWS SDK automatically uses IRSA credentials
}
}
}
}
}
# Check OIDC provider exists
aws iam list-open-id-connect-providers
# Verify IAM role trust policy
aws iam get-role --role-name my-app-s3-access
# Test from pod
kubectl exec -it my-pod -- env | grep AWS
# Should show:
# AWS_WEB_IDENTITY_TOKEN_FILE=/var/run/secrets/eks.amazonaws.com/serviceaccount/token
# AWS_ROLE_ARN=arn:aws:iam::123456789012:role/my-app-s3-access
# Test AWS access
kubectl exec -it my-pod -- aws s3 ls
What it needs: Create/manage ALBs and NLBs
module "lb_controller_irsa" {
source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
role_name = "aws-load-balancer-controller"
attach_load_balancer_controller_policy = true # Pre-built policy!
oidc_providers = {
main = {
provider_arn = module.eks.oidc_provider_arn
namespace_service_accounts = ["kube-system:aws-load-balancer-controller"]
}
}
}
What it needs: Create/attach/delete EBS volumes
module "ebs_csi_irsa" {
source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
role_name = "ebs-csi-controller"
attach_ebs_csi_policy = true # Pre-built policy!
oidc_providers = {
main = {
provider_arn = module.eks.oidc_provider_arn
namespace_service_accounts = ["kube-system:ebs-csi-controller-sa"]
}
}
}
What it needs: Manage Route53 records
module "external_dns_irsa" {
source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
role_name = "external-dns"
attach_external_dns_policy = true # Pre-built policy!
external_dns_hosted_zone_arns = ["arn:aws:route53:::hostedzone/Z123456789"]
oidc_providers = {
main = {
provider_arn = module.eks.oidc_provider_arn
namespace_service_accounts = ["kube-system:external-dns"]
}
}
}
What it needs: Modify Auto Scaling Groups
module "cluster_autoscaler_irsa" {
source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
role_name = "cluster-autoscaler"
attach_cluster_autoscaler_policy = true # Pre-built policy!
cluster_autoscaler_cluster_names = [module.eks.cluster_name]
oidc_providers = {
main = {
provider_arn = module.eks.oidc_provider_arn
namespace_service_accounts = ["kube-system:cluster-autoscaler"]
}
}
}
What it needs: Provision EC2 instances, manage instance profiles
module "karpenter" {
source = "terraform-aws-modules/eks/aws//modules/karpenter"
cluster_name = module.eks.cluster_name
irsa_oidc_provider_arn = module.eks.oidc_provider_arn
# Includes pre-configured IRSA role!
}
What it needs: Read secrets from AWS Secrets Manager
module "external_secrets_irsa" {
source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
role_name = "external-secrets"
attach_external_secrets_policy = true # Pre-built policy!
oidc_providers = {
main = {
provider_arn = module.eks.oidc_provider_arn
namespace_service_accounts = ["kube-system:external-secrets"]
}
}
}
module "app_irsa" {
source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
role_name = "my-app"
role_policy_arns = {
s3 = aws_iam_policy.app_s3_policy.arn
dynamodb = aws_iam_policy.app_dynamodb_policy.arn
}
oidc_providers = {
main = {
provider_arn = module.eks.oidc_provider_arn
namespace_service_accounts = ["production:my-app-sa"]
}
}
}
# Custom policies with least privilege
resource "aws_iam_policy" "app_s3_policy" {
name = "my-app-s3-access"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"s3:GetObject",
"s3:PutObject"
]
Resource = "arn:aws:s3:::my-bucket/my-app/*"
}
]
})
}
import boto3
# AWS SDK automatically detects IRSA credentials
# No configuration needed!
s3 = boto3.client('s3')
response = s3.list_buckets()
print(response['Buckets'])
# The SDK:
# 1. Reads AWS_WEB_IDENTITY_TOKEN_FILE env var
# 2. Reads AWS_ROLE_ARN env var
# 3. Calls STS::AssumeRoleWithWebIdentity
# 4. Uses temporary credentials automatically
import { S3Client, ListBucketsCommand } from "@aws-sdk/client-s3";
// AWS SDK automatically detects IRSA credentials
const s3Client = new S3Client({ region: "us-east-1" });
const response = await s3Client.send(new ListBucketsCommand({}));
console.log(response.Buckets);
import (
"context"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
)
func main() {
// AWS SDK automatically detects IRSA credentials
cfg, _ := config.LoadDefaultConfig(context.TODO())
client := s3.NewFromConfig(cfg)
resp, _ := client.ListBuckets(context.TODO(), &s3.ListBucketsInput{})
fmt.Println(resp.Buckets)
}
For in-depth guides on specific IRSA topics:
OIDC Setup: references/oidc-setup.md
Role Creation: references/role-creation.md
Pod Configuration: references/pod-configuration.md
# ❌ BAD: Sharing service account
apiVersion: v1
kind: ServiceAccount
metadata:
name: shared-sa # Used by multiple apps
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123:role/shared-role
# ✅ GOOD: One service account per app
apiVersion: v1
kind: ServiceAccount
metadata:
name: payment-service-sa
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123:role/payment-service
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: email-service-sa
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123:role/email-service
# Prevent pods from accessing node IAM credentials
module "eks" {
source = "terraform-aws-modules/eks/aws"
eks_managed_node_groups = {
main = {
# Require IMDSv2 (prevents container escape to node credentials)
metadata_options = {
http_endpoint = "enabled"
http_tokens = "required" # IMDSv2 only
http_put_response_hop_limit = 1
}
}
}
}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::my-bucket/my-app/*",
"Condition": {
"StringEquals": {
"aws:PrincipalAccount": "123456789012"
}
}
}
]
}
# Find all IRSA roles
aws iam list-roles --query 'Roles[?contains(AssumeRolePolicyDocument.Statement[0].Principal.Federated, `oidc-provider`)]'
# Check CloudTrail for AssumeRoleWithWebIdentity calls
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=AssumeRoleWithWebIdentity \
--max-results 50
| Issue | Cause | Fix |
|-------|-------|-----|
| AccessDenied | Missing IAM permissions | Check role policy allows action |
| AssumeRoleWithWebIdentity failed | Trust policy mismatch | Verify OIDC provider ARN matches cluster |
| InvalidIdentityToken | Wrong namespace/SA in trust | Check StringEquals condition |
| Pod can't assume role | ServiceAccount not annotated | Add eks.amazonaws.com/role-arn annotation |
| Using node credentials | Pod not using ServiceAccount | Set serviceAccountName in pod spec |
| OIDC provider not found | IRSA not enabled | Set enable_irsa = true in EKS module |
# Create cluster with OIDC enabled
eksctl create cluster \
--name production \
--region us-east-1 \
--with-oidc
# Create IRSA role + ServiceAccount in one command
eksctl create iamserviceaccount \
--name my-app-sa \
--namespace production \
--cluster production \
--attach-policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess \
--approve
# Verify
kubectl get sa my-app-sa -n production -o yaml
Problem: IRSA trust policies include cluster OIDC endpoint
Solution: Update trust policies during upgrade
# Support both blue and green clusters temporarily
resource "aws_iam_role" "app" {
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
Federated = [
module.eks_blue.oidc_provider_arn, # Old cluster
module.eks_green.oidc_provider_arn # New cluster
]
}
Action = "sts:AssumeRoleWithWebIdentity"
Condition = {
StringEquals = {
"${module.eks_blue.oidc_provider}:sub" = "system:serviceaccount:prod:app-sa"
"${module.eks_green.oidc_provider}:sub" = "system:serviceaccount:prod:app-sa"
}
}
}
]
})
}
Note: EKS Pod Identity is the new simplified alternative to IRSA (GA 2024).
Differences:
IRSA vs Pod Identity:
Migration Path: Keep IRSA for existing clusters, use Pod Identity for new ones.
Version: 2025 Best Practices
Terraform Module: terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks
Status: Production-ready
Last Updated: November 2025
development
Setup secure web-based terminal access to WSL2 from mobile/tablet via ttyd + ngrok/Cloudflare/Tailscale. One-command install, start, stop, status. Use when you need remote terminal access, web terminal, browser-based shell, or mobile access to WSL2 environment.
development
Complete development workflows where Claude writes the code while Gemini and Codex provide research, planning, reviews, and different perspectives. Claude remains the main developer. Use for complex projects requiring expert planning and multi-perspective reviews.
development
Systematic progress tracking for skill development. Manages task states (pending/in_progress/completed), updates in real-time, reports progress, identifies blockers, and maintains momentum. Use when tracking skill development, coordinating work, or reporting progress.
testing
Comprehensive testing workflow orchestrating functional testing, example validation, integration testing, and usability assessment. Sequential workflow for complete skill testing from examples through scenarios to integration validation. Use when conducting thorough testing, pre-deployment validation, ensuring skill functionality, or comprehensive quality checks.