Project Information
Type: Cloud Security / Kubernetes Security / Infrastructure as Code
Stack: AWS EKS, IAM, IRSA, Fargate, Terraform, Kubernetes RBAC
Project Summary
InfraSec Lab is a hands-on security laboratory designed to demonstrate how to secure Kubernetes workloads on AWS EKS using IAM Roles for Service Accounts (IRSA).
The project focuses on eliminating static credentials, enforcing least-privilege access, and isolating application permissions at the pod level using AWS IAM and Kubernetes ServiceAccounts.
Security Challenges
- Pods using overly permissive IAM roles
- Static AWS credentials stored in containers
- Lack of visibility and isolation between Kubernetes workloads
- Difficulty auditing AWS access from Kubernetes
Architecture & Solution
Each Kubernetes workload runs in its own namespace and uses a dedicated ServiceAccount linked to a specific IAM Role via IRSA. This allows pods to authenticate to AWS securely using short-lived tokens.
Pods run on AWS Fargate inside private subnets and access the internet through a NAT Gateway when required (image pull, external APIs).
IAM Policies – Least Privilege Design
To secure pod-to-AWS communication, this project applies the principle of least privilege using IAM policies attached to Kubernetes ServiceAccounts via IRSA (IAM Roles for Service Accounts).
External Secrets Operator
Allows secure read-only access to AWS Secrets Manager and SSM Parameter Store.
############################
# IAM Policies (Least Privilege)
############################
# External Secrets Operator
resource "aws_iam_policy" "external_secrets" {
name = "eks-external-secrets-policy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = [
"secretsmanager:GetSecretValue",
"ssm:GetParameter",
"ssm:GetParameters"
]
Resource = "*"
}]
})
}
Application Backend
Backend pods can only read and write to specific DynamoDB tables and send messages to authorized SQS queues.
# Backend applicatif
resource "aws_iam_policy" "app_backend" {
name = "eks-app-backend-policy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"dynamodb:GetItem",
"dynamodb:PutItem"
]
Resource = "arn:aws:dynamodb:us-east-1:*:table/app-*"
},
{
Effect = "Allow"
Action = ["sqs:SendMessage"]
Resource = "arn:aws:sqs:us-east-1:*:app-*"
}
]
})
}
Log Collector
Log collection pods can only push logs to a dedicated S3 bucket and Firehose delivery stream.
# Log collector
resource "aws_iam_policy" "log_collector" {
name = "eks-log-collector-policy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = ["s3:PutObject"]
Resource = "arn:aws:s3:::eks-logs-bucket/*"
},
{
Effect = "Allow"
Action = ["firehose:PutRecord"]
Resource = "*"
}
]
})
}
Each policy is attached to a dedicated IAM role and mapped to a Kubernetes ServiceAccount, ensuring strict workload isolation and preventing lateral privilege escalation.
Kubernetes ServiceAccounts & IRSA
In this architecture, each Kubernetes workload uses a dedicated ServiceAccount mapped to an AWS IAM role. This mechanism is called IRSA (IAM Roles for Service Accounts) and allows pods to authenticate securely to AWS without static credentials.
ServiceAccount to IAM Role Mapping
The annotation eks.amazonaws.com/role-arn tells EKS
which IAM role a pod should assume when it runs using this ServiceAccount.
############################
# ServiceAccounts Kubernetes (IRSA)
############################
resource "kubernetes_service_account" "sa" {
for_each = local.pod_roles
metadata {
name = "${each.key}-sa"
namespace = each.value.namespace
annotations = {
"eks.amazonaws.com/role-arn" = aws_iam_role.pod_role[each.key].arn
}
}
}
How Authentication Works
- The pod starts using its assigned ServiceAccount
- EKS injects a projected OIDC token into the pod
- AWS STS validates the token against the EKS OIDC provider
- STS returns temporary credentials for the mapped IAM role
- The AWS SDK automatically refreshes credentials when needed
Security Testing & Validation ( with App service account )
Authorized Actions
- DynamoDB PutItem and GetItem from app-backend pod
Unauthorized Actions (Expected Failures)
- DeleteItem on DynamoDB tables
Security Benefits
- No AWS credentials stored in containers
- Fine-grained IAM permissions per pod
- Clear audit trail via CloudTrail
- Reduced blast radius in case of compromise
- Scalable and reusable security pattern