CloudNet@ Team Study/EKS Workshop 4th Cohort

EKS Pod with IAM Role : Pod Identity (2)

"Everything about infra" 2026. 4. 16. 01:17
포스팅은 CloudNet@팀 서종호(Gasida)님이 진행하시는
AWS EKS Workshop Study 내용을 참고하여 작성합니다.

 

안녕하세요!

오늘은 EKS Pod가 IAM Role을 사용할 때

주요 동작 방식에 대해 설명드려보고자 합니다.

 

이 챕터에서는 Pod Identity에 대해서 다뤄보도록 하겠습니다.

(전 챕터에서 다룬 IRSA편과 이어지는 내용이오니 참고바랍니다)

 

 

 

📌 등장 배경 : IRSA의 한계

IRSA는 클러스터마다 OIDC Provider를 생성하고, IAM Role의 Trust Policy에 해당

OIDC endpoint를 명시해야 합니다. 이 구조에서 발생하는 문제들이 있습니다.

  • 클러스터 수만큼 Trust Policy 수정 필요
    • blue/green 업그레이드로 신규 클러스터를 만들 때마다 Role을 새로 만들거나 Trust Policy를 수정해야 함
  • AWS 계정당 OIDC Provider 100개 제한 : 클러스터가 많아지면 한계에 도달
  • Trust Policy 크기 제한: 단일 신뢰 정책에 최대 8개의 신뢰 관계만 정의 가능

Pod Identity는 이 문제들을 구조적으로 해결하기 위해 2023년 말 출시됐습니다.

 


01. How Pod identity works?

동작 원리를 다이어그램 기준으로 전체 흐름을 알아볼까요?

 

Step1. IAM Role 생성

EKS User가 AWS IAM에 Role을 생성합니다. IRSA와 달리 Trust Policy에

OIDC endpoint를 명시할 필요가 없고, Trust Principal을 pods.eks.amazonaws.com으로만 설정하면 됩니다.

{
  "Principal": {
    "Service": "pods.eks.amazonaws.com"
  },
  "Action": [
    "sts:AssumeRole",
    "sts:TagSession"
  ]
}

sts:TagSession이 추가되는 점이 중요합니다. Pod Identity는 세션 태그에 클러스터 이름, 네임스페이스,

서비스 어카운트 이름을 포함시켜 ABAC(Attribute-Based Access Control)을 가능하게 합니다.

 

Step2. CreatePodIdentityAssociation

EKS User가 EKS Pod Identity API를 호출해서 연결을 생성합니다.

aws eks create-pod-identity-association \
  --cluster-name myeks \
  --namespace default \
  --service-account my-sa \
  --role-arn arn:aws:iam::123456789:role/my-role

이 Association은 EKS 측에 저장됩니다. IAM 쪽에는 아무것도 바뀌지 않으며,

IAM과 EKS의 설정이 완전히 분리된다는 점이 핵심입니다.

 

Step3. Pod Spec Mutation (EKS Pod Identity Webhook)

Pod가 생성될 때 EKS Control Plane의 EKS Pod Identity Webhook이 Pod Spec을 변조합니다.

IRSA의 pod-identity-webhook과 역할이 유사하지만, 주입하는 내용이 다릅니다.

 

IRSA가 주입하는 것:

AWS_WEB_IDENTITY_TOKEN_FILE, AWS_ROLE_ARN
projected volume (OIDC JWT token)

Pod Identity가 주입하는 것:

AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE
AWS_CONTAINER_CREDENTIALS_FULL_URI  → http://169.254.170.23/v1/credentials

Pod Identity는 STS로 직접 가지 않고, 노드의 로컬 에이전트 엔드포인트로 요청을 보내도록 설정합니다.

 

Step4. 자격증명 조회 (EKS Pod Identity Agent → EKS Auth API → STS)

여기가 IRSA와 가장 다른 부분입니다.

 

📌 AssumeRoleForPodIdentity

Pod Identity Agent(각 노드에 DaemonSet 배포)가 EKS Auth API를 호출합니다.

AWS SDK가 자격증명이 필요할 때 169.254.170.23으로 요청을 보내면, Agent가 받아서 처리합니다.

 

📌 Validate Associations

EKS Auth API는 EKS Pod Identity API에 요청해서 해당 (네임스페이스, 서비스 어카운트)가

