[Infra] GitHub Actions로 AWS 인프라 자동화 (OIDC)

고수가 되고 싶은 감자·2026년 9월 16일
post-thumbnail

GitHub Actions와 AWS OIDC

클라우드 인프라를 구축하고 CI/CD 파이프라인을 다루다 보면 반드시 마주치게 되는 기술이 바로 OIDC(OpenID Connect)이다.

"GitHub Actions에서 AWS 배포할 때 Access Key 쓰지 말고 OIDC 쓰세요"라는 말은 많이 들어봤지만, 막상 코드를 작성하다 보면 여러 가지 의문이 생긴다.

"OIDC는 누가 발급하나?"
"AWS에 접속도 못 했는데, OIDC 신분증은 어떻게 받아오는 거지?"
"GitHub에서 OIDC 발급받는 로직도 테라폼 코드로 짜야 하나?"

최근 프로젝트 인프라를 구축하며 직접 겪었던 트러블슈팅 경험과 함께, OIDC의 핵심 동작 원리와 적용 팁을 알기 쉽게 정리해 보았다.


1. 아직도 GitHub Secrets에 AWS Access Key를 넣고 계신가요?

과거에는 GitHub Actions에서 AWS로 배포하기 위해 다음과 같은 방식을 사용했다.

  1. AWS IAM 콘솔에서 IAM User를 생성한다.
  2. Access Key ID와 Secret Access Key를 발급받는다.
  3. GitHub 저장소의 Settings > Secrets에 이 키들을 복사해 넣는다.

이 방식의 문제점

  • 키 유출 위험: 만에 하나 GitHub Secrets가 노출되거나 빌드 로그에 찍히면 AWS 인프라 전체가 탈취당할 수 있다.
  • 관리 부담: 보안 규정상 몇 개월마다 주기적으로 키를 교체해야 한다.
  • 과도한 권한: 특정 브랜치(main)나 특정 PR에서만 접근하도록 제어하기 어렵다.

이 모든 문제를 깔끔하게 해결해 주는 최신 보안 표준이 바로 OIDC(OpenID Connect)를 통한 Keyless 인증입니다.


2. OIDC의 핵심 동작 원리 (현실 비유)

OIDC(OpenID Connect)는 쉽게 말해 외부 시스템(GitHub)이 AWS에게 신원을 증명하는 1회용 모바일 전자 신분증이다.

나는 OIDC는 AWS에서 발급해주는줄 알았다.
그래서 "AWS에 접속을 못하는데 OIDC 신분증은 어떻게 발급받지?"

라고 생각했다.

하지만 아니었다.
"OIDC 신분증은 AWS가 아니라 'GitHub'이 직접 발급한다"
신분증을 발급받는 순간에는 AWS에 접속할 필요가 전혀 없었다.

비유: 대한민국 여권과 미국 입국 심사대

  • GitHub = 대한민국 정부 (신분증/여권 발급처)
  • GitHub Actions 러너 = 여행객 (나)
  • AWS = 미국 입국 심사대 (방문할 곳)
[1. GitHub 내부 서버] ──(1회용 신분증 발급)──▶ [2. Actions 러너] ──(신분증 제시)──▶ [3. AWS STS]
       (구청)                                    (여행객)                              (입국심사대)
  1. 여권 발급 (GitHub 내부):
    여행객(Actions 러너)은 미국(AWS)에 갈 필요 없이, 한국 구청(GitHub 내부 서버)에 가서 "나 배포해야 하니까 신분증 줘" 하고 1회용 모바일 신분증(JWT 토큰)을 발급받는다.
    • 이 신분증 안에는 "나는 Fundit-Infra 저장소의 main 브랜치에서 실행 중인 프로세스임"이라는 내용과 함께 GitHub의 전자 직인(디지털 서명)이 찍혀 있다.
  2. 미국 입국 (AWS 도착):
    신분증을 손에 쥔 러너가 비로소 AWS(STS)로 찾아가서 신분증을 보여준다.
  3. 임시 체류증 발급:
    AWS는 신분증의 위조 여부를 검증하고, 1시간 동안만 유효한 임시 출입증(STS Session Token)을 건네준 뒤, 배포가 끝나면 이 토큰은 자동으로 소멸한다.

3. AWS는 GitHub이 준 신분증을 어떻게 믿을까?

AWS와 GitHub 사이에는 비대칭 암호화(공개키/개인키) 기반의 신뢰 관계가 설정되어 있다.

  • GitHub: 아무도 모르는 자신만의 비밀 도장(Private Key)으로 OIDC 토큰에 전자서명을 한다.
  • AWS: 우리가 AWS IAM에 등록(미리 해야됨)해 둔 GitHub의 공식 공개 도장(Public Key / Thumbprint)을 대조한다. 도장이 일치하면 위조되지 않은 진짜 GitHub 발행 토큰임이 증명된다.

