- Published on
GitHub Actions OIDC로 AWS AssumeRole 오류 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버리스 배포든 ECR 푸시든, GitHub Actions에서 AWS 자격 증명을 다루는 가장 깔끔한 방식은 OIDC 기반 AssumeRole입니다. 장기 액세스 키를 저장하지 않아도 되고, 리포지토리·브랜치·환경 단위로 신뢰 조건을 강하게 걸 수 있기 때문입니다.
하지만 처음 적용할 때는 AssumeRoleWithWebIdentity 단계에서 다양한 오류가 터집니다. 이 글에서는 실제 현장에서 자주 겪는 오류 메시지를 기준으로 원인을 좁히고, IAM 신뢰 정책과 GitHub Actions 워크플로 설정을 어떻게 맞춰야 하는지 정리합니다.
관련해서 CI에서 캐시나 권한이 꼬일 때의 디버깅 관점은 아래 글도 참고가 됩니다.
OIDC AssumeRole 흐름을 먼저 고정하기
OIDC 기반 AWS 인증은 대략 다음 순서로 동작합니다.
- GitHub Actions 러너가 OIDC 토큰을 발급받음
- AWS STS에
AssumeRoleWithWebIdentity호출 - STS가 IAM Role의 Trust Policy(신뢰 정책)를 검사
- 조건이 맞으면 임시 자격 증명(AccessKeyId/SecretAccessKey/SessionToken)을 발급
즉, 문제의 대부분은 아래 두 범주로 수렴합니다.
- GitHub 쪽: OIDC 토큰 발급이 안 되거나, 토큰의
aud/sub/클레임이 기대와 다름 - AWS 쪽: Role Trust Policy의 Principal/Condition이 토큰과 불일치
GitHub Actions 워크플로에서 가장 흔한 실수 2가지
1) permissions에 id-token: write가 없음
OIDC 토큰 발급은 기본 권한으로는 막혀 있습니다. 아래 권한이 없으면 configure-aws-credentials가 토큰을 못 받아서 실패합니다.
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::123456789012:role/gha-deploy-role
aws-region: ap-northeast-2
실패 시 자주 보이는 증상은 다음과 같습니다.
Error: Could not load credentials from any providersError: No OpenIDConnect provider found in your account for ...와 함께 혼합되어 나타나기도 함
2) role-to-assume ARN 오타 또는 Role 이름 불일치
당연한 얘기지만, OIDC는 “키가 없으니 대충 되겠지”가 안 됩니다. ARN 오타면 즉시 실패합니다.
- 계정 ID가 다른 계정으로 찍힘
- Role 이름이
gha-deploy-role인데 워크플로는github-actions-role을 참조 - GovCloud/중국 리전 계정은 파티션이 다름 (
arn:aws-us-gov:...등)
AWS IAM OIDC Provider 설정 체크
AWS 계정에 GitHub OIDC Provider가 먼저 등록돼 있어야 합니다.
- Provider URL:
https://token.actions.githubusercontent.com - Audience: 보통
sts.amazonaws.com
CLI로 확인하려면:
aws iam list-open-id-connect-providers
aws iam get-open-id-connect-provider \
--open-id-connect-provider-arn arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com
여기서 Audience 목록에 sts.amazonaws.com가 없으면, 이후 Trust Policy에서 aud를 맞춰도 계속 실패합니다.
오류 메시지별 원인과 해결
1) InvalidIdentityToken: audience ... is invalid
원인
OIDC 토큰의 aud(audience) 클레임이 AWS가 기대하는 값과 다릅니다. GitHub Actions의 기본 케이스는 sts.amazonaws.com를 사용합니다.
대표적인 발생 케이스:
- IAM OIDC Provider에 Audience가
sts.amazonaws.com로 등록돼 있지 않음 - Trust Policy에서
token.actions.githubusercontent.com:aud조건이 다른 값으로 박혀 있음 - 워크플로에서 audience를 커스텀했는데(혹은 액션이 그렇게 동작하게 됐는데) AWS 쪽이 못 따라감
해결
Trust Policy에서 aud를 sts.amazonaws.com로 맞추고, OIDC Provider의 Audience에도 동일하게 등록합니다.
신뢰 정책 예시:
{
"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"
}
}
}
]
}
2) AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentity
원인
대부분 Trust Policy의 sub 조건 불일치입니다. sub는 “어떤 리포지토리/브랜치/환경에서 왔는지”를 표현하는 핵심 클레임이라, 여기서 조금만 달라도 거부됩니다.
자주 틀리는 포인트:
- 리포지토리 이름 오타 (
org/repo) - 브랜치 조건을
main으로 걸었는데 실제는master또는 태그 배포 pull_request이벤트에서 실행되는데, Trust Policy는ref:refs/heads/main만 허용- GitHub Environments를 쓰는데
environment:prod조건을 안 맞춤
해결: sub 패턴을 정확히 맞추기
가장 안전한 방식은 허용 범위를 최소화하되, 배포 흐름에 맞게 sub를 설계하는 것입니다.
케이스 A: main 브랜치 push만 허용
{
"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",
"token.actions.githubusercontent.com:sub": "repo:my-org/my-repo:ref:refs/heads/main"
}
}
}
]
}
케이스 B: 특정 태그 릴리스만 허용
태그는 refs/tags/v1.2.3 형태라서 StringLike가 편합니다.
{
"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:my-org/my-repo:ref:refs/tags/v*"
}
}
}
]
}
케이스 C: GitHub Environment 기반 배포만 허용
Environment를 쓰면 sub가 repo:org/repo:environment:prod처럼 바뀝니다. (브랜치 ref 기반이 아니라 환경 기반)
{
"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",
"token.actions.githubusercontent.com:sub": "repo:my-org/my-repo:environment:prod"
}
}
}
]
}
워크플로에서도 job에 environment를 붙여야 동일한 sub가 나옵니다.
jobs:
deploy:
runs-on: ubuntu-latest
environment: prod
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/gha-deploy-role
aws-region: ap-northeast-2
3) No OpenIDConnect provider found in your account
원인
AWS 계정에 GitHub OIDC Provider가 없거나, Provider ARN이 다른 계정/다른 파티션에 있습니다.
또는 Trust Policy에서 Principal의 Federated ARN을 잘못 적은 경우도 많습니다.
해결
- IAM에 OIDC Provider를 생성
- Role Trust Policy의
Principal.Federated를 실제 Provider ARN으로 수정
Trust Policy의 Federated는 반드시 아래 형태여야 합니다.
arn:aws:iam::계정ID:oidc-provider/token.actions.githubusercontent.com
4) The security token included in the request is invalid
원인
OIDC 단계가 아니라, 이후 AWS API 호출에서 자격 증명이 깨진 경우가 많습니다.
configure-aws-credentials단계가 실패했는데도 다음 스텝이 실행됨- 다른 스텝에서
AWS_ACCESS_KEY_ID등을 덮어씀 - 여러 Role을 연속으로 Assume하면서 환경 변수를 예상과 다르게 재정의
해결
configure-aws-credentials직후aws sts get-caller-identity로 검증- AWS 관련 환경 변수를 수동으로 세팅하지 말고 액션에 맡기기
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/gha-deploy-role
aws-region: ap-northeast-2
- name: Verify identity
run: aws sts get-caller-identity
OIDC 토큰 클레임을 “눈으로 확인”하는 디버깅
sub가 뭐로 들어오는지 감이 안 잡히면, 실제 OIDC 토큰을 받아서 디코딩해 보는 게 가장 빠릅니다. 다만 토큰 자체는 민감 정보이므로 로그에 그대로 찍지 않도록 주의하세요.
GitHub Actions에서 OIDC 토큰을 얻는 방식은 공식적으로 제공되지만, 토큰 값을 출력하는 순간 보안 사고가 됩니다. 대신 아래처럼 클레임만 추출해서 확인하는 방식이 안전합니다.
- name: Dump OIDC claims (safe)
shell: bash
run: |
set -euo pipefail
if [ -z "${ACTIONS_ID_TOKEN_REQUEST_URL:-}" ]; then
echo "Missing ACTIONS_ID_TOKEN_REQUEST_URL. Did you set permissions id-token: write?"
exit 1
fi
# audience를 sts.amazonaws.com로 요청
RAW=$(curl -sS -H "Authorization: bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \
"${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=sts.amazonaws.com")
TOKEN=$(python - <<'PY'
import json,sys
print(json.load(sys.stdin)["value"])
PY
<<< "$RAW")
# JWT payload(base64url) 디코드 후 필요한 클레임만 출력
python - <<'PY'
import os,base64,json
token=os.environ["TOKEN"]
payload=token.split(".")[1]
payload += "=" * (-len(payload) % 4)
data=json.loads(base64.urlsafe_b64decode(payload))
keys=["aud","sub","iss","repository","ref","sha","actor","workflow","job_workflow_ref","environment"]
print({k:data.get(k) for k in keys if k in data})
PY
env:
TOKEN: ${{ steps.idtoken.outputs.token }}
위 예시는 설명 목적이라 그대로 쓰기엔 다듬어야 합니다. 핵심은 sub와 aud를 확인해 Trust Policy 조건과 1:1로 맞추는 것입니다.
운영에서 추천하는 Trust Policy 설계 팁
1) StringLike를 남용하지 말고, 필요한 범위만
repo:my-org/*처럼 크게 열면 편하지만, 리포지토리 하나가 털리면 동일 Role을 탈취할 수 있는 범위가 커집니다. 최소 단위는 보통 아래 중 하나입니다.
- 특정 repo + 특정 브랜치
- 특정 repo + 특정 environment
- 특정 repo + 특정 태그 패턴
2) Role 권한 정책은 더 작게 쪼개기
OIDC는 “누가 Assume할 수 있나”를 통제하고, Role의 IAM Policy는 “Assume한 뒤 무엇을 할 수 있나”를 통제합니다. 둘 다 최소 권한으로 가야 합니다.
예를 들어 ECR 푸시용 Role과 Terraform 적용용 Role을 분리하면, 한쪽 워크플로가 뚫려도 피해 반경을 줄일 수 있습니다.
3) PR 이벤트에서는 OIDC를 제한적으로
외부 기여자가 PR을 열 수 있는 공개 저장소라면 특히 위험합니다. pull_request에서 배포 Role을 Assume하게 만들면, 조건이 허술할 때 임시 자격 증명 유출로 이어질 수 있습니다.
가능하면 배포는 push(보호된 브랜치) 또는 workflow_dispatch + Environment 승인으로 제한하세요.
체크리스트: 5분 안에 원인 좁히기
- 워크플로에
permissions: id-token: write가 있는가 role-to-assumeARN이 정확한가- AWS 계정에 GitHub OIDC Provider가 존재하며 Audience에
sts.amazonaws.com가 있는가 - Role Trust Policy에서
- Principal Federated ARN이 올바른가
aud조건이sts.amazonaws.com로 맞는가sub조건이 실제 실행 컨텍스트(브랜치/태그/environment)와 일치하는가
aws sts get-caller-identity로 Assume 성공 여부를 즉시 확인했는가
마무리
GitHub Actions OIDC의 AssumeRole 오류는 복잡해 보이지만, 결국 OIDC 토큰 클레임과 IAM Trust Policy 조건의 불일치로 귀결됩니다. aud와 sub를 먼저 고정하고, 배포 이벤트(브랜치/태그/environment)를 기준으로 Trust Policy를 최소 권한으로 설계하면 대부분의 문제는 빠르게 사라집니다.
배포 파이프라인이 안정화되면, 다음 단계로는 캐시/빌드 최적화나 장애 대응 체크리스트를 함께 정비하는 것이 좋습니다.