- Published on
GitHub Actions OIDC 권한거부 - AWS AssumeRole 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버리스/컨테이너 배포 파이프라인에서 장기 액세스 키를 없애기 위해 GitHub Actions OIDC(OpenID Connect)로 AWS STS AssumeRole을 붙이는 경우가 많습니다. 그런데 실제 적용 단계에서 가장 흔히 마주치는 문제가 AccessDenied 혹은 Not authorized to perform sts:AssumeRoleWithWebIdentity 같은 권한거부입니다.
이 글은 “왜 거부되는지”를 신뢰 정책(Trust policy), 권한 정책(Permission policy), OIDC 토큰 클레임 조건(aud/sub) 세 축으로 나눠서 진단하고, 바로 복붙 가능한 설정 예제로 해결하는 것을 목표로 합니다.
> OIDC 기반 403 전반을 더 넓게 다룬 가이드는 GitHub Actions OIDC로 AWS 배포 403 해결 가이드도 함께 참고하면 좋습니다.
1) 증상 패턴: 어떤 에러가 뜨는가
GitHub Actions에서 aws-actions/configure-aws-credentials를 쓰면 대개 아래 형태로 실패합니다.
An error occurred (AccessDenied) when calling the AssumeRoleWithWebIdentity operation: Not authorized to perform sts:AssumeRoleWithWebIdentityAccessDenied: User: arn:aws:sts::... is not authorized to perform: sts:AssumeRole on resource ...InvalidIdentityToken: Incorrect token audienceNo OpenIDConnect provider found in your account for https://token.actions.githubusercontent.com
각각 원인이 다릅니다.
- AssumeRoleWithWebIdentity 거부: 대개 역할의 신뢰 정책이 OIDC Provider/조건을 제대로 허용하지 않음
- AssumeRole 거부: OIDC가 아니라 다른 방식으로 AssumeRole을 시도했거나, 권한 정책이 막힘
- Incorrect token audience:
aud조건 불일치(보통sts.amazonaws.com) - OIDC provider not found: IAM OIDC Provider 미생성/URL 오타
2) OIDC AssumeRole이 동작하는 구조(핵심만)
GitHub Actions 런너는 token.actions.githubusercontent.com에서 OIDC 토큰(JWT)을 발급받고, AWS STS의 AssumeRoleWithWebIdentity로 역할(Role)을 요청합니다.
이때 AWS는 다음을 검증합니다.
- IAM OIDC Provider가 계정에 존재하는가
- 대상 Role의 Trust policy가 해당 Provider를 신뢰하는가
- Trust policy의 Condition(특히
aud,sub)이 토큰 클레임과 일치하는가 - AssumeRole 성공 후, 그 Role에 붙은 Permission policy가 실제 AWS API 호출을 허용하는가
즉, “AssumeRole 단계에서의 거부”와 “AssumeRole 이후 리소스 접근 거부”는 완전히 다른 문제입니다.
3) 가장 흔한 원인 TOP 6와 해결
3.1 IAM OIDC Provider를 만들지 않음(또는 URL/Thumbprint 문제)
에러 예:
No OpenIDConnect provider found in your account...
해결:
- IAM 콘솔에서 Identity providers → Add provider → OpenID Connect
- Provider URL:
https://token.actions.githubusercontent.com - Audience:
sts.amazonaws.com
CLI로도 생성할 수 있습니다.
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com \
--thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1
> Thumbprint는 AWS 문서/가이드에 따라 변경될 수 있으니, 최신 값을 확인하세요. (요즘은 콘솔 생성이 더 안전한 편입니다.)
3.2 Role Trust policy의 Principal이 잘못됨
OIDC는 Federated principal로 신뢰해야 합니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<ACCOUNT_ID>:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity"
}
]
}
Principal을AWS: arn:aws:iam...:user/...같은 형태로 두면 OIDC로는 절대 AssumeRole이 안 됩니다.
3.3 aud 조건 불일치(Incorrect token audience)
aws-actions/configure-aws-credentials 기본값은 audience: sts.amazonaws.com입니다. Trust policy 조건도 일치해야 합니다.
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
}
}
만약 조직 정책상 커스텀 audience를 쓴다면, 액션 입력값과 Trust policy를 같이 맞춰야 합니다.
3.4 sub 조건이 너무 빡세거나 형식이 다름(브랜치/태그/환경)
가장 많이 틀리는 지점이 sub입니다. GitHub OIDC의 sub는 워크플로 실행 컨텍스트에 따라 달라집니다.
대표 형태:
- 브랜치:
repo:<OWNER>/<REPO>:ref:refs/heads/main - 태그:
repo:<OWNER>/<REPO>:ref:refs/tags/v1.2.3 - 환경(environment) 사용 시:
repo:<OWNER>/<REPO>:environment:prod
따라서 main 브랜치만 허용하려면:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<ACCOUNT_ID>: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:<OWNER>/<REPO>:ref:refs/heads/main"
}
}
}
]
}
StringEquals로sub를 박아두고 실제 실행은refs/heads/master라면 바로 거부됩니다.- 배포를 태그로만 할 계획이면
refs/tags/*로 바꿔야 합니다.
여러 브랜치를 허용하려면 StringLike + 와일드카드를 쓰세요.
"StringLike": {
"token.actions.githubusercontent.com:sub": [
"repo:<OWNER>/<REPO>:ref:refs/heads/main",
"repo:<OWNER>/<REPO>:ref:refs/heads/release/*"
]
}
3.5 GitHub Actions에서 id-token: write 권한 누락
OIDC 토큰 발급 자체가 안 되면, 액션이 AssumeRole 시도 전에 실패하거나 내부적으로 토큰이 비어 실패합니다.
워크플로에 반드시 추가하세요.
permissions:
id-token: write
contents: read
3.6 Role은 AssumeRole 성공했는데, 이후 AWS API가 AccessDenied
이 경우는 Trust policy가 아니라 Permission policy 문제입니다.
예: ECR 푸시에서 실패한다면 Role에 ECR 권한이 있어야 합니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:PutImage"
],
"Resource": "*"
}
]
}
배포 대상이 EKS라면 eks:DescribeCluster 등도 필요할 수 있습니다. EKS 운영 중 STS 토큰/인증 이슈는 EKS Pod에서 STS 403 ExpiredToken 해결법도 참고할 만합니다.
4) 재현 가능한 “정답” 구성 예시
4.1 IAM Role Trust policy(권장 베이스라인)
aud는 고정(sts.amazonaws.com)sub는 최소 권한으로(예: main 브랜치 또는 prod environment)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "GitHubActionsOIDC",
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<ACCOUNT_ID>: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:<OWNER>/<REPO>:ref:refs/heads/main"
}
}
}
]
}
4.2 GitHub Actions workflow 예시
name: deploy
on:
push:
branches: ["main"]
permissions:
id-token: write
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::<ACCOUNT_ID>:role/<ROLE_NAME>
aws-region: ap-northeast-2
- name: Verify caller identity
run: aws sts get-caller-identity
여기서 get-caller-identity가 성공하면 “OIDC → AssumeRole” 체인은 통과한 것입니다. 이후 실패는 대부분 서비스 권한(ECR/EKS/S3/CloudFront 등) 문제로 좁혀집니다.
5) 디버깅 체크리스트(10분 컷)
5.1 토큰 클레임과 Trust policy 조건을 맞춰라
aud는 보통sts.amazonaws.comsub는 브랜치/태그/environment에 따라 달라짐
가장 안전한 접근은 처음에는 sub를 넓게 잡아 원인 분리 후, 점진적으로 좁히는 것입니다.
예(임시로 레포 전체 허용):
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:<OWNER>/<REPO>:*"
}
원인 파악 후 main 또는 environment:prod로 줄이세요.
5.2 CloudTrail로 “왜 거부됐는지”를 본다
AssumeRoleWithWebIdentity 실패는 CloudTrail 이벤트에 흔적이 남습니다.
- Event source:
sts.amazonaws.com - Event name:
AssumeRoleWithWebIdentity - Error message/condition mismatch 단서 확인
5.3 다른 계정/리전에 배포할 때의 함정
- OIDC Provider는 각 AWS 계정에 존재해야 합니다.
role-to-assumeARN이 다른 계정이면, 그 계정에 Provider/Trust policy가 정확히 있어야 합니다.
6) 보안적으로 권장하는 최소 권한 설계
- Trust policy에서
sub를 **레포 + 브랜치(or environment)**로 제한 - Permission policy는 배포에 필요한 AWS API만 허용
- 가능하면 GitHub Environment + 보호 규칙(승인/브랜치 제한)과 함께 사용
이렇게 하면 “어떤 브랜치에서든 OIDC로 프로덕션 배포 Role을 AssumeRole” 같은 사고를 크게 줄일 수 있습니다.
7) 마무리
GitHub Actions OIDC의 AssumeRole 권한거부는 대부분 다음 한 줄로 요약됩니다.
- Trust policy 조건(aud/sub)과 실제 토큰 클레임이 불일치
먼저 aws sts get-caller-identity로 AssumeRole 체인을 분리해 확인하고, 그 다음 서비스 권한(ECR/EKS/S3 등)으로 넘어가면 디버깅 시간이 급격히 줄어듭니다.
추가로 OIDC 기반 403을 케이스별로 더 정리한 글은 GitHub Actions OIDC로 AWS 배포 403 해결 가이드에서 확장해서 볼 수 있습니다.