[Final] PEP 470 - Removing External Hosting Support on PyPI
원문 링크: PEP 470 - Removing External Hosting Support on PyPI
상태: Final 유형: Process 작성일: 12-May-2014
PEP 470 – PyPI 외부 호스팅 지원 제거
초록 (Abstract)
PEP 470은 PyPI(Python Package Index) 외부에서 파일을 호스팅하는 기능과 PEP 438에 의해 추가되었던 rel
정보(링크 분류) 및 메타 태그(API 버전 표시) 기능을 더 이상 사용하지 않도록 하고 제거하는 것을 제안합니다.
배경 (Rationale)
과거 PyPI는 파일을 직접 호스팅하거나 설치 가능한 파일을 자동으로 검색하는 기능이 없었습니다. 대신 이름 충돌을 방지하고 프로젝트를 쉽게 찾을 수 있도록 중앙 레지스트리 역할을 했습니다. 시간이 지나면서 setuptools
는 PyPI 페이지와 링크된 페이지를 스크랩(scraping)하여 자동으로 다운로드 및 설치할 수 있는 항목을 찾기 시작했습니다. 이 과정은 결국 “Simple” API로 발전했으며, PyPI는 프로젝트가 릴리스 파일을 직접 업로드할 수 있는 기능을 추가하여 인덱스 역할 외에 레포지토리(저장소) 역할도 수행하게 되었습니다.
이러한 역사적 배경과 유기적인 성장으로 인해 PyPI는 Python 프로젝트를 쉽게 찾을 수 있는 ‘인덱스’ 역할과 파일을 호스팅, 다운로드 및 설치할 수 있는 ‘중앙 레포지토리’라는 두 가지 중요한 역할을 수행하게 되었습니다. 그러나 이 두 역할 간의 경계가 모호해지면서 최종 사용자와 프로젝트 작성자 모두에게 혼란을 야기했습니다. 특히 외부 서비스가 중단되었을 때, 사용자는 PyPI가 작동하는데도 특정 프로젝트가 설치되지 않는 이유를 이해하기 어려웠습니다.
PEP 438은 프로젝트가 레포지토리 기능을 사용하는지 명시적으로 선언하게 함으로써 이 문제를 해결하려 했습니다. 또한, 설치 관리자가 링크를 “내부(internal)”, “확인 가능한 외부(verifiable external)”, “확인 불가능한 외부(unverifiable external)”로 분류하도록 했습니다. PEP 438은 pip 1.4(2013년 7월 23일 릴리스)에 채택 및 구현되었고 pip 1.5(2014년 1월 2일 릴리스)에서 최종 전환되었습니다. 이 PEP는 PyPI의 레포지토리 기능 활용을 늘리는 데 성공했지만, 최종 사용자와 작성자 모두에게 새로운 혼란과 어려움을 초래했습니다.
PEP 470은 명시적인 다중 레포지토리(Multiple Repository) 사용으로 전환하여 이 두 역할 간의 경계를 명확히 하고, PyPI를 레포지토리로 사용하지 않으려는 사용자에게 발생하는 “숨겨진” 문제들을 제거하고자 합니다.
핵심 사용자 경험 기대치 (Key User Experience Expectations)
- 시스템, 사용자 또는 가상 환경 수준에서 적절히 구성되었을 때 외부 호스팅이 “그냥 작동(just work)”하도록 쉽게 허용합니다.
- 혼란스러운 “확인 가능한 외부” 및 “확인 불가능한 외부” 구분을 사용자 경험에서 완전히 제거합니다.
- PyPI의 레포지토리 측면은 기본 패키지 호스팅 위치(대부분의 클라이언트 도구에서 옵트인(opt-in)이 아닌 옵트아웃(opt-out)으로 취급되는 유일한 위치)가 되어야 합니다.
- 이러한 측면 외에, PyPI에서 호스팅하는 것이 자체 패키지 레포지토리를 호스팅하는 것보다 향상된 사용자 경험을 제공해서는 안 됩니다.
- 대부분의 공격자에 대해 안전한 기본 동작을 제공하면서 위 모든 것을 수행합니다.
추가 레포지토리 사용 이유 (Why Additional Repositories?)
pip
와 easy_install
/setuptools
와 같은 일반적인 설치 도구들은 설치 요구 사항을 충족하기 위해 파일을 검색할 추가 위치 개념을 수년 동안 지원해왔습니다. 이는 새로운 플래그나 개념을 “단계적으로 도입”할 필요가 없으며, PyPI 외의 다른 레포지토리에서 프로젝트를 설치하는 솔루션이 최종 사용자의 설치 관리자 버전에 관계없이 작동한다는 의미입니다. 이 개념은 Python 도구에 오랫동안 존재했을 뿐만 아니라, 다른 언어와 OS 수준의 패키지 도구에서도 보편적으로 사용되어 많은 사용자가 이미 익숙할 가능성이 높습니다.
또한, 다중 레포지토리 접근 방식은 PyPI 인덱스에 포함되기를 원하지만 레포지토리 부분은 사용하지 않으려는 프로젝트에만 국한되지 않고, 더 넓은 범위에서 유용합니다. 예를 들어, 회사가 내부 패키지를 호스팅하는 레포지토리를 운영하거나, 프로젝트가 알파, 베타, 릴리스 후보, 최종 릴리스와 같은 여러 “채널”의 릴리스를 가질 때 유용합니다. 이는 또한 멀티 기가바이트 데이터 파일이나 (현재로서는) Linux Wheels와 같이 PyPI에 업로드할 수 없는 파일을 호스팅하려는 프로젝트에도 사용될 수 있습니다.
PEP 438 또는 유사한 방식이 아닌 이유 (Why Not PEP 438 or Similar?)
pip
와 setuptools
에 추가 검색 위치 지원은 오랫동안 존재했지만, PEP 438 지원은 pip
1.4 버전부터만 존재했으며 setuptools
에는 아직 구현되지 않았습니다. PEP 438의 설계는 오래된 설치 관리자를 사용하는 경우에도 외부 파일이 필요 없는 프로젝트 사용자에게는 여전히 이점을 제공했지만, 외부 파일이 필요한 프로젝트의 경우 사용자는 여전히 잠재적으로 신뢰할 수 없거나 심지어 안전하지 않은 파일을 자동으로 다운로드 받게 됩니다. 이 시스템은 PyPI의 역사에서 비롯된 Python에만 독특한 개념이므로, 대부분의 사용자에게 생소할 가능성이 높습니다.
또한, PEP 438이 제안한 분류 시스템은 실제로 최종 사용자에게 극도로 혼란스럽다는 것이 밝혀졌습니다. 사용자는 프로젝트를 설치하려다가 오류 메시지를 받고 --allow-external
플래그를 추가하라는 메시지를 본 후 다시 시도하고, 또 다른 오류 메시지와 함께 --allow-unverified
를 추가하라는 메시지를 받은 후 세 번째 시도에서야 비로소 설치에 성공하는 경우가 흔했습니다.
이러한 사용자 경험 실패는 다음과 같은 이유로 발생합니다.
pip
가 Simple API에서 프로젝트에 대한 파일을 찾을 수 있다면, 다른 파일을 찾으려 하지 않고 해당 파일을 사용합니다. 이는 PEP 438의 많은 이점을 지울 수 있으므로 일반적으로 올바른 동작이지만, 프로젝트가 오래된 파일을 PyPI에 업로드한 경우 해당 파일이 사용될 수 있습니다.- PEP 438은 대부분의 프로젝트가 PyPI에 직접 업로드하거나 릴리스 파일에 직접 링크할 것이라는 암묵적인 가정을 했습니다. 많은 프로젝트가 PyPI에 업로드하기로 결정했지만, 일부는 PEP 438의 사용자 경험이 너무 나빠서 강요받았다고 느꼈기 때문입니다. 더 우려스러운 점은 매우 적은 수의 프로젝트만이 파일에 직접 안전하게 링크하기로 선택했으며, 대부분은 실제 파일을 찾기 위해 스크랩해야 하는 페이지에 링크되어 있어 안전한 옵션(
--allow-external
)이 거의 무용지물이 되었다는 것입니다. - 작성자가 파일에 직접 링크하고 싶어도 안전하게 링크하는 것이 쉽지 않습니다. URL 해시에 MD5 해시(역사적인 이유로)를 포함해야 하는데, 이를 포함하지 않으면 파일은 “확인되지 않음(unverified)”으로 간주됩니다.
- PEP 438은 보안 중심적인 관점을 취하며, 확인되지 않은 프로젝트에 대한 전역적인 옵트인(opt-in)을 어떤 형태로든 허용하지 않습니다. 이는 일반적으로 좋은 일이지만,
--allow-external myproject --allow-unverified myproject myproject
와 같이 매우 장황하고 반복적인 명령어 호출을 초래합니다.
다중 레포지토리/인덱스 지원 (Multiple Repository/Index Support)
설치 관리자는 여러 URL 위치를 가리키는 기능을 구현하거나 계속 제공해야 합니다. 추가 위치를 사용하려는 사용자에게 어떤 메커니즘을 제공할지는 각 구현에 맡겨집니다.
또한, 여러 레포지토리가 사용될 때 설치 후보를 검색하는 메커니즘도 각 구현에 맡겨지지만, 일단 구성되면 기본 레포지토리가 아니라는 이유만으로 특정 레포지토리 사용을 discourage, warn, 또는 부정적으로 다루어서는 안 됩니다.
현재 pip
와 setuptools
는 두 레포지토리에서 찾을 수 있는 최적의 설치 후보를 사용하여 다중 레포지토리 지원을 구현하고 있으며, 본질적으로 하나의 큰 레포지토리처럼 취급합니다.
설치 관리자는 기본 레포지토리 사용을 제거하거나 비활성화하는 메커니즘도 구현해야 합니다. 이를 달성하는 구체적인 방법은 각 구현에 맡겨집니다.
설치 관리자는 특정 레포지토리에서 설치하려는 프로젝트를 화이트리스트(whitelist) 및 블랙리스트(blacklist)에 추가하는 메커니즘도 구현해야 합니다. 이를 달성하는 구체적인 방법은 각 구현에 맡겨집니다.
Python 패키징 가이드(Python packaging guide)는 자체 레포지토리를 설정하는 옵션을 자세히 설명하는 섹션으로 업데이트되어야 하며, PyPI에 호스팅하지 않으려는 프로젝트가 이 문서를 참조할 수 있도록 해야 합니다. 여기에는 자체 레포지토리를 사용하는 프로젝트가 프로젝트 설명에 설치 방법을 문서화해야 한다는 제안이 포함되어야 합니다.
링크 스크랩(Spidering)의 사용 중단 및 제거 (Deprecation and Removal of Link Spidering)
PyPI에 새로운 호스팅 모드인 pypi-only
가 추가될 예정입니다. 이 모드는 PEP 438이 제공했던 pypi-explicit
, pypi-scrape
, pypi-scrape-crawl
외에 추가되며, 프로젝트의 Simple API 페이지가 PyPI에 직접 호스팅된 파일만 나열하고 다른 어떤 것도 링크하지 않도록 변경합니다.
이 PEP가 승인되고 pypi-only
모드가 추가되면, 모든 새 프로젝트는 기본적으로 pypi-only
모드로 설정되며 이 설정을 변경할 수 없게 됩니다.
그 후 PyPI에만 호스팅되는 모든 프로젝트에 이메일이 발송되어 한 달 이내에 해당 프로젝트가 자동으로 pypi-only
모드로 전환될 것임을 알립니다. 이 이메일 발송 한 달 후, PyPI에만 호스팅되는 해당 프로젝트들은 영구적으로 pypi-only
모드로 설정됩니다.
동시에 PyPI 외부 호스팅에 의존하는 프로젝트에도 이메일이 발송됩니다. 이 이메일은 PyPI에서 외부 호스팅 파일이 더 이상 사용되지 않으며, 이메일 발송 3개월 후에는 모든 외부 링크가 설치 관리자 API에서 제거될 것임을 경고합니다. 이 이메일에는 프로젝트를 PyPI에 호스팅하도록 전환하는 방법, PyPI 자격 증명과 패키지 이름을 입력하면 파일을 자동으로 다운로드하여 PyPI에 다시 호스팅할 수 있는 스크립트 또는 패키지에 대한 링크, 그리고 자체 인덱스 페이지를 설정하는 방법에 대한 지침이 포함되어야 합니다. 또한, PyPI 이용 약관 링크와 설치 가능한 파일이 발견된 PyPI에 등록된 링크 목록도 포함되어야 합니다.
초기 이메일 발송 2개월 후, 여전히 외부 호스팅에 의존하는 프로젝트에 다시 이메일이 발송됩니다. 이 이메일은 첫 번째 이메일과 동일한 모든 정보를 포함하지만, 제거 날짜가 3개월 후가 아닌 1개월 후로 변경됩니다.
마지막으로 한 달 후, 모든 프로젝트가 pypi-only
모드로 전환되고 PyPI는 외부 링크 파일 기능을 제거하도록 수정됩니다.
변경 요약 (Summary of Changes)
레포지토리 측면 (Repository side)
- PEP 438에서 정의한 호스팅 모드를 사용 중단하고 제거합니다.
- Simple API가 레포지토리 내에 포함된 파일만 나열하도록 제한합니다.
클라이언트 측면 (Client side)
- 다중 레포지토리 지원을 구현합니다.
- 기본 레포지토리를 제거/비활성화하는 메커니즘을 구현합니다.
- PEP 438을 사용 중단하고 제거합니다.
영향 (Impact)
이 PEP 작성 시점에 PyPI에 호스팅된 65,232개의 프로젝트 중 59개(0.09%)는 PyPI 외부에서 안전하게 호스팅되는 외부 파일에 의존하고 있었고, 931개(1.4%)는 PyPI 외부에서 안전하지 않게 호스팅되는 외부 파일에 의존하고 있었습니다. 이는 전체 프로젝트의 약 1.5%가 이번 변경으로 영향을 받지만, 98.5%는 기존과 같이 계속 작동할 것임을 나타냅니다. 또한, 영향을 받는 프로젝트 중 5%만이 PEP 438 기능을 사용하여 PyPI 외부에서 안전하게 호스팅하고 있었고, 95%는 중간자 공격(Man In The Middle attack)을 통해 사용자에게 원격 코드 실행(Remote Code Execution) 위험을 노출하고 있었습니다. 실제 영향은 이보다 훨씬 작을 것으로 예상되지만, 가장 광범위한 정의를 사용하여 잠재적인 영향을 측정했습니다.
자주 묻는 질문 (Frequently Asked Questions)
<X>
때문에 PyPI에 프로젝트를 호스팅할 수 없습니다. 어떻게 해야 하나요?
<X>
가 PyPI 자체의 본질적인 문제인지, 아니면 PyPI가 기능을 추가하여 해결할 수 있는 문제인지 먼저 결정해야 합니다. PyPI가 기능을 추가할 수 있다면 해당 기능을 제안해야 합니다. 그러나 <X>
가 파일에 대한 제어 유지와 같이 PyPI에 본질적인 문제라면, 자체 패키지 레포지토리를 설정하고 프로젝트 설명에 사용자의 설치 관리자에 추가하는 방법을 안내해야 합니다.
이 PEP 때문에 사용자 경험이 이전보다 나빠졌는데, 어떻게 설명해야 하나요?
이 답변의 일부는 개별 프로젝트마다 다를 것입니다. 사용자에게 설치 관리자의 기본 레포지토리 목록에 있는 것을 사용하지 않고 자체 레포지토리에 호스팅하기로 결정한 이유를 설명해야 합니다. 그러나 답변의 일부는 외부 링크를 투명하게 포함하던 이전 동작이 보안 위험(대부분의 경우 MITM 공격을 통해 최종 사용자 머신에서 임의의 Python 코드를 실행할 수 있었음)과 신뢰성 문제였다는 점을 설명하는 것입니다. PEP 438은 이를 명시적으로 옵트인하게 함으로써 해결하려 했지만, 여러 가지 심각한 사용성 문제를 야기했습니다. PEP 470은 많은 사용자에게 익숙한 모델, 즉 Linux 배포판 레포지토리와 유사한 모델로 단순화하는 것을 목표로 합니다.
레포지토리 구조로 전환하면 워크플로가 깨지거나 호스트에서 허용되지 않습니다.
레포지토리에 필요한 것을 기꺼이 지원하는 저렴하거나 무료 호스트가 많이 있습니다. 특히 파일이 실제로 있는 위치를 가리키는 올바른 구조의 호스트를 생성할 수 있다면 파일을 다르게 업로드할 필요가 없습니다. 많은 호스트가 공유 도메인 이름을 사용하여 무료 HTTPS를 제공하며, StartSSL이나 가까운 미래에 Let’s Encrypt에서 무료 HTTPS 인증서를 얻을 수 있습니다.
<X>
는 왜 제공하지 않습니까?
여기에 대한 답변은 <X>
가 무엇인지에 따라 다르지만, 일반적으로 다음 중 하나입니다.
- 생각해 본 적이 없고 아무도 제안하지 않았습니다.
<X>
에 대한 적절한 솔루션을 설계할 충분한 경험이 없으며, 도메인 전문가의 도움을 환영합니다.- 우리는 오픈 소스 프로젝트이며, 아직
<X>
를 설계하고 구현할 자원봉사자가 결정되지 않았습니다.
추가 기능 제안을 위한 PEP는 언제나 환영하지만, <X>
를 정확하게 설계할 시간과 전문 지식을 가진 사람이 필요합니다. 이 PEP는 PyPI의 기능을 Linux 배포판 레포지토리와 같은 기존 모델과 유사한, 쉽게 이해할 수 있는 기본 수준으로 명확하게 만드는 데 중점을 둡니다.
어차피 자체 레포지토리를 운영하는데, PyPI에 등록해야 하는 이유는 무엇인가요?
PyPI는 Python 생태계에서 두 가지 중요한 기능을 수행합니다. 하나는 pip
또는 다른 패키지 관리자가 다운로드하고 설치하는 실제 파일의 중앙 레포지토리이며, 이 PEP가 다루는 부분이자 자체 레포지토리를 운영하면 대체될 수 있는 기능입니다. 그러나 PyPI는 이름 충돌을 방지하기 위해 누가 어떤 이름을 소유하는지에 대한 중앙 레지스트리 역할도 합니다. DNS가 Python 패키지용이라고 생각하시면 됩니다. 이름이 선착순으로 할당되도록 보장하는 것 외에도, 사용자가 새로운 프로젝트를 검색하고 찾을 수 있는 단일 장소를 제공합니다. 따라서 간단한 답변은, 이름 충돌을 피하고 사람들이 프로젝트를 쉽게 찾을 수 있도록 여전히 PyPI에 프로젝트를 등록해야 한다는 것입니다.
거부된 제안 (Rejected Proposals)
외부 호스팅 인덱스에 대한 더 쉬운 검색 허용 (Allow easier discovery of externally hosted indexes)
이 PEP의 이전 버전에는 PyPI와 설치 관리자 모두에 새로운 기능이 포함되어 있었습니다. 이 기능은 프로젝트 작성자가 PyPI에 URL 목록을 입력하면 설치 관리자가 PyPI에 업로드된 파일을 무시하고 대신 최종 사용자에게 추가할 수 있는 추가 URL에 대해 오류 메시지를 반환하여 설치가 작동하도록 하는 것이었습니다. 이 기능은 PEP 438 솔루션과 유사한 많은 문제를 야기했던 UX 문제를 피할 솔루션을 개발하기 너무 어렵다는 것이 입증되어 PEP의 범위에서 제거되었습니다. 필요한 경우, 미래의 PEP에서 이 아이디어를 다시 검토할 수 있습니다.
현재 분류 시스템 유지 및 옵션 조정 (Keep the current classification system but adjust the options)
이 PEP는 현재 시스템의 사용성 문제 중 일부를 해결하려 하지만, PEP 438의 일반적인 취지를 유지하는 여러 관련 제안을 거부합니다. 여기에는 다음이 포함됩니다.
- 안전하게 외부 호스팅된 파일은 기본적으로 허용하지만, 안전하지 않게 호스팅된 파일은 허용하지 않습니다.
- 안전하게 외부 호스팅된 파일은 기본적으로 허용하지 않고, 전역 플래그를 통해서만 허용하며, 안전하지 않게 호스팅된 파일은 허용하지 않습니다.
- PEP 438에서 제안한 경로를 계속 따르고 안전하지 않게 외부 호스팅하는 옵션을 제거하지만, 안전하게 외부 호스팅하는 옵션은 계속 허용합니다.
이러한 제안들은 다음과 같은 이유로 거부되었습니다.
- PEP 438에 도입된 분류 시스템은 Python 패키징 맥락에서도 일반적으로 적용할 수 없는 PyPI만의 독특한 개념입니다. 추가 개념을 도입하는 데에는 비용이 따릅니다.
- 분류 시스템 자체는 설명하기 모호하며, 프로젝트가 필요로 할 링크 분류를 미리 결정하려면 프로젝트의
/simple/<project>/
페이지와 해당 페이지에서 링크된 모든 URL을 검사해야 합니다. - 자동 검색을 위해 링크되면서도 외부에서 호스팅할 수 있는 기능은 적은 보상에 비해 상당한 고통과 복잡성을 유발하는 역사적인 유물입니다.
- 설치 관리자가 사용자 인터페이스를 최적화하거나 정리할 수 있는 능력은 수행해야 하는 암묵적인 링크 스크랩의 특성 때문에 제한적입니다. 이는
--allow-*
옵션뿐만 아니라 링크가 실패할 것으로 예상되는지 여부를 판단할 수 없는 점에도 적용됩니다. - 옵션을 활성화할 때 매우 광범위하게 적용되며, PEP 438은 패키지별 옵션으로 이를 제한하려 합니다. 그러나 오랜 기간 존재했던 프로젝트는 종종 Simple Index에 여러 다른 URL이 나열될 수 있습니다. 이들 중 적어도 하나가 더 이상 프로젝트의 제어하에 있지 않은 것은 드문 일이 아닙니다. 등록되지 않은 도메인은 대부분 무해하게 존재하지만,
pip
는 매 검색 단계마다 해당 도메인에서 설치를 시도할 것입니다. 이는 공격자가 안전하지 않은 외부 URL에 의존하는 프로젝트를 찾아 만료된 도메인을 등록하기만 하면 사용자를 공격할 수 있음을 의미합니다.
이 PEP를 구현하되 기존 링크는 제거하지 않음 (Implement this PEP, but Do Not Remove the Existing Links)
이것은 기본적으로 이 PEP의 하위 호환 버전입니다. 이전 클라이언트를 사용하거나 이 PEP를 구현하지 않은 클라이언트를 사용하는 사람들이 아무것도 변경되지 않은 것처럼 계속 작동하도록 허용하려고 합니다. 이 제안은 거부되었습니다. 왜냐하면 대부분의 시나리오가 사용 중단된 기능을 안전하지 않게 사용하는 경우이기 때문입니다. 이 PEP의 의견은 최종 사용자를 대신하여 안전하지 않은 작업이 자동으로 발생하는 것을 허용하는 것은 용납할 수 없는 해결책이라는 것입니다.
저작권 (Copyright)
이 문서는 퍼블릭 도메인에 공개되었습니다. This document has been placed in the public domain.
PEP 470 – PyPI 외부 호스팅 지원 제거
초록 (Abstract)
PEP 470은 PyPI(Python Package Index) 외부에서 파일을 호스팅하는 기능과 PEP 438에 의해 추가되었던 rel
정보(링크 분류) 및 메타 태그(API 버전 표시) 기능을 더 이상 사용하지 않도록 하고 제거하는 것을 제안합니다.
배경 (Rationale)
과거 PyPI는 파일을 직접 호스팅하거나 설치 가능한 파일을 자동으로 검색하는 기능이 없었습니다. 대신 이름 충돌을 방지하고 프로젝트를 쉽게 찾을 수 있도록 중앙 레지스트리 역할을 했습니다. 시간이 지나면서 setuptools
는 PyPI 페이지와 링크된 페이지를 스크랩(scraping)하여 자동으로 다운로드 및 설치할 수 있는 항목을 찾기 시작했습니다. 이 과정은 결국 “Simple” API로 발전했으며, PyPI는 프로젝트가 릴리스 파일을 직접 업로드할 수 있는 기능을 추가하여 인덱스 역할 외에 레포지토리(저장소) 역할도 수행하게 되었습니다.
이러한 역사적 배경과 유기적인 성장으로 인해 PyPI는 Python 프로젝트를 쉽게 찾을 수 있는 ‘인덱스’ 역할과 파일을 호스팅, 다운로드 및 설치할 수 있는 ‘중앙 레포지토리’라는 두 가지 중요한 역할을 수행하게 되었습니다. 그러나 이 두 역할 간의 경계가 모호해지면서 최종 사용자와 프로젝트 작성자 모두에게 혼란을 야기했습니다. 특히 외부 서비스가 중단되었을 때, 사용자는 PyPI가 작동하는데도 특정 프로젝트가 설치되지 않는 이유를 이해하기 어려웠습니다.
PEP 438은 프로젝트가 레포지토리 기능을 사용하는지 명시적으로 선언하게 함으로써 이 문제를 해결하려 했습니다. 또한, 설치 관리자가 링크를 “내부(internal)”, “확인 가능한 외부(verifiable external)”, “확인 불가능한 외부(unverifiable external)”로 분류하도록 했습니다. PEP 438은 pip 1.4(2013년 7월 23일 릴리스)에 채택 및 구현되었고 pip 1.5(2014년 1월 2일 릴리스)에서 최종 전환되었습니다. 이 PEP는 PyPI의 레포지토리 기능 활용을 늘리는 데 성공했지만, 최종 사용자와 작성자 모두에게 새로운 혼란과 어려움을 초래했습니다.
PEP 470은 명시적인 다중 레포지토리(Multiple Repository) 사용으로 전환하여 이 두 역할 간의 경계를 명확히 하고, PyPI를 레포지토리로 사용하지 않으려는 사용자에게 발생하는 “숨겨진” 문제들을 제거하고자 합니다.
핵심 사용자 경험 기대치 (Key User Experience Expectations)
- 시스템, 사용자 또는 가상 환경 수준에서 적절히 구성되었을 때 외부 호스팅이 “그냥 작동(just work)”하도록 쉽게 허용합니다.
- 혼란스러운 “확인 가능한 외부” 및 “확인 불가능한 외부” 구분을 사용자 경험에서 완전히 제거합니다.
- PyPI의 레포지토리 측면은 기본 패키지 호스팅 위치(대부분의 클라이언트 도구에서 옵트인(opt-in)이 아닌 옵트아웃(opt-out)으로 취급되는 유일한 위치)가 되어야 합니다.
- 이러한 측면 외에, PyPI에서 호스팅하는 것이 자체 패키지 레포지토리를 호스팅하는 것보다 향상된 사용자 경험을 제공해서는 안 됩니다.
- 대부분의 공격자에 대해 안전한 기본 동작을 제공하면서 위 모든 것을 수행합니다.
추가 레포지토리 사용 이유 (Why Additional Repositories?)
pip
와 easy_install
/setuptools
와 같은 일반적인 설치 도구들은 설치 요구 사항을 충족하기 위해 파일을 검색할 추가 위치 개념을 수년 동안 지원해왔습니다. 이는 새로운 플래그나 개념을 “단계적으로 도입”할 필요가 없으며, PyPI 외의 다른 레포지토리에서 프로젝트를 설치하는 솔루션이 최종 사용자의 설치 관리자 버전에 관계없이 작동한다는 의미입니다. 이 개념은 Python 도구에 오랫동안 존재했을 뿐만 아니라, 다른 언어와 OS 수준의 패키지 도구에서도 보편적으로 사용되어 많은 사용자가 이미 익숙할 가능성이 높습니다.
또한, 다중 레포지토리 접근 방식은 PyPI 인덱스에 포함되기를 원하지만 레포지토리 부분은 사용하지 않으려는 프로젝트에만 국한되지 않고, 더 넓은 범위에서 유용합니다. 예를 들어, 회사가 내부 패키지를 호스팅하는 레포지토리를 운영하거나, 프로젝트가 알파, 베타, 릴리스 후보, 최종 릴리스와 같은 여러 “채널”의 릴리스를 가질 때 유용합니다. 이는 또한 멀티 기가바이트 데이터 파일이나 (현재로서는) Linux Wheels와 같이 PyPI에 업로드할 수 없는 파일을 호스팅하려는 프로젝트에도 사용될 수 있습니다.
PEP 438 또는 유사한 방식이 아닌 이유 (Why Not PEP 438 or Similar?)
pip
와 setuptools
에 추가 검색 위치 지원은 오랫동안 존재했지만, PEP 438 지원은 pip
1.4 버전부터만 존재했으며 setuptools
에는 아직 구현되지 않았습니다. PEP 438의 설계는 오래된 설치 관리자를 사용하는 경우에도 외부 파일이 필요 없는 프로젝트 사용자에게는 여전히 이점을 제공했지만, 외부 파일이 필요한 프로젝트의 경우 사용자는 여전히 잠재적으로 신뢰할 수 없거나 심지어 안전하지 않은 파일을 자동으로 다운로드 받게 됩니다. 이 시스템은 PyPI의 역사에서 비롯된 Python에만 독특한 개념이므로, 대부분의 사용자에게 생소할 가능성이 높습니다.
또한, PEP 438이 제안한 분류 시스템은 실제로 최종 사용자에게 극도로 혼란스럽다는 것이 밝혀졌습니다. 사용자는 프로젝트를 설치하려다가 오류 메시지를 받고 --allow-external
플래그를 추가하라는 메시지를 본 후 다시 시도하고, 또 다른 오류 메시지와 함께 --allow-unverified
를 추가하라는 메시지를 받은 후 세 번째 시도에서야 비로소 설치에 성공하는 경우가 흔했습니다.
이러한 사용자 경험 실패는 다음과 같은 이유로 발생합니다.
pip
가 Simple API에서 프로젝트에 대한 파일을 찾을 수 있다면, 다른 파일을 찾으려 하지 않고 해당 파일을 사용합니다. 이는 PEP 438의 많은 이점을 지울 수 있으므로 일반적으로 올바른 동작이지만, 프로젝트가 오래된 파일을 PyPI에 업로드한 경우 해당 파일이 사용될 수 있습니다.- PEP 438은 대부분의 프로젝트가 PyPI에 직접 업로드하거나 릴리스 파일에 직접 링크할 것이라는 암묵적인 가정을 했습니다. 많은 프로젝트가 PyPI에 업로드하기로 결정했지만, 일부는 PEP 438의 사용자 경험이 너무 나빠서 강요받았다고 느꼈기 때문입니다. 더 우려스러운 점은 매우 적은 수의 프로젝트만이 파일에 직접 안전하게 링크하기로 선택했으며, 대부분은 실제 파일을 찾기 위해 스크랩해야 하는 페이지에 링크되어 있어 안전한 옵션(
--allow-external
)이 거의 무용지물이 되었다는 것입니다. - 작성자가 파일에 직접 링크하고 싶어도 안전하게 링크하는 것이 쉽지 않습니다. URL 해시에 MD5 해시(역사적인 이유로)를 포함해야 하는데, 이를 포함하지 않으면 파일은 “확인되지 않음(unverified)”으로 간주됩니다.
- PEP 438은 보안 중심적인 관점을 취하며, 확인되지 않은 프로젝트에 대한 전역적인 옵트인(opt-in)을 어떤 형태로든 허용하지 않습니다. 이는 일반적으로 좋은 일이지만,
--allow-external myproject --allow-unverified myproject myproject
와 같이 매우 장황하고 반복적인 명령어 호출을 초래합니다.
다중 레포지토리/인덱스 지원 (Multiple Repository/Index Support)
설치 관리자는 여러 URL 위치를 가리키는 기능을 구현하거나 계속 제공해야 합니다. 추가 위치를 사용하려는 사용자에게 어떤 메커니즘을 제공할지는 각 구현에 맡겨집니다.
또한, 여러 레포지토리가 사용될 때 설치 후보를 검색하는 메커니즘도 각 구현에 맡겨지지만, 일단 구성되면 기본 레포지토리가 아니라는 이유만으로 특정 레포지토리 사용을 discourage, warn, 또는 부정적으로 다루어서는 안 됩니다.
현재 pip
와 setuptools
는 두 레포지토리에서 찾을 수 있는 최적의 설치 후보를 사용하여 다중 레포지토리 지원을 구현하고 있으며, 본질적으로 하나의 큰 레포지토리처럼 취급합니다.
설치 관리자는 기본 레포지토리 사용을 제거하거나 비활성화하는 메커니즘도 구현해야 합니다. 이를 달성하는 구체적인 방법은 각 구현에 맡겨집니다.
설치 관리자는 특정 레포지토리에서 설치하려는 프로젝트를 화이트리스트(whitelist) 및 블랙리스트(blacklist)에 추가하는 메커니즘도 구현해야 합니다. 이를 달성하는 구체적인 방법은 각 구현에 맡겨집니다.
Python 패키징 가이드(Python packaging guide)는 자체 레포지토리를 설정하는 옵션을 자세히 설명하는 섹션으로 업데이트되어야 하며, PyPI에 호스팅하지 않으려는 프로젝트가 이 문서를 참조할 수 있도록 해야 합니다. 여기에는 자체 레포지토리를 사용하는 프로젝트가 프로젝트 설명에 설치 방법을 문서화해야 한다는 제안이 포함되어야 합니다.
링크 스크랩(Spidering)의 사용 중단 및 제거 (Deprecation and Removal of Link Spidering)
PyPI에 새로운 호스팅 모드인 pypi-only
가 추가될 예정입니다. 이 모드는 PEP 438이 제공했던 pypi-explicit
, pypi-scrape
, pypi-scrape-crawl
외에 추가되며, 프로젝트의 Simple API 페이지가 PyPI에 직접 호스팅된 파일만 나열하고 다른 어떤 것도 링크하지 않도록 변경합니다.
이 PEP가 승인되고 pypi-only
모드가 추가되면, 모든 새 프로젝트는 기본적으로 pypi-only
모드로 설정되며 이 설정을 변경할 수 없게 됩니다.
그 후 PyPI에만 호스팅되는 모든 프로젝트에 이메일이 발송되어 한 달 이내에 해당 프로젝트가 자동으로 pypi-only
모드로 전환될 것임을 알립니다. 이 이메일 발송 한 달 후, PyPI에만 호스팅되는 해당 프로젝트들은 영구적으로 pypi-only
모드로 설정됩니다.
동시에 PyPI 외부 호스팅에 의존하는 프로젝트에도 이메일이 발송됩니다. 이 이메일은 PyPI에서 외부 호스팅 파일이 더 이상 사용되지 않으며, 이메일 발송 3개월 후에는 모든 외부 링크가 설치 관리자 API에서 제거될 것임을 경고합니다. 이 이메일에는 프로젝트를 PyPI에 호스팅하도록 전환하는 방법, PyPI 자격 증명과 패키지 이름을 입력하면 파일을 자동으로 다운로드하여 PyPI에 다시 호스팅할 수 있는 스크립트 또는 패키지에 대한 링크, 그리고 자체 인덱스 페이지를 설정하는 방법에 대한 지침이 포함되어야 합니다. 또한, PyPI 이용 약관 링크와 설치 가능한 파일이 발견된 PyPI에 등록된 링크 목록도 포함되어야 합니다.
초기 이메일 발송 2개월 후, 여전히 외부 호스팅에 의존하는 프로젝트에 다시 이메일이 발송됩니다. 이 이메일은 첫 번째 이메일과 동일한 모든 정보를 포함하지만, 제거 날짜가 3개월 후가 아닌 1개월 후로 변경됩니다.
마지막으로 한 달 후, 모든 프로젝트가 pypi-only
모드로 전환되고 PyPI는 외부 링크 파일 기능을 제거하도록 수정됩니다.
변경 요약 (Summary of Changes)
레포지토리 측면 (Repository side)
- PEP 438에서 정의한 호스팅 모드를 사용 중단하고 제거합니다.
- Simple API가 레포지토리 내에 포함된 파일만 나열하도록 제한합니다.
클라이언트 측면 (Client side)
- 다중 레포지토리 지원을 구현합니다.
- 기본 레포지토리를 제거/비활성화하는 메커니즘을 구현합니다.
- PEP 438을 사용 중단하고 제거합니다.
영향 (Impact)
이 PEP 작성 시점에 PyPI에 호스팅된 65,232개의 프로젝트 중 59개(0.09%)는 PyPI 외부에서 안전하게 호스팅되는 외부 파일에 의존하고 있었고, 931개(1.4%)는 PyPI 외부에서 안전하지 않게 호스팅되는 외부 파일에 의존하고 있었습니다. 이는 전체 프로젝트의 약 1.5%가 이번 변경으로 영향을 받지만, 98.5%는 기존과 같이 계속 작동할 것임을 나타냅니다. 또한, 영향을 받는 프로젝트 중 5%만이 PEP 438 기능을 사용하여 PyPI 외부에서 안전하게 호스팅하고 있었고, 95%는 중간자 공격(Man In The Middle attack)을 통해 사용자에게 원격 코드 실행(Remote Code Execution) 위험을 노출하고 있었습니다. 실제 영향은 이보다 훨씬 작을 것으로 예상되지만, 가장 광범위한 정의를 사용하여 잠재적인 영향을 측정했습니다.
자주 묻는 질문 (Frequently Asked Questions)
<X>
때문에 PyPI에 프로젝트를 호스팅할 수 없습니다. 어떻게 해야 하나요?
<X>
가 PyPI 자체의 본질적인 문제인지, 아니면 PyPI가 기능을 추가하여 해결할 수 있는 문제인지 먼저 결정해야 합니다. PyPI가 기능을 추가할 수 있다면 해당 기능을 제안해야 합니다. 그러나 <X>
가 파일에 대한 제어 유지와 같이 PyPI에 본질적인 문제라면, 자체 패키지 레포지토리를 설정하고 프로젝트 설명에 사용자의 설치 관리자에 추가하는 방법을 안내해야 합니다.
이 PEP 때문에 사용자 경험이 이전보다 나빠졌는데, 어떻게 설명해야 하나요?
이 답변의 일부는 개별 프로젝트마다 다를 것입니다. 사용자에게 설치 관리자의 기본 레포지토리 목록에 있는 것을 사용하지 않고 자체 레포지토리에 호스팅하기로 결정한 이유를 설명해야 합니다. 그러나 답변의 일부는 외부 링크를 투명하게 포함하던 이전 동작이 보안 위험(대부분의 경우 MITM 공격을 통해 최종 사용자 머신에서 임의의 Python 코드를 실행할 수 있었음)과 신뢰성 문제였다는 점을 설명하는 것입니다. PEP 438은 이를 명시적으로 옵트인하게 함으로써 해결하려 했지만, 여러 가지 심각한 사용성 문제를 야기했습니다. PEP 470은 많은 사용자에게 익숙한 모델, 즉 Linux 배포판 레포지토리와 유사한 모델로 단순화하는 것을 목표로 합니다.
레포지토리 구조로 전환하면 워크플로가 깨지거나 호스트에서 허용되지 않습니다.
레포지토리에 필요한 것을 기꺼이 지원하는 저렴하거나 무료 호스트가 많이 있습니다. 특히 파일이 실제로 있는 위치를 가리키는 올바른 구조의 호스트를 생성할 수 있다면 파일을 다르게 업로드할 필요가 없습니다. 많은 호스트가 공유 도메인 이름을 사용하여 무료 HTTPS를 제공하며, StartSSL이나 가까운 미래에 Let’s Encrypt에서 무료 HTTPS 인증서를 얻을 수 있습니다.
<X>
는 왜 제공하지 않습니까?
여기에 대한 답변은 <X>
가 무엇인지에 따라 다르지만, 일반적으로 다음 중 하나입니다.
- 생각해 본 적이 없고 아무도 제안하지 않았습니다.
<X>
에 대한 적절한 솔루션을 설계할 충분한 경험이 없으며, 도메인 전문가의 도움을 환영합니다.- 우리는 오픈 소스 프로젝트이며, 아직
<X>
를 설계하고 구현할 자원봉사자가 결정되지 않았습니다.
추가 기능 제안을 위한 PEP는 언제나 환영하지만, <X>
를 정확하게 설계할 시간과 전문 지식을 가진 사람이 필요합니다. 이 PEP는 PyPI의 기능을 Linux 배포판 레포지토리와 같은 기존 모델과 유사한, 쉽게 이해할 수 있는 기본 수준으로 명확하게 만드는 데 중점을 둡니다.
어차피 자체 레포지토리를 운영하는데, PyPI에 등록해야 하는 이유는 무엇인가요?
PyPI는 Python 생태계에서 두 가지 중요한 기능을 수행합니다. 하나는 pip
또는 다른 패키지 관리자가 다운로드하고 설치하는 실제 파일의 중앙 레포지토리이며, 이 PEP가 다루는 부분이자 자체 레포지토리를 운영하면 대체될 수 있는 기능입니다. 그러나 PyPI는 이름 충돌을 방지하기 위해 누가 어떤 이름을 소유하는지에 대한 중앙 레지스트리 역할도 합니다. DNS가 Python 패키지용이라고 생각하시면 됩니다. 이름이 선착순으로 할당되도록 보장하는 것 외에도, 사용자가 새로운 프로젝트를 검색하고 찾을 수 있는 단일 장소를 제공합니다. 따라서 간단한 답변은, 이름 충돌을 피하고 사람들이 프로젝트를 쉽게 찾을 수 있도록 여전히 PyPI에 프로젝트를 등록해야 한다는 것입니다.
거부된 제안 (Rejected Proposals)
외부 호스팅 인덱스에 대한 더 쉬운 검색 허용 (Allow easier discovery of externally hosted indexes)
이 PEP의 이전 버전에는 PyPI와 설치 관리자 모두에 새로운 기능이 포함되어 있었습니다. 이 기능은 프로젝트 작성자가 PyPI에 URL 목록을 입력하면 설치 관리자가 PyPI에 업로드된 파일을 무시하고 대신 최종 사용자에게 추가할 수 있는 추가 URL에 대해 오류 메시지를 반환하여 설치가 작동하도록 하는 것이었습니다. 이 기능은 PEP 438 솔루션과 유사한 많은 문제를 야기했던 UX 문제를 피할 솔루션을 개발하기 너무 어렵다는 것이 입증되어 PEP의 범위에서 제거되었습니다. 필요한 경우, 미래의 PEP에서 이 아이디어를 다시 검토할 수 있습니다.
현재 분류 시스템 유지 및 옵션 조정 (Keep the current classification system but adjust the options)
이 PEP는 현재 시스템의 사용성 문제 중 일부를 해결하려 하지만, PEP 438의 일반적인 취지를 유지하는 여러 관련 제안을 거부합니다. 여기에는 다음이 포함됩니다.
- 안전하게 외부 호스팅된 파일은 기본적으로 허용하지만, 안전하지 않게 호스팅된 파일은 허용하지 않습니다.
- 안전하게 외부 호스팅된 파일은 기본적으로 허용하지 않고, 전역 플래그를 통해서만 허용하며, 안전하지 않게 호스팅된 파일은 허용하지 않습니다.
- PEP 438에서 제안한 경로를 계속 따르고 안전하지 않게 외부 호스팅하는 옵션을 제거하지만, 안전하게 외부 호스팅하는 옵션은 계속 허용합니다.
이러한 제안들은 다음과 같은 이유로 거부되었습니다.
- PEP 438에 도입된 분류 시스템은 Python 패키징 맥락에서도 일반적으로 적용할 수 없는 PyPI만의 독특한 개념입니다. 추가 개념을 도입하는 데에는 비용이 따릅니다.
- 분류 시스템 자체는 설명하기 모호하며, 프로젝트가 필요로 할 링크 분류를 미리 결정하려면 프로젝트의
/simple/<project>/
페이지와 해당 페이지에서 링크된 모든 URL을 검사해야 합니다. - 자동 검색을 위해 링크되면서도 외부에서 호스팅할 수 있는 기능은 적은 보상에 비해 상당한 고통과 복잡성을 유발하는 역사적인 유물입니다.
- 설치 관리자가 사용자 인터페이스를 최적화하거나 정리할 수 있는 능력은 수행해야 하는 암묵적인 링크 스크랩의 특성 때문에 제한적입니다. 이는
--allow-*
옵션뿐만 아니라 링크가 실패할 것으로 예상되는지 여부를 판단할 수 없는 점에도 적용됩니다. - 옵션을 활성화할 때 매우 광범위하게 적용되며, PEP 438은 패키지별 옵션으로 이를 제한하려 합니다. 그러나 오랜 기간 존재했던 프로젝트는 종종 Simple Index에 여러 다른 URL이 나열될 수 있습니다. 이들 중 적어도 하나가 더 이상 프로젝트의 제어하에 있지 않은 것은 드문 일이 아닙니다. 등록되지 않은 도메인은 대부분 무해하게 존재하지만,
pip
는 매 검색 단계마다 해당 도메인에서 설치를 시도할 것입니다. 이는 공격자가 안전하지 않은 외부 URL에 의존하는 프로젝트를 찾아 만료된 도메인을 등록하기만 하면 사용자를 공격할 수 있음을 의미합니다.
이 PEP를 구현하되 기존 링크는 제거하지 않음 (Implement this PEP, but Do Not Remove the Existing Links)
이것은 기본적으로 이 PEP의 하위 호환 버전입니다. 이전 클라이언트를 사용하거나 이 PEP를 구현하지 않은 클라이언트를 사용하는 사람들이 아무것도 변경되지 않은 것처럼 계속 작동하도록 허용하려고 합니다. 이 제안은 거부되었습니다. 왜냐하면 대부분의 시나리오가 사용 중단된 기능을 안전하지 않게 사용하는 경우이기 때문입니다. 이 PEP의 의견은 최종 사용자를 대신하여 안전하지 않은 작업이 자동으로 발생하는 것을 허용하는 것은 용납할 수 없는 해결책이라는 것입니다.
저작권 (Copyright)
이 문서는 퍼블릭 도메인에 공개되었습니다. This document has been placed in the public domain.
⚠️ 알림: 이 문서는 AI를 활용하여 번역되었으며, 기술적 정확성을 보장하지 않습니다. 정확한 내용은 반드시 원문을 확인하시기 바랍니다.
Comments