Workload identity trên AWS: EKS Pod Identity vs IRSA

Pod lấy AWS credentials thế nào mà không cần static key — cơ chế bên trong của IRSA và EKS Pod Identity, khác nhau ở đâu, và cách migrate an toàn.

Read in English →

Pod nào gọi S3, DynamoDB hay SQS cũng cần AWS credentials. Cách tệ nhất là để access key trong Kubernetes Secret: key không bao giờ hết hạn, bị copy vào log CI và file .env, và không ai biết còn những ai đang dùng nó. Cách tệ thứ hai là dùng IAM role của node: mọi pod trên node âm thầm thừa hưởng cùng một bộ quyền.

Workload identity giải quyết chuyện này. Pod chứng minh nó là ai (bằng Kubernetes service account), và AWS đổi bằng chứng đó lấy credentials ngắn hạn, chỉ gắn với đúng một IAM role. Trên EKS có hai cách làm: IRSA (IAM Roles for Service Accounts, ra mắt 2019) và EKS Pod Identity (2023).

Tóm tắt: workload mới trên EKS chạy node Linux EC2 thì dùng EKS Pod Identity, AWS cũng khuyến nghị dùng nó khi có thể. Giữ IRSA cho Fargate, node Windows, cluster không phải EKS, và trường hợp cần dùng OIDC token của cluster để federate sang cloud khác.

IRSA hoạt động thế nào

IRSA dựa trên OIDC federation thuần tuý. Mỗi EKS cluster có một OIDC issuer dùng để ký service account token. Bạn đăng ký issuer đó thành một IAM OIDC identity provider trong account, và IAM sẽ tin các token do issuer này ký.

Luồng IRSA: pod gửi service account token tới STS, STS xác thực qua IAM OIDC provider và trust policy của role, rồi trả credentials
IRSA: SDK trong từng pod tự đổi service account token lấy credentials từ STS. STS tin cluster thông qua một IAM OIDC provider riêng cho mỗi cluster.
  1. Một mutating webhook thấy annotation trên service account, rồi inject hai biến môi trường AWS_ROLE_ARN và AWS_WEB_IDENTITY_TOKEN_FILE, cùng một projected token ở /var/run/secrets/eks.amazonaws.com/serviceaccount/token.
  2. AWS SDK trong pod gọi sts:AssumeRoleWithWebIdentity với token đó.
  3. STS xác thực token qua OIDC provider của cluster và kiểm tra trust policy của role.

Thiết lập:

# Một lần cho mỗi cluster (và mỗi account cần tin cluster đó)
eksctl utils associate-iam-oidc-provider --cluster prod-eu-1 --approve

Trust policy của role phải ghi rõ OIDC provider của cluster và service account:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {
      "Federated": "arn:aws:iam::111122223333:oidc-provider/oidc.eks.eu-west-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE"
    },
    "Action": "sts:AssumeRoleWithWebIdentity",
    "Condition": {
      "StringEquals": {
        "oidc.eks.eu-west-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:sub": "system:serviceaccount:payments:payments-api",
        "oidc.eks.eu-west-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:aud": "sts.amazonaws.com"
      }
    }
  }]
}
apiVersion: v1
kind: ServiceAccount
metadata:
  name: payments-api
  namespace: payments
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::111122223333:role/payments-api

Cách này chạy tốt, nhưng khi số cluster tăng lên thì bắt đầu khổ:

  • Mỗi cluster mới đồng nghĩa với việc phải sửa trust policy. ID của OIDC provider là riêng cho từng cluster, nên một role dùng cho 5 cluster cần 5 statement.
  • Trust policy có giới hạn kích thước. Mặc định là 2.048 ký tự, tức khoảng 4 cặp cluster/service account cho mỗi role, và chỉ khoảng 8 cặp kể cả khi đã xin tăng quota.
  • Số OIDC provider bị giới hạn ở 100 cho mỗi account (mặc định).
  • Mỗi pod tự gọi STS, nên fleet lớn có thể bị STS throttle.

EKS Pod Identity hoạt động thế nào

Với Pod Identity, bạn không phải tự quản lý OIDC nữa. Chính EKS trở thành bên được tin tưởng.

