저장소 이름은 같아도 같은 주체가 아니다: GitHub Actions OIDC의 immutable sub 전환

1. 시크릿을 없애도 신뢰 정책은 남는다

GitHub Actions에서 클라우드에 배포할 때 액세스 키를 저장소 시크릿에 넣는 방식은 단순하다. 문제는 그 키가 워크플로 실행 시간보다 오래 산다는 데 있다. 로그나 잘못된 액션을 통해 값이 노출되면, 사용자가 회전시키기 전까지 별도 장소에서 재사용될 수 있다.

OpenID Connect(OIDC)는 이 보관 문제를 줄인다. 워크플로 job이 GitHub의 OIDC 제공자에서 서명된 ID 토큰을 받고, 클라우드 제공자는 그 토큰을 자기 쪽의 짧은 수명 자격 증명으로 교환한다. 저장소에 장기 클라우드 키를 보관할 필요가 없다. 다만 "OIDC를 켰다"는 사실만으로 권한이 좁아지지는 않는다. 클라우드 역할의 신뢰 정책이 어떤 토큰을 받아들일지 제대로 제한해야 한다.

permissions: id-token: write도 자주 오해한다. GitHub 문서에 따르면 이 설정은 워크플로가 GitHub 자원이나 클라우드 자원을 수정할 권한을 직접 주지 않는다. OIDC 토큰을 요청할 수 있게 할 뿐이다. 실제 클라우드 권한은 교환 뒤 발급되는 역할 자격 증명과 그 역할 정책에서 결정된다.

GitHub Actions job이 서명된 OIDC 토큰을 cloud trust policy에 전달하고 짧은 수명 cloud role 자격 증명을 받는 흐름

그림 1. OIDC는 장기 클라우드 키 보관을 토큰 교환으로 바꾸지만, 신뢰 조건과 role 권한은 따로 설계해야 한다. 출처: docs.github.com/en/actions/concepts/security/openid-connect.

2. 클라우드는 iss, aud, sub를 따로 본다

OIDC 토큰에는 발급자 iss, 수신 대상 aud, 주체 sub, 만료 시각 exp 같은 claim이 들어간다. OpenID Connect Core는 aud가 이 토큰을 받을 클라이언트를 나타내고, exp 이후에는 토큰을 받아들이면 안 된다고 규정한다. GitHub의 발급자는 https://token.actions.githubusercontent.com이다.

여기서 sub가 배포 주체의 경계를 만든다. 기본 형식은 저장소와 실행 맥락을 조합한다. environment를 쓰는 job은 repo:ORG/REPO:environment:ENVIRONMENT 형태다. environment가 없고 pull request에서 실행되면 repo:ORG/REPO:pull_request, 일반 branch 실행이면 repo:ORG/REPO:ref:refs/heads/BRANCH가 된다. 세 형식은 동시에 붙는 태그가 아니라 조건에 따라 달라지는 주체 문자열이다.

