GitHub Actions에서 Trusted Publishing으로 NuGet 패키지 게시하기
태그: nuget, github-actions, #Trusted Publishing, #OpenID Connect, #CI/CD
개요
NuGet.org의 새로운 Trusted Publishing 기능을 사용하면 API 키를 생성하고 저장할 필요 없이 GitHub Actions 워크플로우에서 NuGet 패키지를 게시할 수 있으며, 동시에 보안이 개선됩니다.
기존 NuGet 패키지 게시 방식
현재 NuGet 패키지를 nuget.org에 게시하는 일반적인 방법은 다음과 같습니다:
1. 로컬 빌드 및 수동 업로드
- 패키지를 로컬에서 빌드하고 nuget.org에 수동으로 업로드
2. CI 빌드 후 수동 업로드
- CI에서 패키지를 빌드하고, 다운로드한 후 수동으로 업로드
3. CI에서 직접 게시
- CI에서 패키지를 빌드하고 nuget.org에 직접 푸시
기존 방식의 문제점
CI에서 직접 게시하는 방식은 일관성을 높이고 수동 작업을 줄이지만, 웹사이트를 통한 드래그 앤 드롭 방식보다 복잡합니다:
- API 키 생성 필요
- GitHub Actions에 안전하게 저장 (예: Secret으로)
- GitHub Actions 워크플로우에서 시크릿 전달
- 시크릿 변경 시 로테이션 필요
- 조직에서 패키지를 게시하는 경우, 다른 구성원들도 동일한 작업 수행 가능해야 함
장수명 시크릿의 생명주기 관리는 어려운 것으로 악명 높으며, 이것이 Trusted Publishing이 필요한 이유입니다.
Trusted Publishing이란?
정의
Trusted Publishing은 여러 에코시스템에서 이미 사용되고 있는 표준 방식입니다:
- Python: PyPI
- Ruby: RubyGems.org
- JavaScript: npm
- .NET: nuget.org (최근 추가)
작동 원리
Trusted Publishing은 기존 인증 표준인 **OpenID Connect (OIDC)**를 사용하여 CI 인프라 제공자(예: GitHub Actions, GitLab Pipelines)와 공개 패키지 저장소(예: PyPI, nuget.org)를 연결합니다.
핵심 개념: API 키를 저장하고 관리하는 대신, OpenID Connect를 사용하여 단기 인증 토큰을 검색하고 이를 사용하여 패키지를 패키지 저장소에 푸시합니다.
GitHub Actions와 nuget.org의 작동 흐름
- 사용자가 nuget.org에 “신뢰 정책(trust policy)” 구성
- CI 워크플로우에서 GitHub로부터 OpenID Connect 토큰 검색
- GitHub가 Identity Provider (IdP) 역할 수행
- 토큰은 서명된 JSON Web Token (JWT)이며, 저장소와 실행 중인 워크플로우에 대한 세부 정보 포함
- 토큰을 nuget.org에 전송하여 API 키로 교환
- NuGet.org가 JWT 내용을 구성된 신뢰 정책과 대조하여 검증
- 모든 것이 일치하면, nuget.org가 패키지 게시에 사용할 수 있는 단기 API 토큰 발급
- CI 워크플로우가 API 토큰을 사용하여 .nupkg 패키지를 nuget.org에 푸시
신뢰 정책은 사전에 구성되고, GitHub 인프라에서 실행되므로 이미 GitHub로 인증되어 있어 설정이 매우 간단합니다.
NuGet.org에서 Trusted Publishing 구성하기
초기 워크플로우 (게시 없음)
.github/workflows/BuildAndPack.yml 파일에 저장된 초기 워크플로우:
name: BuildAndPack
on:
push:
branches: [main]
tags: ['*']
pull_request:
jobs:
build-and-publish:
permissions:
contents: read
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: 10.0.x
- name: dotnet pack
run: |
dotnet pack
dotnet pack -r win-x64
이 워크플로우의 작업:
- 저장소 체크아웃
- .NET 10 설치
dotnet pack실행
보안 모범 사례: 저장소에서 읽기 권한만 제한하기 위해 permissions 항목 추가
Trusted Publishing 지원 추가를 위한 워크플로우 업데이트
필요한 세 가지 작업:
id-token: write권한 추가NuGet/login@v1을 사용하여 OIDC 토큰을 NuGet API 키로 교환- 생성된 API 키를 사용하여 패키지를 NuGet에 푸시
업데이트된 워크플로우
name: BuildAndPack
on:
push:
branches: [main]
tags: ['*']
pull_request:
jobs:
build-and-publish:
permissions:
contents: read
id-token: write # 이 작업에 대한 GitHub OIDC 토큰 발급 활성화
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: 10.0.x
- name: dotnet pack
run: |
dotnet pack
dotnet pack -r win-x64
# GitHub 토큰을 사용하여 NuGet에 로그인하고 API 키 검색
- name: NuGet login (OIDC → temp API key)
uses: NuGet/login@v1
id: login
with:
# Secret은 NuGet 사용자 이름 (예: andrewlock)
user: ${{ secrets.NUGET_USER }}
- name: push to NuGet
# 태그를 빌드하는 경우에만 NuGet에 푸시 (선택사항)
if: startsWith(github.ref, 'refs/tags/')
shell: pwsh
# 출력 폴더의 모든 패키지를 반복하여 nuget.org에 푸시
# 이전 로그인 단계에서 생성된 NUGET_API_KEY 사용
run: |
Get-ChildItem artifacts/package/release -Filter *.nupkg | ForEach-Object {
dotnet nuget push $_.FullName `
--api-key "${{ steps.login.outputs.NUGET_API_KEY }}" `
--source https://api.nuget.org/v3/index.json
}
중요: 구성해야 하는 유일한 시크릿은 NUGET_USER 시크릿이며, 이는 nuget.org 사용자 이름(이메일 주소가 아님)으로 설정해야 합니다.
워크플로우 실행 시 발생하는 오류
신뢰 정책을 아직 구성하지 않은 경우, NuGet 로그인 단계가 다음과 같은 오류로 실패합니다:
Requesting GitHub OIDC token from: https://run-actions-1-azure-eastus.actions.githubusercontent.com/100//idtoken/aae150a5-757c-411a-b2b0-f82a9b26401f/1943e299-9d47-50e7-8d56-90681c654f24?api-version=2.0&audience=https%3A%2F%2Fwww.nuget.org
Error: Token exchange failed (401): No matching trust policy owned by user '***' was found.
메시지에서 알 수 있듯이, nuget.org에 신뢰 정책을 구성하지 않았기 때문에 단계가 실패했습니다.
NuGet.org에서 신뢰 정책 구성
신뢰 정책 생성 단계
- nuget.org에 로그인
- Trusted Publishing 페이지로 이동 (계정 메뉴에서 사용 가능)
- Create 클릭하여 새 정책 생성
- 필수 필드 작성:
- 정책 이름: 패키지/GitHub 저장소 이름으로 설정하는 것이 합리적
- 패키지 소유자: 개인 계정인지 조직인지 선택
- GitHub 저장소 세부 정보: 소유자 및 저장소
- 워크플로우 파일: NuGet에 푸시할 워크플로우
신뢰 정책 상태
신뢰 정책을 생성하면 정책을 사용할 때까지 “부분적으로” 활성 상태로 존재합니다.
워크플로우 재실행
신뢰 정책을 구성한 후 워크플로우를 다시 실행하면 로그인 단계가 성공합니다:
Requesting GitHub OIDC token from: https://run-actions-1-azure-eastus.actions.githubusercontent.com/73//idtoken/21d84c5d-b6a5-49e0-bc04-e46694e083df/b3fac26f-8847-5e96-b7ae-47d656a98f51?api-version=2.0&audience=https%3A%2F%2Fwww.nuget.org
Successfully exchanged OIDC token for NuGet API key.
정책이 완전히 활성화되고, 다음 단계에서 패키지가 NuGet에 게시됩니다. 장수명 API 키가 필요하지 않습니다.
주요 이점
보안 개선
- 장수명 자격 증명 불필요: API 키를 저장하고 관리할 필요가 없음
- 단기 토큰 사용: OpenID Connect를 통해 발급된 토큰은 짧은 수명을 가짐
간편한 설정
- 사전 구성된 신뢰 정책: 미리 신뢰 정책을 설정하므로 설정이 용이
- GitHub 인증 활용: GitHub 인프라에서 실행되므로 이미 인증됨
향후 가능성
- 추가 검증 마크: 신뢰 게시를 통해 푸시된 패키지가 추가 검증 마크를 받을 가능성
- 출처 증명과의 통합: 향후 출처 증명(provenance attestations)과의 연계 가능성
실용적인 팁
워크플로우 설정
- 권한 제한: 보안 모범 사례로
permissions를 사용하여 저장소 읽기 권한만 부여 - 태그 빌드 시에만 푸시:
if: startsWith(github.ref, 'refs/tags/')를 사용하여 태그 빌드 시에만 NuGet에 푸시하도록 설정 (선택사항)
시크릿 관리
- NUGET_USER 시크릿: nuget.org 사용자 이름을 저장 (이메일 주소가 아님)
- 공개 정보: 사용자 이름은 공개 정보이지만 시크릿으로 저장
신뢰 정책
- 패키지별 정책: 각 패키지/저장소에 대해 별도의 신뢰 정책 생성 권장
- 저장소 세부 정보: GitHub 저장소 소유자와 저장소 이름을 정확히 입력
요약
Trusted Publishing은 GitHub 저장소에 장수명 자격 증명을 저장할 필요를 없애줌으로써 NuGet 패키지 게시 프로세스를 단순화하고 보안을 개선합니다. OpenID Connect를 활용하여 단기 토큰을 발급받고, 이를 통해 안전하게 패키지를 게시할 수 있습니다. sleep-pc 패키지를 예로 들어, GitHub 저장소에서 nuget.org로 Trusted Publishing을 사용한 게시 구성 방법을 상세히 설명했습니다.