- Published on
GitHub Actions OIDC로 AWS 배포 권한 오류 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버리스든 ECS든 EKS든, GitHub Actions에서 AWS로 배포할 때 가장 흔하게 마주치는 문제는 결국 AccessDenied 류의 권한 오류입니다. 예전처럼 장기 Access Key를 저장해 두면 당장은 해결되지만, 키 유출·로테이션·권한 과다 부여 문제가 따라옵니다.
OIDC(OpenID Connect) 기반 연동은 GitHub가 워크플로 실행 시점에만 짧게 쓸 수 있는 토큰을 발급하고, AWS가 그 토큰을 검증해 IAM Role을 AssumeRoleWithWebIdentity로 넘겨주는 방식이라 운영 관점에서 훨씬 안전합니다. 다만 설정이 조금만 어긋나도 곧바로 권한 오류가 발생합니다.
이 글은 “왜 권한 오류가 나는지”를 증상별로 분류하고, AWS IAM(신뢰 정책·권한 정책)과 GitHub Actions(permissions·audience·subject)의 정합성을 맞춰서 재발을 막는 데 초점을 둡니다.
관련해서 GitHub Actions 자체 디버깅 팁은 GitHub Actions 캐시가 안 먹을 때 디버깅 체크리스트도 함께 보면, 워크플로 진단 흐름을 잡는 데 도움이 됩니다.
OIDC 흐름 한 장 요약
- GitHub Actions Job이
id-token: write권한을 갖고 OIDC 토큰을 요청합니다. aws-actions/configure-aws-credentials가 그 토큰을 AWS STS에 전달합니다.- AWS STS는 IAM Role의 Trust Policy(신뢰 정책)를 검사합니다.
- 조건을 만족하면 STS가 임시 자격 증명(AccessKeyId/SecretAccessKey/SessionToken)을 발급합니다.
- 이후 AWS CLI/SDK는 임시 자격 증명으로 배포 API를 호출합니다.
문제는 3번(신뢰 정책)과 5번(권한 정책)에서 대부분 터집니다.
가장 흔한 에러 메시지와 원인 매핑
1) Not authorized to perform sts:AssumeRoleWithWebIdentity
- IAM Role의 Trust Policy에 OIDC Provider가 없거나
sub/aud조건이 불일치하거나- GitHub Actions에서
id-token: write를 주지 않아 토큰 자체가 발급되지 않는 경우
2) No OpenIDConnect provider found in your account for https://token.actions.githubusercontent.com
- AWS 계정에 GitHub OIDC Provider 생성이 안 됨
3) AccessDenied: ... is not authorized to perform: ecr:PutImage (또는 ecs:RegisterTaskDefinition, cloudformation:CreateChangeSet 등)
- Role은 Assume 되었지만, 배포에 필요한 IAM Permission Policy가 부족함
4) The security token included in the request is invalid
configure-aws-credentials단계가 실패했는데도 다음 단계가 실행되었거나- region/환경변수 꼬임, 혹은 다른 크레덴셜이 덮어쓴 경우
이제부터는 “정상 작동하는 최소 구성”을 만들고, 거기에서 조건을 좁히는 방식으로 해결합니다.
1단계: AWS에 GitHub OIDC Provider 만들기
AWS 콘솔에서 IAM Identity providers에 OIDC Provider가 있어야 합니다.
- Provider URL:
https://token.actions.githubusercontent.com - Audience: 보통
sts.amazonaws.com
CLI로 만들면 대략 아래 형태입니다(인라인 코드 블록에만 https://를 쓰면 MDX 문제는 없습니다).
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com \
--thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1
주의: thumbprint는 AWS 문서/콘솔 기준값을 확인하세요. 잘못 넣으면 이후 신뢰 검증이 실패합니다.
2단계: IAM Role Trust Policy(신뢰 정책) 제대로 만들기
OIDC 연동에서 가장 자주 틀리는 지점이 신뢰 정책의 Condition입니다.
Trust Policy 예시(레포 단위로 제한)
아래는 특정 레포의 main 브랜치에서 실행된 워크플로만 Role을 Assume할 수 있게 제한하는 예시입니다.
{
"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/REPO:ref:refs/heads/main"
}
}
}
]
}
여기서 체크 포인트:
FederatedARN이 내 계정의 OIDC Provider ARN과 일치하는지aud가 워크플로에서 요청하는 audience와 일치하는지sub패턴이 실제 실행 컨텍스트와 일치하는지
sub가 자주 어긋나는 케이스
- PR에서 실행:
ref:refs/pull/.../merge형태가 될 수 있음 - 태그 푸시:
ref:refs/tags/v1.2.3 - 환경 보호 규칙 사용:
environment:prod관련 claim을 추가로 제한하는 구성도 존재
브랜치/태그/PR을 모두 허용하고 싶다면 StringLike를 넓히되, 보안 경계를 어디에 둘지 먼저 정하세요.
예시(브랜치 main과 태그 v로 시작만 허용):
"StringLike": {
"token.actions.githubusercontent.com:sub": [
"repo:ORG/REPO:ref:refs/heads/main",
"repo:ORG/REPO:ref:refs/tags/v*"
]
}
3단계: GitHub Actions 워크플로에 id-token: write 추가
OIDC 토큰 발급은 GitHub Actions의 권한 설정에 의해 막힐 수 있습니다. Job 또는 workflow 레벨에 아래가 필요합니다.
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
aws sts get-caller-identity가 성공하면 “AssumeRole 단계”는 통과한 것입니다. 이제 남는 문제는 대부분 “배포 권한 정책 부족”입니다.
4단계: 배포 대상별 IAM Permission Policy 점검
OIDC는 인증(AssumeRole)까지의 문제를 해결해 주지만, 배포 API 권한은 별개입니다. 배포 방식별로 필요한 권한이 다릅니다.
ECR 푸시 권한(대표적인 ecr:PutImage 오류)
컨테이너 이미지를 푸시한다면 최소한 아래 권한들이 필요합니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:PutImage",
"ecr:BatchGetImage"
],
"Resource": "*"
}
]
}
리포지토리 단위로 더 강하게 제한하려면 Resource를 ECR repository ARN으로 좁히고, GetAuthorizationToken만 *로 남기는 식으로 분리합니다.
ECS 배포(자주 터지는 ecs:RegisterTaskDefinition, iam:PassRole)
ECS는 태스크 실행 역할을 넘겨줘야 해서 iam:PassRole이 빠지면 거의 반드시 실패합니다.
필요 권한 예시:
ecs:RegisterTaskDefinitionecs:UpdateServiceecs:DescribeServicesiam:PassRole(태스크 실행 Role, 태스크 Role)
iam:PassRole은 반드시 대상 Role ARN으로 제한하세요.
S3 배포(정적 사이트)
s3:PutObject,s3:DeleteObject,s3:ListBucket- CloudFront 무효화까지 하면
cloudfront:CreateInvalidation
권한을 줄 때는 “버킷 ARN”과 “오브젝트 ARN”을 구분해야 합니다.
5단계: audience 불일치 문제 해결
어떤 설정/액션 조합에서는 audience를 명시해야 할 때가 있습니다. 기본은 sts.amazonaws.com이지만, 조직 정책이나 커스텀 구성이 들어가면 mismatch가 납니다.
configure-aws-credentials는 audience를 지정할 수 있습니다.
- 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
audience: sts.amazonaws.com
그리고 Trust Policy의 token.actions.githubusercontent.com:aud와 동일해야 합니다.
6단계: 디버깅 체크리스트(재현 가능한 순서)
권한 오류를 “감”으로 고치면 다음 배포에서 또 터집니다. 아래 순서로 한 단계씩 확정하세요.
- 워크플로에
permissions: id-token: write가 있는가 configure-aws-credentials단계 로그에서 OIDC 토큰 발급이 되었는가aws sts get-caller-identity가 성공하는가- 실패한다면 Trust Policy의
FederatedARN,aud,sub를 재검증 - AssumeRole은 성공하지만 배포 API에서 실패한다면, 그 액션 이름 그대로 IAM 정책에 추가
iam:PassRole이 필요한 서비스(ECS, CodeDeploy 등)인지 확인
이때 “어떤 액션이 거부되었는지”는 에러 메시지에 거의 항상 포함됩니다. 예를 들어 is not authorized to perform: cloudformation:CreateChangeSet처럼 액션이 보이면, 해당 액션을 최소 범위 리소스에 부여하면 됩니다.
권한/정책 문제를 진단하는 관점은 리눅스에서 권한 때문에 작업이 안 도는 상황을 점검하는 방식과 유사합니다. 체크리스트형 접근이 필요하다면 리눅스 logrotate가 안 돎? 권한·SELinux 점검도 같은 문제 해결 스타일로 참고할 만합니다.
7단계: 보안적으로 안전한 제한 패턴
OIDC의 장점은 “짧은 토큰”뿐 아니라 “정교한 조건 제한”입니다. 운영에서 권장하는 제한은 보통 아래 3가지입니다.
1) 레포 고정
sub에repo:ORG/REPO:를 반드시 포함
2) 브랜치 또는 태그 고정
- 배포는
main또는 릴리즈 태그만
3) 환경 보호와 결합(선택)
- GitHub Environments 승인(Protected environments)을 쓰는 경우, 환경 단위로 Role을 분리
예: prod 배포 Role은 main + 승인된 환경에서만 Assume 가능하게.
8단계: 자주 하는 실수 모음
- Trust Policy에서
Action을sts:AssumeRole로 넣음 (OIDC는sts:AssumeRoleWithWebIdentity) sub를 너무 넓게 열어repo:ORG/*로 허용 (편하지만 위험)- 워크플로
permissions를 생략해서 OIDC 토큰이 발급되지 않음 - AssumeRole은 됐는데, ECR/ECS/S3 권한을 Role에 붙이지 않음
iam:PassRole누락
마무리: 해결 전략은 “AssumeRole”과 “API 권한” 분리
GitHub Actions OIDC로 AWS 배포 권한 오류를 해결할 때 핵심은 문제를 두 층으로 분리하는 것입니다.
- 1층: STS AssumeRole이 되는가 (신뢰 정책,
aud,sub,id-token) - 2층: 배포 API 권한이 충분한가 (ECR/ECS/S3/CloudFormation 등)
먼저 aws sts get-caller-identity로 1층을 확정하고, 이후 에러 메시지에 나온 AWS API 액션을 기준으로 2층 권한을 최소 범위로 추가하면, 재현 가능하고 안전한 배포 파이프라인을 만들 수 있습니다.