
Access Key를 GitHub Secrets에 박아두는 거, 이제 진짜 그만할 때가 됐다. 우리 팀도 작년에 한 번 키가 외부로 새서 식겁한 적이 있는데, 그 사건 이후로 모든 리포지토리를 OIDC로 옮겼다. 사실 옮기는 작업 자체가 어렵진 않다. 어려운 건 신뢰 정책(trust policy)을 너무 느슨하게 잡거나, 반대로 너무 빡빡하게 잡아서 워크플로우가 자꾸 깨지는 거다.
이번 글에선 GitHub Actions의 OIDC 토큰으로 AWS IAM Role을 assume하는 과정을 실전 기준으로 정리한다. 처음 세팅하는 사람도 따라할 수 있게 단계별로 적었다.
왜 OIDC인가
장기 credential(Access Key/Secret Key)을 Secrets에 저장하면 두 가지가 걸린다. 하나는 키가 유출됐을 때 회수 시점까지 통째로 노출된다는 점. 다른 하나는 어떤 워크플로우가 그 키로 뭘 했는지 추적이 깔끔하지 않다는 점이다.
OIDC는 매 실행마다 GitHub가 발급한 짧은 수명의 JWT를 AWS에 제출하고, AWS가 그걸 검증해서 임시 credential을 발급한다. 키가 디스크에 박히지 않고, 매 실행이 CloudTrail에 assumed role ARN으로 남는다. 사고 후 추적이 훨씬 수월하다.
1. AWS 측 OIDC Provider 등록
콘솔이든 Terraform이든 한 번만 만들면 끝이다. Terraform으로는 이렇게:
resource "aws_iam_openid_connect_provider" "github" {
url = "https://token.actions.githubusercontent.com"
client_id_list = ["sts.amazonaws.com"]
thumbprint_list = ["6938fd4d98bab03faadb97b34396831e3780aea1"]
}
예전엔 thumbprint가 GitHub의 SSL 인증서 변경 때마다 깨지는 게 골치였는데, 요즘은 AWS가 라이브러리 차원에서 자동 검증을 같이 해줘서 thumbprint 값은 사실상 더미에 가깝다. 그래도 명시적으로 넣어두는 걸 권장한다.
2. IAM Role과 신뢰 정책
여기가 진짜 중요한 부분이다. 신뢰 정책을 잘못 짜면 다른 리포지토리도 이 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:my-org/my-repo:ref:refs/heads/main"
}
}
}
]
}
sub claim을 어떻게 좁히느냐가 핵심이다. 우리 팀은 보통 다음 패턴을 쓴다:
- 프로덕션 배포 Role:
repo:org/repo:ref:refs/heads/main— main 브랜치에서만 - 스테이징 배포 Role:
repo:org/repo:environment:staging— staging environment 사용 시 - PR plan 전용 Role:
repo:org/repo:pull_request— 읽기 권한만
한 가지 함정: ForAllValues:StringEquals 같은 연산자는 절대 Allow 문에 쓰지 말 것. claim이 누락되거나 오타가 있으면 true를 반환해서 의도치 않은 접근이 열린다. AWS도 공식적으로 경고하고 있다. StringEquals 또는 StringLike만 써라.
3. 워크플로우에 적용
name: deploy
on:
push:
branches: [main]
permissions:
id-token: write # 이거 없으면 OIDC 토큰 발급 안 됨
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy
aws-region: ap-northeast-2
role-session-name: gha-${{ github.run_id }}
- run: aws sts get-caller-identity
permissions: id-token: write를 빼먹는 실수가 정말 흔하다. 워크플로우가 OIDC 토큰을 요청하려면 이 권한이 필요한데, 기본값이 없어서 무조건 명시해야 한다. 나도 처음에 이거 빼먹고 "Could not load credentials from any providers" 에러로 30분 헤맸다.
role-session-name에 run_id를 넣어두면 CloudTrail에서 어떤 실행이 어떤 행동을 했는지 추적이 편해진다. 사고 났을 때 진가가 드러난다.
4. 환경별 분리 — 한 단계 더
Role 하나로 모든 환경을 다루는 건 권장하지 않는다. 우리 팀은 dev/stg/prod 각각 Role을 따로 둔다. 환경별 IAM 정책 범위가 다르고, 사고 폭발 반경(blast radius)을 줄이고 싶기 때문이다.
GitHub의 Environment 기능과 묶으면 더 좋다. environment: production을 단 워크플로우에서만 prod Role을 assume할 수 있게 신뢰 정책 sub을 repo:org/repo:environment:production으로 잡는다. 그러면 Environment에 reviewer 승인까지 걸어둘 수 있어서, 누가 prod 배포를 트리거했는지 git history와 분리해서도 추적 가능하다.
마이그레이션할 때 체크리스트
기존 Access Key 기반에서 옮길 때 빼먹기 쉬운 것들:
- 이전 Access Key를 곧바로 삭제하지 말고 일단 비활성화만 해두고 며칠 관찰. CloudTrail에 호출이 안 잡히면 그때 삭제.
- 외부 Action(서드파티)도 OIDC 권한이 필요한지 확인. configure-aws-credentials만 쓰면 보통은 OK지만, 직접 STS를 부르는 Action은 별도로 손봐야 한다.
- self-hosted runner를 쓰는 경우 토큰 발급 메커니즘이 달라질 수 있으니 별도 검증 필요.
세팅 끝나면 사실 다시 들여다볼 일이 거의 없다. 키 로테이션 캘린더 신경 쓰지 않아도 되는 게 가장 큰 장점이다.
혹시 더 빡빡하게 잡는 방법(예: tag 기반 배포만 허용, 특정 reusable workflow에서만 호출 가능)이 궁금하면 다음 글에서 다뤄보려고 한다.
'IT > CI CD' 카테고리의 다른 글
| ArgoCD ApplicationSet, 사실 내부적으로는 이렇게 돈다 (0) | 2026.06.22 |
|---|---|
| Tekton vs Argo Workflows, 6개월 굴려보고 우리 팀이 내린 결론 (0) | 2026.06.19 |
| GitHub Actions concurrency 그룹과 matrix, 무심코 쓰면 서로를 죽인다 (0) | 2026.06.17 |
| Flagger vs Argo Rollouts, 1년 굴려보고 내린 결론 (0) | 2026.06.16 |
| ArgoCD ApplicationSet으로 12개 클러스터 한 번에 날린 이야기 (0) | 2026.06.16 |