어느 IAM Role과 연결되어 있는지 검증합니다.

 

검증 후 EKS Auth API가 STS AssumeRoleForPodIdentity를 호출해서 임시 자격증명을 발급받습니다.

이 자격증명에는 아래 세션 태그가 포함됩니다.

태그 키 값 예시
eks-cluster-name myeks
kubernetes-namespace default
kubernetes-service-account my-sa
kubernetes-pod-name my-pod-xxx

 

Step5. AWS 리소스 접근

Pod 내 AWS SDK가 발급받은 임시 자격증명으로 S3 등 AWS 서비스에 접근합니다.

 


 

Differences between EKS Auth API and Pod Identity API

다이어그램을 보면 EKS API가 두 종류로 나누어져 있습니다. 두 API의 차이점을 살펴보겠습니다.

 

실제로는 AWS EKS API 엔드포인트는 하나입니다 (eks.ap-northeast-2.amazonaws.com).

다이어그램에서 두 박스로 나눈 건 API 오퍼레이션의 역할을 기능 단위로 구분해서 표현한 것입니다.

다이어그램 박스 실제 의미 대표 API 오퍼레이션
EKS Pod Identity API Association 관리 (설정 plane) CreatePodIdentityAssociation,
ListPodIdentityAssociations
EKS Auth API 런타임 자격증명 발급 (data plane) AssumeRoleForPodIdentity

둘 다 같은 EKS 서비스 엔드포인트 이지만, 서비스 명이 다르다고 합니다.

 

엄밀히 말하면 eks-auth는 AWS CLI/SDK 상에서 서비스 이름이 분리되어 있습니다.

# 서비스가 다름
aws eks ...       # eks.amazonaws.com
aws eks-auth ...  # eks-auth.amazonaws.com

이렇게 분리한 이유는 호출 주체가 다르기 때문이라고 합니다.

  • eks API → 사람(EKS User, 관리자)이 호출
  • eks-auth API → Pod Identity Agent(DaemonSet) 가 런타임에 자동 호출

생각해보니 호출 빈도, 보안 경계, IAM 권한 분리 측면에서 나누는 게 맞는 것 같네요 !

조금 햇갈리시는 부분이니 IRSA와도 비교해볼까요?

IRSA 흐름:
Pod → STS (AssumeRoleWithWebIdentity) 직접 호출
     ↑ OIDC JWT token을 Pod가 직접 들고 감

Pod Identity 흐름:
Pod → Pod Identity Agent (169.254.170.23) → eks-auth API → STS
     ↑ Pod는 로컬 에이전트만 알면 됨, STS 직접 접근 안 함

IRSA는 Pod가 STS에 직접 요청하지만, Pod Identity는 Agent가 중간에서 eks-auth를 통해

STS를 대신 호출해주는 구조입니다. 그래서 eks-auth가 별도로 분리된 것이겠네요.

 


 

EKS Pod Identity Agent

Pod Identity의 핵심 컴포넌트입니다. EKS Addon으로 설치됩니다.

aws eks create-addon \
  --cluster-name myeks \
  --addon-name eks-pod-identity-agent

하지만 저희는 terraform을 통해서 배포 실습을 하고 있으므로, eks.tf 파일에서 addon을 추가하면 되겠죠?

설치되면 kube-system 네임스페이스에 DaemonSet으로 배포됩니다. 중요한 제약이 있습니다.

  • hostNetwork: true 필요 — 169.254.170.23 link-local 주소를 노드에서 직접 서빙해야 하기 때문
  • Fargate 미지원 — hostNetwork를 사용할 수 없는 환경에서는 Pod Identity를 쓸 수 없음
  • EKS Kubernetes 1.24 이상 필요합니다.

그래서 구성된 인프라에서의 제약 조건을 확인하시어 배포하시길 당부드립니다.

 

마지막으로 Pod Identity vs IRSA를 비교 후, 실습에 들어가보도록 하겠습니다.

