깃허브 액션 OIDC, AWS 장기 키, 불변 주체 | 시크릿에서 액세스 키를 빼는 순서는?
깃허브 시크릿의 AWS 액세스 키를 빼고 OIDC로 IAM 역할만 빌린다. 공급자 URL, aud, 불변 sub, 환경 보호, id-token 권한, configure-aws-credentials, 포크 PR을 클라우드, 데브옵스, 보안, 개발자, CI/CD 기준으로 같은 표에 둔다. 재사용 워크플로 글과 공급망 사고 글과 각을 섞지 않는다.
시크릿에 AWS 액세스 키를 두지 않습니다. 깃허브 액션은 OIDC 토큰을 보내고, AWS는 IAM 역할만 잠깐 빌려 줍니다. 토큰은 보통 한 시간 안에 끝납니다. 키는 저장소가 공개돼도, 포크 PR이 돌아도 그대로입니다. 2026년 7월 15일 이후 만든 저장소는 주체 문자열에 소유자 번호와 저장소 번호가 붙습니다. 예전 신뢰 정책만 있으면 역할 맡기가 거절됩니다. 저는 역할 ARN과 sub 형식을 같은 표에 올립니다.
파이프라인 중복은 재사용 워크플로 글을, 프론트 번들 시크릿은 환경변수 체크리스트를 보면 자리가 겹치지 않습니다. 숫자는 2026년 8월 깃허브 OIDC 레퍼런스와 AWS IAM, configure-aws-credentials 기준입니다.
시크릿에 넣은 액세스 키는 만료가 없다
깃허브 시크릿의 액세스 키는 워크플로가 끝나도 살아 있습니다. IAM 사용자가 만든 키는 직접 지우기 전까지 유효합니다. 저장소를 공개로 바꾸거나, 로그에 echo를 남기거나, 악성 액션이 환경 변수를 읽으면 그 키가 나갑니다.
제가 12사이트 넥스트를 버셀에 올리며 반복한 실수는 이렇습니다. 버셀 환경 변수에 넣던 습관으로 액션 시크릿에도 AWS_ACCESS_KEY_ID를 복사했습니다. 로컬과 운영이 같아서 편했습니다. 공식 액션 README는 OIDC를 먼저 쓰라고 적습니다. 키를 빼기 전에는 누가 그 키를 쓰는지 CloudTrail에서 한 번도 안 봤습니다.
포크에서 온 풀 리퀘스트는 시크릿을 못 읽습니다. 이미 유출된 키를 막아주지는 않습니다. 카드 명세에 서울 리전 요금이 갑자기 찍히면, 그 키를 누가 쓰는지부터 봅니다. npm 설치 스크립트가 키를 가져간 밤은 공급망 사고 글에 있고, 여기는 CI가 클라우드에 들어가는 문만 봅니다. 분 단위 과금은 액션 비용 글에 둡니다.
한 줄 가드 | 배포가 된다고 키를 남기지 않습니다. 시크릿 목록에 ACCESS_KEY가 보이면 역할 ARN으로 바꿉니다. 바꾼 뒤 IAM 사용자 키는 비활성, 하루 뒤 삭제입니다.
공급자, 역할, 워크플로 세 칸을 채운다
OIDC는 세 칸이 맞을 때만 역할이 나옵니다. 계정에 깃허브 공급자를 한 번 만들고, 그 공급자를 믿는 역할을 만들고, 잡에 토큰 쓰기 권한과 공식 액션을 붙입니다.
공급자 URL은 https://token.actions.githubusercontent.com입니다. 공식 액션을 쓰면 Audience는 sts.amazonaws.com입니다. 역할 신뢰 정책은 sts:AssumeRoleWithWebIdentity이고, 조건에 aud와 sub를 같이 겁니다. 조건이 비면 다른 저장소도 그 역할을 청할 수 있습니다.
칸
값
빠지면
공급자 URL
token.actions.githubusercontent.com
IAM이 토큰 발급자를 모름
Audience
sts.amazonaws.com
공식 액션이 거절됨
신뢰 조건 sub
저장소 + 브랜치 또는 환경
다른 저장소도 역할 청함
잡 권한
id-token: write, contents: read
JWT를 못 받음
액션
configure-aws-credentials + role-to-assume
키 환경 변수를 다시 찾음
권한의 write는 저장소를 고친다는 뜻이 아닙니다. OIDC JWT를 받아 외부에 넘기는 권한입니다. 체크아웃은 contents read면 됩니다. YAML 뼈대는 액션 CI 가이드에 있고, 여기는 신뢰 칸만 봅니다.
깃허브는 2026년 7월 15일 이후 만든 저장소의 기본 sub에 소유자 아이디와 저장소 아이디를 넣습니다. 예전 형식은 이름만 있습니다. 이름을 지운 뒤 같은 경로를 다시 만들면, 옛 신뢰 정책이 새 주인을 받아들일 수 있습니다. 불변 형식은 그 구멍을 막습니다. 깃허브 엔터프라이즈 서버에는 아직 없습니다.
제가 8월에 연 사이드 저장소가 여기서 한 번 멈췄습니다. IAM에는 repo:my-org/my-app:ref:refs/heads/main이 있었고, 토큰은 repo:my-org@123456/my-app@456789:ref:refs/heads/main이었습니다. 액션 로그는 Not authorized to perform sts:AssumeRoleWithWebIdentity만 남겼습니다. 키를 다시 넣을 자리가 아닙니다. 디버거로 sub를 찍고 신뢰 정책을 맞춥니다.
7월 15일 이전 저장소는 예전 형식을 유지합니다. 조직이나 저장소에서 불변 주체를 켜면 그때부터 아이디가 붙습니다. 이름 변경과 이전도 그 날짜 이후면 불변 형식으로 바뀝니다. @는 깃허브 이름에 못 들어가서 구분자로 씁니다. 팀 저장소를 회사 조직으로 옮긴 뒤에 배포만 깨지면, 이름보다 번호가 바뀌었는지부터 봅니다. 의존성 봇이 액션 해시를 올리는 자리는 디펜더봇 글과 겹칩니다. 여기는 토큰 문자열만 봅니다.
잡에 environment를 적으면 sub가 브랜치 대신 환경 이름을 담습니다. 형식은 repo:ORG/REPO:environment:prod입니다. 불변 저장소면 이름 뒤에 아이디가 붙습니다. 운영 역할은 이 문자열만 받습니다.
국내 1인 팀은 버셀 프리뷰에 익숙해서 액션도 한 역할로 밀어 넣는 경우가 많습니다. 프리뷰가 운영 버킷에 쓰면, 포크가 아니어도 실수가 운영을 덮습니다. 프리뷰 역할은 객체 읽기와 스테이징 버킷 쓰기만 줍니다. 운영 역할은 환경 보호 규칙과 같이 둡니다. 필수 리뷰어, 대기 시간, 배포 브랜치 제한을 환경에 걸면 토큰만으로 운영에 못 들어갑니다.
재사용 워크플로를 다른 조직에서 부르면, 호출 쪽에 id-token write가 있어야 토큰이 열립니다. 같은 조직 안에서는 호출 맥락으로 토큰이 갑니다. 잡 워크플로 참조를 신뢰 조건에 넣는 자리는 재사용 글의 시크릿 inherit와 다릅니다. 배포 전 관측은 SLO 체크리스트에 두고, 여기는 환경 이름만 맞춥니다. 넥스트 프리뷰 URL CORS는 앱 라우터 체크에 있습니다.
repo:my-org/my-app:*는 모든 브랜치, 모든 환경, 풀 리퀘스트 병합 브랜치가 같은 역할을 청합니다. 키가 없는 것과 권한이 넓은 것은 다릅니다. 기능 브랜치 워크플로가 운영 버킷을 비울 수 있습니다.
StringLike에 별표를 쓰는 예시는 공식 문서에도 있습니다. 그 예시는 설명이 목적입니다. 운영 역할에는 StringEquals로 브랜치 하나 또는 환경 하나를 고정합니다. 여러 브랜치가 필요하면 역할을 나누거나, 허용 목록을 배열로 적습니다.
액션 태그를 @v4로 두면 태그 이동에 같이 움직입니다. 깃허브 문서 예시는 커밋 해시를 고정합니다. 저는 해시로 두고, 올릴 때만 디펜더봇 요청을 봅니다. 구글 클라우드는 워크로드 아이덴티티 페더레이션이고, 조건 문법이 다릅니다. 같은 주체 문자열을 쓰더라도 칸 이름이 다릅니다. 네이버 클라우드도 콘솔의 신뢰 공급자 칸을 그 문서에서 확인합니다. 복사한 신뢰 정책 JSON을 그대로 넣지 않습니다. 국내 1인 팀이 가장 자주 놓치는 칸은 별표가 아니라, 스테이징과 운영을 한 역할로 묶는 일입니다.
키는 만료가 없고, OIDC는 한 시간이다. 7월 15일 이후 저장소는 이름 뒤에 아이디가 붙는다
클레임 형식과 액션 해시는 수시로 바뀝니다. 위 표는 2026년 8월 공개 페이지 기준 점검용이며, 적용의 최종 근거는 공식 화면입니다. 운영 역할을 열기 전에 디버거로 sub를 한 장 남기고, 스테이징 역할로 get-caller-identity가 맞는지 확인합니다.
자주 묻는 질문
역할 ARN만 시크릿에 넣으면 되나?
ARN은 비밀이 아닙니다. 워크플로에 그대로 적어도 됩니다. 숨기고 싶으면 시크릿에 넣어도 유출돼도 역할은 못 맡습니다. 맡으려면 공급자와 sub가 맞아야 합니다. 액세스 키를 ARN 자리에 다시 넣지 않습니다.
7월 15일 전에 만든 저장소도 지금 바꿔야 하나?
당장 배포가 되면 형식만 맞추면 됩니다. 이름 변경이나 이전을 앞두면 불변 주체를 먼저 켭니다. 켠 뒤에는 IAM 조건을 아이디 형식으로 고칩니다. 켜기 전에 디버거로 현재 sub를 한 장 남깁니다.
id-token write가 저장소를 수정하나?
아닙니다. JWT를 받아 외부 공급자에게 넘기는 권한입니다. contents는 read면 체크아웃이 됩니다. 배포 잡에 contents write를 넓히지 않습니다. 패키지 푸시가 필요하면 그 잡만 packages 권한을 따로 줍니다.
포크 PR에서도 OIDC로 운영 역할이 열리나?
포크 워크플로는 시크릿을 못 읽고, 운영 환경 보호가 있으면 배포도 막힙니다. 그래도 신뢰 조건을 repo:org/repo:*로 열어 두면 같은 저장소의 아무 브랜치가 역할을 청합니다. 운영은 환경 이름과 보호 규칙을 같이 겁니다.
셀프 호스트 러너에서도 같나?
토큰 발급자는 같습니다. 러너가 토큰 URL에 닿을 수 있어야 합니다. 러너 머신에 AWS 키를 심어 두면 OIDC를 우회합니다. 러너 IAM과 워크플로 역할을 섞지 않습니다. 클레임의 runner_environment로 깃허브 호스트만 받는 조건도 있습니다.
한국 리전 ap-northeast-2만 쓰면 되나?
서울 리전에 자원이 있으면 액션의 aws-region을 그곳으로 둡니다. 공급자 자체는 글로벌 IAM 객체입니다. 역할 권한은 버킷과 스택이 있는 리전 API만 허용합니다. 버셀만 쓰는 사이트는 액션 OIDC가 없어도 됩니다. S3나 ECS를 액션이 건드리는 밤에만 이 표를 엽니다.