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 패키지 색인이 이미 Python 패키지 색인(PyPI)에 구현된 “Trusted Publishing”이라는 오용 방지형 자격 증명 교환 체계를 지원할 수 있도록 하는 표준 메커니즘을 제안합니다.

이 PEP에서 제안하는 메커니즘은 Trusted Publishing의 PyPI 기존 구현을 캡슐화하면서, 다른 색인도 기존 Python 패키지 업로드 클라이언트가 검색하고 상호 운용할 수 있는 방식으로 동일한 체계를 구현할 수 있도록 설계되었습니다.

동기

“Trusted Publishing”은 신뢰할 수 있는 제3자 서비스(예: CI/CD 또는 클라우드 제공자)의 단기간 유효한 identity credential을 색인에 게시하는 데 사용할 수 있는 단기간 유효하고 최소한의 범위로 제한된 upload credential로 교환하기 위해 OpenID Connect (OIDC) 표준을 사용하는 것을 가리키는 PyPI의 전문 용어입니다.

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

Trusted Publishing 표준이 없으면 채택에 장기적인 장애가 발생합니다. 제3자 색인(PyPI와 TestPyPI 이외의 색인)은 PyPI의 비표준화된 설계를 참조하지 않고는 Trusted Publishing을 쉽게 구현할 수 없습니다. 이는 비표준화된 업로드 API와 유사한 장기적인 성숙도 위험을 초래합니다. 패키지 업로드 클라이언트(예: Twineuv)는 색인 간 동작 차이를 받아들여야 하거나(해킹이 누적됨), PyPI가 아닌 Trusted Publishing 구현을 계속 거부해야 합니다.

근거

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

이 PEP에서 제안하는 설계는 PyPI의 기존 구현을 밀접하게 따르며, 업로드 클라이언트가 PyPI에 특화된 가정 없이 임의의 색인이 Trusted Publishing을 지원하는지 판단할 수 있도록 하는 검색 계층을 추가합니다.

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

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

요약하면, 이 PEP의 근거는 PyPI의 기존 인터페이스를 표준화하고 and이를 검색 가능하게 만드는 동시에 PyPI의 토폴로지와 일치하지 않는 색인 호스트도 Trusted Publishing을 구현할 수 있도록 하는 데 있습니다.

사양

이 PEP의 사양은 두 부분으로 구성됩니다.

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

제약 조건

명시적으로 달리 규정하지 않는 한 다음 제약 조건은 이 PEP 사양의 모든 부분에 적용됩니다.

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

    업로드 클라이언트는 이 제약 조건을 충족하지 않는 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 헤더가 있어야 합니다. Accept 헤더가 없는 경우, 수신 서버는 이 헤더가 있는 것처럼 동작해야 합니다.

    다른 Accept 헤더가 있는 경우, 수신 서버는 406 Not Acceptable 상태 코드로 응답해야 합니다.

  • 달리 지정되지 않는 한, 서버의 모든 오류(4xx 및 5xx) 응답은 RFC 9457 (HTTP API용 문제 세부 정보) 형식을 사용해야 합니다. 특히 서버는 Section 3에 정의된 “Problem Details JSON Object”를 사용해야 하며, 응답에서 application/problem+json 미디어 유형을 사용해야 합니다.

Trusted Publishing 디스커버리

모든 Python 패키지 업로드는 현재 “엔드포인트 주도” 방식입니다. 즉, 업로드 클라이언트(예: twineuv)에는 업로드 URL이 제공되며(그리고 단순히 도메인 이름만 제공되는 것이 아니며), 이를 의미합니다.

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

아래에서 제안하는 디스커버리 메커니즘은 이 사실을 활용하여 단일 도메인이 여러 색인과 해당 업로드 엔드포인트에 대한 지원을 알릴 수 있도록 합니다.