항목 EKS Pod Identity IRSA
OIDC Provider 불필요 클러스터마다 생성 필요
IAM Trust Policy pods.eks.amazonaws.com 고정 클러스터 OIDC endpoint 명시
클러스터 추가 시 Role 재사용 가능, 설정 변경 없음 Trust Policy 수정 또는 Role 신규 생성
OIDC Provider 제한 해당 없음 계정 당 100개 제한
세션 태그 (ABAC) 클러스터명/네임스페이스/SA/Pod명 미지원
지원 환경 Amazon EKS 전용 EKS, EKS Anywhere, OpenShift, EC2 자체관리
Fargate 미지원 (hostNetwork 필요) 지원
EKS 버전 1.24 이상 전 버전 지원

각 장단점이 있다고 생각합니다. 그래서 각 환경별로 어느 방식을 사용해야 할지 간단히 알아보겠습니다.

 

1) Pod Identity가 유리한 경우

  • 클러스터를 자주 만들고 지우는 환경 (blue/green 업그레이드, CI용 임시 클러스터)
  • 하나의 Role을 여러 클러스터에서 공유해야 할 때
  • 세션 태그 기반 ABAC 정책을 쓰고 싶을 때 (태그 값으로 S3 경로 분리 등)

2) IRSA가 필요한 경우

  • Fargate 노드에서 실행되는 Pod
  • EKS 외 환경 (EKS Anywhere, OpenShift 등)
  • 1.24 미만의 레거시 클러스터

02. Check Pod Identity Addon

처음에 eks를 terraform으로 배포를 하고, addon에 추가를 하였으므로 바로 설치가 되었는지 확인해볼까요?

아래와 같이 kube-system 네임스페이스에 DaemonSet으로 배포가 잘 되어있네요.

그렇다면, 이 DaemonSet을 yaml 형태로 좀 더 깊게 파볼까요?

kubectl get ds -n kube-system eks-pod-identity-agent -o yaml

spec: 필드에서 주요 기능들을 순서대로 설명드려보도록 하겠습니다.

 

1) skip-containers annotation

Pod Identity Webhook이 Pod Spec을 mutate할 때 Agent 자신은 건너뛰라는 설정입니다.

Agent Pod에 자격증명 환경변수를 주입하면 순환 참조가 발생하기 때문입니다.

 

2) automountServiceAccountToken

Agent 자체는 Service Account Token을 마운트하지 않습니다. Agent는 노드의 EC2 Instance Profile

AWS API를 호출하기 때문에 SA token이 필요 없습니다. 불필요한 토큰 노출을 막는 보안 설정입니다.

 

3) nodeAffinity

Fargate에는 Pod Identity Agent가 스케줄되지 않습니다.

hostNetwork를 쓸 수 없는 환경이기 때문이며, yaml에서 명시적으로 제외하고 있습니다.

 

4) CAP_NET_BIND_SERVICE

1024 미만 포트(여기선 80)를 root가 아닌 프로세스가 바인딩할 수 있게 허용하는 Linux capability입니다.

privileged 전체 권한 없이 최소 권한으로 포트 바인딩만 허용한 것입니다.

 

5) initContainer: privileged

init container가 privileged 권한으로 먼저 실행됩니다. 이 단계에서 169.254.170.23 주소를

노드의 loopback/네트워크 인터페이스에 바인딩하는 iptables 규칙 또는 ip rule 설정을 수행합니다.

본 컨테이너 실행 전에 네트워크 준비가 완료되어야 하기 때문입니다.

 

6) hostNetwork

위 사진을 같이 참조해보면 hostNetwork가 true로 설정 되어있네요.

앞서 설명한 대로 169.254.170.23 link-local 주소를 노드 네트워크 인터페이스에서 직접 서빙해야 하기 때문임 !

이 설정이 있어야 Pod 안에서 169.254.170.23:80으로 요청이 에이전트에 도달합니다.

* 근거: --port 80 arg와 containerPort: 80 (name: proxy) 가 link-local로 노출되는 포트입니다.

 

자, 전체 흐름을 요약해보면 아래와 같습니다.

노드 부팅
  └─ init container (privileged)
       └─ 169.254.170.23 네트워크 설정 (iptables/ip rule)
  └─ 본 컨테이너 (CAP_NET_BIND_SERVICE)
       └─ :80 포트에서 자격증명 요청 대기
            └─ Pod의 AWS SDK → 169.254.170.23:80 → Agent
                 └─ eks-auth AssumeRoleForPodIdentity → STS → 임시자격증명 반환

 

Check node network information

