- Published on
EKS IRSA 웹아이덴티티 토큰 만료·403 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버리스나 노드 IAM Role 대신 IRSA(IAM Roles for Service Accounts)를 쓰면, 파드가 sts:AssumeRoleWithWebIdentity로 임시 자격 증명을 받아 AWS API를 호출합니다. 그런데 운영에서 자주 보는 증상이 있습니다.
- 일정 시간 잘 되다가 갑자기
ExpiredToken또는WebIdentityErr류 에러 AccessDenied403, 특히AssumeRoleWithWebIdentity단계에서 막힘- 재시작하면 잠깐 살아나지만 다시 실패
이 글은 “웹아이덴티티 토큰 만료·403”을 원인별로 분류하고, 어떤 로그를 어디서 봐야 하는지, 그리고 정확히 무엇을 고치면 되는지를 실전 위주로 정리합니다.
OIDC Thumbprint 변경으로 인한 IRSA 403은 별도 케이스로 자주 발생합니다. 아래 글도 함께 참고하세요.
IRSA 동작 방식: 만료가 “어디서” 나는지부터 나누기
IRSA는 크게 두 단계입니다.
- 쿠버네티스가 파드에 ServiceAccount 토큰(JWT) 을 마운트
- AWS SDK 또는 AWS CLI가 그 토큰을 읽어
AssumeRoleWithWebIdentity호출
여기서 만료는 두 층에서 발생합니다.
- K8s 토큰(JWT) 만료/갱신 문제: 토큰 파일이 갱신되지 않거나 잘못 마운트됨
- STS 임시 자격증명 만료: SDK가 갱신을 못 하거나 캐시가 깨짐
따라서 증상을 보면 먼저 “STS 호출 자체가 403인가”, “STS는 성공했는데 이후 API가 403인가”를 구분해야 합니다.
가장 흔한 403: Trust Policy 조건 불일치
IRSA 403의 다수는 Role의 Trust Policy에서 sub 또는 aud 조건이 실제 토큰과 맞지 않아 발생합니다. 에러는 보통 아래처럼 나옵니다.
AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentity
1) ServiceAccount 어노테이션 확인
kubectl -n <namespace> get sa <serviceaccount> -o yaml
아래가 있어야 합니다.
metadata:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::<account-id>:role/<role-name>
<namespace> 또는 <serviceaccount>가 다르면 Trust Policy의 sub가 어긋납니다.
2) Role Trust Policy의 sub / aud 점검
IRSA용 Trust Policy 예시는 다음과 같습니다. (부등호가 포함될 수 있는 값은 전부 코드로 처리합니다.)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E:aud": "sts.amazonaws.com",
"oidc.eks.ap-northeast-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B716D3041E:sub": "system:serviceaccount:my-ns:my-sa"
}
}
}
]
}
여기서 자주 틀리는 지점:
sub의 네임스페이스 또는 SA 이름 오타aud를sts.amazonaws.com으로 안 맞춤- OIDC provider ARN이 다른 클러스터의 것으로 남아 있음
토큰 만료처럼 보이지만 실제론 “토큰 파일/환경변수” 문제
IRSA는 AWS SDK가 다음 두 값을 기반으로 동작합니다.
AWS_ROLE_ARNAWS_WEB_IDENTITY_TOKEN_FILE
EKS는 보통 파드에 이를 자동 주입합니다. 그러나 다음 케이스에서 깨집니다.
- Helm 템플릿에서 환경변수를 덮어씀
- 사이드카/Init 컨테이너가
/var/run/secrets/eks.amazonaws.com/serviceaccount/token경로를 가정하고 하드코딩 automountServiceAccountToken: false를 켜서 토큰이 아예 마운트되지 않음
파드 내부에서 즉시 확인하는 명령
kubectl -n <namespace> exec -it <pod> -- sh -lc 'env | grep -E "AWS_ROLE_ARN|AWS_WEB_IDENTITY_TOKEN_FILE"'
kubectl -n <namespace> exec -it <pod> -- sh -lc 'ls -al /var/run/secrets/eks.amazonaws.com/serviceaccount || true'
kubectl -n <namespace> exec -it <pod> -- sh -lc 'test -f "$AWS_WEB_IDENTITY_TOKEN_FILE" && echo OK || echo MISSING'
AWS_WEB_IDENTITY_TOKEN_FILE이 비어 있거나 파일이 없으면, SDK는 IRSA를 못 쓰고 다른 크리덴셜 소스(예: 노드 role, 빈 값)로 떨어지며 403이 발생할 수 있습니다.
“만료 후부터 403” 패턴: SDK 갱신 실패 또는 캐시/파일 시스템 이슈
정상이라면 SDK는 STS 임시 자격증명 만료 전에 자동으로 새로 발급받습니다. 그런데 다음 상황에서 갱신이 실패합니다.
- 컨테이너가 STS 엔드포인트로 나갈 수 없음(네트워크/프록시/DNS)
AWS_STS_REGIONAL_ENDPOINTS또는 리전 설정이 꼬여 특정 리전 STS로만 가다 실패- 오래된 SDK 버전이 WebIdentity 로테이션에 버그가 있음
- 자격증명 캐시 파일을 볼륨에 쓰는 앱 로직이 꼬여 오래된 값만 계속 사용
CloudTrail로 “AssumeRoleWithWebIdentity” 자체가 성공했는지 확인
CloudTrail EventName이 AssumeRoleWithWebIdentity인 이벤트를 찾습니다.
- 이벤트가 없다: 파드가 STS 호출을 못 하고 있거나, SDK가 IRSA를 아예 안 쓰는 상태
- 이벤트가 AccessDenied: Trust Policy, OIDC provider,
sub/aud불일치 - 이벤트는 성공인데 이후 API가 403: Role Permission Policy가 부족하거나 다른 Role을 쓰고 있음
OIDC Provider 관련 문제: Thumbprint, Issuer, Provider ARN
EKS IRSA는 IAM OIDC Provider에 의존합니다. 인증서 체인이 바뀌거나(Thumbprint 변경), 클러스터를 재생성했는데 Role의 Federated가 예전 Provider를 가리키면 403이 납니다.
이 케이스는 증상이 매우 전형적이라 별도 글로 정리해두었습니다.
운영 팁은 하나입니다. “파드가 가진 토큰의 issuer”와 “IAM OIDC provider URL”이 일치하는지 항상 맞춰보세요.
쿠버네티스 토큰 자체가 문제인 경우: BoundServiceAccountTokenVolume
최근 쿠버네티스는 ServiceAccount 토큰을 “짧은 TTL + 자동 로테이션” 형태로 제공합니다. 일반적으로는 문제 없지만, 다음 조건에서 만료처럼 보일 수 있습니다.
- 매우 오래된 EKS AMI/쿠버네티스 버전 조합
- 특이한 CSI/보안 에이전트가 토큰 파일을 가로채거나 읽기 권한을 깨뜨림
automountServiceAccountToken설정이 워크로드/SA 수준에서 엇갈림
점검 포인트:
kubectl -n <namespace> get pod <pod> -o jsonpath='{.spec.automountServiceAccountToken}'
kubectl -n <namespace> get sa <serviceaccount> -o jsonpath='{.automountServiceAccountToken}'
둘 중 하나라도 false면 토큰이 마운트되지 않을 수 있습니다.
디버깅을 빠르게 만드는 재현용 Pod
문제 파드가 복잡하면, 동일한 ServiceAccount로 최소 Pod를 띄워 STS 호출만 검증하는 게 가장 빠릅니다.
apiVersion: v1
kind: Pod
metadata:
name: irsa-debug
namespace: my-ns
spec:
serviceAccountName: my-sa
containers:
- name: awscli
image: amazon/aws-cli:2.15.0
command: ["sh", "-lc"]
args:
- |
echo "ROLE=$AWS_ROLE_ARN"
echo "TOKEN_FILE=$AWS_WEB_IDENTITY_TOKEN_FILE"
aws sts get-caller-identity --region ap-northeast-2
restartPolicy: Never
실행 후:
kubectl -n my-ns logs irsa-debug
여기서 get-caller-identity가 실패하면 IRSA 레벨 문제(Trust/OIDC/토큰/네트워크)로 확정할 수 있습니다.
애플리케이션 코드에서 자주 만드는 실수: 자격증명 고정
IRSA는 “임시 자격증명”입니다. 즉, 앱이 아래처럼 자격증명을 한 번만 읽어서 전역 변수로 고정해버리면, 만료 이후 403이 날 수 있습니다.
- 프로세스 시작 시 STS 호출 결과를 파일로 저장하고 계속 재사용
- SDK가 제공하는 Provider Chain을 우회해서 AccessKey/SecretKey/SessionToken을 직접 주입
(예시) Go AWS SDK v2에서 권장 패턴
아래처럼 config.LoadDefaultConfig에 맡기면 WebIdentity를 포함한 기본 체인이 알아서 로테이션합니다.
package main
import (
"context"
"fmt"
"log"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/sts"
)
func main() {
ctx := context.Background()
cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
log.Fatal(err)
}
client := sts.NewFromConfig(cfg)
out, err := client.GetCallerIdentity(ctx, &sts.GetCallerIdentityInput{})
if err != nil {
log.Fatal(err)
}
fmt.Println(*out.Arn)
}
만약 커스텀 Credential Provider를 쓰고 있다면, “만료 갱신 로직”이 있는지부터 의심해야 합니다.
네트워크/프록시로 STS만 막히는 케이스
EKS에서 Private 서브넷 + 제한된 egress 환경이면, STS로의 HTTPS 통신이 막혀서 갱신이 실패할 수 있습니다.
체크리스트:
- NAT Gateway 또는 egress 경로 존재 여부
- VPC Endpoint 사용 시
com.amazonaws.<region>.sts인터페이스 엔드포인트 연결 - 프록시 사용 시
HTTPS_PROXY설정과NO_PROXY에 AWS 도메인 포함 여부
파드에서 간단 확인:
kubectl -n <namespace> exec -it <pod> -- sh -lc 'apk add --no-cache curl >/dev/null 2>&1 || true; curl -sS https://sts.ap-northeast-2.amazonaws.com/ -o /dev/null -w "%{http_code}\n"'
403은 “접근 거부”일 수도 있지만, 여기서는 단순히 STS가 살아있음을 보는 용도입니다. 타임아웃/리졸브 실패가 나오면 네트워크 문제로 좁혀집니다.
해결 절차를 한 장으로 정리
운영에서 시간을 아끼는 순서대로 정리하면 아래입니다.
- 재현용 디버그 Pod로
aws sts get-caller-identity확인 - 파드 내부에서
AWS_ROLE_ARN,AWS_WEB_IDENTITY_TOKEN_FILE및 토큰 파일 존재 확인 - IAM Role Trust Policy의
sub,aud, Federated(OIDC provider ARN) 확인 - CloudTrail에서
AssumeRoleWithWebIdentity이벤트의 성공/실패 확인 - OIDC provider(Thumbprint/Issuer) 변경 여부 확인
- STS 네트워크 경로(NAT/VPC Endpoint/프록시) 확인
- 앱 코드에서 자격증명 고정/캐시 여부 확인 및 SDK 업그레이드
같이 보면 좋은 글: “토큰/JWT 디버깅” 관점
IRSA의 토큰 문제는 결국 “JWT 기반 인증이 예상대로 갱신/검증되는가”의 문제로 수렴합니다. JWT 디버깅 감각을 잡는 데는 아래 글도 도움이 됩니다.
마무리
EKS IRSA의 ExpiredToken과 403은 대부분 “정책이 틀렸다”라기보다, 토큰이 어떤 경로로 발급되고 소비되는지를 놓쳐서 생깁니다. 특히 AssumeRoleWithWebIdentity가 성공하는지부터 확인하면, 문제 영역이 Trust/OIDC인지, 네트워크인지, 애플리케이션 갱신 로직인지가 빠르게 갈립니다.
다음 장애를 대비해 권장하는 최소 안전장치는 두 가지입니다.
- CloudTrail에서
AssumeRoleWithWebIdentity실패 알람(AccessDenied 급증) - 재현용
irsa-debugPod 매니페스트를 레포에 저장해 즉시 검증 가능하게 유지