Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 807 – Trusted Publishing에 대한 색인 지원

Author:
William Woodruff <william at yossarian.net>
Sponsor:
Donald Stufft <donald at stufft.io>
PEP-Delegate:
Donald Stufft <donald at stufft.io>
Discussions-To:
Discourse thread
Status:
Draft
Type:
Standards Track
Topic:
Packaging
Created:
19-Sep-2025
Post-History:
08-Aug-2025, 29-Sep-2025

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

초록

이 PEP는 임의의 Python 패키지 색인이 “Trusted Publishing”을 지원할 수 있도록 하는 표준 메커니즘을 제안합니다. Trusted Publishing은 Python 패키지 색인(PyPI)에 이미 구현되어 있는, 오용에 강한 자격 증명 교환 방식입니다.

이 PEP에서 제안하는 메커니즘은 PyPI의 Trusted Publishing 기존 구현을 포괄하도록 설계되었으며, 동시에 다른 색인도 기존 Python 패키지 업로드 클라이언트가 탐색할 수 있고 상호 운용 가능한 방식으로 같은 방식을 구현할 수 있도록 합니다.

동기

“Trusted Publishing”은 OpenID Connect(OIDC) 표준을 사용하여 신뢰할 수 있는 서드파티 서비스(CI/CD 또는 클라우드 제공자 등)에서 발급한 수명이 짧은 신원 자격 증명을, 색인에 게시하는 데 사용할 수 있는 수명이 짧고 권한 범위가 최소화된 업로드 자격 증명으로 교환하는 것을 가리키는 PyPI의 전문 용어입니다.

Trusted Publishing은 원래 기존 업로드 API와 마찬가지로 비표준(PyPI 전용) 기능으로서 2023년에 PyPI에서 설계되고 활성화되었습니다. 이 기능은 그러한 형태로 널리 채택되었습니다. (2025년 9월 기준) Trusted Publisher를 사용하여 PyPI에 게시된 파일은 100만 개가 넘으며, 이는 이 기능이 제공된 이후 PyPI에 업로드된 파일 약 8개 중 1개에 해당합니다. 또한 PyPI의 설계는 Rust(crates.io), Ruby(RubyGems), JavaScript(npm) 생태계의 유사한 설계에 영향을 주었습니다.

Trusted Publishing에 대한 표준이 없다는 점은 채택에 장기적인 걸림돌이 됩니다. 서드파티 색인(즉, PyPI와 TestPyPI 이외의 색인)은 PyPI의 표준화되지 않은 설계를 참조하지 않고서는 Trusted Publishing을 쉽게 구현할 수 없습니다. 이는 결국 표준화되지 않은 업로드 API와 비슷한 장기적인 성숙도 위험을 낳습니다. 패키지 업로드 클라이언트(Twine이나 uv 등)는 색인 간의 동작 차이를 받아들이거나(그 결과 임시방편이 누적됩니다), PyPI 이외의 Trusted Publishing 구현을 계속 거부해야 합니다.

근거

Trusted Publishing에 대한 기존 표준이 없다는 점이 이 PEP의 주된 근거입니다.

이 PEP에서 제안하는 설계는 PyPI의 기존 구현을 충실히 따르되, 업로드 클라이언트가 PyPI 전용 가정을 하지 않고도 임의의 색인이 Trusted Publishing을 지원하는지 판단할 수 있도록 하는 탐색 계층을 추가합니다.

이 설계의 근거는 다음과 같습니다.

  1. PyPI에 있는 기존의 (표준화되지 않은) Trusted Publishing 구현은 검증된 실적이 있으며, 이미 업로드 도구에서 널리 채택되어 있습니다. 기존 설계에서 크게 벗어나면 불필요한 호환성 위험이 생길 것입니다.
  2. 이 PEP에서 제안하는 탐색 메커니즘은 기계 간 프로토콜에 대한 기존 표준, 즉 RFC 8615 (Well-Known URI)와 일관되도록 설계되었습니다. 또한 이 탐색 메커니즘은 여러 색인을 단일 도메인 아래에서 호스팅할 수 있도록 설계되었는데, 이는 서드파티 색인 호스트에서 흔히 쓰이는 토폴로지입니다.

요컨대 이 PEP의 근거는 PyPI의 기존 인터페이스를 표준화하고 또한 이를 탐색 가능하게 만들면서, PyPI의 토폴로지와 일치하지 않는 색인 호스트도 Trusted Publishing을 구현할 수 있도록 하는 것입니다.

명세

