EKS의 Pod는 기본적으로 EC2 노드의 IAM Role을 그대로 상속받음. 모든 Pod가 동일한 권한을 공유하므로 세밀한 권한 제어가 불가능
[EC2 Node IAM Role]
│
┌───▼────┐
│ Pod A │ → S3, DynamoDB 모두 접근 가능 (과도한 권한)
│ Pod B │ → 실제로는 S3만 필요한데 불필요한 권한까지 보유
└────────┘
보안 문제:

IAM Roles for Service Accounts의 약자. Pod 단위로 AWS IAM 권한을 세밀하게 부여할 수 있는 EKS의 공식 메커니즘
Pod → ServiceAccount → IAM Role → AWS 리소스 접근
동작 방식:
1. Kubernetes ServiceAccount에 IAM Role ARN 부여
2. Pod가 해당 ServiceAccount 사용
3. AWS STS가 임시 자격증명 발급
4. Pod가 IAM Role의 권한으로 AWS 리소스 접근
EKS 클러스터가 발급한 ServiceAccount 토큰(JWT)을 AWS가 검증할 수 있도록 하는 연결 고리
resource "aws_iam_openid_connect_provider" "eks" {
url = aws_eks_cluster.this.identity[0].oidc[0].issuer
client_id_list = ["sts.amazonaws.com"]
thumbprint_list = [data.tls_certificate.eks.certificates[0].sha1_fingerprint]
}
EKS가 발급한 토큰이 유효한지 AWS가 확인
Pod가 assume할 수 있는 IAM Role을 생성. Trust Policy에서 특정 ServiceAccount만 Role을 사용할 수 있도록 제한
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLE"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLE:sub": "system:serviceaccount:my-namespace:my-sa",
"oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLE:aud": "sts.amazonaws.com"
}
}
}
]
}
sub: 특정 namespace의 특정 ServiceAccount만 허용aud: STS 서비스만 토큰 사용 가능실제 AWS 리소스 접근 권한을 정의하는 정책
resource "aws_iam_role_policy_attachment" "s3_read" {
role = aws_iam_role.irsa_role.name
policy_arn = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
}
또는 커스텀 정책:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-bucket",
"arn:aws:s3:::my-bucket/*"
]
}
]
}
IAM Role ARN을 annotation으로 지정한 ServiceAccount 생성
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app-sa
namespace: production
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/my-app-irsa-role
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: production
spec:
template:
spec:
serviceAccountName: my-app-sa
containers:
- name: app
image: my-app:latest
env:
- name: AWS_REGION
value: ap-northeast-2
Pod 생성 시 자동으로 다음 환경변수들이 주입됨:
AWS_ROLE_ARN
AWS_WEB_IDENTITY_TOKEN_FILE
AWS_STS_REGIONAL_ENDPOINTS
1. EKS 클러스터 생성 시 OIDC Provider URL 자동 생성
2. AWS IAM에 OIDC Provider 등록
3. IAM Role 생성
- Trust Policy에 OIDC Provider와 ServiceAccount 조건 명시
- 필요한 AWS 권한 정책 연결
4. Kubernetes ServiceAccount 생성
- IAM Role ARN을 annotation으로 추가
5. Pod에 ServiceAccount 지정
6. Pod 실행 시:
- Kubernetes가 ServiceAccount 토큰 발급
- AWS SDK가 토큰으로 STS AssumeRoleWithWebIdentity 호출
- 임시 자격증명 받아서 AWS 리소스 접근
# OIDC Provider
data "tls_certificate" "eks" {
url = aws_eks_cluster.main.identity[0].oidc[0].issuer
}
resource "aws_iam_openid_connect_provider" "eks" {
url = aws_eks_cluster.main.identity[0].oidc[0].issuer
client_id_list = ["sts.amazonaws.com"]
thumbprint_list = [data.tls_certificate.eks.certificates[0].sha1_fingerprint]
}
# IAM Role
resource "aws_iam_role" "app_irsa" {
name = "my-app-irsa-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = {
Federated = aws_iam_openid_connect_provider.eks.arn
}
Action = "sts:AssumeRoleWithWebIdentity"
Condition = {
StringEquals = {
"${replace(aws_iam_openid_connect_provider.eks.url, "https://", "")}:sub" = "system:serviceaccount:production:my-app-sa"
"${replace(aws_iam_openid_connect_provider.eks.url, "https://", "")}:aud" = "sts.amazonaws.com"
}
}
}]
})
}
# IAM Policy 연결
resource "aws_iam_role_policy_attachment" "app_s3" {
role = aws_iam_role.app_irsa.name
policy_arn = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
}
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app-sa
namespace: production
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/my-app-irsa-role
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: production
spec:
replicas: 2
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
serviceAccountName: my-app-sa
containers:
- name: app
image: my-app:latest
ports:
- containerPort: 8080
| 구분 | 기존 방식 (EC2 Role) | IRSA |
|---|---|---|
| 권한 범위 | 노드 내 모든 Pod 동일 | Pod별 독립적 권한 |
| 보안 수준 | 낮음 (권한 과다 부여) | 높음 (최소 권한 원칙) |
| 권한 변경 | 노드 재시작 필요 | 즉시 반영 |
| 권한 추적 | 어려움 | CloudTrail에서 Pod별 추적 가능 |