4. 흔히 하는 오해: 테라폼 코드가 OIDC를 발급하는 걸까?

결론부터 말하면 아니다. OIDC 발급기는 GitHub 플랫폼에 이미 탑재(Built-in)되어 있다.

① GitHub Actions가 하는 일: "신분증 신청 및 수령"

GitHub Actions 워크플로우 YAML 파일에서 딱 두 가지만 적어주면 끝난다.

# 1. GitHub에게 OIDC 신분증 발급 권한 요청
permissions:
  id-token: write
  contents: read

steps:
  # 2. 공식 액션이 GitHub 내부에서 토큰을 받아와 AWS STS로 교환
  - name: Configure AWS Credentials via OIDC
    uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: arn:aws:iam::123456789012:role/my-terraform-ci-role
      aws-region: ap-northeast-2

② 테라폼 코드가 하는 일: "AWS 수신 창구 설치"

테라폼 코드는 GitHub을 건드리는 게 아니라, AWS 쪽에 수신 창구를 만드는 역할을 한다.

# 1. AWS에 GitHub을 공인 인증기관으로 등록
resource "aws_iam_openid_connect_provider" "github" {
  url             = "https://token.actions.githubusercontent.com"
  client_id_list  = ["sts.amazonaws.com"]
  thumbprint_list = [data.tls_certificate.github.certificates[0].sha1_fingerprint]
}

# 2. 심사 기준 설정: 어떤 신분증을 가진 프로세스에 권한을 줄 것인가?
resource "aws_iam_role" "terraform_ci" {
  name = "my-terraform-ci-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect = "Allow"
      Principal = {
        Federated = aws_iam_openid_connect_provider.github.arn
      }
      Action = "sts:AssumeRoleWithWebIdentity"
      Condition = {
        StringEquals = {
          # 특정 저장소, 특정 브랜치/환경만 허용 (최소 권한 원칙)
          "token.actions.githubusercontent.com:sub" = [
            "repo:my-org/my-repo:pull_request",
            "repo:my-org/my-repo:ref:refs/heads/main"
          ]
          "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
        }
      }
    }]
  })
}

5. 실무 트러블슈팅: dev-apply 승인 게이트와 OIDC sub 클레임 에러

프로젝트에서 CD 파이프라인을 구축할 때, 코드가 main에 머지되었다고 무조건 terraform apply가 실행되면 위험하다고 생각하여 관리자의 수동 승인을 거치도록 GitHub Environment Protection Rule(dev-apply)을 적용했다.

그런데 승인 버튼을 누르고 배포가 돌자마자 Access Denied (AssumeRoleWithWebIdentity failed) 에러가 발생했다.

원인 분석

GitHub Actions는 워크플로우에 environment:가 지정되면, 신분증의 sub 클레임 내용을 브랜치 이름이 아니라 환경 이름으로 바꿔서 발행한다.

  • 일반 브랜치 실행 시 sub: repo:my-org/my-repo:ref:refs/heads/main
  • environment: dev-apply 실행 시 sub: repo:my-org/my-repo:environment:dev-apply

AWS IAM Role의 신뢰 정책에는 dev-apply가 등록되어 있지 않았기 때문에, AWS 수신 창구에서는 등록되지 않은 신분증으로 간주하고 거부했던 것이였다.

🛠️ 해결 방법

AWS IAM Role의 신뢰 정책 sub 조건에 environment:dev-apply를 명시적으로 추가하여 해결했다.
보안 강화를 위해 와일드카드(*) 대신 GitHub 고유 식별 번호가 포함된 불변 식별자를 사용하여 탈취 위험을 완전히 차단했다.

Condition = {
  StringEquals = {
    "token.actions.githubusercontent.com:sub" = [
      "repo:my-org@ID/my-repo@ID:pull_request",
      "repo:my-org@ID/my-repo@ID:ref:refs/heads/main",
      "repo:my-org@ID/my-repo@ID:environment:dev-apply" # ◀ 추가 완료!
    ]
    "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
  }
}

6. 마치며

정리하자면:

  1. OIDC는 AWS 영구 비밀키 없이, GitHub이 즉석에서 찍어준 1회용 신분증으로 AWS에 안전하게 접속하는 기술
  2. GitHub Actions는 permissions: id-token: write로 신분증을 발급받아 AWS에 전달
  3. 테라폼은 AWS에 GitHub을 공인 인증기관으로 등록하고 심사 기준(IAM Role Trust Policy)을 세팅
  4. 배포 승인 게이트(environment)를 쓸 때는 OIDC의 sub 클레임이 environment:<환경명>으로 변경되므로 IAM 신뢰 정책에 반드시 반영하기

이상.

profile
DevOps 엔지니어 지망생

0개의 댓글