이 PEP의 명세는 두 부분으로 구성됩니다.

  • 패키지 업로드 클라이언트가 임의의 Python 패키지 색인 호스트가 Trusted Publishing을 지원하는지 판단하는 데 사용할 수 있는 탐색 메커니즘입니다.
  • 패키지 업로드 클라이언트가 신원 자격 증명을 업로드 자격 증명으로 교환하는 데 사용할 수 있는 토큰 교환 메커니즘입니다.

제약 조건

달리 명시하지 않는 한, 다음 제약 조건은 이 PEP 명세의 모든 부분에 적용됩니다.

  • 모든 URL은 반드시 잠재적으로 신뢰할 수 있는 출처를 가져야 합니다. 실제로 이는 모든 URL이 반드시 https 스킴을 사용하거나, 로컬 루프백의 한 변형(localhost, 127.0.0.1 등)이거나, 그 밖에 상호 작용의 맥락에서 선험적으로 신뢰할 수 있다고 간주되는 것(예: 내부 네트워크)이어야 한다는 의미입니다.

    업로드 클라이언트는 이 제약 조건을 충족하지 않는 URL을 반드시 거부해야 합니다.

  • 서버가 제공하는 모든 URL(즉, 탐색 응답에 포함된 URL)은 반드시 사용자가 제공한 업로드 URL과 동일한 호스트 하위 구성 요소를 가져야 합니다. 업로드 클라이언트는 이 제약 조건을 충족하지 않는 URL을 반드시 거부해야 합니다.

    실제로 이는 https://upload.example.com/.well-known/pytp?discover={key}에 대한 탐색 요청이 upload.example.com 호스트를 가진 URL만 반환할 수 있다는 의미입니다.

  • 모든 클라이언트 요청에는 Accept: application/vnd.pypi.pytp.v1+json 헤더가 있어야 합니다(SHOULD). Accept 헤더가 없으면 수신 서버는 이 헤더가 있는 것처럼 동작해야 합니다(MUST).

    다른 Accept 헤더가 있으면 수신 서버는 406 Not Acceptable 상태 코드로 응답해야 합니다(SHOULD).

  • 달리 명시되지 않는 한, 서버의 모든 오류(4xx 및 5xx) 응답은 RFC 9457 (Problem Details for HTTP APIs) 형식을 사용해야 합니다(MUST). 특히, 서버는 Section 3에 정의된 “Problem Details JSON 객체”를 반드시 사용해야 하며, 응답에 application/problem+json 미디어 타입을 사용하는 것이 좋습니다.

Trusted Publishing 탐색

현재 모든 Python 패키지 업로드는 업로드 클라이언트(twine이나 uv 등)가 단순한 도메인 이름이 아니라 업로드 URL을 받는다는 의미에서 “엔드포인트 주도(endpoint driven)” 방식입니다.

예를 들어 PyPI에 업로드하려면 업로드 클라이언트는 https://upload.pypi.org/legacy/에 연결해야 합니다.

아래에서 제안하는 탐색 메커니즘은 이 사실을 활용하여 단일 도메인이 여러 색인(및 그에 대응하는 업로드 엔드포인트)에 대한 지원을 알릴 수 있게 합니다.

