- Published on
AWS IAM AssumeRole AccessDenied 원인 10가지
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
AssumeRole은 AWS 권한 문제 중에서도 특히 “정책은 맞는 것 같은데 왜 거부되지?”라는 상황을 자주 만듭니다. 이유는 단순히 호출자(소스) IAM 정책만 보는 게 아니라, 대상 역할(Role)의 신뢰 정책(Trust policy), 권한 경계(Permissions boundary), 세션 정책(Session policy), 조직 정책(SCP), 태그 기반 조건(ABAC)까지 여러 겹의 평가가 동시에 일어나기 때문입니다.
이 글에서는 sts:AssumeRole 호출이 AccessDenied로 떨어지는 원인을 10가지로 분류하고, 각 케이스별로 어떤 로그/설정에서 확인해야 하는지와 예시 정책을 함께 정리합니다.
관련해서 OIDC 기반 AssumeRole(예: GitHub Actions)에서의 403 케이스는 아래 글도 함께 보면 진단 속도가 빨라집니다.
먼저 확인할 것: 에러 메시지의 “누가 누구를”
에러는 보통 다음 형태입니다.
User: arn:aws:iam::111111111111:user/alice is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::222222222222:role/TargetRole
여기서 핵심은 2가지입니다.
- 호출자(Principal):
user/alice또는role/SourceRole또는 OIDC/Federated principal - 대상 리소스:
role/TargetRole
이제부터의 10가지 원인은 “호출자 쪽 정책 문제”, “대상 역할의 신뢰 정책 문제”, “조직/경계/세션 등 상위 제약”으로 나눠서 보면 빠릅니다.
원인 1) 호출자 IAM 정책에 sts:AssumeRole 허용이 없다
가장 기본인데도 많이 놓칩니다. 호출자(사용자/역할)에 아래처럼 명시적 Allow가 있어야 합니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::222222222222:role/TargetRole"
}
]
}
체크 포인트
- Resource가
*가 아니라 특정 역할 ARN인데, 실제 대상 Role ARN과 불일치 sts:AssumeRole이 아니라iam:PassRole만 허용해둔 경우(서로 다른 권한)
원인 2) 대상 Role의 Trust policy(신뢰 정책)에 호출자가 없다
AssumeRole은 “호출자 정책에서 허용”만으로는 부족하고, 대상 역할의 신뢰 정책이 호출자를 신뢰해야 합니다.
다음은 동일 계정 내에서 특정 역할만 신뢰하는 예시입니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::222222222222:role/SourceRole"
},
"Action": "sts:AssumeRole"
}
]
}
체크 포인트
Principal에 사용자 ARN을 넣어야 하는데 역할 ARN을 넣었거나 반대- 교차 계정(1111
->2222)인데 계정 ID가 틀림 - 신뢰 정책은 “대상 Role”에 있고, 호출자 정책과 위치가 다름
원인 3) Trust policy에 ExternalId 조건이 있는데 요청에 없다/불일치
서드파티가 교차 계정 Role을 Assume하도록 구성할 때 sts:ExternalId 조건을 넣는 경우가 많습니다. 이때 요청에 ExternalId가 없으면 거부됩니다.
Trust policy 예시:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::111111111111:root" },
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "my-external-id"
}
}
}
]
}
CLI 호출 예시:
aws sts assume-role \
--role-arn arn:aws:iam::222222222222:role/TargetRole \
--role-session-name test \
--external-id my-external-id
체크 포인트
- ExternalId 값이 환경별로 다르게 배포되어 불일치
- 공급사 문서의 ExternalId를 그대로 쓰지 않고 공백/개행이 섞임
원인 4) OIDC/SAML 웹 아이덴티티인데 Trust policy의 sub/aud 조건 불일치
AssumeRoleWithWebIdentity 또는 AssumeRoleWithSAML은 일반 AssumeRole과 신뢰 정책 조건이 훨씬 빡빡한 편입니다.
예를 들어 OIDC에서는 token.actions.githubusercontent.com:sub 같은 클레임 조건이 맞아야 합니다.
Trust policy 예시(일부):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::222222222222:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:org/repo:*"
}
}
}
]
}
체크 포인트
aud가sts.amazonaws.com이 아닌 값으로 들어옴sub패턴이 브랜치/환경에 따라 달라져 매칭 실패
이 영역은 케이스가 많아서, OIDC 중심으로는 아래 글이 더 직접적인 해법을 제공합니다.
원인 5) 명시적 Deny가 어딘가에 있다(정책, SCP, 세션 정책)
AWS 권한 평가는 “명시적 Deny가 있으면 무조건 Deny”입니다. 즉, Allow를 아무리 추가해도 Deny가 남아 있으면 계속 실패합니다.
Deny가 숨어있는 대표 위치
- 호출자에 붙은 IAM 정책(관리형/인라인)
- 호출자 또는 대상 Role에 붙은 권한 경계(Permissions boundary)
- AWS Organizations SCP
- AssumeRole 시 함께 붙는 세션 정책(Session policy)
CloudTrail에서 errorCode가 AccessDenied일 때 additionalEventData 또는 requestParameters만 보지 말고, Organizations/SCP 적용 여부도 같이 확인해야 합니다.
원인 6) Permissions Boundary가 sts:AssumeRole을 막는다
권한 경계는 “이 주체가 가질 수 있는 최대 권한”을 제한합니다. 호출자에게 sts:AssumeRole Allow가 있어도, 경계에서 막으면 실패합니다.
예: 경계 정책이 STS를 전부 거부하는 경우
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": "sts:*",
"Resource": "*"
}
]
}
체크 포인트
- “권한 경계는 정책 목록에서 잘 안 보인다”는 점
- IAM 콘솔에서 사용자/역할 상세의 Permissions boundary 항목 확인
원인 7) 대상 Role의 Trust policy에 aws:PrincipalArn/aws:PrincipalOrgID 조건이 있다
보안 강화를 위해 “특정 조직만”, “특정 ARN 패턴만” 신뢰하도록 조건을 거는 경우가 많습니다.
예시(조직 ID로 제한):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "AWS": "*" },
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"aws:PrincipalOrgID": "o-abcdefghij"
}
}
}
]
}
체크 포인트
- 호출 계정이 조직에서 빠졌거나, 다른 조직에서 호출 중
aws:PrincipalArn조건에 매칭되지 않는 역할 이름(예: 배포용 Role 이름 변경)
원인 8) 세션 태그(Session Tags)나 ABAC 조건 때문에 거부된다
sts:AssumeRole은 세션 태그를 전달할 수 있고, 조직/정책에서 태그 기반(ABAC)으로 제어하는 경우가 많습니다.
대표적인 실패 패턴
- Trust policy 또는 호출자 정책에
sts:TagKeys/aws:RequestTag/xxx조건이 있는데, 요청에 태그가 없거나 키가 다름 - 반대로 “태그를 못 달게” 막아놨는데 SDK/툴이 자동으로 태그를 달아 요청
CLI에서 태그를 명시하는 예:
aws sts assume-role \
--role-arn arn:aws:iam::222222222222:role/TargetRole \
--role-session-name deploy \
--tags Key=env,Value=prod Key=team,Value=platform
Trust policy에서 태그 키를 제한하는 예:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::111111111111:role/SourceRole" },
"Action": "sts:AssumeRole",
"Condition": {
"ForAllValues:StringEquals": {
"sts:TagKeys": ["env", "team"]
}
}
}
]
}
체크 포인트
- 태그 키 대소문자 불일치
- CI 도구가 자동으로 붙이는 태그(예:
GitHub관련 키)가 조건에 의해 차단
원인 9) MFA 조건이 있는데 MFA 없이 호출한다
AssumeRole에 MFA를 강제하는 구성도 흔합니다. 이때는 호출자 자격 증명에 MFA 컨텍스트가 포함되어야 합니다.
호출자 정책 또는 신뢰 정책에서 MFA를 요구하는 예:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::222222222222:role/TargetRole",
"Condition": {
"Bool": {
"aws:MultiFactorAuthPresent": "true"
}
}
}
]
}
체크 포인트
- 콘솔에서는 되는데 CLI/SDK에서만 실패(로컬이 MFA 없이 장기 키를 쓰는 경우)
- MFA 디바이스 ARN을 잘못 지정하거나 토큰 코드 만료
원인 10) 호출 체인이 꼬였다: 이미 Assume한 Role에서 다시 Assume하는데 신뢰/권한이 다름
운영에서 흔한 형태가 “기본 자격 증명 -> Role A -> Role B”처럼 체인이 길어지는 경우입니다. 이때 에러 메시지의 Principal이 “원래 사용자”가 아니라 “Role A의 세션 ARN”으로 나타납니다.
예:
arn:aws:sts::111111111111:assumed-role/RoleA/session123
이 경우 확인해야 할 것은
- Role B의 Trust policy가 Role A(또는 Role A가 속한 계정 root)를 신뢰하는지
- Role A의 권한에 Role B로의
sts:AssumeRole이 있는지 - 세션 정책/태그가 Role A 단계에서 이미 제한을 걸고 있지 않은지
빠른 진단 루틴(실전 체크리스트)
문제 재현이 가능하다면 아래 순서가 가장 빠릅니다.
1) CloudTrail에서 STS 이벤트를 먼저 본다
찾을 이벤트
eventSource:sts.amazonaws.comeventName:AssumeRole또는AssumeRoleWithWebIdentityerrorCode:AccessDenied
CloudTrail에서 특히 유용한 필드
userIdentity.arn: 실제 호출 주체(assumed-role 세션 ARN일 수 있음)requestParameters.roleArn: 대상 Role ARNrecipientAccountId: 대상 계정
2) IAM Policy Simulator로 “호출자 정책”을 검증한다
시뮬레이터에서는
- Action:
sts:AssumeRole - Resource: 대상 Role ARN
을 넣고, 명시적 Deny 여부를 확인합니다.
3) 대상 Role의 Trust policy를 “호출자 기준”으로 다시 읽는다
신뢰 정책은 다음을 동시에 만족해야 합니다.
- Principal이 맞는가
- Action이 맞는가(
AssumeRolevsAssumeRoleWithWebIdentity) - Condition이 현재 호출 컨텍스트(ExternalId, sub, aud, MFA, OrgID, 태그)와 일치하는가
코드 예제: 최소 재현용 AssumeRole 호출
로컬/서버에서 가장 단순하게 확인하려면 CLI가 좋습니다.
aws sts assume-role \
--role-arn arn:aws:iam::222222222222:role/TargetRole \
--role-session-name debug-assume
결과로 자격 증명이 나오면 성공이고, 실패하면 에러 메시지의 Principal과 Resource를 그대로 복사해 정책/신뢰 정책을 대조하세요.
자주 섞이는 403 계열 이슈와의 구분
AssumeRole의 AccessDenied는 “권한 평가” 문제인 경우가 대부분입니다. 반면 SDK에서 MissingAuthenticationToken 같은 메시지는 서명/인증 헤더 자체가 누락된 경우가 많아 진단 방향이 달라집니다. EKS에서 AWS SDK 인증 헤더 문제를 겪고 있다면 아래 글이 참고됩니다.
마무리: 10가지를 한 문장으로 요약하면
sts:AssumeRole의 AccessDenied는 (1) 호출자 정책 Allow, (2) 대상 Role 신뢰 정책 Allow, (3) 조건(ExternalId, OIDC 클레임, MFA, 태그), (4) 상위 제약(SCP, Boundary, 세션 정책) 중 어느 한 곳이라도 Deny 또는 불일치가 있으면 발생합니다.
장애 대응 시에는 “호출자 ARN과 대상 Role ARN을 확정”하고, CloudTrail 이벤트 1건을 기준으로 정책과 신뢰 정책을 역추적하면 대부분 10분 내로 원인을 좁힐 수 있습니다.