직접 session manager로 인스턴스에 접근하여 네트워크 정보를 확인해볼까요?

 

1) ss 출력 : Agent가 리스닝 중인 포트

169.254.170.23:80   ← Pod들의 자격증명 요청 수신 (핵심)
127.0.0.1:2703      ← readiness/liveness probe 엔드포인트
*:2705              ← 추가 내부 포트 (metrics 등)
[fd00:ec2::23]:80   ← IPv6 link-local, 동일 역할
169.254.170.23:80이 핵심입니다.
Pod 안의 AWS SDK가 AWS_CONTAINER_CREDENTIALS_FULL_URI=http://169.254.170.23/v1/credentials로
요청을 보내면 이 포트가 받습니다.

127.0.0.1:2703 은 DaemonSet yaml에서 봤던 probes-port입니다.
kubelet이 /healthz, /readyz를 이 포트로 체크합니다.

 

2) ip route : 169.254.170.23 라우팅 정보 확인

169.254.170.23 dev pod-id-link0

169.254.170.23으로 가는 트래픽은 pod-id-link0 인터페이스로 라우팅됩니다.

서브넷 마스크 없이 host route(/32) 로 등록되어 있습니다.

이 라우팅 규칙이 init container(privileged)가 실행될 때 세팅한 것입니다.
이 규칙이 있어야 Pod에서 나온 169.254.170.23 트래픽이 노드의 Agent 프로세스에 도달합니다.

 

3) ip addr : pod-id-link0 인터페이스 정보 확인

pod-id-link0     UNKNOWN        169.254.170.23/32

init container가 생성한 가상 네트워크 인터페이스. Agent가 이 인터페이스에 바인딩해서 트래픽을 수신합니다.

UNKNOWN 상태로 보이는 건 이상한 게 아닙니다.
물리적 링크가 없는 가상 인터페이스는 carrier 감지가 안 돼서 UNKNOWN으로 표시되는 게 정상입니다 (loopback도 동일).

 

그 외 ENI 인터페이스들을 잠깐 살펴보자면

ens5   192.168.16.245   ← 노드 primary ENI
ens6   192.168.19.200   ← secondary ENI (VPC CNI용)
ens7   192.168.18.249   ← secondary ENI (VPC CNI용)

192.168.16.85  dev enid365831130b   ← Pod IP (ENI에 할당된 secondary IP)
192.168.16.210 dev eni3128cb21df5
192.168.17.113 dev eni9c4b73bdfad
...

AWS VPC CNI가 노드에 ENI를 추가로 붙이고, 각 ENI의 secondary IP를 Pod에 할당하는 구조입니다.

eni* 네임의 veth 인터페이스들이 각 Pod와 연결된 것들입니다.

 

📌 전체 트래픽 경로 정리

Pod (AWS SDK)
  │  AWS_CONTAINER_CREDENTIALS_FULL_URI=http://169.254.170.23/v1/credentials
  │
  ▼
Pod의 veth (eni*)
  │  목적지 169.254.170.23 → route table에 의해 pod-id-link0으로
  │
  ▼
pod-id-link0 (169.254.170.23/32)
  │
  ▼
eks-pod-identity-agent 프로세스 (:80)
  │  eks-auth AssumeRoleForPodIdentity 호출
  ▼
AWS EKS Auth API → STS → 임시 자격증명 반환

init container가 pod-id-link0 인터페이스 생성과 라우팅 규칙 추가를 담당하고,

본 컨테이너가 그 인터페이스에서 :80으로 리스닝하는 구조가 ss와 ip 출력에서 그대로 확인됩니다.

 


 

03. Set up PodIdentityAssociation with eksctl

eksctl을 이용하여 pod identity association을 설정해 볼까요? (CloudFormation 방식)

eksctl create podidentityassociation \
--cluster $CLUSTER_NAME \
--namespace default \
--create-service-account \
--service-account-name s3-sa \
--role-name s3-eks-pod-identity-role \
--permission-policy-arns arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess \
--region ap-northeast-2

이렇게 생성하면 늘 그랬듯이 CloudFormation이 IAM Role과 K8S SA가 동시에 생성하겠죵?

참고로 AWS 액세스 콘솔에서 EKS > 클러스터 > 액세스 부분에서 확인이 가능합니다.

 

