Published on

GitHub Actions OIDC로 AWS 키 없이 배포 실패 해결

Authors

서버리스든 ECS든 S3 배포든, GitHub Actions에서 AWS ACCESS_KEY_ID/SECRET_ACCESS_KEY 없이 OIDC로 붙이면 보안은 좋아지지만 설정이 조금만 어긋나도 배포가 바로 실패합니다. 특히 AssumeRoleWithWebIdentity 단계에서 터지는 에러는 로그만 보고는 감이 안 오는 경우가 많습니다.

이 글은 GitHub Actions OIDC로 AWS 배포가 실패할 때, “어디가 잘못됐는지”를 빠르게 좁히는 실전 가이드입니다. 설정 예시(IAM OIDC Provider, Role trust policy, GitHub workflow)와 함께, 에러 메시지별 원인과 해결책을 체크리스트 형태로 정리합니다.

문맥상 함께 보면 좋은 글: GitHub Actions 캐시가 안 먹을 때 속도 3배 올린 실전


OIDC 배포 흐름 한 장 요약

OIDC 방식은 “GitHub가 짧은 수명의 ID 토큰을 발급하고, AWS STS가 그 토큰을 검증한 뒤 Role을 Assume해서 임시 자격 증명”을 발급하는 구조입니다.

  1. Workflow가 id-token: write 권한으로 OIDC 토큰을 요청
  2. GitHub가 OIDC 토큰(JWT)을 발급
  3. aws-actions/configure-aws-credentials가 STS AssumeRoleWithWebIdentity 호출
  4. AWS가 OIDC Provider 설정과 Role trust policy 조건을 검증
  5. 통과하면 임시 자격 증명 발급 후 배포 작업 수행

따라서 실패 지점은 크게 두 가지입니다.

  • STS Assume 단계에서 실패(신뢰 정책, OIDC Provider, 조건 불일치)
  • Assume은 성공했지만 실제 리소스 권한이 부족(권한 정책 문제)

가장 흔한 실패 1: No OpenIDConnect provider found

증상

로그에 보통 아래 같은 메시지가 나옵니다.

  • No OpenIDConnect provider found in your account for https://token.actions.githubusercontent.com

원인

AWS 계정에 GitHub OIDC Provider가 없거나, URL/Thumbprint/Audience가 잘못 등록된 경우입니다.

해결

IAM에 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

여기서 ClientIDListsts.amazonaws.com가 없으면 Role trust 조건에서 aud 검증이 실패할 수 있습니다.


가장 흔한 실패 2: Not authorized to perform sts:AssumeRoleWithWebIdentity

증상

  • Not authorized to perform sts:AssumeRoleWithWebIdentity
  • 또는 AccessDenied가 Assume 단계에서 발생

원인

Role의 Trust Policy가 OIDC 토큰의 sub/aud/iss 조건과 맞지 않거나, Principal Federated가 OIDC Provider ARN을 가리키지 않는 경우입니다.

해결: Trust Policy를 토큰 클레임에 맞게 구성

가장 안전하고 흔한 형태는 sub를 repo/branch 단위로 제한하는 것입니다.

예시 Trust Policy:

{
  "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:OWNER/REPO:ref:refs/heads/main"
        }
      }
    }
  ]
}

여기서 가장 자주 틀리는 지점:

  • OWNER/REPO 대소문자/오타
  • refrefs/heads/main이 아닌데 main으로 고정
  • PR 이벤트인데 refs/pull/... 형태로 들어오는 경우
  • 태그 배포인데 refs/tags/v1.2.3인데 heads로 제한

