- Published on
GitHub Actions OIDC AWS 배포 InvalidIdentityToken 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서론
GitHub Actions에서 장기 액세스 키 없이 AWS에 배포하려면 OIDC(OpenID Connect) 기반의 AssumeRoleWithWebIdentity 구성이 사실상 표준입니다. 그런데 파이프라인은 멀쩡히 돌다가도 배포 단계에서 아래와 같은 에러로 멈추는 경우가 많습니다.
InvalidIdentityToken: No OpenIDConnect provider found in your account for https://token.actions.githubusercontent.comInvalidIdentityToken: Incorrect token audienceInvalidIdentityToken: The provided token is malformed or otherwise invalid
이 글은 “왜 InvalidIdentityToken이 뜨는지”를 토큰 검증 관점에서 쪼개서 설명하고, GitHub Actions 워크플로우(YAML)와 AWS IAM(OIDC Provider, Role Trust Policy)을 정답 형태로 제시합니다. 또한 같은 OIDC 계열 이슈를 EKS IRSA에서 겪었다면 원인 구조가 거의 동일하므로 함께 참고할 만한 내부 글도 연결합니다.
관련해서 EKS에서 OIDC Provider가 꼬였을 때 전체 IRSA가 실패하는 패턴은 구조적으로 유사합니다: EKS OIDC Provider 삭제로 IRSA 전부 실패했을 때 복구
InvalidIdentityToken의 본질: AWS가 검증하는 3가지
AWS STS는 AssumeRoleWithWebIdentity 요청을 받으면, 전달된 JWT 토큰을 다음 관점에서 검증합니다.
- issuer(iss): 토큰 발급자 URL이 IAM OIDC Provider로 등록되어 있는가?
- audience(aud): 토큰 대상이 신뢰 정책(Condition)과 일치하는가?
- subject(sub): 어떤 GitHub repo/branch/tag/workflow에서 발급된 토큰인지가 신뢰 정책과 일치하는가?
즉 InvalidIdentityToken은 “토큰이 가짜”라기보다, AWS 계정의 OIDC Provider/Role Trust Policy가 토큰 클레임과 맞지 않는다는 의미인 경우가 대부분입니다.
1) 가장 흔한 원인: OIDC Provider 미등록(issuer 불일치)
에러 예:
No OpenIDConnect provider found in your account for https://token.actions.githubusercontent.com
해결: IAM OIDC Provider 생성
AWS 콘솔에서 IAM → Identity providers → Add provider
- Provider type: OpenID Connect
- Provider URL:
https://token.actions.githubusercontent.com - Audience:
sts.amazonaws.com
CLI로도 가능합니다.
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com \
--thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1
> 참고: thumbprint는 문서/시점에 따라 변경될 수 있습니다. AWS 공식 문서의 최신 값을 확인하는 편이 안전합니다. 다만 GitHub Actions OIDC는 현재 대부분 AWS가 자동으로 처리/갱신하는 구간이 있어, 콘솔로 생성하는 것이 실무에서 덜 삽질합니다.
빠른 점검
aws iam list-open-id-connect-providers
aws iam get-open-id-connect-provider --open-id-connect-provider-arn <ARN>
여기서 URL이 token.actions.githubusercontent.com로 정확히 잡혀 있어야 합니다.
2) audience(aud) 불일치: "Incorrect token audience"
에러 예:
InvalidIdentityToken: Incorrect token audience
GitHub Actions에서 발급하는 OIDC 토큰의 aud는 기본적으로 sts.amazonaws.com를 사용합니다(aws-actions/configure-aws-credentials의 일반적인 설정).
문제는 아래 둘 중 하나로 터집니다.
- IAM OIDC Provider의 Client ID list에
sts.amazonaws.com가 없다. - Role Trust Policy에서
token.actions.githubusercontent.com:aud조건이 다르게 걸려 있다.
권장 Trust Policy 조건
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<ACCOUNT_ID>:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
}
}
}
]
}
여기에 sub 조건을 추가해 최소 권한으로 좁혀야 합니다(다음 섹션).
3) subject(sub) 불일치: repo/branch/tag 조건이 안 맞을 때
OIDC 토큰의 sub는 GitHub가 아래 형태로 만들어 넣습니다.
- 브랜치 기준:
repo:<OWNER>/<REPO>:ref:refs/heads/<BRANCH> - 태그 기준:
repo:<OWNER>/<REPO>:ref:refs/tags/<TAG> - 환경(environment) 사용 시:
repo:<OWNER>/<REPO>:environment:<ENV_NAME>
실무에서 가장 많이 하는 실수는:
- trust policy는
main브랜치만 허용했는데, 실제 배포는release/*또는 tag로 수행 - environment 기반 배포인데
sub를 ref 기반으로 걸어 둠
안전한 Trust Policy 예시(브랜치 main만 허용)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<ACCOUNT_ID>: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"
}
}
}
]
}
release 브랜치 패턴까지 허용(와일드카드)
sub는 보통 StringLike로 패턴 매칭을 씁니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<ACCOUNT_ID>: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:ref:refs/heads/main",
"repo:my-org/my-repo:ref:refs/heads/release/*"
]
}
}
}
]
}
GitHub Environment로 배포한다면(sub가 아예 달라짐)
예: production environment에서만 배포 허용
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<ACCOUNT_ID>: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:environment:production"
}
}
}
]
}
여기서 trust policy는 맞는데 계속 실패한다면, 실제 워크플로우가 environment를 사용하고 있는지(또는 반대로 사용하지 않는지)부터 확인해야 합니다.
4) GitHub Actions 설정 실수: permissions 누락
OIDC 토큰은 GitHub가 아무 때나 주지 않습니다. 워크플로우에 다음 권한이 필요합니다.
permissions: id-token: write
이게 없으면 보통 Could not load credentials from any providers 류로 터지지만, 구성에 따라 STS 호출이 꼬이면서 토큰 관련 에러처럼 보일 수 있습니다.
정답 워크플로우 예시
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 caller
run: aws sts get-caller-identity
- name: Deploy
run: |
# 예시: ECR push, ECS update, S3 sync 등
echo "deploying..."
aws sts get-caller-identity가 통과하면 OIDC/STS 신뢰 체인은 정상입니다. 이후 실패는 IAM policy(권한) 또는 배포 대상 설정 문제로 범위를 좁힐 수 있습니다.
5) 토큰 클레임을 직접 확인해서 5분 안에 원인 찾기
“sub가 뭔지”, “aud가 뭔지”를 감으로 맞추지 말고 실제 토큰을 까보면 끝납니다.
GitHub Actions에서는 OIDC 토큰을 요청할 수 있는 환경 변수가 주어집니다.
ACTIONS_ID_TOKEN_REQUEST_URLACTIONS_ID_TOKEN_REQUEST_TOKEN
다음 스텝을 임시로 넣고, JWT payload를 확인하세요.
- name: Debug OIDC token claims
shell: bash
run: |
set -euo pipefail
echo "Requesting OIDC token..."
RAW=$(curl -sSf \
-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
여기서 확인할 것:
iss가https://token.actions.githubusercontent.com인가?aud가sts.amazonaws.com인가?sub가 trust policy에 걸어둔 값/패턴과 일치하는가?
sub만 정확히 맞춰도 InvalidIdentityToken의 절반 이상이 해결됩니다.
6) 멀티 계정/멀티 리전에서 자주 생기는 함정
(1) 계정 A에 OIDC Provider 만들고, 계정 B Role에 붙임
OIDC Provider는 Role이 존재하는 동일 계정에 있어야 합니다. 배포 대상이 여러 계정이라면 계정마다 provider/role을 구성하거나, 중앙 계정에서 role chaining(AssumeRole)을 설계해야 합니다.
(2) role-to-assume ARN 오타
role-to-assume가 다른 계정/다른 role을 가리키면, trust policy가 안 맞아서 토큰 에러처럼 보일 수 있습니다. 먼저 aws sts get-caller-identity로 “누가 되었는지”를 확인하고, 그 다음 권한 문제를 보세요.
7) 재발 방지 체크리스트
- IAM OIDC Provider URL:
https://token.actions.githubusercontent.com - Client ID list(audience):
sts.amazonaws.com - Role trust policy에
aud는StringEqualssub는 배포 방식(ref vs environment)에 맞게StringEquals또는StringLike
- GitHub Actions
permissions: id-token: write포함 - 파이프라인 초기에
aws sts get-caller-identity로 자가진단
EKS에서 IRSA가 한 번에 전부 죽는 상황도 결국 “OIDC Provider/Trust 관계가 깨짐”이 핵심인 경우가 많습니다. OIDC 계열 장애를 더 넓게 복기하려면 다음 글도 함께 보면 원인 구조를 빠르게 일반화할 수 있습니다.
- EKS OIDC Provider 삭제로 IRSA 전부 실패했을 때 복구
- (권한/403 계열 트러블슈팅 관점) EKS에서 AWS Load Balancer Controller 403 해결법
결론
GitHub Actions OIDC의 InvalidIdentityToken은 대부분 “AWS가 토큰을 검증하는 조건(iss/aud/sub)과 IAM 설정이 어긋난 상태”입니다. 해결 순서는 간단합니다.
- OIDC Provider가 계정에 존재하는지(issuer) 확인 → 2) aud가
sts.amazonaws.com로 맞는지 확인 → 3) sub가 실제 배포 ref/environment와 일치하는지 확인 → 4) workflow permissions(id-token) 확인.
특히 sub는 배포 트리거(브랜치/태그/environment)가 바뀌면 즉시 깨지므로, trust policy를 “최소 권한”으로 유지하되 운영 방식 변화에 맞춰 패턴을 명시적으로 관리하는 것이 가장 안전합니다.