Published on

GitHub Actions OIDC assume-role 실패 7분 해결법

Authors

서론

GitHub Actions에서 aws-actions/configure-aws-credentials로 OIDC 기반 AssumeRoleWithWebIdentity를 쓰면, 장기 Access Key 없이도 안전하게 AWS 권한을 위임할 수 있습니다. 하지만 실제로는 AccessDenied, No OpenIDConnect provider found, InvalidIdentityToken, Not authorized to perform sts:AssumeRoleWithWebIdentity 같은 오류로 파이프라인이 멈추는 일이 흔합니다.

이 글은 “원인 분석을 길게” 하기보다, 실패한 assume-role을 7분 안에 복구하는 데 초점을 맞춥니다. 특히 아래 4가지만 빠르게 점검하면 대부분 해결됩니다.

  1. 워크플로우 permissionsid-token: write가 있는가
  2. AWS IAM Role Trust Policy의 Principal/Federated, aud, sub 조건이 맞는가
  3. AWS 계정에 GitHub OIDC Provider가 올바른 URL/Thumbprint/Audience로 등록되어 있는가
  4. 실제로 어떤 sub 클레임으로 토큰이 발급되는지(브랜치/태그/환경)와 Trust 조건이 일치하는가

추가로, OIDC가 403/권한거부로 보일 때의 케이스는 별도 글로 더 깊게 정리해두었습니다: GitHub Actions OIDC 403·권한거부 원인 7가지

0. 증상별로 먼저 분류하기(30초)

실패 메시지에 따라 “어디를 먼저 볼지”가 달라집니다.

  • Error: Not authorized to perform sts:AssumeRoleWithWebIdentity
    • 99%: Role trust policy 조건 불일치(sub/aud) 또는 OIDC provider ARN 불일치
  • No OpenIDConnect provider found in your account for https://token.actions.githubusercontent.com
    • AWS IAM OIDC Provider 미등록/오등록
  • InvalidIdentityToken 또는 audience is invalid
    • aud 클레임 불일치(대개 sts.amazonaws.com 아닌 값으로 조건을 걸어둠)
  • Could not load credentials from any providers
    • OIDC 이전 단계에서 실패(permissions, action 버전, job 조건) 가능

이제부터는 실제로 7분 안에 끝내는 순서로 진행합니다.

1. 1분: workflow permissions에 id-token이 있는지 확인

OIDC는 GitHub가 워크플로우에 JWT를 발급해주는 기능이고, 이를 받으려면 Job 또는 Workflow 레벨에 다음 권한이 필요합니다.

name: deploy
on:
  push:
    branches: [ main ]

permissions:
  id-token: write   # OIDC 토큰 발급에 필수
  contents: read    # checkout에 보통 필요

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
          aws-region: ap-northeast-2

체크 포인트

  • 레포지토리 Settings → Actions → General에서 “Workflow permissions”가 Read repository contents permission으로 제한되어 있어도, 위처럼 명시하면 대개 해결됩니다.
  • pull_request 이벤트에서 포크 PR은 보안상 id-token이 제한될 수 있습니다. 이 경우 pull_request_target 사용 여부, 환경 보호 규칙, 별도 배포 전략을 검토해야 합니다.

2. 2분: AWS OIDC Provider 등록 상태를 CLI로 검증

AWS 계정에 GitHub OIDC Provider가 있어야 Principal.Federated로 신뢰를 맺을 수 있습니다.

2.1 Provider 존재 여부

aws iam list-open-id-connect-providers \
  --query 'OpenIDConnectProviderList[*].Arn' \
  --output text

출력에 아래 형태가 있어야 합니다.

  • arn:aws:iam::<ACCOUNT_ID>:oidc-provider/token.actions.githubusercontent.com

없다면 생성합니다.

2.2 Provider 생성(없을 때)

aws iam create-open-id-connect-provider \
  --url https://token.actions.githubusercontent.com \
  --client-id-list sts.amazonaws.com \
  --thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1

주의

  • Thumbprint는 문서/예시를 그대로 쓰기보다, 조직 보안 기준에 맞춰 최신 값을 확인하는 게 좋습니다. 다만 실제 장애 상황에서는 “Provider 자체가 없어서” 실패하는 경우가 많고, 위 명령으로 즉시 복구되는 케이스가 흔합니다.

3. 2분: IAM Role Trust Policy의 핵심 3요소(Principal/aud/sub)

assume-role 실패의 대부분은 Trust Policy 조건이 실제 토큰 클레임과 다르기 때문입니다.

3.1 올바른 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"
        }
      }
    }
  ]
}

여기서 자주 틀리는 지점은 다음입니다.

  • Principal.Federated ARN이 다른 계정/다른 provider를 가리킴
  • audsts.amazonaws.com이 아닌 값으로 제한
  • sub를 브랜치가 아닌 태그/환경/PR 형태로 제한해놓고 실제 실행 컨텍스트가 다름

3.2 sub 패턴을 “너무 타이트하게” 잡아 생기는 장애

예를 들어 릴리즈 태그에서 배포하는데 Trust Policy가 main 브랜치만 허용하면 실패합니다.

  • 브랜치 실행: repo:OWNER/REPO:ref:refs/heads/main
  • 태그 실행: repo:OWNER/REPO:ref:refs/tags/v1.2.3
  • 환경(environment) 보호 사용 시: repo:OWNER/REPO:environment:prod

운영에서는 보안을 위해 타이트하게 잡는 게 맞지만, 장애 상황에서 원인 파악을 위해 일시적으로 sub 조건을 넓혀 확인하는 방법이 유효합니다.