운영 팁:

  • 브랜치 여러 개를 허용하려면 StringLike에 와일드카드 사용
  • 태그 릴리즈를 허용하려면 refs/tags/*도 포함

예: main과 tag 배포 둘 다 허용

"StringLike": {
  "token.actions.githubusercontent.com:sub": [
    "repo:OWNER/REPO:ref:refs/heads/main",
    "repo:OWNER/REPO:ref:refs/tags/*"
  ]
}

가장 흔한 실패 3: Could not load credentials from any providers

증상

aws CLI나 SDK 호출 시 자격 증명을 못 찾는다는 에러가 납니다.

원인

Workflow에서 OIDC 토큰 발급 권한이 없어서 configure-aws-credentials가 토큰을 요청하지 못하는 경우가 많습니다.

해결: permissionsid-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/github-actions-deploy
          aws-region: ap-northeast-2

      - name: Who am I
        run: aws sts get-caller-identity

추가로 확인할 점:

  • 조직(Org) 정책에서 GitHub Actions OIDC가 제한되어 있지 않은지
  • Self-hosted runner에서 네트워크가 STS로 나갈 수 있는지

가장 흔한 실패 4: Assume은 성공했는데 배포 권한이 없음

증상

  • AccessDenied가 S3, ECR, ECS, CloudFormation, Lambda 등에서 발생
  • aws sts get-caller-identity는 성공

원인

Trust Policy는 통과했지만, Role에 붙은 Permission Policy가 부족합니다.

해결: 최소 권한으로 필요한 액션만 추가

예를 들어 S3에 정적 파일 배포라면 대략 아래가 필요합니다.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:ListBucket"
      ],
      "Resource": "arn:aws:s3:::my-bucket"
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:DeleteObject",
        "s3:GetObject"
      ],
      "Resource": "arn:aws:s3:::my-bucket/*"
    }
  ]
}

CloudFront invalidation까지 한다면 cloudfront:CreateInvalidation도 추가해야 합니다.


OIDC 토큰 클레임 확인으로 원인 확정하기

Trust Policy 조건이 맞는지 감으로 맞추지 말고, 실제 토큰의 sub가 무엇인지 확인하면 진단이 빨라집니다.

GitHub Actions에서 OIDC 토큰을 직접 받아 디코딩(페이로드)하는 예시입니다.

- name: Debug OIDC token claims
  run: |
    echo "Requesting OIDC token"
    resp=$(curl -sS -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
      "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=sts.amazonaws.com")
    token=$(echo "$resp" | jq -r '.value')
    echo "$token" | awk -F. '{print $2}' | base64 -d | jq

여기서 출력되는 JSON의 sub를 보고 Trust Policy의 token.actions.githubusercontent.com:sub 조건을 정확히 맞추면 됩니다.

주의:

  • 토큰 전체를 로그에 그대로 찍지 말고 페이로드만 확인하세요.
  • 조직 보안 정책에 따라 토큰 노출은 감사 이슈가 될 수 있습니다.

이벤트별 sub가 달라서 생기는 함정

같은 레포라도 어떤 트리거로 실행되느냐에 따라 sub 패턴이 달라질 수 있습니다.

  • push to branch: repo:OWNER/REPO:ref:refs/heads/main
  • tag push: repo:OWNER/REPO:ref:refs/tags/v1.0.0
  • PR: repo:OWNER/REPO:pull_request 또는 PR 관련 ref

따라서 “배포는 push에서만” 같은 룰을 정하고, Trust Policy도 그에 맞게 제한하는 편이 운영이 단순합니다.

실전 권장:

  • 배포 워크플로는 push to main 또는 workflow_dispatch로만
  • PR 워크플로는 테스트만 수행

멀티 AWS 계정/환경에서 Role 설계 패턴

개발/스테이징/운영 계정이 분리되어 있다면, GitHub 환경(Environment)과 Role을 1:1로 매핑하는 방식이 깔끔합니다.

  • environment: prodrole-to-assume를 prod 계정 Role로
  • environment: staging은 staging Role로

예시:

jobs:
  deploy:
    environment: prod
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v4
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::999999999999:role/gha-prod-deploy
          aws-region: ap-northeast-2
      - run: aws sts get-caller-identity

추가로 GitHub Environment protection rule(승인자, 브랜치 제한)을 걸면 OIDC + 승인 기반 배포가 됩니다.


빠른 복구용 체크리스트

배포가 깨졌을 때 아래 순서로 보면 대부분 10분 내로 원인이 좁혀집니다.

  1. Workflow에 permissions: id-token: write가 있는가
  2. aws-actions/configure-aws-credentials@v4 사용 중인가
  3. AWS 계정에 OIDC Provider가 있고 URL이 https://token.actions.githubusercontent.com인가
  4. OIDC Provider의 Audience에 sts.amazonaws.com가 있는가
  5. Role Trust Policy의 Federated가 올바른 OIDC Provider ARN인가
  6. Trust Policy의 aud 조건이 sts.amazonaws.com로 맞는가
  7. Trust Policy의 sub 조건이 실제 이벤트의 sub와 일치하는가
  8. aws sts get-caller-identity가 성공하는가
  9. 이후 리소스 권한은 Permission Policy에서 허용되는가

마무리: OIDC는 “정확한 조건”이 전부다

GitHub Actions OIDC는 장기 키를 없애고, 유출 리스크를 크게 줄여주는 대신 “신뢰 정책 조건이 1글자만 달라도 배포가 실패”합니다. 하지만 반대로 말하면, 토큰 클레임을 확인하고 Trust Policy를 정확히 맞추는 순간 문제는 깔끔하게 끝납니다.

추가로 OIDC 설정 자체는 맞는데도 워크플로가 느려서 배포 피드백이 늦다면, 캐시 튜닝도 함께 보세요: GitHub Actions 캐시가 안 먹을 때 속도 3배 올린 실전

더 많은 OIDC 배포 오류 케이스를 에러 메시지별로 보고 싶다면 아래 글도 참고할 수 있습니다.