Luồng EKS Pod Identity: pod hỏi agent trên node, agent gọi EKS Auth, EKS Auth tra association, assume role và trả credentials kèm session tags
EKS Pod Identity: pod chỉ nói chuyện với agent trên node của nó. EKS Auth tra association và assume role, nên không cần OIDC provider hay trust policy riêng cho từng cluster.
  1. Cài add-on EKS Pod Identity Agent một lần cho mỗi cluster. Nếu dùng EKS Auto Mode thì agent đã có sẵn.
  2. Tạo một association qua EKS API: cluster + namespace + service account → IAM role. Không cần thay đổi gì trong Kubernetes, cũng không cần annotation.
  3. Một webhook inject AWS_CONTAINER_CREDENTIALS_FULL_URI và AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE. Container credential provider của SDK lấy credentials từ agent trên node, còn agent lấy chúng từ EKS Auth.

Trust policy giống hệt nhau cho mọi cluster:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Service": "pods.eks.amazonaws.com" },
    "Action": ["sts:AssumeRole", "sts:TagSession"]
  }]
}
aws eks create-addon --cluster-name prod-eu-1 --addon-name eks-pod-identity-agent

aws eks create-pod-identity-association \
  --cluster-name prod-eu-1 \
  --namespace payments \
  --service-account payments-api \
  --role-arn arn:aws:iam::111122223333:role/payments-api

Hoặc dùng Terraform:

resource "aws_eks_pod_identity_association" "payments_api" {
  cluster_name    = aws_eks_cluster.prod.name
  namespace       = "payments"
  service_account = "payments-api"
  role_arn        = aws_iam_role.payments_api.arn
}

Giờ thêm cluster thứ 6, thứ 10 hay thứ 50 chỉ cần tạo một association cho mỗi cluster, không phải động vào IAM.

Session tags và ABAC

Credentials do Pod Identity cấp có kèm session tags: eks-cluster-arn, eks-cluster-name, kubernetes-namespace, kubernetes-service-account, kubernetes-pod-name và kubernetes-pod-uid. Nhờ đó có thể làm ABAC (phân quyền theo thuộc tính): chỉ cần một role, mỗi namespace chỉ truy cập được dữ liệu của chính nó.

{
  "Effect": "Allow",
  "Action": ["s3:GetObject", "s3:PutObject"],
  "Resource": "arn:aws:s3:::tenant-data/${aws:PrincipalTag/kubernetes-namespace}/*"
}
Một IAM role dùng chung cho ba namespace, mỗi namespace chỉ truy cập được prefix S3 của mình nhờ session tags
ABAC với session tags: một role, một policy, nhưng mỗi namespace chỉ chạm được vào prefix của chính nó.

