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를 통해
노드 부팅
└─ 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 방식)
IRSA: IAM Trust Policy가 보안 경계
→ Condition에 클러스터/네임스페이스/SA 명시
Pod Identity: EKS Association이 보안 경계
→ Association 없으면 Agent가 Role을 찾지 못함
→ Trust Policy는 단순, 접근 제어는 EKS 서비스 레이어에서 담당
Trust Policy만 보면 Pod Identity가 더 느슨해 보이지만, 실제 접근 제어는 EKS Association으로 이동한 것입니다.
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로 직접 재현해서 자격증명 발급 흐름 전체를 눈으로 확인한 것입니다.