Contents
see List왜 배포 키를 GitHub Secrets에 오래 보관하면 위험한가
CI/CD 배포를 처음 만들 때는 AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY 같은 장기 키를 GitHub Secrets에 넣고 워크플로에서 꺼내 쓰는 방식이 가장 빨라 보입니다. 하지만 이 방식은 운영 시간이 길어질수록 관리 비용과 사고 가능성이 커집니다. 키가 한 번 유출되면 회수 전까지 계속 사용할 수 있고, 어느 저장소와 어느 브랜치에서 어떤 목적으로 쓰는 키인지 추적하기 어렵습니다. 퇴사자 계정, 오래된 테스트 워크플로, 복사된 저장소에 같은 키가 남아 있으면 회전 작업도 부담스러워집니다.
GitHub Actions의 OpenID Connect, 즉 OIDC를 사용하면 워크플로가 실행될 때마다 짧게 살아 있는 토큰을 발급받고, 클라우드의 IAM 역할을 임시로 맡아 배포할 수 있습니다. 핵심은 GitHub가 저장소, 브랜치, 환경, 워크플로 실행 정보를 담은 신원 토큰을 제공하고, AWS 같은 클라우드가 그 토큰의 조건을 검사한 뒤 임시 자격 증명을 내주는 구조입니다. 저장소에는 장기 클라우드 키를 넣지 않고, 클라우드 쪽 신뢰 정책에서 허용 범위를 좁힙니다.
설계 기준: 저장소, 브랜치, 환경을 신뢰 조건으로 묶기
OIDC를 적용할 때 가장 중요한 것은 단순히 id-token 권한을 켜는 것이 아니라, 어떤 워크플로가 어떤 조건에서 어떤 역할을 맡을 수 있는지 명확히 제한하는 일입니다. 예를 들어 main 브랜치에 병합된 배포만 운영 계정 역할을 맡게 하고, pull_request에서는 토큰을 발급하지 않도록 나누어야 합니다. GitHub Environments를 쓰는 팀이라면 production 환경 승인 절차와 OIDC subject 조건을 함께 묶는 것이 좋습니다.
- 저장소 단위로 IAM 역할을 분리합니다. 여러 서비스가 하나의 배포 역할을 공유하면 사고 범위가 커집니다.
- 운영 배포 역할은 main 또는 release 브랜치, 혹은 production environment 조건으로 제한합니다.
- 워크플로의 permissions는 필요한 값만 명시합니다. OIDC에는 id-token: write가 필요하지만, contents는 대부분 read면 충분합니다.
- 역할에 붙이는 IAM 정책은 배포에 필요한 리소스로 좁힙니다. S3 전체, ECR 전체, ECS 전체 권한을 한 역할에 몰아넣지 않습니다.
- 수동 실행 workflow_dispatch를 허용한다면 GitHub Environment 승인자와 배포 로그 보관 기준을 같이 정합니다.
AWS IAM 신뢰 정책 예시
AWS에서는 GitHub OIDC Provider를 등록한 뒤 IAM Role의 trust policy에 토큰 발급자, audience, subject 조건을 둡니다. 아래 예시는 softmoa/example-service 저장소의 main 브랜치에서 실행되는 워크플로만 역할을 맡을 수 있게 제한합니다. 실제 운영에서는 저장소명, 계정 번호, 역할명, 리전, 배포 대상 리소스를 환경에 맞게 바꾸어야 합니다.
{
"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:softmoa/example-service:ref:refs/heads/main"
}
}
}
]
}
이 정책은 GitHub 저장소에 있는 임의의 Secret이 아니라, GitHub가 발급한 OIDC 토큰의 claim을 신뢰합니다. 따라서 공격자가 단순히 저장소의 일반 Secret 값을 훔치는 방식으로는 AWS 배포 권한을 얻을 수 없습니다. 단, 워크플로 파일 수정 권한이 있는 사용자가 배포 경로를 바꿀 수 있으므로 브랜치 보호, 코드 리뷰, 환경 승인, CODEOWNERS도 함께 적용해야 합니다.
GitHub Actions 워크플로 예시
워크플로에서는 permissions를 명시하고, aws-actions/configure-aws-credentials 액션이 OIDC 토큰을 사용해 역할을 맡도록 설정합니다. 아래 예시는 main 브랜치 push에서만 실행되는 단순 배포 골격입니다. 실제 배포 명령은 서비스 방식에 맞게 ECR 푸시, ECS 서비스 업데이트, S3 동기화, Lambda 배포 등으로 교체하면 됩니다.
name: deploy
on:
push:
branches: [main]
permissions:
contents: read
id-token: write
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/example-service-deploy
aws-region: ap-northeast-2
- name: Deploy application
run: |
aws sts get-caller-identity
./scripts/deploy.sh production
운영 점검 포인트
OIDC 전환은 보안 기능을 켜는 작업이면서 동시에 배포 권한 구조를 정리하는 작업입니다. 먼저 기존 GitHub Secrets에 남아 있는 장기 클라우드 키를 목록화하고, 어떤 워크플로가 어떤 리소스에 접근하는지 표로 정리합니다. 그다음 서비스별 IAM 역할을 만들고, 신뢰 정책의 subject 조건을 저장소와 브랜치 기준으로 좁힙니다. 전환 후에는 기존 장기 키를 비활성화하고, 일정 기간 CloudTrail에서 AssumeRoleWithWebIdentity 이벤트를 확인해 예상한 워크플로에서만 역할을 쓰는지 검증합니다.
- 첫 적용은 운영보다 개발 또는 스테이징 배포에서 먼저 검증합니다.
- role-to-assume 값은 저장소 Secret보다 GitHub Environment variable이나 워크플로 상수로 관리할 수 있습니다. 민감한 비밀이 아니라 역할 식별자이기 때문입니다.
- fork pull_request에서는 배포 job이 실행되지 않도록 이벤트와 조건문을 분리합니다.
- 배포 실패 시 임시 자격 증명 만료, audience 불일치, subject 패턴 오류, IAM 권한 부족을 순서대로 확인합니다.
- 장기 키 제거 후에는 문서와 온보딩 가이드에서 더 이상 AWS access key 발급을 요구하지 않도록 수정합니다.
마무리 체크리스트
GitHub Actions OIDC의 목표는 배포 자동화를 복잡하게 만드는 것이 아니라, 장기 키 보관과 회전 부담을 줄이고 배포 권한을 실행 조건에 맞춰 좁히는 것입니다. 적용 전에는 저장소별 배포 권한을 분리하고, 적용 중에는 id-token 권한과 IAM trust policy를 함께 검토하며, 적용 후에는 기존 키 폐기와 감사 로그 확인까지 끝내야 합니다. 체크리스트는 간단합니다. 장기 키가 Secrets에 남아 있지 않은가, 운영 역할은 main 또는 production 환경으로 제한되는가, IAM 정책은 필요한 리소스만 허용하는가, 실패 로그에서 원인을 추적할 수 있는가. 이 네 가지를 만족하면 OIDC 기반 배포는 실무에서 충분히 안정적으로 운영할 수 있습니다.