CI/CD 용어 사전
GitHub ActionsOpenID Connect · id-token

OIDC

장기 클라우드 키를 시크릿에 두지 않는다. 워크플로가 단기 토큰을 발급받아 역할을 맡는다.

permissions:
  id-token: write
  contents: read
steps:
  - uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: arn:aws:iam::123456789012:role/gha-deploy
      aws-region: ap-northeast-2

무엇을 없애는가

예전 방식은 클라우드 액세스 키를 시크릿에 저장하는 것이었다. 문제가 셋이다.

  • 무기한 유효하다 — 유출되면 알아챌 때까지 계속 쓸 수 있다
  • 교체가 번거롭다 — 그래서 몇 년째 그대로인 키가 쌓인다
  • 어디에 복사됐는지 알 수 없다 — 저장소마다 붙여 넣은 같은 키가 흩어져 있다

OIDC 는 저장할 장기 자격증명 자체를 없앤다. 워크플로가 실행될 때 GitHub 이 "이 실행은 org/repo 의 main 브랜치에서 도는 것" 이라고 서명한 토큰을 발급하고, 클라우드가 그 서명을 검증한 뒤 몇 분짜리 임시 자격증명을 내준다.

신뢰 조건이 곧 보안 경계다

"Condition": {
  "StringEquals": {
    "token.actions.githubusercontent.com:sub":
      "repo:my-org/my-repo:ref:refs/heads/main"
  }
}

여기가 가장 자주 잘못 쓰이는 지점이다. 편하다고 repo:my-org/* 로 넓히면, 조직 안 아무 저장소나 — 심지어 누가 방금 만든 저장소나 포크 PR 까지 — 운영 배포 권한을 얻는다.

조건은 저장소와 브랜치까지 못 박는다. 환경(environment)을 쓰면 더 좁힐 수 있고, 승인 게이트와 묶어 "승인된 배포에서만" 을 강제할 수도 있다.

StringLike 로 와일드카드를 쓸 때는 특히 조심한다 — 접두사만 맞으면 통과하므로 의도보다 넓어지기 쉽다.

순서를 알면 무엇을 지키는지 보인다

① 워크플로가 GitHub 에 토큰을 요청한다   (id-token: write 필요)
② GitHub 이 서명된 JWT 를 발급한다
     sub = repo:my-org/my-repo:ref:refs/heads/main
     aud = sts.amazonaws.com
③ 워크플로가 그 JWT 로 AWS STS 에 역할을 요청한다
④ AWS 가 GitHub 의 공개키로 서명을 검증하고
   신뢰 정책의 조건과 sub 를 맞춰 본다
⑤ 통과하면 1시간짜리 임시 자격증명을 준다

핵심은 비밀이 오간 적이 한 번도 없다는 점이다. 오간 것은 서명된 주장뿐이고, 검증은 공개키로 한다. 훔쳐 갈 장기 비밀이 애초에 존재하지 않는다.

그리고 ④가 유일한 관문이다. GitHub 은 "이 실행이 어디서 도는지" 를 정직하게 적어 줄 뿐, 그것을 받아들일지는 클라우드가 정한다. 그래서 신뢰 정책을 넓게 쓰면 GitHub 쪽에서 아무리 조여도 소용이 없다.

다른 클라우드도 같다

GCP 는 Workload Identity Federation, Azure 는 워크로드 ID 연합이라는 이름으로 같은 구조를 제공한다. 용어만 다르고 "서명된 신원 주장 → 검증 → 단기 자격증명" 이라는 흐름은 동일하다. 하나를 이해하면 나머지는 설정 이름의 문제다.

면접 함정

  • "OIDC 를 쓰면 안전하다" → 신뢰 조건을 넓게 쓰면 오히려 더 위험하다. 키는 유출돼야 문제지만 넓은 조건은 처음부터 열려 있다.
  • "시크릿이 아예 필요 없어진다" → 클라우드 인증에 한한 이야기다.
  • "id-token: write 는 위험한 권한" → 토큰 발급 요청 권한일 뿐이고, 실제 접근 여부는 클라우드 쪽 신뢰 조건이 정한다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — GitHub Actions — 문법과 실행 모델