- Published on
GitHub Actions OIDC 403·권한거부 원인 7가지
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버리스/쿠버네티스/클라우드 배포 파이프라인에서 GitHub Actions OIDC(OpenID Connect)는 이제 사실상 표준입니다. 장기 액세스 키를 저장하지 않고, 워크플로 실행 시점에만 짧은 수명의 토큰으로 클라우드 역할(Role)을 Assume 하는 방식이라 보안적으로도 좋습니다.
그런데 OIDC는 “한 번만 맞추면 끝”이 아니라, 조금만 조건이 어긋나도 403(권한거부)로 단칼에 실패합니다. 특히 AWS의 sts:AssumeRoleWithWebIdentity가 실패하거나, Azure Federated Credential 매칭이 안 되거나, GCP Workload Identity Provider의 attribute 매핑이 어긋날 때 에러 메시지가 매우 불친절하게 느껴질 수 있습니다.
이 글은 GitHub Actions OIDC에서 흔히 마주치는 403/AccessDenied의 원인 7가지를 “어디를 봐야 하는지” 중심으로 정리한 실전 체크리스트입니다. (AWS 예시 중심이지만, 개념은 Azure/GCP에도 동일하게 적용됩니다.)
> 참고로 EKS IRSA에서도 OIDC/TrustPolicy/ServiceAccount 조건 불일치로 AccessDenied가 자주 발생합니다. 원리 자체가 거의 동일하니 함께 보면 진단 속도가 빨라집니다: EKS IRSA인데 AccessDenied? OIDC·TrustPolicy·SA 점검
OIDC 403을 빠르게 분류하는 법
먼저 “403”이 어디서 발생하는지부터 분류하면 원인 탐색이 절반은 끝납니다.
- GitHub → 클라우드로 역할 위임(Assume) 단계에서 403
- 예: AWS
AssumeRoleWithWebIdentity가AccessDenied또는InvalidIdentityToken - 대개 Trust Policy / OIDC Provider / 조건(Subject/Audience) 문제
- 예: AWS
- 역할 위임은 성공했는데, 이후 리소스 호출에서 403
- 예:
ecr:GetAuthorizationToken/s3:PutObject/eks:DescribeCluster등이AccessDenied - 대개 Role Permission Policy(권한 정책) 문제
- 예:
아래 7가지는 이 두 부류를 모두 커버합니다.
1) workflow에 id-token: write 권한이 없음 (가장 흔함)
GitHub Actions에서 OIDC 토큰 발급은 기본적으로 “아무나” 못 합니다. 워크플로 권한에 id-token: write가 없으면, OIDC 토큰을 요청하는 단계에서 실패하거나, 액션이 내부적으로 권한 부족으로 멈춥니다.
증상
Error: Unable to get OIDC token류- 또는 configure-credentials 액션이 STS 호출 전 단계에서 실패
해결
워크플로 최상단 혹은 필요한 job에 다음을 추가합니다.
name: deploy
on:
push:
branches: ["main"]
permissions:
contents: read
id-token: write
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials via OIDC
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/gha-deploy
aws-region: ap-northeast-2
> 조직/리포지토리 설정에서 “Workflow permissions”가 Read-only로 강제되어 있으면 job에서 permissions를 줘도 제한될 수 있습니다. 이 경우 리포지토리 Settings → Actions → General도 함께 확인합니다.
2) IAM OIDC Provider(issuer) 등록이 잘못됨 또는 다른 계정/리전에 등록됨
AWS에서 GitHub OIDC를 쓰려면 IAM에 OIDC Provider를 등록해야 합니다.
- Issuer:
https://token.actions.githubusercontent.com - Thumbprint: GitHub 문서 기준 값(변경 가능성 있으니 최신 확인)
- Audience(client ID): 보통
sts.amazonaws.com
증상
InvalidIdentityToken: No OpenIDConnect provider found in your account for https://token.actions.githubusercontent.com- 또는 AssumeRoleWithWebIdentity에서 즉시 거절
진단 포인트
- 역할(Role)이 있는 같은 AWS 계정에 OIDC Provider가 존재하는지
- Provider ARN이 Trust Policy에 정확히 들어갔는지
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
해결
OIDC Provider를 올바른 계정에 생성하고, Trust Policy의 Principal Federated에 해당 Provider ARN을 넣습니다.
3) Trust Policy의 sub 조건이 실제 토큰과 불일치 (repo/branch/environment)
OIDC에서 가장 자주 틀리는 부분이 sub(subject) 조건입니다. GitHub OIDC 토큰의 sub는 “어떤 워크플로 실행 맥락인지”를 표현하며, 다음 요소에 따라 달라집니다.
- repo:
repo:OWNER/REPO:... - ref(브랜치/태그):
ref:refs/heads/main - environment:
environment:prod등 - pull_request 이벤트 등
증상
- AWS:
AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentity - (권한 정책이 아니라) Trust Policy 조건 불일치로 거절
실전 팁: 먼저 토큰 클레임을 직접 확인
GitHub Actions에서 OIDC 토큰을 받아 JWT를 디코드해 sub, aud, iss를 확인하면 가장 빠릅니다.
permissions:
id-token: write
contents: read
jobs:
debug-oidc:
runs-on: ubuntu-latest
steps:
- name: Print OIDC claims
shell: bash
run: |
set -euo pipefail
TOKEN=$(curl -sS -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=sts.amazonaws.com" | jq -r .value)
echo "$TOKEN" | awk -F. '{print $2}' | base64 -d 2>/dev/null | jq
이 출력에서 sub 값을 그대로 Trust Policy 조건에 맞추면 됩니다.
AWS Trust Policy 예시(브랜치 main만 허용)
{
"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"
}
}
}
]
}
> environment 보호 규칙을 쓰는 경우 sub가 repo:...:environment:prod 형태로 바뀌어 매칭이 깨질 수 있습니다. 브랜치 기반에서 환경 기반으로 전환할 때 특히 많이 터집니다.
4) aud(audience) 불일치: 토큰은 받았는데 STS가 거절
OIDC 토큰에는 aud가 들어가고, 클라우드는 이 값을 조건으로 검증합니다. AWS의 일반적인 audience는 sts.amazonaws.com입니다.
증상
InvalidIdentityToken: Incorrect token audience- 또는 Trust Policy에서
aud조건 때문에 Assume 실패
원인 패턴
- 워크플로에서 토큰 요청 시
audience파라미터를 커스텀으로 줬는데 Trust Policy는sts.amazonaws.com만 허용 - 반대로 Trust Policy에서 audience를 다른 값으로 제한했는데 실제 토큰은 기본값
해결
- AWS를 쓴다면 보통 다음 조합으로 고정합니다.
- 토큰 요청
audience=sts.amazonaws.com - Trust Policy
token.actions.githubusercontent.com:aud = sts.amazonaws.com
- 토큰 요청
5) Role Permission Policy가 부족함 (Assume은 성공했는데 API가 403)
OIDC 설정이 맞으면 AssumeRoleWithWebIdentity는 성공합니다. 그 다음부터는 “그 역할이 실제로 무엇을 할 수 있느냐”의 문제입니다.
증상
AccessDeniedException(EKS)AccessDenied(S3)denied: User is not authorized to perform: ecr:*(ECR)
진단
- 먼저 configure-credentials 단계 로그에서 Assume 성공 여부를 확인
- AWS CLI로
sts get-caller-identity가 기대한 Role ARN을 가리키는지 확인
- name: Who am I
run: aws sts get-caller-identity
- 그 다음 실패하는 API 액션을 보고 정책에 추가
예: ECR 푸시 최소 권한(예시)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:PutImage"
],
"Resource": "*"
}
]
}
ECR에서 403이 나는 케이스는 OIDC 자체 문제와도 섞여 보이기 쉬운데, 실제로는 “이미지 pull/push 권한” 문제인 경우가 많습니다. 이 주제는 별도로 정리한 글도 참고하세요: EKS ImagePullBackOff 403 - ECR 권한·토큰 만료 해결
6) 조직/리포지토리/엔터프라이즈 정책으로 OIDC가 제한됨
GitHub는 보안 강화를 위해 OIDC 사용을 조직 단위로 제한할 수 있습니다.
증상
- 같은 YAML인데 개인 리포에서는 되는데 조직 리포에서만 실패
- 특정 브랜치/환경에서만 토큰 발급이 실패
체크리스트
- Organization Settings → Actions → General
- Allowed actions 정책
- Workflow permissions
- Environment protection rules(승인 필요 등)
- Repository Settings → Actions
- GitHub Enterprise의 정책(엔터프라이즈 레벨에서 override)
해결 방향
- OIDC 토큰이 필요한 job에만
permissions: id-token: write를 주고, 나머지는 최소화 - 환경(Environment) 보호 규칙을 쓰면
sub가 달라지므로 Trust Policy도 함께 조정
7) 이벤트 타입(pull_request/fork) 때문에 의도적으로 OIDC가 막힘
GitHub는 보안상 fork에서 올라온 PR 같은 신뢰할 수 없는 컨텍스트에서는 비밀/토큰 권한이 제한됩니다. OIDC도 예외가 아닙니다.
증상
- main 브랜치 push에서는 성공
- PR에서는 OIDC 토큰 발급/Assume 단계가 실패
원인
- PR은 외부 기여자가 워크플로를 악용해 클라우드 권한을 탈취할 수 있기 때문에, GitHub가 기본적으로 권한을 제한
해결 패턴
- 배포 job은
push(보호된 브랜치) 또는workflow_dispatch에서만 실행 - PR에서는 빌드/테스트까지만 수행하고, 배포는 승인된 경로로 분리
예:
on:
push:
branches: ["main"]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm test
deploy:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
permissions:
id-token: write
contents: read
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
- run: ./deploy.sh
403이 계속될 때: “토큰 클레임 vs Trust 조건”을 1:1로 맞춰라
OIDC 403의 본질은 대부분 이것입니다.
- GitHub가 발급한 토큰의 클레임(iss/aud/sub 등)
- 클라우드가 요구하는 조건(Provider/Trust Policy/Federated Credential/Attribute mapping)
이 둘이 완전히 동일하게 맞물려야 합니다.
최소 진단 루틴(추천)
- 워크플로에
permissions: id-token: write확인 - OIDC 토큰 클레임을 출력해
iss/aud/sub를 실제 값으로 확보 - (AWS) IAM OIDC Provider 존재/ARN 확인
- (AWS) Role Trust Policy에서
aud/sub조건이 실제 토큰과 일치하는지 확인 - Assume 성공 후
aws sts get-caller-identity로 역할 확인 - 이후 API 403이면 Permission Policy를 실패 액션 기준으로 보강
이 루틴대로 하면 “감으로” IAM을 만지지 않아도 대부분 10~20분 내로 원인이 좁혀집니다.
마무리
GitHub Actions OIDC의 403/권한거부는 대개 (1) 토큰 발급 권한 문제, (2) 신뢰(Trust) 조건 불일치, (3) 실제 권한 정책 부족 중 하나입니다. 특히 sub/aud 조건은 한 글자만 달라도 실패하므로, 반드시 토큰 클레임을 출력해 사실 기반으로 Trust Policy를 맞추는 습관이 중요합니다.
OIDC 기반 권한 위임은 GitHub Actions뿐 아니라 EKS IRSA 같은 영역에서도 동일한 패턴으로 반복됩니다. OIDC가 낯설다면 아래 글의 체크리스트도 함께 보며 “조건 매칭” 감각을 잡는 것을 권합니다.