긴 수명의 배포 비밀을 없애는 법: OIDC와 서명 URL로 권한을 짧게 만들기
CI/CD와 서버리스 함수가 외부 리소스에 접근할 때, 왜 비밀값을 잘 숨기기보다 비밀값을 오래 살리지 않기가 더 안전할까?
긴 수명의 배포 비밀을 없애는 법: OIDC와 서명 URL로 권한을 짧게 만들기
- 카테고리: infra_dev
- 예상 읽기 시간: 10분
- 오늘의 질문: CI/CD와 서버리스 함수가 외부 리소스에 접근할 때, 왜 “비밀값을 잘 숨기기”보다 “비밀값을 오래 살리지 않기”가 더 안전할까?
- 핵심 출처:
- Vercel Blob now supports OIDC authentication - 게시일 2026-06-01, 확인일 2026-06-09
- Signed URLs are now available for Vercel Blob - 게시일 2026-06-02, 확인일 2026-06-09
- Trusted Sources for Deployment Protection - 게시일 2026-05-13, 확인일 2026-06-09
- OpenID Connect (OIDC) Federation - 공식 문서, 확인일 2026-06-09
- Configuring OpenID Connect in cloud providers - GitHub Docs - 공식 문서, 확인일 2026-06-09
1. 왜 지금 봐야 하나
작은 팀의 인프라 사고는 거창한 제로데이보다 단순한 비밀값 유출에서 시작되는 경우가 많다. BLOB_READ_WRITE_TOKEN, 배포 보호 우회 토큰, 클라우드 액세스 키, 서비스 계정 JSON을 CI 변수나 로컬 .env에 넣어두고, 한 번 새면 언제 어디서 쓰였는지 추적하기 어렵다. 그래서 운영의 첫 질문은 “이 secret을 어디에 저장하지?”가 아니라 “이 secret이 반드시 오래 살아야 하나?”가 되어야 한다.
최근 Vercel은 이 방향의 변경을 연속으로 내놓았다. 2026년 6월 1일 Vercel Blob은 OIDC 인증을 지원하고 새 프로젝트 연결의 기본값으로 삼았다. Vercel 발급 OIDC 토큰은 짧게 살고 자동으로 회전되기 때문에 장기 BLOB_READ_WRITE_TOKEN 없이 Blob에 접근할 수 있다. 다음 날인 6월 2일에는 Blob signed URL을 공개해, 브라우저나 클라이언트에는 전체 저장소 권한 대신 특정 경로·작업·만료시간으로 제한된 URL만 줄 수 있게 했다. 앞서 5월 13일에는 Deployment Protection의 Trusted Sources가 OIDC 토큰을 검증해 장기 자동화 우회 secret 공유를 줄이는 방식을 제시했다.
이 글은 Vercel 기능 소개가 아니다. 핵심은 CI/CD, 서버리스 함수, 브라우저 업로드, 내부 서비스 호출에서 권한을 “저장된 비밀”이 아니라 “검증 가능한 신원과 짧은 위임”으로 바꾸는 운영 패턴이다.
2. 핵심 개념
OIDC 기반 접근은 대략 세 단계로 이해하면 된다.
- 신원 발급자(IdP)가 실행 주체를 증명한다. 예를 들어 Vercel은 빌드와 함수 실행에 OIDC 토큰을 제공하고, GitHub Actions는 워크플로가
id-token: write권한을 가질 때 job별 OIDC 토큰을 요청할 수 있다. - 받는 쪽이 토큰의 claim을 검증한다. issuer, audience, repository, branch, environment, project, team 같은 claim이 정책과 맞는지 확인한다.
- 짧은 권한으로 교환하거나 요청을 허용한다. 클라우드 provider는 임시 access token을 주고, Vercel Deployment Protection은 trusted source인지 확인하며, Blob 서버는 OIDC로 인증한 서버가 signed URL을 만들게 한다.
여기서 중요한 차이는 “누가 secret을 알고 있나”가 아니라 “어떤 실행 컨텍스트가 어떤 조건에서 어떤 기간 동안 무엇을 할 수 있나”다. 장기 token은 복사되면 토큰 자체가 권한이다. 반면 OIDC는 실행 환경, 프로젝트, 브랜치, environment 같은 조건이 맞아야 하고, 토큰 수명이 짧아 재사용 창이 작다.
Signed URL은 OIDC와 다른 층의 도구다. 서버는 OIDC로 Blob에 인증한 뒤, 클라이언트에는 put, get, head, delete 중 하나의 작업, 하나의 pathname, 최대 7일의 만료 같은 좁은 권한만 담긴 URL을 준다. 즉 OIDC는 서버/자동화의 신원 문제를 줄이고, signed URL은 브라우저나 외부 클라이언트에 넘기는 위임 범위를 줄인다.
3. 최신 이슈와 연결
Vercel Blob의 OIDC 전환은 “스토리지 토큰을 환경변수로 계속 들고 있지 말자”는 메시지다. 문서에 따르면 Vercel Functions는 토큰을 요청 헤더로 받고, 빌드에서는 VERCEL_OIDC_TOKEN 환경변수로 사용할 수 있다. 토큰은 매 실행마다 완전히 새로 생성되는 방식은 아니며 함수에서는 최대 45분 캐시되고 TTL은 60분이라고 문서화되어 있다. 이 세부사항은 중요하다. “짧다”는 말은 무한히 안전하다는 뜻이 아니라, 공격자가 사용할 수 있는 시간을 제한한다는 뜻이다.
Deployment Protection의 Trusted Sources는 배포 미리보기나 보호된 endpoint를 자동화가 호출할 때 장기 bypass secret을 공유하던 관행을 바꾼다. 호출자는 x-vercel-trusted-oidc-idp-token 헤더에 OIDC 토큰을 붙이고, Vercel은 서명과 claim, environment rule을 확인한다. GitHub Actions 같은 외부 OIDC provider도 trusted source로 등록할 수 있다.
GitHub Docs도 같은 원리를 강조한다. 워크플로는 permissions: id-token: write로 OIDC 토큰 요청 권한을 얻지만, 이 설정 자체가 클라우드 쓰기 권한을 주는 것은 아니다. 실제 권한은 클라우드나 대상 서비스의 trust policy가 어떤 sub, repository, branch, environment를 허용하느냐에 달려 있다. 또한 GitHub는 environment를 OIDC policy에 쓸 때 branch/tag 제한 같은 protection rule을 추가하라고 권한다.
4. 개발자 관점 해석
개발자가 실수하기 쉬운 지점은 OIDC를 “secret 없이 모든 것이 자동으로 안전해지는 기능”으로 보는 것이다. 실제로는 정책 설계가 보안의 대부분이다.
- 신뢰 범위를 너무 넓히면 OIDC도 위험하다. 예를 들어 모든 브랜치, 모든 workflow, 모든 preview 환경에서 production Blob 쓰기를 허용하면 장기 token을 없앴을 뿐 권한 경계는 여전히 약하다.
- 클라이언트에 직접 권한을 주면 비용 폭탄이 된다. signed URL은 좁은 권한이지만 만료가 너무 길거나 pathname이 예측 가능하거나 업로드 크기·rate limit이 없으면 스토리지 비용과 악성 업로드 문제가 생긴다.
- 로컬 개발 편의와 보안은 충돌한다. Vercel 문서는
vercel env pull로 로컬에VERCEL_OIDC_TOKEN을 받을 수 있다고 설명한다. 편하지만,.env.local이 git에 들어가지 않는지, 로컬 토큰 만료와 재발급 흐름이 문서화되어 있는지 확인해야 한다. - 관측성이 없으면 사고 대응이 어렵다. 장기 secret을 제거해도 누가 어떤 claim으로 어떤 object를 썼는지 로그가 없으면 incident review에서 원인을 좁히기 어렵다.
즉 좋은 설계는 “secret 제거”가 아니라 “권한의 주체, 범위, 시간, 감사 로그를 분리”하는 것이다.
5. 내 프로젝트에 적용할 체크포인트
- CI/CD에서 클라우드 키나 저장소 토큰을 GitHub Secrets에 장기 보관하고 있는가?
- 배포 workflow가 production environment를 쓴다면 branch/tag protection과 승인 규칙이 있는가?
- OIDC trust policy가 repository 전체가 아니라 특정 branch, environment, workflow, audience까지 제한하는가?
- preview 배포가 production 데이터나 production Blob store에 쓸 수 있는가?
- 브라우저 업로드는 서버를 거치지 않더라도 signed URL의 operation, pathname, 만료, 파일 크기, MIME type, rate limit을 제한하는가?
- 삭제 URL에는 가능하면 ETag/조건부 삭제 같은 충돌 방지 조건이 있는가?
- object write/read/delete 로그를 request id, user id, deployment id, workflow run id와 연결할 수 있는가?
- fallback용 장기 token이 남아 있다면 누가 언제 회전하고 폐기하는지 정해져 있는가?
비용 관점에서는 signed upload가 서버 egress와 함수 실행 시간을 줄일 수 있다. 대신 잘못 열어두면 스토리지와 대역폭 비용이 곧바로 사용자 입력에 노출된다. 보안 관점에서는 OIDC가 secret 유출 면적을 줄이지만, 잘못된 trust policy는 “짧게 사는 넓은 권한”을 만들어낸다.
6. 오늘 10분 액션
오늘은 코드를 바꾸기보다 권한 지도를 그려보자.
- 현재 프로젝트의 secret 목록을 연다. GitHub Actions secrets, Vercel env, 로컬
.env.local, 클라우드 service account를 포함한다. - 각 secret 옆에 “사용 주체 / 대상 리소스 / 가능한 작업 / 만료 여부 / 회전 주기”를 적는다.
- 만료가 없고 쓰기 권한이 있는 항목을 별표 표시한다.
- 그중 하나를 골라 OIDC로 바꿀 수 있는지 확인한다. GitHub Actions라면
id-token: write와 cloud trust policy가 후보이고, Vercel Blob이라면 OIDC 업그레이드와@vercel/blob최신화가 후보가 된다. - 브라우저가 직접 스토리지에 접근하는 흐름이 있다면, 전체 토큰이 아니라 signed URL로 줄일 수 있는지 적는다.
10분 안에 migration을 끝내지 않아도 된다. 오늘의 목표는 “어떤 secret이 가장 위험한 장기 권한인가”를 찾는 것이다.
7. 더 볼 자료
- Vercel OIDC 문서: 빌드, 함수, 로컬 개발에서 토큰이 어떻게 전달되는지 확인하기 좋다.
- GitHub Actions OIDC 문서:
sub,aud, environment protection을 trust policy에 연결하는 관점을 잡기 좋다. - Vercel Blob signed URL changelog: 클라이언트 업로드·다운로드·삭제를 operation별로 쪼개는 예시가 있다.
- Deployment Protection Trusted Sources changelog: 내부 서비스나 CI가 protected deployment를 호출할 때 장기 bypass secret 대신 OIDC를 쓰는 패턴을 보여준다.
중복 회피 메모
로컬 content/generated와 Supabase의 최근 infra_dev 글은 Kubernetes Job suspend, 리소스 재조정, 큐·비용·신뢰성 관점에 집중했다. 이번 글은 Kubernetes나 배치 스케줄링을 반복하지 않고, Vercel과 GitHub의 최근 OIDC·signed URL 변화를 바탕으로 배포/서버리스/스토리지 권한을 장기 secret에서 짧은 신원 기반 위임으로 바꾸는 보안·비용·운영 패턴을 다룬다.
핵심 출처
로그인하면 이 글을 북마크하고, 나만 보는 한 줄 메모를 남길 수 있어요.
댓글 0
최신순 ▾혹시 이 글을 읽는 동료 개발자가 있다면, GitHub으로 로그인하고 한 줄 흔적을 남겨줘요. (스팸 방지용 로그인이에요)