- Published on
GitHub Actions OIDC로 AWS 배포 실패 해결 가이드
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버리스든 EC2든, GitHub Actions로 AWS에 배포할 때 가장 깔끔한 인증 방식은 OIDC(OpenID Connect)입니다. 장기 액세스 키를 저장하지 않아도 되고, 워크플로 실행 시점에만 STS 임시 자격 증명을 발급받아 사용하므로 보안과 운영 편의성이 모두 좋아집니다.
그런데 OIDC는 한 번 설정이 어긋나면 배포가 통째로 막히는 경우가 많습니다. 특히 AccessDenied, InvalidIdentityToken, Not authorized to perform sts:AssumeRoleWithWebIdentity 같은 에러는 원인이 다양해서 로그만 보고는 감이 안 올 때가 많습니다.
이 글에서는 GitHub Actions OIDC로 AWS 배포가 실패할 때, 어디부터 확인해야 하는지와 가장 흔한 실수 패턴을 “재현 가능한 설정”과 함께 정리합니다.
문제 유형 중 AccessDenied를 집중적으로 다룬 글은 아래 내부 링크도 함께 참고하면 좋습니다.
OIDC 동작 구조를 먼저 간단히 정리
OIDC 기반 배포 흐름은 대략 아래 순서로 진행됩니다.
- GitHub Actions 러너가 OIDC 토큰을 발급받음
- AWS IAM Role의 Trust Policy가 해당 토큰의 발급자와 클레임을 검증
- 검증되면 AWS STS가 임시 자격 증명(AccessKeyId, SecretAccessKey, SessionToken)을 발급
- 이후 AWS CLI나 SDK가 이 임시 자격 증명으로 배포 작업 수행
즉, 실패 지점은 크게 두 군데입니다.
- STS AssumeRoleWithWebIdentity 단계에서 실패(신뢰 정책, OIDC Provider, 클레임 조건 문제)
- AssumeRole은 성공했는데 실제 AWS API 호출 권한이 부족(권한 정책 문제)
로그에서 먼저 구분해야 합니다.
AssumeRoleWithWebIdentity자체가 거부되면 Trust Policy나 OIDC Provider 문제- AssumeRole은 되었는데
s3:PutObject,cloudformation:*,ecr:*등이 거부되면 Permission Policy 문제
GitHub Actions 워크플로 기본 예제(정상 동작 기준)
가장 먼저 워크플로가 OIDC 토큰을 받을 권한이 있는지 확인해야 합니다. 핵심은 permissions: id-token: write 입니다.
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
- name: Who am I
run: aws sts get-caller-identity
여기서 aws sts get-caller-identity가 실패하면, 배포 로직 이전에 인증/신뢰 설정부터 깨진 겁니다.
실패 1: id-token 권한 누락으로 OIDC 토큰 발급이 안 됨
증상
No OpenIDConnect provider found와는 다른 형태로, GitHub 쪽에서 토큰을 못 받는 로그가 보이거나aws-actions/configure-aws-credentials단계에서 토큰 관련 에러가 발생
해결
워크플로 상단 또는 job 단위에 아래 권한이 반드시 있어야 합니다.
permissions:
id-token: write
contents: read
조직 정책이나 리포지토리 설정에서 워크플로 권한이 제한되어 있으면, 리포지토리 Settings의 Actions 권한도 함께 확인하세요.
실패 2: IAM OIDC Provider 설정 불일치(발급자 URL, Audience)
증상
InvalidIdentityToken또는No OpenIDConnect provider found in your account for https://token.actions.githubusercontent.com
원인
AWS 계정에 GitHub OIDC Provider가 없거나, Provider의 URL이 다르거나, Audience 조건이 맞지 않으면 STS가 토큰을 신뢰하지 않습니다.
GitHub Actions의 표준 발급자(issuer)는 아래입니다.
https://token.actions.githubusercontent.com
해결: OIDC Provider 생성 확인
AWS 콘솔에서 IAM Identity providers에 OIDC Provider가 존재하는지 확인합니다.
CLI로도 확인 가능합니다.
aws iam list-open-id-connect-providers
Provider ARN을 확인한 뒤 상세를 봅니다.
aws iam get-open-id-connect-provider \
--open-id-connect-provider-arn arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com
여기서 Url이 token.actions.githubusercontent.com인지, ClientIDList에 sts.amazonaws.com가 포함되어 있는지 확인하세요. 일반적으로 GitHub Actions OIDC의 audience는 sts.amazonaws.com로 두는 구성이 가장 흔합니다.
실패 3: Trust Policy 조건 미스매치(특히 sub)
OIDC에서 가장 많이 틀리는 부분이 Role의 Trust Policy 조건입니다. 특히 sub 클레임을 너무 빡세게 걸어두면 브랜치나 환경이 바뀌는 순간 바로 실패합니다.
대표 증상
AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentityThe role ... cannot be assumed류 메시지
sub 클레임 형태 이해
GitHub OIDC 토큰의 sub는 보통 아래 패턴 중 하나입니다.
- 브랜치 기반:
repo:ORG/REPO:ref:refs/heads/main - 태그 기반:
repo:ORG/REPO:ref:refs/tags/v1.2.3 - 환경 기반:
repo:ORG/REPO:environment:prod
즉, Trust Policy에서 sub를 브랜치로 묶으면, 태그 배포나 환경 배포로 바꾸는 순간 AssumeRole이 실패합니다.
권장 Trust Policy 예시
아래는 “특정 리포지토리에서만” Role을 Assume하도록 제한하면서도, 브랜치/태그/환경 변화에 덜 깨지는 예시입니다.
{
"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:*"
}
}
}
]
}
aud는 정확히 일치시키는 편이 안전합니다.sub는 최소한repo:ORG/REPO:*수준으로 시작한 뒤, 요구 보안 수준에 맞춰ref또는environment로 점진적으로 좁히는 전략이 운영에서 덜 흔들립니다.
실패 4: 브랜치 보호, PR 이벤트에서 sub가 달라짐
증상
push에서는 되는데pull_request에서만 AssumeRole 실패- 또는
main브랜치에서는 되는데 릴리즈 브랜치에서만 실패
원인
이벤트 타입에 따라 토큰 클레임이 달라집니다. 예를 들어 PR 기반 워크플로는 ref가 기대와 다르게 들어가거나, 보안상 제한된 토큰이 내려올 수 있습니다.
해결 전략
- 배포 워크플로는 가급적
push또는workflow_dispatch로만 실행 pull_request에서는 배포 대신 빌드/테스트만 수행- Trust Policy의
sub조건을 이벤트별로 분기하거나, 배포 이벤트만 허용하도록 설계
예를 들어 배포는 수동 실행만 허용하고, 환경을 명시하는 패턴도 자주 씁니다.
on:
workflow_dispatch:
inputs:
env:
required: true
type: choice
options: ["dev", "prod"]
그리고 Trust Policy는 environment:prod만 허용하도록 좁힐 수 있습니다.
실패 5: AssumeRole은 성공했는데 배포 API 권한이 부족함
증상
aws sts get-caller-identity는 성공- 하지만 이후 단계에서
AccessDenied가 발생 - 예: ECR 푸시, S3 업로드, CloudFormation 배포, ECS 업데이트 등
원인
Role의 Permission Policy에 필요한 액션이 빠졌습니다. OIDC 문제라기보다 IAM 권한 설계 문제입니다.
해결: 최소 권한으로 필요한 액션을 역추적
CloudTrail의 Event name과 Error code를 보면 어떤 액션이 거부됐는지 빠르게 찾을 수 있습니다.
예를 들어 ECR 푸시에는 최소한 아래가 자주 필요합니다.
ecr:GetAuthorizationTokenecr:BatchCheckLayerAvailabilityecr:InitiateLayerUploadecr:UploadLayerPartecr:CompleteLayerUploadecr:PutImage
S3 업로드라면 보통 아래가 필요합니다.
s3:PutObjects3:PutObjectAcl(필요한 경우에만)s3:ListBucket(동기화나 존재 확인 시)
정확한 액션은 “실패한 API 호출” 기준으로 추가하는 방식이 가장 빠릅니다.
실패 6: 리전 또는 STS 엔드포인트 혼선
증상
- 특정 리전에서만 실패
- 또는 로컬에서는 되는데 Actions에서만 실패
체크 포인트
- 워크플로에서
aws-region이 실제 리소스 리전과 일치하는지 - 조직에서 STS를 특정 리전에만 허용하는 정책이 있는지
가능하면 워크플로 초기에 아래를 찍어서 환경을 고정합니다.
- name: Print AWS region
run: |
aws configure list
echo "AWS_REGION=$AWS_REGION"
echo "AWS_DEFAULT_REGION=$AWS_DEFAULT_REGION"
실패 7: 디버깅을 위한 최소 재현 파이프라인 만들기
배포 파이프라인이 길어질수록 원인 파악이 어려워집니다. OIDC 문제를 확인할 때는 아래 2단계만 남긴 “최소 재현” 워크플로로 줄여서 확인하는 게 좋습니다.
- OIDC로 Role Assume
get-caller-identity호출
name: oidc-smoke-test
on:
workflow_dispatch:
permissions:
id-token: write
contents: read
jobs:
smoke:
runs-on: ubuntu-latest
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/gha-deploy-role
aws-region: ap-northeast-2
- run: aws sts get-caller-identity
이게 통과하면 “OIDC 신뢰 체인”은 정상입니다. 이후는 순수하게 권한 정책 또는 배포 도구 설정 문제로 범위를 좁히면 됩니다.
운영 팁: 보안과 안정성을 함께 잡는 권장 패턴
1) Role을 배포 대상별로 쪼개기
하나의 Role에 모든 권한을 몰아주면 편하지만, 나중에 사고가 납니다.
gha-deploy-s3-rolegha-deploy-ecr-rolegha-deploy-ecs-role
처럼 분리하고, 워크플로에서 필요한 Role만 Assume하도록 설계하면 권한 최소화가 쉬워집니다.
2) sub를 너무 타이트하게 시작하지 않기
초기에 ref:refs/heads/main까지 박아두면, 릴리즈 태그 배포나 핫픽스 브랜치가 생겼을 때 운영이 자주 멈춥니다. 처음엔 repo:ORG/REPO:*로 안정화하고, 이후 환경 기반으로 좁히는 방법이 실무에서 덜 고통스럽습니다.
3) 장애 추적 접근법을 표준화하기
OIDC든 어떤 장애든, “로그를 어디서부터 좁힐지”가 중요합니다. 원인 추적 사고방식 자체는 다른 시스템 장애에도 그대로 적용됩니다. 예를 들어 서비스가 계속 재시작될 때 원인을 좁히는 방식은 아래 글과 결이 비슷합니다.
체크리스트: OIDC 배포 실패 시 5분 진단 순서
- 워크플로에
permissions: id-token: write가 있는가 aws-actions/configure-aws-credentials다음에aws sts get-caller-identity가 성공하는가- IAM에 OIDC Provider가 있고 URL이
token.actions.githubusercontent.com인가 - Role Trust Policy에서
aud가sts.amazonaws.com로 맞는가 - Trust Policy의
sub조건이 실제 배포 이벤트의sub와 맞는가 - AssumeRole은 되는데 API가 막히면 Permission Policy를 CloudTrail로 역추적했는가
마무리
GitHub Actions OIDC는 한 번 제대로 잡아두면 장기 키 관리에서 해방되고, 조직 보안 감사에도 강해집니다. 반대로 초기 설정에서 aud, sub, Provider URL, 워크플로 권한 중 하나만 어긋나도 배포가 바로 멈춥니다.
가장 빠른 해결법은 “OIDC 신뢰 문제인지, AWS 권한 문제인지”를 먼저 분리하고, get-caller-identity를 기준 테스트로 삼아 범위를 좁히는 것입니다. 그 다음 Trust Policy의 sub를 이벤트 특성에 맞게 조정하고, 실제 배포 API 권한은 CloudTrail 기반으로 최소 권한을 쌓아가면 재발이 크게 줄어듭니다.