- Published on
GitHub Actions OIDC로 AWS AssumeRole 실패 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버리스 배포든 EKS 배포든, GitHub Actions에서 OIDC(OpenID Connect) 기반으로 AWS Role을 Assume 하도록 구성하면 액세스 키를 저장하지 않아도 되어 보안적으로 매우 깔끔합니다. 하지만 실제로는 AssumeRoleWithWebIdentity 단계에서 한 번쯤은 막히기 마련입니다. 특히 에러 메시지가 애매하게 나오거나(403/AccessDenied) 조건이 조금만 어긋나도 바로 실패하기 때문에, 원인-해결을 체계적으로 정리해두면 다음에 같은 시간을 낭비하지 않습니다.
이 글에서는 GitHub Actions OIDC로 AWS AssumeRole이 실패할 때, 가장 빈번한 실패 패턴을 로그와 정책(Trust policy) 관점에서 빠르게 좁혀가는 방법을 다룹니다. 이미 403 이슈를 폭넓게 다룬 글이 필요하면 아래 글도 함께 참고하세요.
실패 증상: 어떤 에러가 나오나
대부분 아래 형태 중 하나로 실패합니다.
An error occurred (AccessDenied) when calling the AssumeRoleWithWebIdentity operation: Not authorized to perform sts:AssumeRoleWithWebIdentityNo OpenIDConnect provider found in your account for https://token.actions.githubusercontent.comInvalidIdentityToken: Incorrect token audienceThe security token included in the request is invalidRequestExpired/RequestTimeTooSkewed(간접적으로 STS 호출 실패)
핵심은 STS AssumeRoleWithWebIdentity는 “권한 정책”이 아니라 “신뢰 정책(Trust policy)”이 거의 전부를 결정한다는 점입니다. 권한(permissions policy)은 Assume 성공 후에야 의미가 있습니다.
1) GitHub Actions 워크플로우 기본 점검 (permissions, role-to-assume)
OIDC를 쓰려면 워크플로우에 id-token: write가 필수입니다. 이게 없으면 configure-aws-credentials 액션이 OIDC 토큰을 발급받지 못해 실패합니다.
name: deploy
on:
push:
branches: ["main"]
permissions:
id-token: write # OIDC 토큰 발급 필수
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy
aws-region: ap-northeast-2
- name: Who am I?
run: aws sts get-caller-identity
여기서 get-caller-identity가 실패한다면, 거의 항상 Trust policy / OIDC Provider / 조건(aud/sub) 문제입니다.
2) IAM OIDC Provider 존재 여부 확인
계정에 GitHub OIDC Provider가 없으면 다음 에러가 흔합니다.
No OpenIDConnect provider found in your account for https://token.actions.githubusercontent.com
AWS 콘솔에서 IAM → Identity providers에 아래 Provider가 있어야 합니다.
- Provider URL:
https://token.actions.githubusercontent.com - Audience: 보통
sts.amazonaws.com
CLI로는 다음처럼 확인합니다.
aws iam list-open-id-connect-providers
# ARN을 알면 상세 확인
aws iam get-open-id-connect-provider \
--open-id-connect-provider-arn arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com
만약 없다면 콘솔에서 추가하거나 IaC(Terraform/CloudFormation)로 생성해야 합니다.
3) Trust policy에서 가장 많이 틀리는 것: Principal, Action, Condition
OIDC AssumeRole 실패의 80%는 Trust policy의 조건식이 맞지 않아서입니다. 아래는 GitHub Actions에 권장되는 형태(가장 무난한 베이스)입니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012: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:ref:refs/heads/main"
}
}
}
]
}
3-1) aud 불일치: Incorrect token audience
에러 예:
InvalidIdentityToken: Incorrect token audience
원인:
- Trust policy는
aud=sts.amazonaws.com을 기대하는데, 실제 토큰의aud가 다름
해결:
- 워크플로우에서 별도 audience를 쓰지 않는다면, Trust policy의
aud를sts.amazonaws.com으로 고정 - 반대로 커스텀 audience를 쓰는 경우(드묾)에는 액션/코드에서 발급받는 토큰 audience와 일치시켜야 합니다.
3-2) sub 불일치: 브랜치/태그/환경(environment) 조건이 다름
sub는 워크플로우 실행 컨텍스트에 따라 형태가 달라집니다.
- 브랜치 푸시:
repo:ORG/REPO:ref:refs/heads/main - 태그:
repo:ORG/REPO:ref:refs/tags/v1.2.3 - PR:
repo:ORG/REPO:pull_request - environment 사용 시:
repo:ORG/REPO:environment:prod형태가 포함될 수 있음
따라서 Trust policy를 너무 빡빡하게 걸면, main에서는 되는데 tag 릴리즈에서만 실패 같은 상황이 생깁니다.
예: main + tags 모두 허용하고 싶다면
"StringLike": {
"token.actions.githubusercontent.com:sub": [
"repo:ORG/REPO:ref:refs/heads/main",
"repo:ORG/REPO:ref:refs/tags/*"
]
}
또는 조직 내 여러 레포에서 같은 Role을 쓰려면(권장하진 않지만) 최소한 prefix를 제한하세요.
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:ORG/*:*"
}
3-3) StringEquals vs StringLike 실수
sub는 와일드카드가 필요하면StringLikeaud는 보통 정확히 일치해야 하므로StringEquals
둘을 반대로 쓰면 조건이 매칭되지 않아 AssumeRole이 실패합니다.
4) Role ARN/Account/Region 착각: 다른 계정 Role을 가리킴
의외로 많습니다.
role-to-assumeARN의 account id가 다른 계정- 동일 이름 Role이 여러 계정에 존재
워크플로우에서 aws sts get-caller-identity가 되더라도, 원하는 계정/Role이 맞는지 확인하세요.
aws sts get-caller-identity
aws iam get-role --role-name github-actions-deploy
5) Permissions policy 문제는 Assume 이후에 터진다
AssumeRole 자체는 성공했는데, 다음 단계에서 실패한다면(예: ECR push, S3 sync, CloudFormation deploy) 그건 Trust policy가 아니라 Role의 permissions policy 문제입니다.
예:
ecr:PutImage권한 없음s3:PutObject권한 없음
이 경우에는 CloudTrail에서 AccessDenied 이벤트의 eventName을 보고 해당 액션을 허용하면 됩니다.
6) CloudTrail로 원인 확정하기 (가장 빠른 방법)
애매한 403/AccessDenied는 CloudTrail이 정답을 줍니다.
- Event source:
sts.amazonaws.com - Event name:
AssumeRoleWithWebIdentity - Error code/message 확인
특히 Trust policy 조건 불일치면 CloudTrail에 거절이 남고, requestParameters에 audience/subject 단서가 남는 경우가 많습니다.
7) 시간 문제로 STS가 실패하는 케이스 (RequestTimeTooSkewed)
GitHub-hosted runner에서는 흔치 않지만, self-hosted runner(특히 VM/온프레/사설망)에서는 시간 드리프트로 STS/TLS가 연쇄 실패할 수 있습니다.
RequestTimeTooSkewedRequestExpired- TLS 핸드셰이크 오류가 동반되기도 함
Kubernetes/EKS 환경에서 시간 드리프트로 STS/TLS가 깨지는 사례는 아래 글이 진단에 도움이 됩니다.
self-hosted runner라면 NTP/chrony를 점검하고, 컨테이너 기반 러너면 호스트 시간 동기화가 우선입니다.
8) 실전 체크리스트 (10분 안에 끝내기)
8-1) 워크플로우
-
permissions: id-token: write존재 -
role-to-assumeARN이 정확(계정/Role명) -
aws-region올바름(보통 STS는 글로벌이지만 서비스 호출은 리전 영향)
8-2) AWS IAM
- OIDC Provider 존재:
token.actions.githubusercontent.com - Role Trust policy에
sts:AssumeRoleWithWebIdentity -
Principal.Federated가 OIDC Provider ARN과 일치 -
aud=sts.amazonaws.com일치 -
sub가 실제 실행 컨텍스트(main/tag/pr/environment)와 일치
8-3) 증거 기반 확인
- CloudTrail에서
AssumeRoleWithWebIdentity이벤트 확인 - 실패 시 error message로 Trust policy 조건 수정
9) 권장 패턴: 최소 권한 + 명확한 sub 제한
OIDC의 장점은 “토큰 기반 임시 자격”이지만, 반대로 말하면 Trust policy가 허술하면 외부에서 악용될 여지가 생깁니다. 실무에서는 다음 정도를 권장합니다.
- 레포 단위로
sub제한(최소repo:ORG/REPO:*) - 브랜치/태그/환경을 명시적으로 허용
- Role은 환경별로 분리(dev/prod)
예: prod는 main + environment=prod만 허용
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012: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:ref:refs/heads/main",
"repo:ORG/REPO:environment:prod"
]
}
}
}
]
}
마무리
GitHub Actions OIDC에서 AssumeRole이 실패하면, 대부분은 (1) 워크플로우의 id-token 권한 누락, (2) OIDC Provider 미생성, (3) Trust policy의 aud/sub 조건 불일치 중 하나입니다. 특히 sub는 브랜치/태그/PR/environment에 따라 달라져서 “어제는 됐는데 오늘은 안 됨”의 대표 원인이 됩니다.
문제가 길어지면 감으로 수정하지 말고, CloudTrail의 AssumeRoleWithWebIdentity 이벤트를 기준으로 조건을 맞추는 방식이 가장 빠릅니다. 추가로 배포 단계에서 403이 이어진다면 Trust policy가 아니라 Role 권한 정책을 의심하고, 403 전반을 다루는 글도 함께 확인해보세요.