디스커버리 메커니즘은 다음과 같습니다.

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

    위의 예에서 path component는 /legacy/입니다.

  3. 업로드 클라이언트는 path component에 대해 쿼리 안전 URL 인코딩을 수행하고(즉, 정방향 슬래시와 공백의 인코딩을 포함하여 RFC 3986에 정의된 퍼센트 인코딩을 수행하여) discovery key를 생성합니다.

    위의 예에서 discovery key는 %2Flegacy%2F입니다. [1]

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

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

  5. 업로드 클라이언트는 discovery URL에 HTTP GET 요청을 수행합니다.
  6. 해당 색인이 주어진 업로드 URL에 대해 Trusted Publishing을 지원하는 경우 서버는 200 OK 상태 코드와 JSON 객체를 포함하는 본문으로 응답합니다.

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

    • audience-endpoint: 토큰 교환 중 사용할 OIDC audience endpoint의 URL을 포함하는 문자열입니다.
    • token-mint-endpoint: 토큰 교환 중 사용할 토큰 발급 엔드포인트의 URL을 포함하는 문자열입니다.

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

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

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

    {
       "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 상태 코드로 응답해야 합니다.

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

Trusted Publishing 토큰 교환

업로드 클라이언트가 성공적으로 discovery 절차를 완료하면 실제 Trusted Publishing 토큰 교환을 진행할 수 있습니다.

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

  1. 업로드 클라이언트는 검색 과정에서 얻은 audience endpoint를 사용하여 색인에 예상 OIDC audience를 요청합니다.
  2. 업로드 클라이언트는 예상 audience를 사용하여 사용 중인 Trusted Publishing 제공자(즉, 업로드가 수행되는 CI/CD 또는 클라우드 제공자)로부터 적절하게 바인딩된 identity credential을 얻습니다. 이 단계의 세부 사항은 제공자별로 다르며 이 PEP의 범위에 포함되지 않습니다. [2]
  3. 업로드 클라이언트는 검색 과정에서 얻은 token minting endpoint를 사용하여 획득한 identity credential을 색인에 업로드하는 데 사용할 수 있는 단기 upload credential로 교환합니다.

Audience 검색

예상 OIDC audience를 검색하기 위해 업로드 클라이언트는 discovery에서 얻은 audience endpoint로 HTTP GET 요청을 수행합니다.

성공하면 서버는 200 OK상태 코드와 다음 필드를 포함하는 JSON 객체가 본문에 담긴 응답을 반환합니다.

  • audience: 예상 OIDC audience를 포함하는 문자열입니다.

실패하면 서버는 적절한 오류 상태를 나타내기 위해 400 또는 500 범위의 표준 HTTP 오류 코드를 사용하여 MUST 응답해야 합니다.

토큰 발급

업로드 클라이언트가 Audience Retrieval을 수행하고 Trusted Publishing 제공자로부터 identity credential을 얻은 후에는 upload credential을 발급할 수 있습니다.

upload credential을 발급하기 위해 업로드 클라이언트는 discovery중에 얻은 token minting endpoint로 HTTP POST 요청을 수행합니다. POST 요청의 페이로드는 다음을 포함하는 JSON 객체여야 MUST 합니다.

  • token: Trusted Publishing 제공자로부터 얻은 identity credential을 포함하는 문자열입니다.
  • features: 발급되는 upload credential에 원하는 기능을 나타내는 optional 문자열 배열입니다. 클라이언트가 이 필드를 제공하지 않으면 서버는 검색 과정에서 default-features필드에 지정된 자체 기본 기능을 사용해야 MUST 합니다.

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

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

성공하면 서버는 200 OK상태 코드와 다음 필드를 포함하는 JSON 객체가 본문에 담긴 응답을 반환합니다.

  • token: upload credential을 포함하는 문자열입니다. upload credential의 형식은 구현 및 색인에 따라 정의됩니다.
  • expires: upload credential이 만료되는 시점을 나타내는 Unix 타임스탬프를 포함하는 optional 정수입니다. 이 필드가 없으면 업로드 클라이언트는 만료 시점이 요청 시각으로부터 15분(900초)을 초과하지 않는다고 간주해도 MAY 합니다.

    서버는 요청 시각으로부터 15분(900초)보다 짧거나 6시간(21,600초)보다 긴 시간 후에 만료되는 임시 upload credential을 발급해서는 MUST NOT 합니다.

    최대 만료 시간으로 6시간을 선택한 것은 GitHub Actions와 같은 인기 CI/CD 제공자의 일반적인 런타임 제한에 맞추기 위한 것입니다.

    업로드 클라이언트는 필요한 경우 이 시간(또는 위에서 지정한 최소 시간)을 사용하여 upload credential을 언제 갱신할지 결정해도 MAY 합니다.

실패하면 서버는 적절한 오류 상태를 나타내기 위해 400 또는 500 범위의 표준 HTTP 오류 코드를 사용하여 MUST 응답해야 합니다.

기능 협상

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

다음 기능이 정의됩니다:

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

보안 영향

이 PEP는 PyPI에서 이미 사용 중인 Trusted Publishing 흐름을 공식적으로 표준화하여 Python 패키징 생태계의 보안성과 투명성을 향상하고자 합니다.

이 PEP는 Trusted Publishing 검색 또는 교환 흐름 자체와 관련된 긍정적이거나 부정적인 보안 영향을 식별하지 않습니다.

이러한 흐름과는 별개로 Trusted Publishing 자체PyPI의 보안 모델을 갖추고 있으며, 장기간 유효한 API 토큰이나 비밀번호보다 더 안전한 대안으로 간주됩니다. Trusted Publishing의 주요 긍정적 보안 영향은 다음과 같습니다:

  • 발급되는 모든 업로드 자격 증명은 수명이 짧고 범위를 최소한으로 지정할 수 있으므로, 손상된 자격 증명의 “피해 범위”를 제한합니다. 특히 자동 만료 기능으로 인해 공격자는 Trusted Publishing을 사용하는 패키지를 대상으로 “지금 수집하고 나중에 사용하기” 공격을 수행할 수 없습니다.
  • Trusted Publishing은 업로드된 패키지를 해당 패키지의 업로드 권한이 있는 CI/CD 또는 클라우드 제공자의 신원과 개념적으로 연결합니다. 이 연결은 다운스트림 소비자의 관점에서는 암시적이지만, PEP 740 증명 또는 (덜 공식적으로는) URL 검증을 통해 명시적으로 만들 수 있습니다.

하위 호환성

이 PEP는 기존 동작을 변경하지 않으며 기존 업로드 클라이언트 및 인덱스와 완전한 하위 호환성을 유지합니다.

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

이를 가르치는 방법

이 PEP는 Python 패키징 생태계에서 이미 널리 도입된 Trusted Publishing의 정식화입니다. 이러한 도입과 함께 최종 사용자가 Trusted Publishing을 도입하는 방법을 다루는 다양한 교육 자료도 제공되었으며, 그 예는 다음과 같습니다:

거부된 아이디어

“측면” 검색

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

고려된 대안은 “측면” 검색 메커니즘을 사용하는 것이었으며, 이 메커니즘에서는 업로드 클라이언트가 업로드 URL에 상대적인 인접 경로를 구성하여 검색을 시도합니다. 예를 들어 https://upload.example.com/legacy/의 경우, 업로드 클라이언트는 https://upload.example.com/legacy/pytp(또는 이에 준하는 경로)에서 Trusted Publishing 지원을 검색합니다.

이 접근 방식의 장점은 .well-known 체계가 인덱스 운영자에게 (하위) 도메인을 제어할 것을 요구하는 반면, 이 방식에서는 그러한 요구가 없다는 점입니다(잘 알려진 URI는 도메인의 루트에서만 제공할 수 있기 때문입니다).

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

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

“암시적” 검색

고려된 또 다른 대안은 Trusted Publishing과 관련하여 현재 PyPI가 수행하는 방식과 유사하게 “암시적” 검색을 수행하는 것이었습니다. 명시적인 discovery 단계 대신, 업로드 클라이언트가 audience 및 토큰 발급 단계를 바로 시도하고 발생하는 오류를 처리할 수 있습니다.

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

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

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

각주