탐색 메커니즘은 다음과 같습니다:

  1. 업로드 클라이언트는 업로드 URL(예: https://upload.example.com/legacy/)을 받습니다.
  2. 업로드 클라이언트는 RFC 3986에 정의된 대로 URL의 경로 구성 요소(path component)를 추출합니다. 경로 구성 요소가 비어 있으면 빈 문자열을 사용해야 합니다.

    위 예에서 경로 구성 요소는 /legacy/입니다.

  3. 업로드 클라이언트는 경로 구성 요소에 쿼리 안전 URL 인코딩(즉, 슬래시와 공백의 인코딩을 포함하여 RFC 3986에 정의된 퍼센트 인코딩)을 수행하여 탐색 키(discovery key)를 생성합니다.

    위 예에서 탐색 키는 %2Flegacy%2F입니다. [1]

  4. 업로드 클라이언트는 업로드 URL의 스킴 및 권한(authority) 구성 요소(RFC 3986에 정의됨)를 가져와 경로로 /.well-known/pytp를 덧붙여 탐색 URL(discovery URL)을 구성합니다. 그런 다음 업로드 클라이언트는 탐색 키를 discover 쿼리 매개변수의 값으로 덧붙입니다.

    위 예에서 탐색 URL은 https://upload.example.com/.well-known/pytp?discover=%2Flegacy%2F입니다.

  5. 업로드 클라이언트는 탐색 URL로 HTTP GET 요청을 보냅니다.
  6. 색인이 주어진 업로드 URL에 대해 Trusted Publishing을 지원하면, 서버는 200 OK 상태 코드와 JSON 객체를 담은 본문으로 응답합니다.

    JSON 객체는 다음 필드를 포함해야 합니다(MUST):

    • audience-endpoint: 토큰 교환 중에 사용할 OIDC audience 엔드포인트의 URL을 담은 문자열입니다.
    • token-mint-endpoint: 토큰 교환 중에 사용할 토큰 발급(minting) 엔드포인트의 URL을 담은 문자열입니다.

    또한 JSON 객체는 다음 필드를 포함할 수 있습니다(MAY):

    • features: 색인의 Trusted Publishing 구현이 지원하는 선택적 기능을 나타내는 문자열 배열입니다. 가능한 기능의 집합은 yeokja-pep-0807-target-13212에 정의되어 있습니다.
    • default-features: 요청이 기능을 명시적으로 지정하지 않을 때 색인의 Trusted Publishing 구현이 사용하는 기본 기능을 나타내는 문자열 배열입니다. default-features 필드가 없으면 업로드 클라이언트는 기본값을 ["multi-use-token"]으로 가정해야 합니다(MUST).

    위 예에서 유효한 응답 본문은 다음과 같습니다:

    {
       "audience-endpoint": "https://upload.example.com/_/oidc/audience",
       "token-mint-endpoint": "https://upload.example.com/_/oidc/mint-token",
       "features": ["single-use-token", "multi-use-token"],
       "default-features": ["multi-use-token"]
    }
    

서버가 주어진 업로드 URL에 대해 Trusted Publishing을 지원하지 않으면 404 Not Found 상태 코드로 응답해야 합니다(MUST).

서버는 적절한 오류 상황을 나타내기 위해 400 또는 500 범위의 다른 표준 HTTP 오류 코드로 추가로 응답할 수도 있습니다(MAY).

Trusted Publishing 토큰 교환

업로드 클라이언트가 탐색 흐름을 성공적으로 수행하고 나면, 실제 Trusted Publishing 토큰 교환을 진행할 수 있습니다.

토큰 교환은 세 단계로 이루어집니다:

  1. 업로드 클라이언트는 탐색 중에 얻은 오디언스 엔드포인트를 사용하여 색인에 해당 색인이 기대하는 OIDC 오디언스를 요청합니다.
  2. 업로드 클라이언트는 기대 오디언스를 사용하여, 사용 중인 Trusted Publishing 제공자(즉, 업로드가 수행되는 CI/CD 또는 클라우드 제공자)로부터 적절히 바인딩된 신원 자격 증명을 얻습니다. 이 단계의 세부 사항은 제공자마다 다르며, 이 PEP의 범위를 벗어납니다. [2]
  3. 업로드 클라이언트는 탐색 중에 얻은 토큰 발행 엔드포인트를 사용하여, 얻은 신원 자격 증명을 색인에 업로드할 때 사용할 수 있는 수명이 짧은 업로드 자격 증명으로 교환합니다.

오디언스 조회

기대 OIDC 오디언스를 조회하기 위해, 업로드 클라이언트는 탐색 중에 얻은 오디언스 엔드포인트로 HTTP GET 요청을 보냅니다.

성공하면 서버는 200 OK 상태 코드와 함께, 다음 필드를 포함하는 JSON 객체를 본문으로 응답합니다:

  • audience: 기대 OIDC 오디언스를 담은 문자열입니다.

실패하면 서버는 적절한 오류 상황을 나타내기 위해 400 또는 500 범위의 표준 HTTP 오류 코드로 반드시 응답해야 합니다.

토큰 발행

업로드 클라이언트가 Audience Retrieval을 수행하고 Trusted Publishing 제공자로부터 신원 자격 증명을 얻은 후에는, 업로드 자격 증명 발행을 진행할 수 있습니다.

업로드 자격 증명을 발행하기 위해, 업로드 클라이언트는 탐색 중에 얻은 토큰 발행 엔드포인트로 HTTP POST 요청을 보냅니다. POST 요청의 페이로드는 반드시 다음을 포함하는 JSON 객체여야 합니다:

  • token: Trusted Publishing 제공자로부터 얻은 신원 자격 증명을 담은 문자열입니다.
  • features: 발행될 업로드 자격 증명에 원하는 기능을 나타내는 선택적 문자열 배열입니다. 클라이언트가 이 필드를 제공하지 않으면, 서버는 탐색 중 default-features 필드에 명시된 자체 기본 기능을 반드시 사용해야 합니다.

예를 들어, 유효한 요청 본문은 다음과 같습니다:

{
   "token": "ey...",
   "features": ["single-use-token"]
}

성공하면 서버는 200 OK 상태 코드와 함께, 다음 필드들을 포함하는 JSON 객체를 본문으로 응답합니다:

  • token: 업로드 자격 증명을 담은 문자열입니다. 업로드 자격 증명의 형식은 구현에 따라 정의되며 색인마다 다릅니다.
  • expires: 업로드 자격 증명이 만료되는 시점을 나타내는 Unix 타임스탬프를 담은 선택적 정수입니다. 이 필드가 없으면, 업로드 클라이언트는 자격 증명이 요청 시점으로부터 15분(900초) 이내에 만료된다고 가정할 수 있습니다.

    서버는 요청 시점으로부터 15분(900초) 미만 또는 6시간(21,600초) 초과 후에 만료되는 임시 업로드 자격 증명을 발급해서는 안 됩니다.

    최대 만료 시간 6시간은 GitHub Actions 같은 널리 쓰이는 CI/CD 제공자의 일반적인 런타임 제한에 맞추기 위해 선택되었습니다.

    업로드 클라이언트는 필요한 경우 이 시간(또는 위에서 명시한 최솟값)을 사용하여 업로드 자격 증명을 언제 갱신할지 결정할 수 있습니다.

실패하면 서버는 적절한 오류 상황을 나타내기 위해 400 또는 500 범위의 표준 HTTP 오류 코드 중 하나로 반드시 응답해야 합니다.

기능 협상

이 PEP에서 정의하는 프로토콜은 업로드 클라이언트와 이를 받는 색인 서버 사이에서 기본값이 아닌 기능을 협상하기 위한 선택적 메커니즘을 지원합니다. 이러한 기능은 탐색 응답의 features 필드에 문자열 배열로 제시되며, 클라이언트는 토큰 발행 요청의 features 필드에 하나 이상의 기능을 포함하여 해당 기능을 요청할 수 있습니다.

다음 기능이 정의됩니다:

  • single-use-token: 색인 서버가 발급하는 토큰은 일회용 토큰이어야 합니다(MUST). 다시 말해, 토큰 발급 엔드포인트가 반환한 토큰은 단 한 번의 업로드 작업에만 사용할 수 있어야 합니다(MUST). 같은 토큰을 사용하는 이후의 모든 업로드 시도는 색인 서버가 거부해야 합니다(MUST). single-use-token 기능을 요청하는 클라이언트는 여러 번의 업로드 작업이 필요한 경우 토큰 발급 작업을 여러 번 수행할 준비가 되어 있어야 합니다(MUST).
  • multi-use-token: 색인 서버가 발급하는 토큰은 다회용 토큰이어야 합니다(MUST). 다시 말해, 토큰 발급 엔드포인트가 반환한 토큰은 만료될 때까지 여러 번의 업로드 작업에 사용할 수도 있습니다(MAY).

보안 영향

이 PEP는 PyPI가 이미 사용하고 있는 신뢰할 수 있는 게시(Trusted Publishing) 흐름을 공식적으로 표준화하여 Python 패키징 생태계의 보안과 투명성을 향상하고자 합니다.

이 PEP는 Trusted Publishing 탐색 흐름이나 교환 흐름 자체와 관련된 긍정적 또는 부정적 보안 영향을 찾아내지 못했습니다.

이러한 흐름과는 별개로, Trusted Publishing 자체에는 PyPI의 보안 모델이 있으며, 수명이 긴 API 토큰이나 비밀번호보다 더 안전한 대안으로 여겨집니다. Trusted Publishing의 주요한 긍정적 보안 영향은 다음과 같습니다:

  • 발급되는 모든 업로드 자격 증명은 수명이 짧고 권한 범위를 최소한으로 제한할 수 있으므로, 자격 증명이 유출되었을 때의 “피해 반경”을 줄여 줍니다. 특히 자동 만료 덕분에 공격자는 Trusted Publishing을 사용하는 패키지를 대상으로 “지금 수집하고 나중에 사용하는” 공격을 벌일 수 없습니다.
  • Trusted Publishing은 업로드된 패키지를 그 패키지의 업로드 권한을 가진 CI/CD 또는 클라우드 제공자의 신원과 개념적으로 연결합니다. 이 연결은 다운스트림 소비자의 관점에서는 암묵적이지만, PEP 740 증명(attestation)이나 (덜 공식적인 방법으로) URL 검증을 통해 명시적으로 만들 수 있습니다.

하위 호환성

이 PEP는 기존 동작을 전혀 바꾸지 않으며, 기존 업로드 클라이언트 및 색인과 완전한 하위 호환성을 가집니다.

PyPI의 비표준 Trusted Publishing 업로드 흐름을 수행하는 기존 클라이언트는 이전과 같이 계속 동작하며, Trusted Publishing을 구현하지 않는 모든 색인으로의 기존 업로드도 마찬가지입니다.

교육 방법

이 PEP는 Python 패키징 생태계에서 이미 널리 채택된 신뢰할 수 있는 게시(Trusted Publishing)를 공식화한 것입니다. 이러한 채택과 함께 최종 사용자로서 Trusted Publishing을 도입하는 방법에 관한 다양한 교육 자료도 만들어졌으며, 그 예는 다음과 같습니다:

기각된 아이디어

“측면(lateral)” 탐색

이 PEP의 탐색 메커니즘은 RFC 8615에 정의된 .well-known 위치 체계를 사용합니다. 이 체계는 OpenID Connect 자체(OpenID Connect Discovery에서 사용)를 비롯한 기계 간 프로토콜에서 널리 채택되어 있습니다.

검토된 대안 아이디어는 “측면(lateral)” 탐색 메커니즘을 사용하는 것이었습니다. 이 방식에서는 업로드 클라이언트가 업로드 URL을 기준으로 인접 경로를 구성하여 탐색을 시도합니다. 예를 들어 https://upload.example.com/legacy/의 경우, 업로드 클라이언트는 https://upload.example.com/legacy/pytp(또는 이와 동등한 경로)에서 Trusted Publishing 지원 여부를 탐색하려고 시도합니다.

이 접근 방식의 장점은 색인 연산자가 .well-known 체계에서 요구하는 자신의 (하위) 도메인에 대한 제어 권한을 가질 필요가 없다는 점입니다(well-known URI는 도메인의 루트에서만 제공될 수 있기 때문입니다).

그러나 이 방식에도 단점이 있습니다:

  • 이 방식은 임의의 색인이 기존 기능을 방해하지 않으면서 인접 경로를 제공할 수 있다고 가정하지만, 반드시 그렇지는 않습니다. 예를 들어, 어떤 서드파티 구현은 이미 /legacy/{*} 아래의 모든 경로를 다른 용도로 사용하고 있을 수 있습니다.
  • 이 방식은 기존의 기계 간 프로토콜 관례와 일관성이 떨어지는데, 이러한 관례는 압도적으로 .well-known 체계를 사용합니다. 여기서 사용자 정의 위치 체계를 개발하면 .well-known 체계에 익숙한 서버 관리자와 연산자를 위한 추가 안내 자료가 필요하게 됩니다.

“암묵적” 발견

검토한 또 다른 대안은 PyPI가 현재 신뢰할 수 있는 게시(Trusted Publishing)에서 하는 것과 유사하게 “암묵적” 발견을 수행하는 것이었습니다. 즉, 명시적인 발견 단계 대신, 업로드하는 클라이언트가 곧바로 audience 및 토큰 발급 단계를 시도하고 발생하는 오류를 처리할 수 있습니다.

이 방식의 장점은 단순성입니다. 발견 단계에 필요한 네트워크 왕복을 없애고, 발견 응답에서 audience 및 토큰 발급 엔드포인트를 얻는 간접 과정도 없앱니다.

이 방식에도 단점이 있습니다:

  • PyPI의 암묵적 “발견” 단계는 업로드 URL의 기본 도메인을 기준으로 audience 및 토큰 발급 엔드포인트를 구성하는 것이므로, 이 방식은 특정 도메인을 암묵적으로 단일 색인/업로드 구현으로 제한합니다. 이 제한은 PyPI와 같은 단일 색인 호스트의 맥락에서는 수용할 수 있지만, 다른 색인 토폴로지(격리된 비공개 색인을 제공하는 색인 호스트 등)로는 일반화되지 않습니다.
  • 이 방식은 audience 및 토큰 발급 엔드포인트에 대해 전적으로 정적인 엔드포인트 구성 규칙에 의존하므로, 해당 엔드포인트를 변경해야 할 경우 기존 클라이언트에 상당한 혼란을 초래합니다.

각주