eksctl로 pod identity association이 배포가 되었다면 확인해봅시다.

IRSA의 ServiceAccount와 비교해보면 차이가 명확합니다.

# IRSA SA였다면 이 annotation이 있어야 함
annotations:
  eks.amazonaws.com/role-arn: arn:aws:iam::911283464785:role/s3-eks-pod-identity-role

Pod Identity의 SA에는 IAM Role ARN annotation이 없습니다.

SA 자체는 평범한 k8s 오브젝트이고, Role 매핑 정보는 EKS Pod Identity Association에만 존재합니다.

SA와 IAM이 완전히 분리된 구조가 yaml에서 그대로 확인됩니다.

 

그렇다면 Association을 확인해볼까요?

세 가지 정보를 연결하는 매핑하는 레코드입니다.

(namespace=default, serviceaccount=s3-sa) → s3-eks-pod-identity-role

Pod가 s3-sa를 사용하면 Agent가 이 Association을 조회해서 어느 Role로 AssumeRole 할지 결정합니다.

 

여기서 또 IRSA와 차이점은?

IRSA는 SA의 annotation → IAM Trust Policy Condition으로 연결고리가 양방향으로 존재했지만,

Pod Identity는 EKS Association이 단방향으로 중간을 연결하는 구조입니다.

 

Pod Identity IAM Role도 확인해봅시다.

IRSA와 비교해보는 것이 이해가 더 빠를 것 같아서, 아래와 같이 IRSA의 Trust Policy를 가져와보겠습니다.

// IRSA (비교용)
{
  "Principal": {
    "Federated": "arn:aws:iam::911283464785:oidc-provider/oidc.eks.../id/8666CEF708F17A894EE182009A11AA18"
  },
  "Action": "sts:AssumeRoleWithWebIdentity",
  "Condition": {
    "StringEquals": {
      "...8666...:sub": "system:serviceaccount:default:s3-sa"
    }
  }
}

자, Pod Identity 방식과 IRSA의 방식의 핵심 차이는 3가지 입니다.

 

1) Principal : 클러스터 정보 없음.

Pod Identity Trust Policy에는 클러스터 식별자가 전혀 없습니다.

pods.eks.amazonaws.com은 AWS EKS 서비스 전체를 가리키는 고정값입니다.

"그러면 다른 클러스터에서도 이 Role을 마음대로 쓸 수 있는 거 아닌가?" 라는 의문이 생기는데

이건 Association이 제어합니다. Association이 없는 클러스터에서는

Agent가 Role을 찾지 못해서 AssumeRole 자체가 호출되지 않습니다.

 

2) Condition 없음.

IRSA는 Trust Policy Condition에 네임스페이스와 SA 이름을 명시해야 했습니다.

Pod Identity는 이 정보도 Association이 들고 있기 때문에 Condition이 불필요합니다.

 

3) sts:TagSession (ABAC 지원)

sts:AssumeRole만으로는 세션 태그를 붙일 수 없습니다.

sts:TagSession이 함께 허용되어야 EKS Auth API가 아래 태그를 임시 자격증명에 주입할 수 있습니다.

eks-cluster-name           = myeks
kubernetes-namespace       = default
kubernetes-service-account = s3-sa
kubernetes-pod-name        = (pod명)
kubernetes-pod-uid         = (pod uid)

 

이렇듯 두 방식의 보안 경계 역시 다릅니다.

IRSA:  IAM Trust Policy가 보안 경계
         → Condition에 클러스터/네임스페이스/SA 명시

Pod Identity: EKS Association이 보안 경계
         → Association 없으면 Agent가 Role을 찾지 못함
         → Trust Policy는 단순, 접근 제어는 EKS 서비스 레이어에서 담당

Trust Policy만 보면 Pod Identity가 더 느슨해 보이지만, 실제 접근 제어는 EKS Association으로 이동한 것입니다.

 


 

04. Creating a test pod and verifying AWS usage

 

먼저, 테스트용 파드를 생성해보겠습니다.

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: eks-pod-identity
spec:
  serviceAccountName: s3-sa
  containers:
    - name: my-aws-cli
      image: amazon/aws-cli:latest
      command: ['sleep', '36000']
  restartPolicy: Never
  terminationGracePeriodSeconds: 0
