- Published on
GitHub Actions OIDC로 AWS 키 없이 배포 오류 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버리스든 ECS든 EKS든, GitHub Actions로 AWS에 배포할 때 가장 흔한 실패 지점은 결국 인증입니다. 예전처럼 AWS_ACCESS_KEY_ID 와 AWS_SECRET_ACCESS_KEY 를 리포지토리 시크릿에 넣어두면 당장은 편하지만, 키 유출·로테이션·권한 과다 부여 문제가 계속 따라옵니다.
OIDC(OpenID Connect)를 쓰면 이 문제를 구조적으로 해결할 수 있습니다. GitHub Actions가 실행될 때마다 짧은 수명의 토큰을 발급받아 AWS STS AssumeRoleWithWebIdentity 로 역할(Role)을 임시로 빌려 쓰는 방식입니다. 즉, 장기 키가 아예 필요 없습니다.
다만 OIDC는 설정이 조금만 어긋나도 바로 배포가 깨집니다. 특히 Not authorized to perform sts:AssumeRoleWithWebIdentity 같은 메시지로 막히면 원인을 감으로 찾기 어렵습니다. 이 글에서는 실제로 자주 발생하는 오류를 “왜 발생하는지”와 “어떻게 고치는지” 관점에서 정리합니다.
관련해서 토큰·JWKS·kid 같은 개념이 낯설다면, 인증서/키 캐시 만료 관점은 Node.js JWT 검증 실패 - kid·JWKS 캐시 만료 대응 글도 함께 보면 OIDC를 이해하는 데 도움이 됩니다.
OIDC로 바꾸면 뭐가 달라지나
기존 방식(Access Key 기반)은 GitHub Actions 런타임이 “정적 자격증명”을 들고 AWS API를 호출합니다. 반면 OIDC는 다음 흐름을 탑니다.
- GitHub Actions가 OIDC 토큰을 발급받음(워크플로에서
permissions: id-token: write필요) - AWS STS가 해당 토큰을 검증하고, 신뢰 정책(Trust Policy)에 맞으면 Role 세션을 발급
- 이후 AWS SDK/CLI는 임시 자격증명으로 배포 작업 수행
여기서 실패의 대부분은 2번입니다. 즉, AWS IAM Role의 Trust Policy와 GitHub에서 넘어오는 토큰 클레임(claim)이 맞지 않아서 거절됩니다.
필수 구성 요소 체크리스트
OIDC 구성은 크게 3가지만 맞으면 됩니다.
- AWS IAM OIDC Provider 등록(
token.actions.githubusercontent.com) - IAM Role 생성 및 Trust Policy 설정
- GitHub Actions 워크플로에서 OIDC 토큰 요청 및 Role Assume 설정
이 중 하나라도 틀리면 STS Assume이 실패합니다.
AWS 설정 1: OIDC Provider 등록
AWS 콘솔에서 IAM Identity providers 에 GitHub OIDC Provider를 추가합니다.
- Provider URL:
https://token.actions.githubusercontent.com - Audience: 보통
sts.amazonaws.com
CLI로 확인하려면 다음처럼 조회합니다.
aws iam list-open-id-connect-providers
OIDC Provider가 없거나 URL이 다르면, 이후 단계가 모두 실패합니다.
AWS 설정 2: Role Trust Policy(가장 많이 틀림)
다음은 “특정 리포지토리의 main 브랜치에서만” Assume을 허용하는 예시 Trust Policy입니다.
주의: 본문에 부등호 문자가 있으면 MDX에서 빌드 에러가 날 수 있어, 비교 연산자나 제네릭 표기는 인라인 코드로만 표기합니다.
{
"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_NAME/REPO_NAME:ref:refs/heads/main"
}
}
}
]
}
여기서 핵심은 두 가지입니다.
aud가 워크플로에서 요청하는 audience와 일치해야 함(대부분sts.amazonaws.com)sub가 “어떤 리포지토리/브랜치/태그/환경에서 실행된 워크플로인지”를 나타내며, 이 패턴이 정확히 맞아야 함
브랜치가 아니라 태그 배포라면
릴리스 태그에서만 배포한다면 sub 패턴이 달라집니다.
- 예:
repo:ORG/REPO:ref:refs/tags/v*
{
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:ORG_NAME/REPO_NAME:ref:refs/tags/v*"
}
}
GitHub Environments를 쓰면 조건이 또 달라짐
Environment 보호 규칙을 쓰는 경우 sub 가 다음처럼 나올 수 있습니다.
- 예:
repo:ORG/REPO:environment:prod
이때는 브랜치 조건과 환경 조건 중 무엇을 신뢰 기준으로 삼을지 결정해야 합니다.
GitHub Actions 설정: OIDC 토큰 권한과 Role Assume
워크플로에서 반드시 permissions 를 지정해야 합니다. 기본 권한으로는 OIDC 토큰을 못 받는 경우가 많습니다.
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/github-actions-deploy
aws-region: ap-northeast-2
- name: Verify identity
run: aws sts get-caller-identity
aws sts get-caller-identity 가 성공하면 OIDC Assume은 통과한 것입니다. 이후 ECR 로그인, S3 업로드, CloudFormation 배포 등 원하는 배포 스텝을 이어 붙이면 됩니다.
자주 나는 오류와 해결법
1) Not authorized to perform sts:AssumeRoleWithWebIdentity
가장 흔한 오류입니다. 원인 후보는 아래 순서로 좁히면 빠릅니다.
원인 A: Trust Policy의 sub 조건 불일치
- 브랜치명이 다름(
mainvsmaster) - 트리거가
pull_request인데ref조건을heads로 잡음 - 태그 배포인데 브랜치 패턴을 씀
- Environment 배포인데
ref조건만 둠
해결은 Trust Policy의 token.actions.githubusercontent.com:sub 패턴을 실제 실행 컨텍스트에 맞추는 것입니다.
원인 B: aud 불일치
Trust Policy에서 aud 를 sts.amazonaws.com 으로 고정했는데, 워크플로가 다른 audience로 토큰을 요청하면 실패합니다.
aws-actions/configure-aws-credentials 는 기본 audience를 sts.amazonaws.com 으로 쓰는 경우가 많지만, 조직 정책에 따라 바뀌는 경우가 있습니다. Trust Policy와 워크플로 설정이 일치하는지 확인하세요.
원인 C: OIDC Provider ARN이 다른 계정/다른 Provider
Role의 Principal.Federated 가 실제 Provider ARN과 다르면 무조건 실패합니다. 멀티 계정에서 역할을 복사해 오다 자주 발생합니다.
2) Could not load credentials from any providers
이 메시지는 “OIDC가 아니라, AWS SDK/CLI가 자격증명을 전혀 못 찾는다”에 가깝습니다.
체크 포인트:
- 워크플로에
permissions: id-token: write가 있는가 aws-actions/configure-aws-credentials@v4스텝이 실행됐는가role-to-assumeARN 오타가 없는가
특히 permissions 누락이 가장 흔합니다.
3) InvalidIdentityToken 또는 No OpenIDConnect provider found
대부분 AWS에 OIDC Provider가 없거나 URL이 다를 때 발생합니다.
- Provider URL이
https://token.actions.githubusercontent.com인지 확인 - Thumbprint를 수동으로 잘못 넣었거나 오래된 가이드를 따라간 경우, 콘솔에서 다시 생성하는 편이 빠릅니다
4) Assume은 되는데 배포 단계에서 AccessDenied
OIDC 자체는 성공했지만 Role에 붙인 IAM Permission Policy가 부족한 상태입니다.
예를 들어 ECR 푸시가 필요하면 최소한 다음 액션들이 필요합니다.
ecr:GetAuthorizationTokenecr:BatchCheckLayerAvailabilityecr:InitiateLayerUploadecr:UploadLayerPartecr:CompleteLayerUploadecr:PutImage
S3 배포면 s3:PutObject 와 s3:ListBucket 등이 필요합니다.
여기서 중요한 점은 “키를 없앴더니 보안이 좋아졌다”에서 끝나면 안 되고, Role 권한을 최소화(Least Privilege)하면서도 배포에 필요한 권한은 정확히 주어야 한다는 것입니다.
ECR 관련 권한/네트워크 이슈가 섞이면 403 으로 보일 때가 있는데, 그 경우 VPC 엔드포인트 정책도 원인이 될 수 있습니다. 상황이 비슷하다면 EKS ImagePullBackOff 403? ECR VPC 엔드포인트 정책 점검 도 참고하세요.
디버깅 팁: 토큰 클레임을 “눈으로” 확인하기
OIDC 문제는 결국 “토큰 클레임과 Trust Policy 조건 매칭” 문제입니다. GitHub가 어떤 sub 를 발급했는지 확인하면 해결이 빨라집니다.
방법은 두 가지가 있습니다.
- GitHub 문서의 클레임 규칙을 보고, 현재 트리거에 맞는
sub형태를 추정 - 실제로 발급된 토큰을 받아 디코딩
두 번째 방법은 보안상 로그에 토큰을 그대로 남기면 위험할 수 있으니, 실무에서는 최소한의 범위에서만 사용하세요. 디버깅 목적이라면 토큰의 payload만 추출해 확인하는 식으로 접근합니다.
예를 들어, OIDC 토큰은 JWT 형태이므로 로컬에서 payload를 확인할 수 있습니다.
python - <<'PY'
import os, json, base64
def b64url_decode(s: str) -> bytes:
s += '=' * (-len(s) % 4)
return base64.urlsafe_b64decode(s.encode())
token = os.environ.get('ACTIONS_ID_TOKEN_REQUEST_TOKEN')
print('has_request_token:', bool(token))
PY
위 값은 “OIDC 토큰 자체”가 아니라 GitHub OIDC 엔드포인트에서 토큰을 받아오기 위한 요청 토큰입니다. 실제 토큰은 aws-actions/configure-aws-credentials 가 내부적으로 받아 STS에 전달합니다. 즉, 일반적으로는 위 스크립트만으로 토큰 payload를 바로 보기는 어렵고, 보안까지 고려하면 “Trust Policy를 올바르게 구성하고 sts get-caller-identity 로 검증”하는 방식이 가장 안전하고 재현성이 좋습니다.
권장 운영 패턴: 배포 역할을 더 안전하게 만드는 방법
OIDC를 도입했다면 다음까지 적용하는 것을 권합니다.
1) 리포지토리 단위로 Role 분리
- 하나의 Role을 여러 리포지토리가 공유하면 Trust Policy가 넓어지고 사고 반경이 커집니다.
repo:ORG/REPO:*범위를 최소화하세요.
2) 브랜치와 태그를 동시에 허용하지 말기
- 브랜치 배포와 태그 배포를 동시에 열어두면 의도치 않은 배포 경로가 생깁니다.
- 운영 배포는 태그만 허용하는 식으로 단순화하는 편이 안전합니다.
3) 세션 이름과 CloudTrail 추적성 확보
aws-actions/configure-aws-credentials 는 세션 이름을 지정할 수 있습니다. CloudTrail에서 “어떤 워크플로 런이 어떤 권한으로 무엇을 했는지” 추적이 쉬워집니다.
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy
role-session-name: gha-${{ github.repository }}-${{ github.run_id }}
aws-region: ap-northeast-2
4) 실패 시 체크리스트를 런북으로 만들기
CI/CD는 장애가 나면 심리적으로 급해져서, “일단 키를 시크릿에 넣고 우회” 같은 선택을 하기 쉽습니다. OIDC 전환의 목적이 사라지지 않도록, 아래 순서대로 점검하는 런북을 추천합니다.
- 워크플로
permissions에id-token: write존재 여부 - Role ARN 오타 여부
- IAM OIDC Provider 존재 및 URL 일치 여부
- Trust Policy의
aud와sub매칭 여부 - Role Permission Policy의 최소 권한 충족 여부
aws sts get-caller-identity로 Assume 성공 여부
마무리
GitHub Actions OIDC는 “AWS 키를 없애는 기능”이 아니라, 인증을 임시 세션 기반으로 바꾸고 신뢰 경계를 코드로 고정하는 방식입니다. 설정이 조금 까다로운 대신, 한 번 안정화하면 키 유출 리스크와 운영 부담이 크게 줄어듭니다.
배포 오류가 났을 때는 대부분 Trust Policy의 sub 와 aud 불일치에서 시작합니다. 먼저 aws sts get-caller-identity 가 통과하도록 최소 구성으로 만든 뒤, 필요한 AWS 권한을 단계적으로 추가하는 방식으로 접근하면 가장 빠르게 해결할 수 있습니다.