- Published on
EKS IRSA는 되는데 SSM Parameter Store만 403일 때
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서론
EKS에서 IRSA(IAM Roles for Service Accounts) 설정을 끝내고 sts:GetCallerIdentity까지 잘 찍히면 보통 “권한은 다 됐다”고 착각하기 쉽습니다. 그런데 실제 앱에서 SSM Parameter Store를 읽는 순간 403 AccessDeniedException이 터지는 케이스가 꽤 흔합니다. 이유는 간단합니다. IRSA는 “누가 호출하느냐(인증)”를 해결해주지만, SSM은 “무엇을 할 수 있느냐(인가)”에 더해 리소스 정책, KMS, 네트워크(엔드포인트), 경로 기반 권한 등에서 막히기 쉽기 때문입니다.
이 글은 “IRSA는 되는데 SSM Parameter Store만 403” 상황에서, 원인을 체계적으로 분해하고 재현/검증/수정까지 이어지도록 체크리스트 형태로 정리합니다. (유사하게 S3에서 403이 나는 패턴과도 닮아 있습니다: EKS Pod에서 S3 403 AccessDenied 원인 10가지)
증상 정리: IRSA는 정상인데 SSM만 403
대표 증상은 아래 중 하나입니다.
- Pod 안에서
aws sts get-caller-identity는 성공 (역할 ARN이 기대값) - 하지만
aws ssm get-parameter또는 애플리케이션 SDK 호출에서AccessDeniedException: User: arn:aws:sts::...:assumed-role/... is not authorized to perform: ssm:GetParameter on resource: arn:aws:ssm:...:parameter/...- 혹은
kms:Decrypt관련 AccessDenied - 혹은
AccessDeniedException만 던지고 CloudTrail에는ssm:GetParameters가 찍힘
핵심은 “IRSA가 정상”은 STS로 역할을 얻는 단계가 정상이라는 의미일 뿐, SSM에 필요한 세부 권한이 맞는지는 별개라는 점입니다.
1) 먼저 ‘정말 IRSA 역할로 호출 중인지’ 확인
가장 먼저 Pod 내부에서 아래를 확인합니다.
kubectl exec -it deploy/myapp -- sh
# 1) IRSA 환경 변수/토큰 확인
env | egrep 'AWS_ROLE_ARN|AWS_WEB_IDENTITY_TOKEN_FILE|AWS_REGION|AWS_DEFAULT_REGION'
ls -l $AWS_WEB_IDENTITY_TOKEN_FILE
# 2) 실제 호출 주체 확인
aws sts get-caller-identity
여기서 Arn이 다음처럼 나와야 합니다.
arn:aws:sts::<ACCOUNT_ID>:assumed-role/<ROLE_NAME>/<SESSION_NAME>
만약 노드 IAM Role(EC2 instance profile)로 찍히면 IRSA가 아닌 것입니다. 다만 질문의 전제(“IRSA는 됨”)라면 여기까지는 통과할 확률이 높습니다.
서비스어카운트 어노테이션 재확인
kubectl get sa my-sa -o yaml | yq '.metadata.annotations'
예상:
eks.amazonaws.com/role-arn: arn:aws:iam::<ACCOUNT_ID>:role/<ROLE_NAME>
2) SSM Parameter Store 권한은 ‘액션 + 리소스 ARN + 경로’가 맞아야 함
SSM Parameter Store는 흔히 아래 액션이 필요합니다.
ssm:GetParameterssm:GetParametersssm:GetParametersByPath- (옵션)
ssm:DescribeParameters
그리고 리소스는 보통 다음 형태입니다.
arn:aws:ssm:<REGION>:<ACCOUNT_ID>:parameter/<PATH>
여기서 자주 틀리는 지점이 3가지입니다.
- 리전(Region) 불일치: 앱은
ap-northeast-2로 호출하는데 정책은us-east-1ARN만 열어둠 - 경로(prefix) 불일치:
/prod/app/*만 열어뒀는데 실제는/prod/apps/* - GetParametersByPath 권한 누락: 코드는 by-path로 긁는데 정책은
GetParameter만 있음
최소 동작 정책 예시
아래는 /prod/myapp/ 경로만 읽게 하는 예시입니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadMyAppParameters",
"Effect": "Allow",
"Action": [
"ssm:GetParameter",
"ssm:GetParameters",
"ssm:GetParametersByPath"
],
"Resource": "arn:aws:ssm:ap-northeast-2:123456789012:parameter/prod/myapp/*"
}
]
}
> 팁: GetParametersByPath를 쓰면 Resource가 ...:parameter/prod/myapp/* 형태로 맞아야 합니다. parameter/prod/myapp처럼 별표가 없으면 403이 납니다.
CLI로 동일 호출 재현(가장 빠른 검증)
aws ssm get-parameter \
--name "/prod/myapp/db/password" \
--with-decryption \
--region ap-northeast-2
aws ssm get-parameters-by-path \
--path "/prod/myapp/" \
--recursive \
--with-decryption \
--region ap-northeast-2
여기서도 403이면 애플리케이션 문제가 아니라 IAM/SSM/KMS 쪽입니다.
3) SecureString이면 ‘KMS Decrypt’가 별도로 필요 (가장 흔한 함정)
Parameter Store의 SecureString은 KMS로 암호화됩니다.
- AWS 관리형 키
alias/aws/ssm를 쓰면 대개 자동으로 되는 줄 알지만, 역할에kms:Decrypt가 없으면 막힐 수 있습니다(특히 커스텀 키 사용 시 거의 100% 필요). - 커스텀 KMS Key(CMK)를 쓴다면 IAM 정책 + KMS Key policy 둘 다에서 허용돼야 합니다.
IAM 정책에 kms:Decrypt 추가 예시
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowDecryptForSSM",
"Effect": "Allow",
"Action": ["kms:Decrypt"],
"Resource": "arn:aws:kms:ap-northeast-2:123456789012:key/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
}
]
}
KMS Key policy에서 역할 허용 예시(핵심 부분)
{
"Sid": "AllowIRSAroleToDecrypt",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/my-eks-irsa-role"
},
"Action": ["kms:Decrypt"],
"Resource": "*"
}
> KMS는 “IAM에서 Allow”만으로 끝나지 않습니다. 키 정책에서 거부되면 무조건 실패합니다.
4) 리소스 기반 정책/Organizations SCP/Permission Boundary도 확인
SSM Parameter 자체에 리소스 정책을 붙이는 경우는 상대적으로 적지만, 조직 환경에서는 다음이 더 흔합니다.
- **SCP(Service Control Policy)**가
ssm:GetParameter또는kms:Decrypt를 Deny - IAM Role에 Permission Boundary가 붙어 SSM/KMS가 잘림
- 세션 정책(Session policy) 또는 AssumeRole 시 policy 제한
이 경우 IAM 정책만 봐서는 “Allow인데 왜?”가 됩니다. 확인 포인트:
- AWS 콘솔에서 해당 Role의 Permission boundary 존재 여부
- Organizations SCP에서 SSM/KMS 관련 Deny
- CloudTrail에서
errorCode=AccessDenied와 함께explicitDeny여부
5) 호출 리전/엔드포인트 문제로 ‘403처럼 보이는’ 케이스
보통 네트워크 문제는 타임아웃/5xx가 많은데, 환경에 따라 프록시/게이트웨이/WAF/엔드포인트 정책 때문에 403으로 보이기도 합니다.
특히 프라이빗 서브넷에서 VPC 엔드포인트(Interface Endpoint)로 SSM을 쓰는 구성이라면 다음을 확인하세요.
com.amazonaws.<region>.ssmVPC Endpoint 존재- (SecureString이면)
com.amazonaws.<region>.kms도 필요할 수 있음 - Endpoint의 Security Group이 Pod/노드에서 443 접근 허용
- Endpoint policy가
ssm:GetParameter*를 막고 있지 않은지
엔드포인트 정책 예시(너무 제한적으로 걸면 403)
{
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": ["ssm:GetParameter", "ssm:GetParameters", "ssm:GetParametersByPath"],
"Resource": "*"
}
]
}
엔드포인트 정책을 최소권한으로 줄이려다 ARN 패턴을 잘못 써서 403이 나는 일이 많습니다.
네트워크/엔드포인트/IRSA가 얽히면 “IRSA는 되는데 AWS API만 간헐적으로 실패” 같은 증상도 나옵니다. 이 결의 문제는 STS 단계부터 타임아웃이 나기도 하니, 필요하면 EKS Pod STS AssumeRole 타임아웃 - NAT·PrivateLink·DNS도 함께 점검하세요.
6) External Secrets/CSI Driver 사용 시: 실제로 어떤 API를 치는지 확인
Kubernetes에서 Parameter Store를 가져오는 방식이 다양합니다.
- 앱이 AWS SDK로 직접 호출
- External Secrets Operator가 SSM을 읽어 Secret 생성
- Secrets Store CSI Driver + AWS provider
이때 “내 앱 Role”이 아니라 컨트롤러/드라이버의 ServiceAccount Role이 호출 주체일 수 있습니다. 즉,
- 앱 Pod는 IRSA 정상
- 하지만 SSM을 읽는 주체는
external-secrets네임스페이스의 SA - 결과적으로 앱 입장에서는 Secret이 안 생겨서 장애처럼 보임
ExternalSecret을 쓰는 경우, 아래 글의 체크리스트도 같이 보면 원인 좁히기가 빨라집니다.
누가 호출하는지 CloudTrail로 역추적
CloudTrail 이벤트에서 userIdentity.arn을 보면 실제 호출 Role이 드러납니다.
- 기대:
...:assumed-role/my-eks-irsa-role/... - 실제:
...:assumed-role/external-secrets-role/...
호출 주체가 다르면 정책을 붙일 대상도 달라집니다.
7) 디버깅을 ‘정답에 수렴’시키는 순서
아래 순서로 가면 삽질을 줄일 수 있습니다.
7.1 Pod에서 최소 재현
aws sts get-caller-identity
aws ssm get-parameter --name "/prod/myapp/test" --region ap-northeast-2
- 여기서 실패하면 앱 로직/라이브러리 문제가 아니라 AWS 권한/리소스 문제
7.2 에러 메시지에서 액션/리소스 정확히 추출
대부분 에러에 다음이 포함됩니다.
perform: ssm:GetParametersByPathon resource: arn:aws:ssm:...:parameter/...
이 문자열 그대로 IAM 정책의 Action/Resource와 1:1 비교합니다.
7.3 SecureString이면 KMS로 분기
--with-decryption에서만 실패하면 거의 KMS- CloudTrail에서 KMS
Decrypt가 Deny로 찍히는지 확인
7.4 조직/바운더리/엔드포인트 정책 확인
- Allow인데도 계속 403이면 “상위에서 Deny”를 의심
8) 실전 예시: 흔한 잘못된 정책 3가지
예시 1: 리전이 다른 ARN
"Resource": "arn:aws:ssm:us-east-1:123456789012:parameter/prod/myapp/*"
앱이 ap-northeast-2를 치면 403.
예시 2: 별표 누락
"Resource": "arn:aws:ssm:ap-northeast-2:123456789012:parameter/prod/myapp/"
/prod/myapp/db/password는 매칭되지 않습니다.
예시 3: GetParameter만 허용했는데 실제 호출은 ByPath
"Action": ["ssm:GetParameter"]
SDK/라이브러리가 내부적으로 GetParametersByPath를 쓰면 403.
9) 마무리: “IRSA 성공”은 시작일 뿐
정리하면, “IRSA는 되는데 SSM Parameter Store 403”은 보통 아래 중 하나로 귀결됩니다.
- SSM 액션/리소스 ARN(경로/리전) 불일치
SecureString인데kms:Decrypt또는 KMS Key policy 누락- External Secrets/CSI 등 다른 ServiceAccount Role이 실제 호출 주체
- SCP/Permission Boundary/Endpoint policy 같은 상위 Deny
IRSA는 인증을 해결해주지만, SSM은 권한 모델이 더 입체적입니다. 위 순서대로 “호출 주체 → 액션/리소스 → KMS → 상위 Deny/엔드포인트”로 좁혀가면, 대부분 30분 안에 원인을 특정할 수 있습니다.
부록: 빠른 점검 스니펫
# (1) 호출 주체
aws sts get-caller-identity
# (2) 파라미터 타입 확인(권한 있으면)
aws ssm describe-parameters --parameter-filters Key=Name,Option=BeginsWith,Values=/prod/myapp/ --region ap-northeast-2
# (3) 단일 파라미터
aws ssm get-parameter --name "/prod/myapp/db/password" --with-decryption --region ap-northeast-2
# (4) 경로 기반
aws ssm get-parameters-by-path --path "/prod/myapp/" --recursive --with-decryption --region ap-northeast-2