EOF

테스트 파드가 배포가 되었다면 확인해볼까요?

EKS가 EKS Pod Identity 연결이 있는 서비스 계정을 사용하는 새 Pod를 시작하면

EKS Pod Identity 웹훅이 실행됩니다.

 

Pod spec에는 위 출력된 환경변수들을 전혀 정의하지 않았습니다.

EKS Pod Identity Webhook이 Pod 생성 시점에 자동으로 주입한 것입니다.

 

AWS SDK의 자격증명 체인은 AWS_CONTAINER_CREDENTIALS_FULL_URI가 있으면 해당 URL로

자격증명을 요청합니다. 즉, SDK 코드 변경 없이 Pod Identity가 동작합니다.

 

다시 한번 IRSA와 동작 방식을 짚고 넘어가자면,

# IRSA가 주입하는 환경변수
AWS_WEB_IDENTITY_TOKEN_FILE=/var/run/secrets/eks.amazonaws.com/serviceaccount/token
AWS_ROLE_ARN=arn:aws:iam::911283464785:role/...

# Pod Identity가 주입하는 환경변수
AWS_CONTAINER_CREDENTIALS_FULL_URI=http://169.254.170.23/v1/credentials
AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE=...  # Agent 호출 시 인증에 사용

IRSA는 SDK가 STS로 직접 가지만, Pod Identity는 SDK가 로컬 Agent로 갑니다.

 

Check token information

kubectl exec -it eks-pod-identity -- ls /var/run/secrets/pods.eks.amazonaws.com/serviceaccount/
kubectl exec -it eks-pod-identity -- cat /var/run/secrets/pods.eks.amazonaws.com/serviceaccount/eks-pod-identity-token

위 명령으로 토큰 정보를 확인해봅시다.

자, JWT 형식의 긴 문자열이 출력되었는데 저번처럼 jwt.io에서 디코딩을 해보겠습니다.

 

1) Header 

{
  "alg": "RS256",
  "kid": "0cc9062ac2e6e0fab719d083825029addffed5be",
  "typ": "JWT"
}

alg: RS256 : RSA + SHA-256 서명 알고리즘입니다.

EKS Auth API가 이 토큰을 받으면 OIDC JWKS endpoint에서 공개키를 가져와서 서명을 검증합니다.

토큰 수신 (EKS Auth API)
  └─ header.kid = "0cc9062a..."
       └─ JWKS endpoint에서 kid 일치하는 공개키 조회
            └─ RS256 알고리즘으로 서명 검증
                 └─ 성공 → payload 신뢰 → Association 조회

 

2) Payload

{
  "aud": [
    "pods.eks.amazonaws.com"
  ],
  "exp": 1776706150,
  "iat": 1776621570,
  "iss": "https://oidc.eks.ap-northeast-2.amazonaws.com/id/71253B971023DE65920BD6505DF7AA37",
  "jti": "ac88fb4d-32dc-4890-907f-5b28ae39a969",
  "kubernetes.io": {
    "namespace": "default",
    "node": {
      "name": "ip-192-168-16-245.ap-northeast-2.compute.internal",
      "uid": "bad729e7-38d9-4ad9-942c-cec9890640a0"
    },
    "pod": {
      "name": "eks-pod-identity",
      "uid": "0d714cf4-1a9c-4246-a710-d7bf2713acc7"
    },
    "serviceaccount": {
      "name": "s3-sa",
      "uid": "6f236e58-42c4-4f02-ab61-5b5c39952262"
    }
  },
  "nbf": 1776621570,
  "sub": "system:serviceaccount:default:s3-sa"
}

aud : 토큰의 수신자를 제한합니다. EKS Auth API만 이 토큰을 수락합니다.

IRSA 토큰(aud: sts.amazonaws.com)과 달리 STS에 직접 제출하면 거부됩니다.

 

iss : 토큰 발급자(OIDC 제공업체)를 나타내며, 토큰 서명 키의 출처입니다.

EKS Auth API는 이 URL의 JWKS endpoint에서 공개키를 가져와 header의 kid와 매칭해서 서명을 검증합니다.

 

kubernetes.io : 이 토큰은 네임스페이스 내의 서비스 계정을 가진 특정 파드에 바인딩 되어 있음을 나타냄.