따라서 main branch만 허용하려고 branch 형식 하나를 trust policy에 넣었는데 job이 나중에 environment: production을 참조하도록 바뀌면, 발급된 sub도 environment 형식으로 바뀐다. 배포가 거부되는 것이 정상이다. 반대로 repo:ORG/*처럼 넓은 와일드카드로 급히 풀면 다른 저장소까지 같은 역할을 받을 수 있다.

3. 이름 기반 sub에는 재사용 문제가 있었다

기존 기본 형식은 조직명과 저장소명만 사용했다. 이름이 바뀌거나 namespace가 다른 주체에게 다시 배정되면, 문자열만으로는 이전 저장소와 새 저장소를 구분하기 어렵다. GitHub는 OIDC sub가 로컬에서 고유하고 다시 배정되지 않아야 한다는 요구를 설명하면서 owner ID와 repository ID를 포함한 immutable 형식을 도입했다.

예전 branch 주체는 다음처럼 보였다.

repo:octo-org/octo-repo:ref:refs/heads/main

immutable 형식은 이름 옆에 숫자 ID를 붙인다.

repo:octo-org@123456/octo-repo@456789:ref:refs/heads/main

GitHub 공식 문서의 현재 rollout 기준은 분명하다. 2026년 7월 15일 이후 만들어진 저장소는 새 기본 형식을 사용한다. 그 전에 만들어진 저장소는 opt-in하기 전까지 기존 형식을 유지한다. 같은 날짜 이후 repository rename이나 transfer가 일어나도 immutable 형식으로 이동한다. GitHub Enterprise Server에는 아직 이 기능이 제공되지 않는다.

이름만 포함한 기존 GitHub OIDC subject와 owner ID와 repository ID를 포함한 immutable subject 비교

그림 2. 새 immutable subject는 이름 옆에 변경되지 않는 owner ID와 repository ID를 넣어 namespace 재사용을 구분한다. 출처: docs.github.com/en/actions/reference/openid-connect-reference.

4. 전환은 토큰과 클라우드 정책을 한 쌍으로 바꿔야 한다

새 sub는 이름 재사용 위험을 줄이지만, 이미 배포 중인 역할과 자동으로 맞춰지지는 않는다. AWS IAM, Azure, Google Cloud, Vault 예시는 모두 클라우드 쪽 trust condition이 정확한 subject 문자열을 받아들이도록 설정한다. 토큰은 ID 포함 형식인데 trust policy가 예전 이름 형식만 허용하면 교환이 실패한다. 반대로 정책부터 새 값만 허용했는데 워크플로 토큰이 아직 이전 형식이면 같은 문제가 생긴다.

안전한 순서는 현재 job에서 실제 OIDC claim을 읽기 전용으로 확인하고, 클라우드 감사 로그에서 지금 사용 중인 role과 audience를 확인한 다음, 짧은 겹침 구간을 두고 trust policy를 갱신하는 것이다. 그 뒤 opt-in이나 rename·transfer를 수행하고, 실제 배포 성공과 발급된 subject를 다시 확인한 후 예전 조건을 제거한다. 제공자마다 조건 문법과 여러 subject를 허용하는 방식이 다르므로 복사한 예시를 그대로 쓰지 말아야 한다.

이때 aud를 빼먹지 않는다. subject가 맞더라도 다른 relying party를 대상으로 발급된 토큰을 같은 것으로 취급하면 검증 경계가 넓어진다. AWS는 GitHub OIDC 역할의 token.actions.githubusercontent.com:sub를 특정 조직·저장소·branch로 제한하라고 권고하고, 환경을 쓸 때는 GitHub environment 보호 규칙도 함께 쓰라고 안내한다.

5. environment와 재사용 워크플로는 별도 경계다

production 배포라면 저장소와 branch만 보는 것보다 GitHub environment를 사용하는 편이 운영 규칙을 설명하기 쉽다. environment에는 허용 branch나 tag 같은 deployment protection rule을 둘 수 있다. 그러면 OIDC trust의 sub와 GitHub 쪽 배포 승인을 서로 다른 층으로 관리할 수 있다. 하나는 토큰 교환 조건이고, 다른 하나는 job이 production environment에 진입할 조건이다.

중앙 재사용 워크플로를 쓰는 조직은 job_workflow_ref도 살펴볼 만하다. GitHub OIDC 참조 문서는 reusable workflow의 경로와 ref를 claim에 넣을 수 있고, custom subject template로 이를 sub에 포함할 수 있다고 설명한다. 단, 조직 템플릿을 만들었다고 기존 저장소에 자동 적용되는 것은 아니다. 저장소가 use_default=false로 opt-in해야 한다. 중앙 배포 워크플로를 강제하려면 cloud policy만 고치지 말고 토큰 claim template의 실제 적용 상태까지 읽어야 한다.

6. 배포 전에 확인할 다섯 가지

첫째, workflow의 id-token: write 범위를 OIDC가 필요한 job에만 둔다. 둘째, 실제 토큰의 iss, aud, sub 형식을 디버그용 claim 출력이나 제공자 로그로 확인하되 토큰 원문은 로그에 남기지 않는다. 셋째, cloud trust는 조직 전체 와일드카드보다 repository, environment 또는 branch까지 좁힌다. 넷째, environment 보호 규칙과 role permission policy를 별도로 검토한다. 다섯째, 2026년 7월 15일 이후 생성·rename·transfer된 저장소라면 owner ID와 repository ID가 포함된 immutable 형식을 전제로 검사한다.

현재 claim 확인부터 trust policy 겹침, 형식 전환, 실제 배포 검증, 이전 조건 정리까지의 다섯 단계

그림 3. 실제 claim과 cloud trust를 함께 전환하고, 검증 뒤 이전 조건을 제거해야 배포 중단과 과도한 신뢰를 모두 줄일 수 있다. 출처: docs.github.com/en/actions/reference/openid-connect-reference.

OIDC는 장기 키 보관을 없애는 데는 효과적이다. 하지만 사고의 중심이 "시크릿 값 관리"에서 "서명 토큰을 어떤 조건으로 믿을 것인가"로 옮겨갈 뿐이다. 전환 작업의 성공 기준도 workflow가 한 번 통과했다는 사실이 아니다. 실제 sub 형식, audience, environment 보호, cloud role 권한이 의도한 저장소와 배포 단계에만 묶였는지 확인해야 한다.

이번 글은 GitHub Actions OIDC 개념·참조 문서, AWS IAM의 GitHub OIDC trust 권고, OpenID Connect Core 1.0을 대조했다. 특정 클라우드 계정에서 실제 role을 만들거나 배포를 실행하지는 않았다. 예시의 숫자 ID와 조직·저장소 이름은 GitHub 공식 문서의 설명용 값이다.

참고 자료

댓글

이 블로그의 인기 게시물

체크아웃은 분리됐는데 Git 상태는 공유된다: Codex 0.154 worktree의 세 경계

잠근 작업이 커밋 뒤 다시 나온다: PostgreSQL 에이전트 큐의 소유권 설계

코딩 에이전트는 API 문서보다 AGENTS.md를 먼저 읽었다