- Published on
GitHub Actions OIDC로 AWS 자격증명 0초 발급
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버리스 배포, ECR 푸시, S3 업로드 같은 CI/CD 작업을 GitHub Actions로 돌릴 때 가장 찝찝한 지점은 결국 AWS 자격증명입니다. 기존 방식은 보통 IAM 사용자의 AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY를 GitHub Secrets에 넣고 굴리는데, 이 방식은 다음 문제가 반복됩니다.
- 키가 장기 자격증명(Long-lived) 이라 유출 시 피해가 큼
- 로테이션/폐기/재발급 운영 비용이 큼
- 레포/조직/환경별로 키가 늘어나며 관리가 지옥이 됨
- 최소 권한을 지키기 어렵고, 결국 과권한 키가 남음
GitHub Actions OIDC(OpenID Connect)를 쓰면 이 문제를 거의 제거할 수 있습니다. 핵심은 GitHub가 워크플로 실행 시점에만 유효한 OIDC 토큰을 발급하고, AWS STS가 그 토큰을 검증한 뒤 임시 자격증명(AssumeRoleWithWebIdentity) 을 내려주는 구조입니다. 즉, 액세스 키를 “저장”하지 않고 “그때그때 발급”합니다.
이 글에서는 OIDC 구성 원리부터 AWS IAM 설정, GitHub Actions 워크플로 예제, 그리고 실무에서 자주 터지는 권한 오류 포인트까지 한 번에 정리합니다.
OIDC로 자격증명이 발급되는 흐름
전체 흐름은 다음 5단계로 이해하면 쉽습니다.
- GitHub Actions Job이 시작됨
- Job이 GitHub의 OIDC Provider에서 ID 토큰(JWT) 을 요청함
- AWS STS가
AssumeRoleWithWebIdentity로 토큰을 검증함 - 검증 성공 시 STS가 임시 Access Key/Secret/Session Token 을 발급함
- 이후 AWS CLI/SDK는 그 임시 자격증명으로만 AWS API 호출
여기서 “0초 발급”이라는 표현은 과장이 아니라, 사전에 키를 만들고 저장하는 시간이 0초라는 의미에 가깝습니다. 실제로는 토큰 교환이 수백 ms~수 초 내로 끝납니다.
사전 준비: 어떤 것들이 필요하나
필요한 구성 요소는 딱 3가지입니다.
- AWS 계정에 GitHub OIDC 신뢰를 위한 IAM OIDC Provider
- GitHub가 Assume할 IAM Role (신뢰 정책 + 권한 정책)
- GitHub Actions 워크플로에서
permissions: id-token: write설정
추가로, 레포/브랜치/환경별로 접근을 제한하려면 신뢰 정책에서 sub 같은 클레임을 조건으로 걸어야 합니다.
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) AWS: IAM Role 신뢰 정책(Trust policy) 구성
OIDC의 핵심은 “누가 이 Role을 Assume할 수 있나”를 신뢰 정책에서 엄격히 제한하는 것입니다.
아래 예시는 특정 레포의 main 브랜치에서만 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",
"token.actions.githubusercontent.com:sub": "repo:my-org/my-repo:ref:refs/heads/main"
}
}
}
]
}
sub 조건을 꼭 걸어야 하는 이유
sub는 GitHub가 발급하는 토큰의 주체를 나타내며, 보통 다음 형태를 가집니다.
- 브랜치 기준:
repo:ORG/REPO:ref:refs/heads/BRANCH - 태그 기준:
repo:ORG/REPO:ref:refs/tags/TAG - 환경(Environment) 기준(설정에 따라):
repo:ORG/REPO:environment:prod
sub를 안 걸면 같은 OIDC Provider를 쓰는 다른 레포/워크플로가 Role을 Assume할 여지가 생깁니다(설정에 따라 위험도가 달라지지만, 원칙적으로 잠가야 합니다).
3) AWS: Role 권한 정책(Permission policy) 최소화
신뢰 정책이 “들어올 수 있는 문”이라면, 권한 정책은 “들어온 뒤 무엇을 할 수 있나”입니다.
예를 들어 S3 특정 버킷에만 업로드하는 배포라면 다음처럼 최소 권한을 부여합니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:PutObject", "s3:DeleteObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::my-deploy-bucket",
"arn:aws:s3:::my-deploy-bucket/*"
]
}
]
}
ECR 푸시나 ECS 배포, CloudFront invalidation 등은 서비스별로 필요한 액션이 다르니, 실제 호출 API를 기준으로 최소화하세요.
4) GitHub Actions: OIDC로 AWS 자격증명 설정
가장 널리 쓰는 방식은 aws-actions/configure-aws-credentials 액션을 사용하는 것입니다.
중요 포인트는 Job 권한에 id-token: write를 명시하는 것입니다. 이게 없으면 OIDC 토큰을 못 받아서 AssumeRole이 실패합니다.
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-role
aws-region: ap-northeast-2
- name: Verify identity
run: aws sts get-caller-identity
- name: Deploy to S3
run: aws s3 sync ./dist s3://my-deploy-bucket --delete
세션 시간(만료) 조절
임시 자격증명은 기본 만료 시간이 있으며, Role 설정의 MaxSessionDuration과 워크플로의 role-duration-seconds로 제어합니다.
- 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
role-duration-seconds: 1800
빌드/배포가 길어지는 파이프라인이라면 만료로 인한 중간 실패를 막기 위해 적절히 늘리되, 불필요하게 길게 잡지는 마세요.
5) 브랜치/태그/환경별로 Role을 쪼개는 전략
실무에서는 보통 dev/staging/prod 계정 또는 최소한 Role을 분리합니다.
main브랜치는prodRole만 Assume 가능develop브랜치는stagingRole만 Assume 가능- 태그
v*푸시는prod배포만 가능
예: 태그 기반 신뢰 정책 조건
{
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:my-org/my-repo:ref:refs/tags/v*"
}
}
이렇게 하면 “누가 언제 어떤 배포를 할 수 있는지”가 IAM 레벨에서 강제됩니다.
자주 겪는 장애: 403/AccessDenied 디버깅 체크리스트
OIDC 구성은 한번 맞추면 편하지만, 처음 연결할 때는 AccessDenied나 403이 자주 납니다. 원인은 대개 아래 범주입니다.
- 워크플로에
permissions: id-token: write가 없음 - Role 신뢰 정책의
sub가 실제 실행 컨텍스트(브랜치/태그/환경)와 불일치 aud조건이sts.amazonaws.com와 다름- 레포 이름 변경, 포크 PR 등으로
sub가 달라짐 - Role 권한 정책이 부족해서 Assume은 되는데 서비스 호출에서 거부
이 주제는 케이스가 많아서 별도로 정리된 글을 함께 보면 해결이 빠릅니다.
또, OIDC와 무관하게 GitHub API 권한 문제로 GITHUB_TOKEN이 막히는 경우도 많습니다.
보안 관점에서 OIDC가 강한 이유
정리하면 OIDC는 다음 장점이 큽니다.
- Secrets에 AWS 키를 저장하지 않음: 유출 표면이 급격히 줄어듦
- 짧은 만료 시간: 토큰/세션이 짧게 살아 피해 범위가 제한됨
- 정교한 조건부 신뢰:
sub/aud로 레포/브랜치/태그/환경을 강제 - 감사 추적: CloudTrail에서 AssumeRole 이벤트로 누가 언제 어떤 Role을 썼는지 추적 가능
반대로, OIDC를 도입했는데도 과권한 Role을 주거나 sub 조건을 느슨하게 잡으면(예: 조직 전체 레포 허용) 보안 이점이 크게 줄어듭니다. “키를 없앴다”보다 “신뢰 경계를 코드로 잠갔다”에 가치를 둬야 합니다.
실전 팁: 운영에서 안정적으로 굴리는 패턴
1) Role 이름에 목적을 박아두기
예: github-actions-ecr-push-role, github-actions-s3-deploy-role처럼 Role 목적을 명확히 하면 권한 최소화가 쉬워집니다.
2) aws sts get-caller-identity를 항상 넣기
초기 구축/장애 대응 시, 현재 어떤 Role로 인증됐는지 즉시 확인할 수 있어 디버깅 시간이 크게 줄어듭니다.
3) 환경(Environment) 보호 규칙과 함께 쓰기
GitHub Environments의 승인(Manual approval), 브랜치 제한과 IAM sub 조건을 함께 쓰면, 실수로 main에 푸시했을 때도 바로 prod 배포가 나가지 않게 이중 안전장치를 만들 수 있습니다.
마무리
GitHub Actions OIDC를 쓰면 AWS 액세스 키를 만들고 저장하고 로테이션하는 비용이 사실상 사라집니다. 대신 신뢰 정책의 sub/aud 조건과 Role 권한 정책을 제대로 설계해야 하고, 그게 곧 보안 품질을 결정합니다.
처음 한 번만 올바르게 구성해두면, 이후에는 레포/서비스가 늘어도 “키 배포”가 아니라 “Role 추가”로 확장할 수 있어 운영이 훨씬 단단해집니다.