- Published on
GitHub Actions OIDC로 AWS 키 없이 배포하기
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서로 다른 리포지토리/환경에서 배포 파이프라인을 운영하다 보면 결국 한 번은 이런 요구가 나옵니다.
- GitHub Actions에
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY를 저장하지 말자 - 키 로테이션/유출 대응 비용을 줄이자
- PR/브랜치/환경별로 “누가 무엇을 배포할 수 있는지”를 더 강하게 통제하자
이때 가장 깔끔한 해법이 GitHub Actions OIDC(OpenID Connect) + AWS STS AssumeRoleWithWebIdentity 조합입니다. 요약하면, GitHub가 워크플로 실행 시 OIDC 토큰(JWT)을 발급하고, AWS는 그 토큰을 검증한 뒤 짧은 만료 시간의 임시 자격증명을 내려줍니다. 결과적으로 장기 키(Access Key) 없이 배포가 가능합니다.
아래에서는 OIDC의 동작 원리부터 AWS/GitHub 설정, Terraform 예시, 실제 배포 워크플로, 그리고 운영 중 자주 만나는 에러 포인트까지 한 번에 정리합니다.
왜 OIDC인가: 장기 키의 운영 리스크 제거
기존 방식(장기 키 기반)은 다음 문제가 반복됩니다.
- GitHub Secrets에 키를 저장해야 함(유출/오남용 리스크)
- 키 로테이션이 번거로움(사람/프로세스 의존)
- 키 권한이 과대해지기 쉬움(“일단 되게 하자”)
OIDC 방식은 다음 장점이 있습니다.
- 키 저장 불필요: GitHub Secrets에 AWS 키를 넣지 않음
- 임시 자격증명: STS 토큰은 일반적으로 15~60분 만료
- 조건 기반 신뢰 정책: 특정 리포지토리/브랜치/환경만 AssumeRole 허용
- 감사 추적 용이: CloudTrail에
AssumeRoleWithWebIdentity기록이 남음
전체 아키텍처 한 장 요약
- GitHub Actions 워크플로가 실행됨
- 워크플로가 GitHub OIDC Provider에서 ID Token(JWT) 요청
aws-actions/configure-aws-credentials가 STS에AssumeRoleWithWebIdentity호출- AWS가 토큰의
iss/aud/sub등 클레임을 검증 - 검증 성공 시 임시 자격증명(AccessKey/SecretKey/SessionToken)을 워크플로 런타임에만 제공
- 이후 AWS CLI/SDK로 ECR/ECS/EKS/S3/CloudFront 등 배포 수행
1) AWS: GitHub OIDC Identity Provider 생성
AWS IAM에 GitHub OIDC Provider를 등록합니다.
- Provider URL:
https://token.actions.githubusercontent.com - Audience(
aud): 보통sts.amazonaws.com
Terraform 예시:
resource "aws_iam_openid_connect_provider" "github" {
url = "https://token.actions.githubusercontent.com"
client_id_list = [
"sts.amazonaws.com"
]
# GitHub OIDC의 TLS 인증서 지문(thumbprint)은 AWS 문서/검증 방식에 따라 달라질 수 있습니다.
# 최신 Terraform/AWS Provider에서는 thumbprint를 요구하거나 자동 갱신을 지원하는 경우가 있으니
# 사용하는 provider 버전에 맞춰 확인하세요.
thumbprint_list = ["6938fd4d98bab03faadb97b34396831e3780aea1"]
}
> thumbprint는 환경/시점에 따라 변경될 수 있으니, 운영에서는 AWS 공식 가이드에 따라 최신 값을 확인하는 편이 안전합니다.
2) AWS: 배포용 IAM Role 만들기(신뢰 정책이 핵심)
OIDC에서 가장 중요한 건 Role의 Trust Policy(신뢰 정책) 입니다. 여기서 “어떤 워크플로가 이 Role을 Assume할 수 있는지”를 결정합니다.
다음 예시는 my-org/my-repo 리포지토리의 main 브랜치에서만 AssumeRole을 허용합니다.
{
"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:my-org/my-repo:ref:refs/heads/main"
}
}
}
]
}
sub 조건을 어떻게 잡을까?
sub는 GitHub가 토큰에 넣는 “주체(subject)”로, 보통 아래 패턴을 씁니다.
- 브랜치 제한:
repo:ORG/REPO:ref:refs/heads/main - 태그 제한:
repo:ORG/REPO:ref:refs/tags/v* - 환경(Environment) 제한:
repo:ORG/REPO:environment:prod
운영에서는 브랜치 + 환경까지 묶어 두면 사고 확률이 크게 줄어듭니다(예: prod 배포는 GitHub Environment 승인 필요).
3) AWS: 최소 권한 정책 붙이기(예: ECR push + ECS deploy)
Role에 붙일 IAM Policy는 “배포에 필요한 만큼만” 주는 게 정석입니다.
예를 들어 ECR에 이미지를 push하고 ECS 서비스를 업데이트한다면 대략 이런 권한이 필요합니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"ecr:BatchCheckLayerAvailability",
"ecr:CompleteLayerUpload",
"ecr:InitiateLayerUpload",
"ecr:PutImage",
"ecr:UploadLayerPart"
],
"Resource": "arn:aws:ecr:ap-northeast-2:123456789012:repository/my-app"
},
{
"Effect": "Allow",
"Action": [
"ecs:RegisterTaskDefinition",
"ecs:UpdateService",
"ecs:DescribeServices",
"iam:PassRole"
],
"Resource": "*"
}
]
}
ecr:GetAuthorizationToken은 Resource가*여야 하는 경우가 많습니다.iam:PassRole은 ECS Task Execution Role/Task Role을 넘겨야 해서 필요합니다. 대신 PassRole 대상 Role ARN을 조건으로 제한하는 게 좋습니다.
4) GitHub Actions: OIDC 권한 선언 및 AWS 자격증명 구성
워크플로에서 반드시 아래 권한이 필요합니다.
permissions: id-token: write(OIDC 토큰 발급)permissions: contents: read(checkout)
그리고 aws-actions/configure-aws-credentials 액션으로 Role을 Assume합니다.
예시: ECR 빌드/푸시 + ECS 배포
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/github-actions-deploy-role
aws-region: ap-northeast-2
- name: Login to ECR
uses: aws-actions/amazon-ecr-login@v2
- name: Build and push
env:
ECR_REGISTRY: 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com
ECR_REPO: my-app
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t "$ECR_REGISTRY/$ECR_REPO:$IMAGE_TAG" .
docker push "$ECR_REGISTRY/$ECR_REPO:$IMAGE_TAG"
- name: Deploy to ECS
env:
CLUSTER: my-cluster
SERVICE: my-service
run: |
aws ecs update-service \
--cluster "$CLUSTER" \
--service "$SERVICE" \
--force-new-deployment
이 구성의 포인트는 다음입니다.
- AWS 키를 Secrets에 저장하지 않습니다.
- Role 신뢰 정책이 허용한 워크플로만 배포 가능합니다.
- 자격증명은 job 런타임에만 존재하고 만료됩니다.
5) (선택) GitHub Environment로 prod 배포를 더 강하게 잠그기
main 브랜치 push만으로 prod가 배포되면 위험합니다. GitHub Environment를 만들고 승인자(Reviewers) 를 지정하면, 워크플로가 environment: prod를 사용할 때 승인 없이는 진행되지 않습니다.
워크플로 예시:
jobs:
deploy:
environment: prod
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
# ...
그리고 AWS Role Trust Policy에서 sub를 환경 기반으로 제한합니다.
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:my-org/my-repo:environment:prod"
}
이렇게 하면 승인된 prod 환경 배포만 Role Assume이 가능해집니다.
6) 자주 터지는 문제와 빠른 진단 포인트
6.1 InvalidIdentityToken / Not authorized to perform sts:AssumeRoleWithWebIdentity
대부분 Trust Policy의 조건이 실제 토큰 클레임과 불일치해서 발생합니다.
aud가sts.amazonaws.com인지 확인sub패턴이 정확한지 확인(브랜치/태그/환경)- OIDC Provider ARN이 맞는지 확인
이 케이스는 원인별로 체크리스트가 길어지기 쉬워서, 아래 글에 정리된 진단 흐름을 같이 보면 시간을 많이 줄일 수 있습니다.
6.2 EKS까지 배포하는데 Pod가 IMDS 401을 뱉는다?
OIDC 자체는 GitHub→AWS 인증이고, EKS Pod 내부에서 AWS API를 호출할 때는 IRSA(IAM Roles for Service Accounts)로 이어집니다. 배포는 성공했는데 런타임에서 IMDS 401이 보이면, 노드/파드의 메타데이터 접근 정책이나 HopLimit, IRSA 설정을 의심해야 합니다.
6.3 ECR Pull/Push 권한 문제(403, 토큰 만료)
OIDC로 AssumeRole은 됐는데 ECR에서 403이 나면, 거의 항상 ECR 권한 범위 또는 로그인 토큰 재발급 흐름 문제입니다.
7) 운영 팁: 보안/감사/유지보수 관점에서의 권장사항
- Role 분리: dev/stage/prod를 같은 Role로 쓰지 말고 Role을 분리하세요.
- Trust Policy를 좁게:
sub를repo:*처럼 넓게 열지 말고, 브랜치/환경/태그로 구체화하세요. - 권한 최소화: ECR/ECS/EKS/S3 등 필요한 액션만, 가능한 리소스 ARN까지 제한하세요.
- 세션 이름/태그 활용: CloudTrail에서 누가 어떤 배포를 했는지 추적하기 위해 세션 태그(예: repo, workflow, actor)를 남기는 구성을 고려하세요.
- 만료 시간 관리: 장시간 배포가 필요하면 STS 세션 시간을 늘릴 수 있지만, 보안상 꼭 필요한 만큼만 늘리세요.
마무리
GitHub Actions OIDC는 “키 없이 배포”라는 목표를 가장 현실적으로 달성하는 표준 패턴입니다. 핵심은 단순히 OIDC를 붙이는 게 아니라, Trust Policy의 조건(sub/aud)으로 배포 주체를 정확히 제한하고, Role 권한을 최소화하는 것입니다.
이미 AWS 키 기반으로 돌아가는 파이프라인이 있다면, 먼저 configure-aws-credentials@v4를 OIDC로 바꾸고(Secrets 제거), 다음 단계로 환경별 Role 분리와 정책 최소화를 적용하는 순서로 마이그레이션하면 리스크 없이 전환할 수 있습니다.