GitHub Actions 강좌 #7 AWS 배포와 OIDC: 액세스 키 없는 자격 증명

4 분 소요

지난 편에서 이미지를 만들었으니 이제 AWS에 배포할 차례인데, 그 전에 넘어야 할 관문이 자격 증명입니다. 예전 방식은 IAM 사용자의 액세스 키를 발급해 secrets에 넣는 것이었습니다. 동작은 하지만 만료가 없는 강력한 키가 GitHub에 상시 보관되는 구조라, 유출 시 피해가 크고 주기적 로테이션은 사람이 잊습니다. 현행 표준은 키를 아예 보관하지 않는 OIDC 연동이며, 테라폼 실전 강좌 #7에서 AWS 쪽 준비를 이미 마쳐 두었습니다. 이번 편에서 워크플로 쪽을 완성해 양쪽을 잇습니다.

동작 원리: 90초 설명 #

OIDC 연동의 흐름은 토큰 교환 한 번입니다.

토큰 교환 흐름
1. 워크플로 실행 시작
2. GitHub가 이 실행의 신원 토큰(OIDC 토큰)을 발급
   : "이 토큰은 my-org/myapp 저장소의 main 브랜치 실행이다"라는 서명된 증명
3. 워크플로가 토큰을 AWS STS에 제출
4. AWS가 서명을 검증하고, 신뢰 정책의 조건과 대조
5. 조건이 맞으면 단기 자격 증명(1시간 등)을 발급
6. 워크플로가 그 자격 증명으로 AWS 작업 수행, 만료되면 끝

핵심은 저장되는 비밀이 없다는 것입니다. 토큰은 실행마다 새로 발급되고 수명이 짧으며, AWS는 “GitHub가 서명한, 우리 저장소의, 지정 조건에 맞는 실행"에만 자격 증명을 내줍니다. 유출될 키가 없으니 로테이션할 키도 없습니다.

AWS 쪽: 신뢰 구성 복습 #

AWS 쪽에는 두 가지가 필요하고, 테라폼 실전 #7에서 코드로 만들었습니다. GitHub의 OIDC provider 등록, 그리고 신뢰 정책에 조건을 건 IAM 역할입니다. 조건의 핵심만 다시 봅니다.

신뢰 정책의 sub 조건
"token.actions.githubusercontent.com:sub": "repo:my-org/myapp:*"

sub 클레임은 토큰의 출처(저장소·브랜치·environment)를 담는 필드이고, 이 조건이 우리 저장소의 실행만 역할을 맡게 하는 잠금입니다. 더 조일 수도 있습니다. repo:my-org/myapp:ref:refs/heads/main으로 main 브랜치 실행만, repo:my-org/myapp:environment:production으로 특정 environment 실행만 허용하는 식입니다. #3의 environment 보호 규칙과 조합하면 “승인을 통과한 production 잡만 이 역할을 맡는다"라는 이중 잠금이 됩니다.

워크플로 쪽: 두 조각 #

워크플로에 필요한 것은 권한 선언과 액션 하나입니다.

.github/workflows/deploy.yml
permissions:
  id-token: write      # OIDC 토큰 발급 허용
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4

      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ vars.DEPLOY_ROLE_ARN }}
          aws-region: ap-northeast-2

      - run: aws s3 sync ./dist "s3://${{ vars.SITE_BUCKET }}" --delete
      - run: >
          aws cloudfront create-invalidation
          --distribution-id "${{ vars.CF_DIST_ID }}" --paths "/*"

permissions의 id-token: write가 이 잡에 OIDC 토큰 발급을 허용하는 스위치입니다. 이 선언이 없으면 토큰 발급 자체가 거부되므로, OIDC가 “권한이 없다"라며 실패하면 가장 먼저 확인할 줄입니다. configure-aws-credentials 액션이 앞의 토큰 교환 흐름 전체를 수행해, 이후 스텝의 AWS CLI는 이미 로그인된 상태로 동작합니다. 역할 ARN은 비밀이 아니므로 #3의 기준대로 vars에 둡니다.

예제는 정적 사이트 배포(S3 동기화 + CloudFront 무효화)를 골랐습니다. 지난 편의 이미지를 ECS에 반영하는 배포라면 마지막 두 스텝이 태스크 정의 갱신과 서비스 업데이트로 바뀔 뿐, 자격 증명 구조는 완전히 동일합니다. 테라폼 운영 강좌 #1의 plan·apply 워크플로도 같은 구조 위에 있으니, 이 연동 하나로 배포·인프라 파이프라인 전체의 자격 증명이 통일된 셈입니다.

자주 만나는 실패 두 가지 #

  • “Not authorized to perform sts:AssumeRoleWithWebIdentity”: 신뢰 정책의 sub 조건과 실제 실행의 출처가 안 맞는 경우가 대부분입니다. 브랜치 제한을 걸었는데 다른 브랜치에서 실행했거나, environment 조건을 걸었는데 잡에 environment 지정이 없는 경우입니다. 조건을 조일수록 이 대조를 정확히 맞춰야 합니다.
  • 토큰 발급 거부: id-token: write 누락입니다. 특히 워크플로 수준에 permissions를 선언하면 기본 권한이 전부 초기화되므로, 다른 권한(contents: read 등)도 함께 명시해야 checkout이 깨지지 않습니다.

정리 #

이번 글에서 다룬 내용입니다.

  • 액세스 키 보관 방식은 무만료 키의 상시 보관이라는 구조적 위험입니다. OIDC는 실행마다 단기 토큰을 교환해 저장되는 비밀을 없앱니다
  • AWS 신뢰 정책의 sub 조건이 잠금의 핵심이며, 브랜치·environment 단위까지 조일 수 있습니다
  • 워크플로 쪽은 id-token write 권한과 configure-aws-credentials 액션 두 조각입니다
  • AssumeRole 거부는 sub 조건 불일치, 토큰 발급 거부는 id-token 권한 누락이 단골 원인입니다
  • 정적 사이트, ECS, 테라폼까지 모든 AWS 파이프라인이 같은 자격 증명 구조를 공유합니다

다음 글(#8 셀프호스티드 러너)에서는 GitHub 호스티드 러너로 안 되는 상황들, 내부망 접근과 비용과 특수 하드웨어를 위해 러너를 직접 운영하는 방법을 다룹니다.

X