Pod trong namespace payments chỉ đọc/ghi được tenant-data/payments/*. IRSA không có session tags, nên mọi pod dùng chung một role đều nhận toàn bộ quyền của role đó.

Cross-account với target role

Role trong association phải nằm cùng account với cluster. Muốn truy cập tài nguyên ở account khác, bạn thêm một target role. Pod Identity sẽ tự chain hai role, ứng dụng không cần viết code AssumeRole:

aws eks create-pod-identity-association \
  --cluster-name prod-eu-1 \
  --namespace payments \
  --service-account payments-api \
  --role-arn arn:aws:iam::111122223333:role/payments-api \
  --target-role-arn arn:aws:iam::444455556666:role/payments-data
Truy cập cross-account: Pod Identity role ở account A được chain sang target role ở account B
Target role: EKS tự chain Pod Identity role sang role ở account khác. Code ứng dụng không phải làm gì thêm.

Session tags có tính bắc cầu (transitive), nên trust policy ở account đích vẫn kiểm tra được aws:RequestTag/kubernetes-namespace và aws:RequestTag/kubernetes-service-account.

So sánh trực tiếp

IRSA EKS Pod Identity
Cơ chế tin cậy Một IAM OIDC provider cho mỗi cluster Service principal pods.eks.amazonaws.com
Cấu hình ở đâu Trust policy IAM + annotation trên SA Association trong EKS API, không đổi gì trong Kubernetes
Thêm cluster mới Sửa trust policy của mọi role Chỉ tạo association
Số mapping mỗi trust policy ~4 cluster/SA (giới hạn 2.048 ký tự) Không giới hạn (trust policy dùng chung)
Giới hạn cluster 100 OIDC provider mỗi account (mặc định) Không áp dụng
Số mapping tối đa — 5.000 association mỗi cluster
Gọi STS Mỗi pod tự gọi AssumeRoleWithWebIdentity EKS Auth assume role, agent cache theo node
Session tags / ABAC Không Có (cluster, namespace, SA, pod)
Cross-account OIDC provider ở account đích, hoặc tự chain role Có sẵn target role
Chạy trên EKS, Fargate, Windows, EKS Anywhere, ROSA, K8s tự dựng Chỉ EKS, node Linux EC2 (không có Fargate, Windows, Outposts)
Thành phần thêm Pod Identity webhook (EKS quản lý) DaemonSet Pod Identity Agent (hostNetwork, port 80/2703)
SDK tối thiểu Gần như mọi SDK từ ~2019 SDK mới (vd. boto3 1.34.41, Java v2 2.21.30, CLI 2.15.0)
Token dùng được ngoài AWS Có, GCP/Azure tin được OIDC issuer Không

Migrate từ IRSA sang Pod Identity

  1. Kiểm tra phiên bản SDK trong mọi image. SDK cũ không biết container credential provider, và sẽ lặng lẽ rơi xuống provider tiếp theo trong chain, thường là role của node.

  2. Thêm statement tin pods.eks.amazonaws.com vào trust policy của role hiện có, nhưng giữ lại statement OIDC trong lúc chuyển đổi.

  3. Tạo association cho đúng namespace và service account đó.

  4. Xoá annotation eks.amazonaws.com/role-arn rồi restart pod. Bước này rất quan trọng: credential chain mặc định của SDK kiểm tra web identity (IRSA) trước container credentials (Pod Identity). Chừng nào annotation còn đó, pod vẫn dùng IRSA.

  5. Kiểm tra từ bên trong pod:

    kubectl exec -n payments deploy/payments-api -- env | grep AWS_CONTAINER
    kubectl exec -n payments deploy/payments-api -- aws sts get-caller-identity
  6. Dọn dẹp: khi đã migrate xong, xoá statement OIDC khỏi trust policy. Chỉ xoá OIDC provider của cluster nếu không còn thứ gì khác dùng nó.

Những cái bẫy khi chạy production

  • Chặn pod truy cập IMDS. Workload identity vô nghĩa nếu pod vẫn lấy được role của node qua instance metadata service. Dùng IMDSv2 với HttpPutResponseHopLimit=1 trên node. Pod chạy hostNetwork: true thì luôn truy cập được IMDS.
  • Credentials được cache. Agent cache credentials 6 giờ (59 phút nếu có target role). Đổi role trong association sẽ không ảnh hưởng tới pod đang chạy cho tới khi cache hết hạn, nên muốn áp dụng ngay thì restart pod.
  • Association có độ trễ (eventually consistent), khoảng vài giây. Hãy tạo association trong pipeline provisioning, đừng tạo trong luồng khởi động của ứng dụng.
  • Proxy. Nếu pod dùng HTTP(S)_PROXY, thêm 169.254.170.23 và [fd00:ec2::23] vào NO_PROXY, nếu không request lấy credentials sẽ đi qua proxy và thất bại.
  • Cluster tắt IPv6 thì cũng phải tắt IPv6 trong cấu hình của agent.
  • Lỗi PackedPolicyTooLarge có thể do session tags. Tắt session tags trên association sẽ hết lỗi, nhưng mất khả năng ABAC.
  • Quản lý association bằng code. Dùng Terraform, hoặc công cụ Kubernetes-native như resource PodIdentityAssociation của ACK EKS controller, để quyền truy cập nằm trong Git cạnh code ứng dụng thay vì bấm tay trên console.

Nên chọn cái nào?

  • Workload EKS mới trên node Linux EC2 hoặc EKS Auto Mode: dùng Pod Identity. Vận hành đơn giản hơn, role dùng lại được qua nhiều cluster, có ABAC, và ít tải lên STS hơn.
  • Fargate, node Windows, EKS Anywhere, ROSA hoặc Kubernetes tự dựng: dùng IRSA, vì Pod Identity không hỗ trợ các môi trường này.
  • Workload multi-cloud cần xác thực thêm với GCP hoặc Azure: giữ lại OIDC issuer của cluster. Các cloud đó có thể tin cùng một service account token Kubernetes, điều mà Pod Identity không làm được.

Cả hai cách đều cho pod credentials ngắn hạn, riêng cho từng workload, không có static key ở bất cứ đâu. Đó mới là thứ quan trọng nhất. Chọn IRSA hay Pod Identity chủ yếu quyết định bạn phải duy trì bao nhiêu “ống nước” IAM khi số cluster tăng lên.

Nguồn

Bình luận