- Published on
GitHub Actions OIDC로 AWS 키 없이 배포하기
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버리스든 ECS든 EKS든, CI에서 AWS로 배포할 때 가장 찝찝한 지점은 결국 AWS_ACCESS_KEY_ID 와 AWS_SECRET_ACCESS_KEY 입니다. GitHub Secrets에 넣어도 “유출되면 끝”인 장기 자격 증명이라는 사실은 바뀌지 않고, 키 로테이션·권한 관리·감사까지 운영 부담이 커집니다.
GitHub Actions OIDC는 이 문제를 꽤 깔끔하게 해결합니다. 워크플로우가 실행될 때마다 GitHub가 발급하는 OIDC 토큰을 AWS STS가 검증하고, 그 실행에만 유효한 임시 자격 증명(AssumeRole)을 내려주는 방식입니다. 즉, 저장된 키가 없고, 만료되는 크리덴셜만 쓰게 됩니다.
이 글에서는 OIDC 기반으로 AWS에 “키 없이” 배포하는 전체 구성을 단계별로 정리하고, 실무에서 자주 만나는 실패 원인과 보안 팁까지 같이 다룹니다.
관련해서 GitHub Actions 운영을 더 단단히 하고 싶다면 GitHub Actions 캐시가 안 먹을 때 속도 3배 올린 실전도 함께 참고하면 좋습니다. 모노레포라면 GitHub Actions 재사용 워크플로우로 모노레포 CI 통합도 연결됩니다.
OIDC로 “키 없이 배포”가 가능한 이유
핵심은 세 가지입니다.
- GitHub Actions 런너가 OIDC 토큰을 요청한다
- AWS IAM에 등록된 OIDC Provider가 그 토큰의 발급자와 서명을 검증한다
- IAM Role의 신뢰 정책(Trust Policy)이 토큰의 클레임을 조건으로 허용하면 STS가 임시 크리덴셜을 발급한다
이 흐름에서 장기 키는 등장하지 않습니다. 임시 크리덴셜은 기본적으로 짧은 TTL을 갖고, 특정 워크플로우 실행 컨텍스트(리포지토리, 브랜치, 환경 등)에만 묶이도록 제약할 수 있습니다.
아키텍처 구성 요소
- GitHub Actions Workflow
- GitHub OIDC 토큰(워크플로우 런타임에만 발급)
- AWS IAM OIDC Provider(
token.actions.githubusercontent.com) - AWS IAM Role(신뢰 정책에 OIDC 조건 포함)
- STS
AssumeRoleWithWebIdentity
1) AWS에 GitHub OIDC Provider 등록
AWS 콘솔에서 IAM Identity providers 에 OIDC Provider를 추가합니다.
- 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 공식 문서 기준으로 확인하세요. 실무에선 콘솔로 등록하는 편이 실수 확률이 낮습니다.
2) 배포용 IAM Role 만들기(Trust Policy가 핵심)
OIDC의 진짜 보안 포인트는 “누가 이 Role을 Assume할 수 있느냐”를 토큰 클레임으로 강하게 제한하는 것입니다.
아래는 특정 리포지토리의 main 브랜치에서 실행된 워크플로우만 허용하는 예시 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",
"token.actions.githubusercontent.com:sub": "repo:ORG/REPO:ref:refs/heads/main"
}
}
}
]
}
여기서 중요한 클레임은 보통 다음입니다.
aud: 대상(audience). GitHub Actions에서는sts.amazonaws.com를 흔히 사용sub: 실행 주체를 표현. 리포지토리, 브랜치/태그, 환경 등 컨텍스트가 포함됨
브랜치 대신 Environment로 잠그기
배포를 GitHub Environments(예: production)로 운영한다면 sub를 더 안전하게 묶을 수 있습니다.
예시(환경 기반):
repo:ORG/REPO:environment:production
이 방식은 “승인(Required reviewers)” 같은 환경 보호 규칙과 결합하기 좋아서, 운영 배포에 특히 유리합니다.
3) Role에 최소 권한(Least Privilege) 정책 부여
Trust Policy는 “누가 Assume 가능한가”이고, Permissions Policy는 “Assume 후 무엇을 할 수 있는가”입니다.
예를 들어 S3에 정적 사이트를 업로드하고 CloudFront 무효화를 하는 배포라면 대략 이런 정책이 필요합니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-prod-bucket",
"arn:aws:s3:::my-prod-bucket/*"
]
},
{
"Effect": "Allow",
"Action": [
"cloudfront:CreateInvalidation"
],
"Resource": "*"
}
]
}
CloudFront는 리소스 ARN을 세밀하게 제한하기 애매한 작업이 있어 *가 들어가기 쉬운데, 가능하면 배포 대상 Distribution으로 제한하는 방향을 검토하세요.
4) GitHub Actions 워크플로우 작성
OIDC를 쓰려면 워크플로우에 반드시 permissions로 id-token: write가 필요합니다. 이게 없으면 토큰 발급 자체가 안 됩니다.
아래는 OIDC로 Role을 Assume하고, S3 동기화 후 CloudFront 무효화를 수행하는 예시입니다.
name: deploy
on:
push:
branches: [ main ]
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
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-role
aws-region: ap-northeast-2
- name: Build
run: |
npm ci
npm run build
- name: Upload to S3
run: |
aws s3 sync ./dist s3://my-prod-bucket --delete
- name: Invalidate CloudFront
run: |
aws cloudfront create-invalidation --distribution-id ABCDEFG123456 --paths "/*"
포인트:
AWS_ACCESS_KEY_ID같은 시크릿이 전혀 필요 없습니다.configure-aws-credentials액션이 내부적으로 OIDC 토큰을 받아 STS로 Role을 Assume합니다.
5) 자주 터지는 오류와 빠른 진단법
1) Not authorized to perform sts:AssumeRoleWithWebIdentity
대부분 Trust Policy 조건이 맞지 않습니다.
체크리스트:
- OIDC Provider ARN이 정확한가
aud가sts.amazonaws.com로 일치하는가sub조건이 실제 실행 컨텍스트와 일치하는가- 브랜치 이름 오타
main대신master- 태그 배포인데 브랜치 조건을 걸어둠
- Environment를 쓰는데
sub를 브랜치로 묶어둠
실무 팁: 처음엔 sub 조건을 너무 빡세게 걸면 디버깅이 어려워집니다. 먼저 리포지토리 단위로만 제한해 성공을 확인한 뒤, 브랜치/환경으로 점진적으로 강화하세요.
2) RequestError: credentials are missing
대부분 워크플로우 permissions에 id-token: write가 빠진 케이스입니다.
- 반드시
permissions: id-token: write를 추가
또는 조직 정책으로 Actions 권한이 제한되어 OIDC 토큰 발급이 막혀 있을 수도 있습니다.
3) AccessDenied (S3, ECR, ECS 등 개별 API에서 실패)
Assume 자체는 성공했지만, Role 권한이 부족한 상황입니다.
- CloudTrail에서 거부된 API와 리소스 ARN을 확인
- 필요한 액션만 최소로 추가
4) PR에서 배포가 되는 사고
pull_request 이벤트에서 배포 워크플로우가 실행되면 위험합니다. 특히 포크 PR에서는 더 민감합니다.
권장:
- 배포는
pushto protected branch 또는workflow_dispatch로 제한 - GitHub Environments 승인 프로세스 사용
6) 보안 하드닝 체크리스트
sub를 가능한 한 좁게repo:ORG/REPO:ref:refs/heads/main또는repo:ORG/REPO:environment:production
- GitHub Environments로 운영 배포를 통제
- 승인자, 대기 시간, 브랜치 제한 등
- Role 권한은 최소 권한으로
- 배포 대상 리소스 ARN을 가능한 구체적으로
- 임시 크리덴셜 TTL을 과도하게 늘리지 않기
- CloudTrail로
AssumeRoleWithWebIdentity이벤트 감사
EKS를 운영 중이라면, 노드/파드에서 의도치 않게 메타데이터로 자격 증명을 가져가는 경로도 같이 차단하는 게 좋습니다. 이 주제는 EKS에서 AWS SDK의 IMDS 접근을 확실히 차단하는 법에서 더 깊게 다룹니다.
7) 확장 패턴: 재사용 워크플로우로 표준화
팀/조직에서 여러 서비스가 같은 방식으로 배포한다면, OIDC Assume 단계와 공통 배포 단계를 재사용 워크플로우로 빼서 표준화하는 게 유지보수에 유리합니다.
- 공통 입력:
role-to-assume,aws-region,deploy-target - 서비스별 워크플로우는 빌드 산출물과 배포 명령만 다르게
이 패턴은 GitHub Actions 재사용 워크플로우로 모노레포 CI 통합에서 소개한 방식과 결이 같습니다.
마무리
GitHub Actions OIDC는 “AWS 키를 안전하게 보관하는 법”이 아니라, “애초에 키를 보관하지 않는 법”에 가깝습니다. 장기 키를 제거하면 유출 표면이 크게 줄고, 배포 권한을 워크플로우 컨텍스트(리포지토리·브랜치·환경)로 강하게 묶을 수 있어 운영 안전성이 올라갑니다.
정리하면:
- AWS에는 OIDC Provider와 Role(Trust Policy 조건 포함)만 준비
- GitHub Actions에는
permissions와configure-aws-credentials만 추가 - 이후 배포는 임시 크리덴셜로 수행
다음 단계로는 GitHub Environments 기반 승인 흐름, Role 권한 최소화, 그리고 CloudTrail 감사까지 붙여 “키 없는 배포”를 운영 표준으로 만드는 것을 추천합니다.