이 토큰을 다른 Pod에 복사해서 사용해도 EKS Auth API가 현재 실행 중인 Pod의 uid와 대조해서 거부합니다.

토큰 탈취 시나리오를 차단하는 장치입니다.

 

전체 클레임을 요약하자면,

iss  → 서명 검증에 쓸 공개키 위치
aud  → 이 토큰을 수락할 서비스 (EKS Auth API만)
sub  → Association 조회 키 (namespace + SA)
pod.uid → 토큰 바인딩 검증 (탈취 방지)
exp/iat → 유효기간 (24시간, kubelet이 자동 갱신)
jti → replay attack 방지

 

자격 증명 흐름을 토큰 기준으로 다시 정리해보고자 합니다.

Pod 시작
  └─ Webhook이 주입
       ├─ AWS_CONTAINER_CREDENTIALS_FULL_URI=http://169.254.170.23/v1/credentials
       └─ AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE=.../eks-pod-identity-token
            └─ JWT 내용: aud=pods.eks.amazonaws.com, pod uid 바인딩

AWS SDK 호출 시
  └─ 169.254.170.23/v1/credentials 요청
       └─ Header에 eks-pod-identity-token 첨부 (Authorization token)

Pod Identity Agent 수신
  └─ EKS Auth API: AssumeRoleForPodIdentity
       ├─ 토큰 검증: aud, pod uid, sub 확인
       ├─ Association 조회: default/s3-sa → s3-eks-pod-identity-role
       └─ STS AssumeRole + TagSession
            └─ 임시자격증명 반환 → Agent → SDK → S3 접근

 


 

05. Deep dive analysis of Pod Identity

📌 curl로 Agent에 직접 자격증명 요청

AWS SDK가 내부적으로 하는 동작을 수동으로 재현 해봤습니다.

SDK가 AWS_CONTAINER_CREDENTIALS_FULL_URI와 AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE을 읽어서 자동으로 이 curl과 동일한 요청을 합니다.

 

AccessKeyID가 ASIA로 시작하네요. 무슨 차이인지 볼까요?

ASIA → STS가 발급한 임시 자격증명
AKIA → IAM이 발급한 영구 자격증명

Exp를 보니 약 1시간짜리 임시 자격증명이네요. 만료 전에 SDK가 자동으로 갱신 요청 합니다.

 

그렇다면, 이 curl이 증명하는 건 뭘까요?

Pod 내부에서
  JWT 토큰을 Authorization 헤더에 담아
  169.254.170.23/v1/credentials 로 요청
    ↓
Pod Identity Agent 수신
    ↓
EKS Auth API → STS AssumeRoleForPodIdentity
    ↓
임시 자격증명 반환

AWS SDK가 내부적으로 하는 동작을 curl로 직접 재현해서 자격증명 발급 흐름 전체를 눈으로 확인한 것입니다.

 

📌 CloudTrail 이벤트 조회

EVENT_NAME="AssumeRoleForPodIdentity"
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=$EVENT_NAME \
  --max-items=1 | jq '.Events[] | (.CloudTrailEvent | fromjson)'

curl로 자격증명을 요청하는 순간 내부적으로 EKS Auth API가 STS를 호출하고,

그 호출이 CloudTrail에 기록됩니다. 이 명령어로 그 기록을 조회하는 것입니다.

CloudTrail 관점에서 전체적인 흐름을 정리해보았습니다.

curl 169.254.170.23/v1/credentials (Pod 내부)
  ↓
Pod Identity Agent (노드: i-0a7a20241ced18d8f)
  ↓ EC2 Instance Profile (myeks-ng-1) 으로 인증
eks-auth.amazonaws.com AssumeRoleForPodIdentity
  ↓ CloudTrail 기록 (token: HIDDEN)
STS → 임시자격증명 발급
  ↓ responseElements: null (기록 안 됨)
Agent → Pod → AWS SDK

Pod가 직접 AWS API를 호출하는 게 아니라 Agent가 노드 권한으로 대신 호출하는 구조가

CloudTrail 로그에서 그대로 확인됩니다.

 


긴 글 읽어주셔서 감사합니다.

 

다음 포스팅은 EKS를 운영하면서 직접 겪은 휴먼 에러와 트러블 슈팅 내용들을 작성해보고자 합니다.