예시(임시 진단용: 레포 단위로만 제한)

"StringLike": {
  "token.actions.githubusercontent.com:sub": "repo:OWNER/REPO:*"
}

원인 확인 후에는 다시 브랜치/태그/환경 단위로 좁히는 것을 권장합니다.

4. 1분: 실제 OIDC 토큰 클레임을 출력해 sub/aud를 확정

가장 빠른 해결법은 “추측”이 아니라 “실제 토큰의 sub/aud를 보고 Trust Policy를 맞추는 것”입니다.

GitHub Actions에서 OIDC 토큰은 ACTIONS_ID_TOKEN_REQUEST_URL로 받아볼 수 있습니다. 다음은 진단용으로 페이로드를 출력하는 예시입니다.

- name: Dump OIDC token claims (diagnostic)
  shell: bash
  run: |
    set -euo pipefail

    echo "Requesting OIDC token..."
    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")

    echo "$token" | awk -F'.' '{print $2}' | tr '_-' '/+' | base64 -d 2>/dev/null | python -m json.tool

출력된 JSON에서 최소한 아래를 확인합니다.

  • aud 값이 무엇인지 (sts.amazonaws.com인지)
  • sub가 어떤 형태인지 (브랜치/태그/환경)
  • repository, ref, job_workflow_ref 등 추가 힌트

이 값을 기준으로 Trust Policy의 StringEquals/StringLike를 정확히 수정하면 재발 확률이 크게 줄어듭니다.

참고로, 위 스크립트처럼 set -euo pipefail을 쓰면 진단 단계에서 변수 미정의로 스텝이 바로 죽을 수 있습니다. 파이프라인 안정성 관점에서의 함정은 이 글이 도움이 됩니다: bash set -euo pipefail 함정과 안전한 예외처리

5. 자주 터지는 ‘의외의’ 원인 5가지(현장 체크리스트)

5.1 액션 버전/입력값 문제

aws-actions/configure-aws-credentials@v4 사용을 권장합니다. 구버전은 OIDC 처리/로그가 부족해 원인 파악이 더 어렵습니다.

또한 role-to-assume ARN 오타(계정 ID, role 이름, path 포함 여부)가 생각보다 많습니다.

5.2 role session name 충돌/추적성 부족

실패 자체의 원인은 아니지만, CloudTrail에서 추적이 어려워 원인 분석이 길어집니다.

- uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: arn:aws:iam::123456789012:role/gha-deploy
    role-session-name: gha-${{ github.run_id }}-${{ github.job }}
    aws-region: ap-northeast-2

5.3 조직/레포 이름 변경 후 sub 불일치

레포를 이관하거나 이름을 바꾸면 subrepo:OWNER/REPO가 바뀌어 즉시 실패합니다. 특히 모노레포/조직 이관 직후 장애가 잦습니다.

5.4 environment 보호 규칙 도입 후 sub 형태 변경

environment: prod 같은 보호 규칙을 붙이면, Trust Policy가 ref:refs/heads/main만 허용할 때 실패할 수 있습니다. 배포 Job이 실제로 환경 컨텍스트로 토큰을 받는지 확인하고, 필요하면 sub 조건을 environment 형태로 맞추세요.

5.5 캐시/빌드 산출물 문제로 OIDC처럼 보이는 실패

간혹 assume-role 실패가 아니라, 앞 단계 빌드가 꼬여서 “자격 증명 설정 단계가 실행되지 않거나” 잘못된 바이너리를 참조해 엉뚱한 오류로 보이기도 합니다. 캐시 충돌로 워크플로우가 비정상 상태일 때는 이 전략이 유용합니다: GitHub Actions 캐시 충돌 시 빌드 완전 초기화 전략

6. 최종 점검: 7분 복구용 ‘정답 템플릿’

마지막으로, 가장 재현이 잘 되는 구성(레포 main 브랜치만 허용)을 한 번에 정리합니다.

6.1 GitHub Actions workflow

name: aws-oidc-test
on:
  push:
    branches: [ main ]

permissions:
  id-token: write
  contents: read

jobs:
  sts:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/gha-deploy
          aws-region: ap-northeast-2

      - name: Verify caller identity
        run: aws sts get-caller-identity

6.2 IAM Role 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"
        }
      }
    }
  ]
}

여기까지 맞춰도 실패한다면, 남는 가능성은 크게 두 가지입니다.

  • 실행 이벤트가 생각과 다르다(태그/PR/환경 컨텍스트)
  • provider/role이 다른 AWS 계정/리전에 섞여 있다(특히 멀티계정에서 ARN 혼동)

이 경우에는 4절의 “토큰 클레임 덤프”로 sub/aud를 확정하고, CloudTrail에서 AssumeRoleWithWebIdentity 이벤트를 찾아 거부 사유(조건 불일치)를 교차 확인하면 됩니다.

결론

GitHub Actions OIDC assume-role 실패는 겉보기엔 복잡하지만, 실제로는 permissions → provider → trust policy(aud/sub) 순서로 확인하면 대부분 7분 안에 복구됩니다. 특히 sub는 브랜치/태그/환경에 따라 바뀌므로, 장애 상황에서는 “실제 토큰 클레임을 출력해서” 신뢰 정책을 맞추는 것이 가장 빠르고 확실합니다.

다음 단계로는, 운영 배포에서는 repo:*처럼 넓은 조건을 피하고, 브랜치/태그/환경을 명시적으로 제한하며, CloudTrail 추적을 위해 role-session-name을 규칙적으로 부여하는 것을 권장합니다.