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

Python 개선 제안 한국어 번역

PEP 458 – 서명된 저장소 메타데이터로 PyPI 다운로드 보호하기

Author:
Trishank Karthik Kuppusamy <karthik at trishank.com>, Vladimir Diaz <vladimir.diaz at nyu.edu>, Marina Moore <mm9693 at nyu.edu>, Lukas Puehringer <lukas.puehringer at nyu.edu>, Joshua Lock <jlock at vmware.com>, Lois Anne DeLong <lad278 at nyu.edu>, Justin Cappos <jcappos at nyu.edu>
Sponsor:
Alyssa Coghlan <ncoghlan at gmail.com>
BDFL-Delegate:
Donald Stufft <donald at stufft.io>
Discussions-To:
Discourse thread
Status:
Accepted
Type:
Standards Track
Topic:
Packaging
Created:
27-Sep-2013
Post-History:
06-Jan-2019, 13-Nov-2019
Resolution:
Discourse message

Table of Contents

번역·라이선스 안내

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

초록

이 PEP는 사용자가 PyPI에서 유효한 패키지를 받도록 보장하는 데 필요한 PyPI 인프라 변경 사항을 설명합니다. 이러한 변경 사항이 생태계의 다른 부분에 미치는 영향은 최소한이어야 합니다. 이 PEP는 PyPI와 사용자 간의 통신에 초점을 맞추므로, 패키지 개발자에게 어떠한 조치도 요구하지 않습니다. 개발자는 현재의 절차대로 패키지를 업로드하며, PyPI가 이 패키지들에 대해 서명된 저장소 메타데이터를 자동으로 생성합니다.

보안 메커니즘이 효과를 발휘하려면, PyPI가 제공하는 서명과 메타데이터를 검증하는 추가 작업을 (pip 같은) PyPI 소비자가 수행해야 합니다. 이 검증은 (실패하지 않는 한) 사용자가 의식하지 못하게 이루어질 수 있으며, 자동화된 보안 메커니즘을 제공합니다. TUF 메타데이터를 소비하는 방법에 대한 문서는 TUF 저장소에 있습니다. 그러나 PyPI 소비자의 변경은 PyPI가 메타데이터를 게시하기 위한 전제 조건이 아니며, 각 프로젝트의 일정과 우선순위에 따라 진행할 수 있습니다.

제안하는 TUF 통합

이 PEP는 The Update Framework(업데이트 프레임워크) [2] (TUF)를 Python 패키지 색인(Python Package Index, PyPI [1])에 통합하는 방법을 제안합니다. TUF는 소프트웨어 업데이터나 패키지 관리자에 덧붙이는 유연한 보안 추가 기능으로 설계되었습니다. 이 프레임워크를 완전히 구현하면 역할 책임 분리, 패키지 서명에 다수인 규칙(many-man rule) 적용, 서명 키의 오프라인 보관, 만료되었거나 유출된 서명 키의 폐기와 같은 모범 보안 관행이 통합됩니다. 그 결과, 공격자가 저장소에서 사용 가능한 파일을 지정하는 역할을 장악하려면 각각 따로 보관된 여러 개의 서명 키를 탈취해야 합니다. 또는 그 대신, 저장소의 최신 스냅숏을 알리는 역할까지 장악해야 할 수도 있습니다.

이 PEP에서 제안하는 초기 통합을 통해 pip [3] 같은 최신 패키지 관리자는 PyPI 미러와 PyPI 자체 콘텐츠 배포 네트워크에 대한 공격에 더 안전해지며, 사용자를 그러한 공격으로부터 더 잘 보호할 수 있습니다. 구체적으로, 이 PEP는 TUF 메타데이터를 생성하고 포함하도록 PyPI 프로세스를 어떻게 조정해야 하는지(즉, 최소 보안 모델)를 설명합니다. 이 최소 보안 모델은 PyPI에 저장된 키로 서명된 PyPI 배포 패키지의 검증을 지원합니다. 개발자가 업로드한 배포 패키지는 PyPI가 서명하므로 개발자는 (배포 패키지 업로드 외에) 아무런 조치도 할 필요가 없으며, 업로드된 배포 패키지는 즉시 다운로드할 수 있습니다. 최소 보안 모델은 또한 서명 과정의 상당 부분을 자동화하여 PyPI의 관리 책임을 최소화합니다.

이 PEP에서는 개발자가 서명한 프로젝트 배포 패키지의 지원(최대 보안 모델)을 다루지 않습니다. 향후 가능한 이 확장은 PEP 480에서 자세히 다룹니다. 최대 보안 모델은 PyPI 관리 작업을 더 많이 요구하며(클라이언트에게는 추가 작업이 없지만), 개발자/배포자를 위한 사용하기 쉬운 키 관리 솔루션, 향후 PyPI 인프라에 도입될 수 있는 빌드 팜과 인터페이스하는 방법에 대한 아이디어, 그리고 종단 간 서명의 실현 가능성도 함께 제안합니다.

이 PEP는 구현 권장 사항을 제공하기는 하지만, pip 같은 패키지 관리자가 TUF 메타데이터를 사용하여 PyPI에서 프로젝트를 설치하거나 업데이트하도록 정확히 어떻게 조정해야 하는지는 규정하지 않습니다. 클라이언트 측에서 TUF 도입에 관심이 있는 패키지 관리자는 이러한 목적으로 작성된 TUF의 라이브러리 문서를 참고할 수 있습니다.

목표가 아닌 것

이 PEP는 PyPI의 기존 기능을 하나도 없애지 않습니다. 특히 OpenPGP 서명에 대한 기존 지원을 대체하지 않습니다. 개발자는 계속해서 배포물과 함께 분리형(detached) OpenPGP 서명을 업로드할 수 있습니다. 향후에는 PEP 480을 통해 개발자가 자신의 OpenPGP 키로 TUF 메타데이터에 직접 서명할 수 있게 될 수도 있습니다.

PEP 상태

이 PEP를 구현하는 데 필요한 작업량 때문에, 2019년 초에 이 PEP는 구현에 필요한 적절한 자금을 확보할 수 있을 때까지 보류되었습니다. Python Software Foundation이 이 자금을 확보했고 [22], 새로운 PEP 공동 작성자들이 PEP 논의를 재개했습니다.

동기

매우 훌륭한 보안 관행을 갖춘 조직에서도 소프트웨어 저장소에 대한 공격은 흔히 발생합니다. 그 결과로 저장소가 침해되면 공격자는 저장소에 저장된 모든 파일을 편집하고, 저장소에 저장된 아무 키(온라인 키)로나 그 파일들에 서명할 수 있습니다. 많은 서명 방식(TLS 등)에서는 이러한 접근 권한만 있으면 공격자가 저장소의 파일을 바꿔치기하고 그 파일들이 PyPI에서 온 것처럼 보이게 만들 수 있습니다. 신뢰하는 개인 키를 폐기하고 교체할 방법이 없으면 저장소 침해에서 복구하기가 매우 어렵습니다. 저장소 침해의 위험 외에도, 소프트웨어 저장소는 네트워크상의 공격자(MITM)가 파일을 가로채 변경하는 공격에 취약합니다. 소프트웨어 저장소에 대한 이러한 공격과 그 밖의 공격은 여기에 자세히 설명되어 있습니다.

이 PEP는 PEP 480의 후속 제안과 함께, PyPI 패키지의 무결성, 일관성, 최신성 속성이 침해되지 않도록 PyPI 사용자를 보호하고, 키 위험을 완화하며 PyPI 또는 그 서명 키가 침해되었을 때 복구할 수 있는 메커니즘을 제공함으로써 침해에 대한 복원력을 높이는 것을 목표로 합니다.

2013년 1월 5일, 파이썬 소프트웨어 재단(PSF)은 Python과 Jython의 python.org 위키에서 보안 침해가 발생했다고 발표했습니다 [4]. 그 결과 위키 데이터가 모두 파괴되었습니다. 다행히 PyPI 인프라는 이 침해의 영향을 받지 않았습니다. 하지만 이 사건은 PyPI가 침해 발생 시 사용자를 최대한 보호할 수 있도록 방어 조치를 취해야 한다는 점을 일깨워 줍니다. 소프트웨어 저장소에 대한 공격은 늘 일어납니다 [5]. PyPI는 수천 명, 어쩌면 수백만 명이 사용하는 귀중한 자원이므로, PSF는 보안 침해의 가능성을 받아들이고 그에 맞게 PyPI를 대비시켜야 합니다.

위키 공격 이전에 PyPI는 MD5 해시를 사용하여 pip 같은 패키지 관리자에게 배포 파일이 전송 중에 손상되었는지 여부를 알려 주었습니다. 하지만 SSL이 없었기 때문에 패키지 관리자가 PyPI와의 전송 무결성을 검증하기 어려웠습니다. 따라서 pip와 PyPI 사이에서 중간자 공격을 벌여 배포물의 내용을 임의로 변경하기가 쉬웠습니다. 그 결과 사용자가 속아서 악성 배포물을 설치할 수도 있었습니다. 위키 공격 이후, 이전보다 훨씬 높은 수준의 보안을 제공하기 위한 여러 조치가 제안되었습니다(그중 일부는 구현되었습니다). 이러한 조치에는 PyPI와 통신할 때 SSL 사용 의무화 [6], 프로젝트 이름 제한 [7], MD5 해시에서 SHA-2 해시로의 전환 [8] 등이 포함되었습니다.

이러한 단계는 필요하기는 하지만, 다른 경로를 통한 공격이 여전히 가능하므로 배포판을 보호하기에는 충분하지 않습니다. 예를 들어 공개 미러는 PyPI를 정직하게 미러링할 것으로 신뢰받지만, 일부 미러는 실수로든 악의적인 개입으로든 잘못 동작할 수 있습니다. pip 같은 패키지 관리자는 공개 미러에서 내려받은 배포 파일을 PyPI의 서명으로 검증해야 하지만, 실제로 그렇게 하는 패키지 관리자는 알려진 바가 없습니다 [10]. 따라서 공개 미러나 콘텐츠 전송 네트워크 [11] (CDN)로부터의 공격을 탐지할 수 있도록 보안 조치를 더 추가하는 것이 현명할 것입니다.

공식 미러는 PyPI에서 폐지되었지만, 패키지 관리자를 노리는 다른 공격 경로는 여전히 매우 다양하게 남아 있습니다 [13]. 이러한 공격은 클라이언트 시스템을 중단시키거나, 오래된 배포판이 설치되게 하거나, 심지어 공격자가 임의의 코드를 실행하도록 허용할 수도 있습니다. 2013년 9월에 Distutils 메일링 리스트에 한 게시물이 올라왔는데, 이 게시물은 (당시) 최신 버전의 pip이 이러한 공격에 취약하다는 점과 TUF가 이러한 공격으로부터 사용자를 어떻게 보호할 수 있는지를 보여 주었습니다 [14]. 구체적으로는 TUF를 사용할 때와 사용하지 않을 때 pip이 이러한 공격에 어떻게 반응하는지 확인하는 테스트가 수행되었습니다. 테스트한 공격에는 재전송 및 동결 공격, 임의 설치 공격, 느린 검색 공격, 무한 데이터 공격이 포함되었습니다. 이 게시물에는 PyPI가 침해되었을 때 pip이 어떻게 반응하는지에 대한 시연도 포함되어 있었습니다.

침해에 강한 PyPI 보호를 제공하기 위해, 이 PEP는 The Update Framework(업데이트 프레임워크) [2] (TUF)의 사용을 제안합니다. TUF는 소프트웨어 업데이트 시스템에 대한 다양한 공격으로부터 보호를 제공하는 동시에, 저장소 침해로부터 복구할 수 있는 메커니즘도 제공합니다. TUF는 여러 조직에서 실제 운영 환경에 사용되어 왔으며, 여기에는 Docker 레지스트리(Docker Registry)에서 컨테이너 이미지 서명을 위한 인프라를 제공하는 Cloud Native Computing Foundation의 Notary 서비스도 포함됩니다. TUF 명세는 세 차례의 독립적인 보안 감사를 받았습니다.

이 PEP의 범위는 PyPI 미러, 그리고 PyPI 자체의 TLS 종단 및 콘텐츠 배포 인프라의 침해로부터 사용자를 보호하는 것입니다. PyPI 자체의 침해로부터의 보호는 PEP 480에서 다룹니다.

위협 모델

위협 모델은 다음을 가정합니다:

  • 오프라인 키는 안전하며, 안전한 곳에 보관되어 있습니다.
  • 공격자는 온라인에 저장된 PyPI의 신뢰된 키를 침해할 수 없습니다.
  • 공격자는 클라이언트 요청에 응답할 수 있습니다.

공격자가 클라이언트로 하여금 소프트웨어 배포 파일의 최신 버전이 아닌 다른 무언가를 설치하게(또는 설치된 상태로 남겨 두게) 할 수 있다면 공격자가 성공한 것으로 간주합니다. 공격자가 업데이트 설치를 방해하고 있다면, 공격자는 클라이언트가 무언가 잘못되었다는 사실을 알아차리기를 원하지 않습니다.

이 위협 모델은 최소 보안 모델을 설명합니다. PEP 480에서 설명하는 최대 보안 모델은 공격자가 PyPI의 온라인 키를 침해할 수 있다는 점도 가정합니다.

정의

이 문서에서 사용하는 키워드 “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, “OPTIONAL”은 RFC 2119에 설명된 대로 해석해야 합니다.

이 PEP는 TUF를 PyPI에 통합하는 것에만 초점을 맞춥니다. 그러나 독자는 TUF 설계 원칙 [2]을 검토해 보기를 권장하며, TUF 명세 [16]에 익숙해야 합니다(SHOULD).

이 PEP에서 사용하는 다음 용어는 Python 패키징 용어집 [17]에 정의되어 있습니다: 프로젝트, 릴리스, 배포판.

이 PEP에서 사용하는 추가 용어는 다음과 같이 정의합니다:

  • 역할: TUF는 하나의 root 역할과, root 역할이 직접 또는 간접적으로 책임을 위임하는 여러 다른 역할을 지정합니다. 최상위 역할이라는 용어는 루트 역할과 루트 역할이 직접 지정하는 모든 역할, 즉 타임스탬프, 스냅숏, 타깃 역할을 가리킵니다. 각 역할은 제공하도록 신뢰받는 메타데이터 파일을 하나씩 가집니다.
  • 배포 파일: 릴리스를 배포하는 데 사용되는 Python 패키지, 모듈 및 기타 리소스 파일을 포함하는, 버전이 지정된 아카이브 파일입니다. 이 PEP에서는 배포 파일, 배포 패키지 [17], 또는 간단히 배포판이나 패키지라는 용어를 서로 바꿔 쓸 수 있습니다.
  • 단순 색인: 배포 파일에 대한 내부 링크를 담고 있는 HTML 페이지입니다.
  • 대상 파일: 대체로 대상 파일은 TUF로 무결성을 보장해야 하는 PyPI상의 모든 파일입니다. 일반적으로 여기에는 배포 파일과 단순 색인 같은 PyPI 메타데이터가 포함됩니다.
  • 메타데이터: 메타데이터는 역할, 다른 메타데이터, 대상 파일을 기술하는 서명된 파일입니다. 달리 명시하지 않는 한 메타데이터는 TUF 고유의 메타데이터를 의미합니다.
  • 저장소: 저장소는 이름이 붙은 메타데이터와 대상 파일의 출처입니다. 클라이언트는 저장소에 저장된 메타데이터와 대상 파일을 요청합니다.
  • 일관된 스냅숏: 어느 고정된 시점에 존재하던 PyPI상의 모든 프로젝트의 완전한 상태를 담아내는 TUF 메타데이터와 대상 파일의 집합입니다.
  • 개발자: 프로젝트의 TUF 메타데이터와 대상 파일을 갱신할 수 있도록 허용된 프로젝트 소유자 또는 유지 관리자입니다.
  • 온라인 키: PyPI 서버 인프라에 반드시 저장해야 하는 비공개 암호 키입니다. 이는 보통 해당 키로 자동 서명을 할 수 있도록 하기 위함입니다. 그러나 PyPI 인프라를 장악한 공격자는 이 키들을 읽을 수 있게 됩니다.
  • 오프라인 키: PyPI 서버 인프라와 독립적으로 반드시 저장해야 하는 비공개 암호 키입니다. 이렇게 하면 해당 키로 자동 서명을 할 수 없습니다. PyPI 인프라를 장악한 공격자라도 이 키들을 즉시 읽을 수는 없습니다.
  • 임계값 서명 방식: 역할은 메타데이터에 서명하는 데 n개의 키 중 최소 t개가 반드시 필요하다고 지정함으로써 키 유출에 대한 복원력을 높일 수 있습니다. t-1개의 키가 유출되는 것만으로는 역할 자체를 장악하기에 충분하지 않습니다. 어떤 역할이 (t, n)개의 키를 요구한다고 말하는 것은 임계값 서명 속성을 나타냅니다.

TUF 개요

가장 높은 수준에서 보면, TUF는 애플리케이션이 파일의 새 버전을 알아내고 얻을 수 있는 안전한 방법을 제공합니다. 겉보기에는 이 모든 것이 간단해 보입니다. 애플리케이션을 업데이트하는 기본 단계는 다음과 같습니다:

  • 업데이트가 존재함을 알아냅니다.
  • 업데이트된 파일의 최신 버전의 올바른 사본을 내려받습니다.

문제는 애플리케이션 업데이트가 악의적인 활동이 전혀 없을 때에만 간단하다는 점입니다. 공격자가 이처럼 간단해 보이는 단계를 방해하려 한다면, 공격자가 할 수 있는 일은 매우 많습니다.

소프트웨어 업데이터가 대부분의 시스템(적어도 보안을 신경 쓰는 시스템)과 같은 방식을 쓴다고 가정해 보겠습니다. 업데이터는 원하는 파일과 그 파일의 암호학적 서명을 함께 내려받습니다. 소프트웨어 업데이터는 서명을 만들 수 있다고 신뢰하는 키가 무엇인지 이미 알고 있습니다. 업데이터는 서명이 올바른지, 그리고 이 신뢰하는 키로 만들어졌는지 확인합니다. 안타깝게도 소프트웨어 업데이터는 여전히 여러 면에서 위험에 노출되어 있으며, 다음과 같은 시나리오가 그 예입니다.

  • 공격자가 소프트웨어 업데이터에 같은 업데이트 파일을 계속 제공하여, 업데이터가 업데이트가 있다는 사실을 끝내 알아차리지 못하게 합니다.
  • 공격자가 소프트웨어 업데이터에 이미 가지고 있는 파일의 더 오래되고 안전하지 않은 버전을 제공하여, 업데이터가 그 파일을 내려받고 더 새로운 버전이라고 여겨 무작정 사용하게 합니다.
  • 공격자가 소프트웨어 업데이터에 파일의 더 새로운 버전을 제공하지만, 그것은 가장 최신 버전이 아닙니다. 소프트웨어 업데이터에게는 더 새로운 파일이지만, 안전하지 않아 공격자가 악용할 수 있는 파일일 수 있습니다.
  • 공격자가 이 파일들에 서명하는 데 쓰이는 키를 탈취하여, 이제 소프트웨어 업데이터는 올바르게 서명된 악성 파일을 내려받게 됩니다.

TUF는 저장소에 서명된 메타데이터(저장소의 파일을 설명하는 텍스트 파일)를 추가하고 업데이트 과정에서 이 메타데이터 파일을 참조함으로써, 이러한 공격과 그 밖의 공격에 대응하도록 설계되었습니다. 저장소 파일은 소프트웨어 업데이트 시스템에 넘겨지기 전에 메타데이터에 담긴 정보와 대조하여 검증됩니다. 이 프레임워크는 다중 서명 신뢰, 암호 키의 명시적 및 암묵적 폐기, 메타데이터의 책임 분리, 키 위험 최소화도 제공합니다. TUF가 다루는 저장소 공격과 소프트웨어 업데이터 약점의 전체 목록과 개요는 부록 A를 참조하십시오.

PyPI와 TUF 통합하기

소프트웨어 업데이트 시스템이 TUF와 통합하려면 두 가지 주요 작업을 완료해야 합니다. 첫째, 서버 측 저장소가 서명된 TUF 메타데이터를 제공하도록 반드시(MUST) 수정되어야 합니다. 이 PEP는 통합의 첫 번째 부분과, TUF를 사용한 소프트웨어 업데이트를 지원하기 위해 PyPI에 필요한 변경 사항을 다룹니다.

둘째, 업데이트 시스템의 클라이언트 측에 프레임워크를 추가해야 합니다. 예를 들어 TUF를 pip 패키지 관리자와 통합할 수 있습니다(MAY). 따라서 앞으로 나올 새 버전의 pip는 PyPI에서 배포 패키지를 설치하기 전에 기본적으로 TUF를 사용해 내려받고 검증해야 합니다(SHOULD). 하지만 사용자가 TUF를 통해 pip 자체를 포함한 배포 패키지를 설치하거나 업데이트하지 못하게 만드는 예기치 못한 문제가 생길 수 있습니다. 따라서 pip는 이러한 문제가 해결될 때까지 이를 우회할 수 있도록 --unsafely-disable-package-verification 같은 옵션을 제공해야 합니다(SHOULD). 제안된 옵션 이름은 일부러 길게 지었다는 점에 유의하십시오. 이 동작이 안전하지 않으며 일반적으로 권장되지 않는다는 점을 사용자가 이해하도록 도와야 하기 때문입니다.

pip는 PyPI에서만 내려받은 배포 패키지를 검증하는 데 TUF를 사용한다고 가정합니다. pip는 다른 곳에서 내려받은 배포 패키지도 TUF로 검증하기 위해 TAP 4를 지원할 수 있습니다(MAY).

PyPI에 어떤 추가 저장소 파일이 필요합니까?

pip 같은 패키지 관리자가 TUF로 배포 패키지를 내려받고 검증하려면, PyPI에 몇 가지 추가 파일을 반드시(MUST) 더해야 합니다. 이 추가 저장소 파일을 TUF 메타데이터라고 하며, 여기에는 신뢰할 수 있는 키, 파일의 암호학적 해시, 서명, 메타데이터 버전 번호, 메타데이터가 만료된 것으로 간주되어야 하는 날짜 등의 정보가 담깁니다.

패키지 관리자는 업데이트를 확인하려고 할 때 그 작업을 TUF에 요청합니다. 즉, 패키지 관리자는 이 추가 메타데이터를 직접 다루거나 내부에서 무슨 일이 일어나는지 이해할 필요가 전혀 없습니다. 사용 가능한 업데이트가 있다고 TUF가 알려 주면, 패키지 관리자는 PyPI에서 해당 파일을 다운로드하도록 TUF에 요청할 수 있습니다. TUF는 파일을 다운로드한 뒤, 저장소에서 함께 다운로드한 TUF 메타데이터와 대조하여 검사합니다. 다운로드한 대상 파일을 신뢰할 수 있으면, TUF는 그 파일을 패키지 관리자에게 넘겨줍니다.

TUF 명세의 Document formats 섹션은 필요한 메타데이터의 각 유형과 그 예상 내용에 관한 정보를 제공합니다. 다음 섹션에서는 PyPI에 권장되는(RECOMMENDED) 여러 종류의 메타데이터를 다룹니다.

또한 모든 대상 파일은 디스크에 최소 두 번 존재해야 합니다(SHOULD). 한 번은 하위 호환성을 제공하기 위해 원래 파일 이름으로, 또 한 번은 파일 이름에 SHA-512 해시를 포함한 형태로 존재해야 합니다. 이는 Consistent Snapshots 생성에 필요합니다.

사용하는 파일 시스템에 따라, 대상 파일의 물리적 복사본으로 인한 저장 공간 증가를 피하기 위해 다양한 데이터 중복 제거 메커니즘을 사용할 수 있습니다(MAY).

PyPI와 TUF 메타데이터

TUF 메타데이터는 클라이언트가 업데이트 결정을 내리는 데 사용할 수 있는 정보를 제공합니다. 예를 들어, targets 메타데이터는 PyPI에서 사용 가능한 대상 파일을 나열하며, 각 파일에 필요한 서명, 암호화 해시, 파일 크기를 포함합니다. 서로 다른 메타데이터 파일은 서로 다른 정보를 제공하며, 각각 별도의 역할이 서명합니다. root 역할은 각 역할에 어떤 메타데이터가 속하는지를 나타냅니다. 역할이라는 개념 덕분에 TUF는 책임을 여러 역할에 위임할 수 있으며, 그 결과 어느 한 역할이 침해되더라도 그 영향을 최소화할 수 있습니다.

TUF에는 네 가지 최상위 역할이 필요합니다. 이 역할들은 루트, 타임스탬프, 스냅숏, 타깃입니다. root 역할은 (자기 자신을 포함한) 최상위 역할들의 공개 암호화 키를 지정합니다. timestamp 역할은 최신 snapshot을 참조하며, 저장소의 새 스냅숏이 언제 사용 가능해지는지를 알릴 수 있습니다. 스냅숏 역할은 (타임스탬프를 제외한) 모든 TUF 메타데이터 파일의 최신 버전을 나타냅니다. targets 역할은 사용 가능한 대상 파일의 파일 경로를 해당 파일의 암호화 해시와 함께 나열합니다. 파일 경로는 기본 URL에 대한 상대 경로로 지정해야 합니다. 이를 통해 클라이언트가 기본 URL에 접근할 수 있기만 하면 실제 대상 파일을 어디에서든 제공할 수 있습니다. 각 최상위 역할은 예외 없이 자신의 책임을 수행합니다. 표 1은 TUF에서 사용하는 역할의 개요를 제공합니다.

Roles and Responsibilities
root The root role is the locus of trust for the entire repository. The root role signs the root.json metadata file. This file indicates which keys are authorized for each of the top-level roles, including for the root role itself. The roles “root”, “snapshot”, “timestamp” and “targets” must be specified and each has a list of public keys.
targets The targets role is responsible for indicating which target files are available from the repository. More precisely, it shares the responsibility of providing information about the content of updates. The targets role signs targets.json metadata, and can delegate trust for repository files to other roles (delegated roles).
delegated roles If the top-level targets role performs delegation, the resulting delegated roles can then provide their own metadata files. The format of the metadata files provided by delegated targets roles is the same as that of targets.json. As with targets.json, the latest version of metadata files belonging to delegated roles are described in the snapshot role’s metadata.
snapshot The snapshot role is responsible for ensuring that clients see a consistent repository state. It provides repository state information by indicating the latest versions of the top-level targets and delegated targets metadata files on the repository in snapshot.json. root and timestamp are not listed in snapshot.json, because timestamp signs for its freshness, after snapshot.json has been created, and root, which has all top-level keys, is required ahead of time to trust any of the top-level roles.
timestamp The timestamp role is responsible for providing information about the timeliness of available updates. Timeliness information is made available by frequently signing a new timestamp.json file that has a short expiration time. This file indicates the latest version of snapshot.json.

표 1: TUF 역할 개요.

별도로 명시되지 않는 한, 이 PEP는 모든 메타데이터 파일 또는 대상 파일을 SHA-2 계열의 SHA2-512 함수로 해시할 것을 권장합니다(RECOMMENDS). SHA-2는 Python 2와 3에서 네이티브로 지원되며 충분히 검증되었습니다(따라서 Python이 아닌 추가 의존성 없이 이 해시들을 검증할 수 있습니다). 더 강력한 보안 보장이 필요하다면, 대신 SHA2-256과 SHA2-512를 함께 사용하거나 SHA2-256과 SHA3-256을 함께 사용할 수 있습니다(MAY). SHA2-256과 SHA3-256은 서로 매우 다른 설계에 기반하므로 충돌 공격에 대한 추가적인 보호를 제공합니다. 그러나 SHA-3을 사용하려면 Python 2에서 Python이 아닌 추가 의존성을 설치해야 합니다.

메타데이터 서명과 저장소 관리

최상위 루트 역할은 최상위 타임스탬프, 스냅숏, 타깃, 루트 역할의 키에 서명합니다. timestamp 역할은 저장소 메타데이터의 모든 새 스냅숏에 서명합니다. 스냅숏 역할은 루트, 타깃, 그리고 위임된 모든 타깃 역할에 서명합니다. 위임된 targets 역할인 bins는 다시 bin-n 역할들에 위임하며, 이 역할들은 등록된 PyPI 프로젝트에 속한 모든 배포 파일에 서명합니다.

그림 1은 PyPI에서 사용할 수 있는 역할들의 개요를 보여 주며, 여기에는 최상위 역할들과 targets가 위임한 역할들이 포함됩니다. 또한 이 그림은 각 역할에 서명하는 데 사용되는 키의 유형과, 어떤 역할이 PyPI에서 제공되는 파일에 서명하도록 신뢰받는지도 나타냅니다. 다음 두 절에서는 저장소 파일 서명의 세부 사항과 각 역할에 사용되는 키의 유형을 다룹니다.

../_images/pep-0458-1.png

그림 1: PyPI에서 사용할 수 있는 역할 메타데이터의 개요.

가장 자주 변경되는 역할은 타임스탬프, 스냅숏, 그리고 bins가 위임하는 역할(즉, bin-n)입니다. 타임스탬프와 스냅숏 메타데이터는 루트, 타깃 또는 위임된 메타데이터가 갱신될 때마다 반드시(MUST) 갱신되어야 합니다. 하지만 root 및 targets 메타데이터는 위임된 메타데이터만큼 자주 갱신될 가능성이 훨씬 낮다는 점에 유의하십시오. 마찬가지로, bins 역할은 bin-n 역할이 추가, 갱신 또는 제거될 때만 갱신됩니다. 따라서 프로젝트의 지속적 전달을 지원하기 위해 위임된 메타데이터가 자주 갱신되므로, 타임스탬프, 스냅숏, bin-n 메타데이터는 매우 자주(어쩌면 매분) 갱신될 가능성이 높습니다. 지속적 전달은 다른 스냅숏과 독립적으로 안전하게 공존하고 삭제될 수 있는 스냅숏을 생성하기 위해 PyPI가 사용하는 일련의 프로세스입니다 [18].

매년 PyPI 관리자는 root 및 targets 역할 키에 서명해야 합니다(SHOULD). 자동화된 절차가 모든 프로젝트의 타임스탬프가 찍힌 스냅숏에 지속적으로 서명합니다. 저장소 메타데이터 API가 제공되며, 이를 사용해 TUF 저장소를 관리할 수 있습니다.

표준 운영에서는 새 배포물이 PyPI에 업로드될 때 bin-n 메타데이터가 갱신되고 서명됩니다. 그러나 PyPI가 재초기화될 때마다 PyPI 저장소에 속한 기존의 모든 배포물에 대해 bin-n 메타데이터를 생성하고 서명하기 위한 일회성 온라인 초기화 메커니즘도 필요합니다.

PyPI 루트 키에 대한 초기 신뢰를 확립하는 방법

pip 같은 패키지 관리자는 사용자가 처음 내려받는 설치 파일과 함께 root 메타데이터 파일을 반드시 배포해야 합니다. 여기에는 모든 최상위 역할에 대해 신뢰하는 키에 관한 정보(루트 키 자체 포함)가 들어 있습니다. 패키지 관리자는 TUF 클라이언트 라이브러리도 함께 포함해야 합니다. TUF 클라이언트 라이브러리가 내려받을 수 있는 root 메타데이터의 새 버전은 모두 패키지 관리자에 처음 포함된 루트 키로 검증됩니다. 루트 키가 손상되었지만 임계값 이상의 키가 여전히 안전하다면, PyPI 관리자는 손상된 키에 대한 신뢰를 철회하는 새 root 메타데이터를 반드시 게시해야 합니다. 임계값 이상의 루트 키가 손상되었다면 root 메타데이터는 반드시 대역 외(out-of-band)로 갱신해야 합니다. (다만 루트 키의 임계값은 이런 일이 극히 일어나기 어렵도록 정해야 합니다.) 패키지 관리자의 새 릴리스 사이에 루트 키가 철회되거나 추가되더라도 패키지 관리자를 반드시 즉시 갱신할 필요는 없습니다. TUF 갱신 과정은 이전 root 키 중 임계값 이상의 키가 새 root 키에 서명하는 경우를 자동으로 처리하기 때문입니다(사용하는 TUF 명세에 하위 호환성을 깨는 변경이 없다고 가정합니다). 예를 들어 패키지 관리자가 처음에 root 메타데이터 버전 1과 함께 배포되었고, 버전 1의 root 키 중 임계값 이상의 키가 root 메타데이터 버전 2에 서명했으며, 버전 2의 root 키 중 임계값 이상의 키가 root 메타데이터 버전 3에 서명했다면, 패키지 관리자는 TUF 클라이언트 라이브러리를 사용해 자신이 가진 *root 메타데이터 사본을 버전 1에서 버전 3으로 투명하게 갱신할 수 있어야 합니다.

다시 말해, CPython과 함께 (ensurepip를 통해) 배포되는 pip의 모든 새 버전에는 최신의 올바른 root 메타데이터 사본과 TUF 클라이언트 라이브러리가 반드시 포함되어야 합니다. 그러면 패키지 관리자 안의 TUF 클라이언트 라이브러리가 root 메타데이터를 읽어 들이고 나머지 역할을 내려받으며, root 메타데이터가 바뀌었다면 이를 갱신하는 일도 함께 수행합니다. 갱신 과정의 개요를 참고할 수 있습니다.

최소 보안 모델

TUF를 PyPI에 통합할 때 고려할 보안 모델은 두 가지입니다. 이 PEP에서 제안하는 것은 최소 보안 모델로, PyPI에 저장된 개인 암호 키로 서명한 PyPI 배포물의 검증을 지원합니다. 개발자가 업로드한 배포물은 PyPI가 서명하며 즉시 내려받을 수 있습니다. PEP 480에서 논의하는, 이 PEP의 향후 확장안은 최대 보안 모델을 제안하며 개발자가 자신의 프로젝트에 직접 서명할 수 있게 합니다. 개발자 키는 온라인에 저장되지 않으므로 PyPI가 손상되더라도 프로젝트는 안전합니다.

최소 보안 모델은 개발자에게 아무런 조치를 요구하지 않으며, 악의적인 CDN [19]과 공개 미러로부터 보호합니다. 업로드된 배포물을 지속적으로 제공할 수 있도록 PyPI는 온라인 키로 프로젝트에 서명합니다. 이 수준의 보안은 미러나 CDN이 프로젝트를 실수로든 고의로든 변조하는 것을 막습니다. 미러와 CDN 모두 프로젝트에 서명하는 데 필요한 키를 전혀 갖고 있지 않기 때문입니다. 그러나 PyPI를 손상시킨 공격자로부터는 프로젝트를 보호하지 못합니다. 이런 공격자는 온라인에 저장된 키를 사용해 TUF 메타데이터를 조작할 수 있기 때문입니다.

이 PEP는 bin-n 역할이 온라인 키로 모든 PyPI 프로젝트에 서명하도록 제안합니다. 이 bin-n 역할은 모두 오프라인 키로 서명되는 상위 수준의 bins 역할로부터 반드시 위임받아야 하며, 이 상위 역할은 다시 마찬가지로 오프라인 키로 서명되는 최상위 targets 역할로부터 반드시 위임받아야 합니다. 즉, pip 같은 패키지 관리자가 (TUF를 사용해) PyPI의 어떤 프로젝트에서 배포 파일을 내려받을 때, 해당 배포 파일의 TUF 메타데이터에 대해 targets 역할에 질의합니다. targets가 bins를 거쳐 위임한 bin-n 역할 중 최종적으로 그 배포 파일을 명시하는 역할이 하나도 없다면, 그 파일은 PyPI에 존재하지 않는 것으로 간주합니다.

targets가 bin-n에 직접 위임하지 않고 중간의 bins 역할을 사용하는 이유는 bins에서 bin-n으로의 매핑에 영향을 주지 않고 다른 위임을 쉽게 추가하거나 제거할 수 있도록 하기 위해서입니다. 이는 PEP 480을 구현하는 데 매우 중요합니다.

메타데이터 만료 시간

root, targets, bins 역할의 메타데이터는 각각 1년 후에 만료되어야 합니다. 이 메타데이터 파일들은 거의 바뀌지 않을 것으로 예상되기 때문입니다.

CDN이나 미러는 매일 PyPI와 동기화해야(SHOULD) 하므로, 타임스탬프, 스냅숏, bin-n 메타데이터는 각각 하루 후에 만료되어야(SHOULD) 합니다. 또한 이처럼 넉넉한 기간은 시각이 크게 어긋나 있거나 틀어진 클라이언트 시계도 고려한 것입니다.

메타데이터 확장성

저장소의 프로젝트와 배포본 수가 늘어나면 TUF 메타데이터도 그에 맞춰 커져야 합니다. 예를 들어 bins 역할을 생각해 보십시오. 2013년 8월에 bins 역할이 약 220K개의 PyPI 타깃(simple 색인과 배포본)에 직접 서명하면 bins 메타데이터의 크기가 약 42MB가 된다는 사실이 확인되었습니다. 이 PEP에서는 자세히 다루지 않지만, TUF는 큰 타깃 메타데이터 파일 하나를 여러 개의 작은 파일로 나누는 이른바 “hashed bin delegation” 방식을 제공합니다. 이 방식을 사용하면 TUF 클라이언트 업데이터는 bins 역할이 서명한 프로젝트를 업데이트할 때 적은 수의 TUF 메타데이터 파일만 지능적으로 골라 내려받을 수 있습니다. 예를 들어 앞의 저장소에 이 방식을 적용한 결과, pip가 TUF를 통해 PyPI 프로젝트를 설치하거나 업그레이드할 때 내려받은 양은 1.3KB에서 111KB 사이였습니다.

이 문서를 구현에 맞춰 갱신한 시점(2019년 11월 7일)까지 얻어 표 2-3에 정리한 결과에 따르면, PyPI는 bins 역할의 모든 타깃을 16,384개의 bin-n 역할에 위임하여 분할해야 합니다(SHOULD)(표 2의 C10 참고). 각 bin-n 역할은 SHA2-512 해시가 해당 bin에 속하는 PyPI 타깃에 서명합니다(그림 1과 Consistent Snapshots 참고). 이 bin 수를 사용하면 기존 사용자에게는 5-9%의 메타데이터 오버헤드(내려받는 배포 파일의 평균 크기 대비. 표 3의 V13과 V15 참고)가 생기고, pip를 처음 설치하는 신규 사용자에게는 69%의 오버헤드(표 3의 V17 참고)가 생기는 것으로 확인되었습니다.

이 메타데이터 오버헤드 비율을 계산할 때 사용한 몇 가지 가정은 다음과 같습니다:

  1. root, timestamp, 최상위 targets 메타데이터는 고려하지 않습니다.
  2. pip에는 항상 모든 역할에 대한 최신의 정상 메타데이터 사본이 함께 포함됩니다.
이름 설명 값
C1 SHA2-512 16진수 다이제스트의 바이트 수 128
C2 SHA2-512 공개 키 ID의 바이트 수 64
C3 Ed25519 서명의 바이트 수 128
C4 Ed25519 공개 키의 바이트 수 64
C5 타깃 상대 파일 경로의 바이트 수 256
C6 대상 파일 크기를 인코딩하는 데 필요한 바이트 수 7
C7 버전 번호를 인코딩하는 데 필요한 바이트 수 6
C8 대상 수(단순 색인 및 배포 파일) 2,273,539
C9 다운로드된 배포 파일의 평균 바이트 수 2,184,393
C10 빈 수 16,384

C8은 릴리스 파일 수를 조회하여 계산했습니다. C9는 지난 31일 동안 다운로드된 릴리스 파일의 평균 크기를 대략 추정한 값(1,628,321바이트)과 디스크에 저장된 릴리스 파일의 평균 크기(2,740,465바이트)의 평균을 구하여 도출했습니다. Ee Durbin이 2019년 11월 7일에 이 수치를 제공하는 데 도움을 주었습니다.

표 2: 메타데이터 오버헤드를 계산하는 데 사용한 상수 목록.

이름 설명 수식 값
V1 경로 해시 접두사의 길이 math.ceil(math.log(C10, 16)) 4
V2 경로 해시 접두사의 총수 16**V1 65,536
V3 빈당 평균 대상 수 math.ceil(C8/C10) 139
V4 빈당 SHA-512 해시의 평균 크기 V3*C1 17,792
V5 빈당 대상 경로의 평균 크기 V3*C5 35,584
V6 빈당 길이 정보의 평균 크기 V3*C6 973
V7 bin-n 메타데이터의 평균 크기(바이트) V4+V5+V6 54,349
V8 빈에 포함된 공개 키 ID의 총 크기 C10*C2 1,048,576
V9 빈에 포함된 경로 해시 접두사의 총 크기 V1*V2 262,144
V10 bins 메타데이터의 추정 크기(바이트) V8+V9 1,310,720
V11 스냅숏 메타데이터 예상 크기(바이트) C10*C7 98,304
V12 재방문 사용자 1인당 배포 패키지당 메타데이터 오버헤드 추정 크기 (동일 스냅숏) 2*V7 108,698
V13 재방문 사용자 1인당 배포 패키지당 메타데이터 오버헤드 추정 비율 (동일 스냅숏) round((V12/C9)*100) 5%
V14 재방문 사용자 1인당 배포 패키지당 메타데이터 오버헤드 추정 크기 (다른 스냅숏) V12+V11 207,002
V15 재방문 사용자 1인당 배포 패키지당 메타데이터 오버헤드 추정 비율 (다른 스냅숏) round((V14/C9)*100) 9%
V16 신규 사용자 1인당 배포 패키지당 메타데이터 오버헤드 추정 크기 V14+V10 1,517,722
V17 신규 사용자 1인당 배포 패키지당 메타데이터 오버헤드 추정 비율 round((V16/C9)*100) 69%

표 3: 신규 사용자와 재방문 사용자의 메타데이터 오버헤드 추정치.

관심 있는 독자는 메타데이터 오버헤드 계산기의 대화형 버전을 여기에서 확인할 수 있습니다:

재방문 사용자의 메타데이터 오버헤드가 50%를 넘으면 bin의 수를 늘려야 합니다(SHOULD). 현재로서는 대상의 수가 200만 개 이상에서 2,200만 개 이상으로 최소 10배 증가할 때 bin의 수를 늘려야 합니다(SHOULD). bin의 수가 그대로라고 가정하면, 이 시점에 재방문 사용자와 신규 사용자의 메타데이터 오버헤드는 각각 약 50-54%와 114%가 됩니다. bin의 수를 늘리면 사실상 모든 사용자가 신규 사용자와 같은 비용을 부담하게 됩니다. bins 메타데이터에 담긴 많은 위임을 (가끔) 내려받는 비용이 이들 비용의 대부분을 차지하게 되기 때문입니다. 주로 bins 메타데이터를 내려받는 오버헤드 때문에 신규 사용자의 비용이 지나치게 커질 것으로 보이면, 그런 상황이 오기 전에 이 문제를 다시 검토해야 합니다(SHOULD).

서버에서 bin의 수를 바꾸는 것은 클라이언트에 투명하게 처리된다는 점에 유의하십시오. 패키지 관리자는 신규 사용자처럼 메타데이터 전체를 새로 내려받아야 하지만, 이 작업에는 별도의 코드 로직이나 사용자 개입이 필요하지 않습니다.

TUF 메타데이터를 JSON 텍스트 형식 대신 바이너리 형식으로 표현하면 더 작게 만들 수 있습니다. 그렇더라도 프로젝트와 배포 패키지의 수가 충분히 많아지면 언젠가는 확장성 문제가 생기므로, 이 문제를 해결하려면 bins 역할에는 여전히 (그림 1에 나타낸 것과 같은) 위임이 필요합니다. JSON 형식은 데이터 교환을 위한 개방적이고 널리 알려진 표준이며 TUF 참조 구현이 이미 지원하므로, 이 PEP는 JSON을 데이터 형식으로 권장합니다. 다만 위임의 수가 많으므로, 모든 메타데이터의 압축본도 기존 Warehouse의 HTTP 압축 메커니즘을 통해 클라이언트에 제공해야 합니다(SHOULD). 또한 JSON 메타데이터를 클라이언트에 보내기 전에 압축할 수도 있습니다. TUF 참조 구현은 현재 압축된 JSON 메타데이터의 다운로드를 지원하지 않지만, 메타데이터 크기를 줄이기 위해 이 기능을 추가할 수 있습니다.

PyPI와 키 요구 사항

이 절에서는 PyPI에서 TUF 역할에 서명하는 데 필요한 키의 종류를 살펴봅니다. TUF는 디지털 서명 알고리즘을 어떤 것으로 선택하든 상관없습니다. 그러나 이 PEP는 모든 디지털 서명을 Ed25519 알고리즘 [15]으로 생성할 것을 권장합니다(RECOMMENDS). Ed25519는 Python에서 네이티브로 지원되고 충분히 검증되었으며(따라서 Python이 아닌 추가 의존성 없이 서명을 검증할 수 있습니다), 작은 키를 사용하고, 최신 HSM과 인증 토큰 하드웨어에서 지원됩니다.

권장되는 키의 수와 종류

root 역할 키는 보안에 매우 중요하며 극히 드물게 사용해야 합니다. 이 키는 주로 키 폐기에 사용되며, PyPI 전체의 신뢰가 이 키에서 출발합니다. root 역할은 (자기 자신을 포함한) 각 최상위 역할에 권한이 부여된 키에 서명합니다. root 역할에 속한 키는 매우 철저하게 보호하고 모든 키 중에서 가장 드물게 사용하도록 되어 있습니다. PSF 이사회가 현재의 신뢰할 수 있는 루트 키 보유자들을 정할 것을 권장합니다(RECOMMENDED). 이들은 각자 (강력한) 루트 키를 하나씩 보유합니다. 그러면 이들 중 과반수가 정족수를 이루어 모든 최상위 키에 대한 신뢰를 철회하거나 부여할 수 있습니다. 또는 PyPI의 시스템 관리자가 root 역할에 서명하는 책임을 맡을 수도 있습니다. 따라서 root 역할에는 (t, n)개의 키가 필요해야 합니다(SHOULD). 여기서 n은 PSF 이사회가 정한 키 보유자의 수이고, t > 1입니다(최소 두 명이 root 역할에 서명해야 합니다).

targets 역할은 모든 대상을 bins 역할에 정적으로 위임하는 데 서명하는 용도로만 사용합니다. 이 대상 위임은 키가 탈취되는 경우에도 공격으로부터 안전해야 하므로, targets 역할의 키는 반드시 오프라인으로 보관하고 다른 키와 독립적이어야 합니다(MUST). 보안을 희생하지 않으면서 키 관리를 단순하게 하기 위해, targets 역할의 키는 생성해서 해당 역할에 서명하는 데 사용한 즉시 영구 폐기할 것을 권장합니다(RECOMMENDED). 따라서 targets 역할에는 (2, 2)개의 키가 필요해야 합니다(SHOULD). 다시 말하지만, 이는 키를 영구 폐기할 것이기 때문이며, 암호 알고리즘의 다양성을 유지하지 않는 한 오프라인 키를 늘려도 키 복구 공격 [20]을 막는 데 도움이 되지 않습니다.

같은 이유로 bins 역할의 키도 targets 역할의 키와 비슷하게 설정해야 합니다(SHOULD).

지속적 전달을 지원하기 위해 타임스탬프, 스냅숏, 그리고 모든 bin-n 역할의 키는 반드시(MUST) 온라인 상태여야 합니다. 공격자가 PyPI를 침해하면 이 역할들도 모두 침해할 수 있을 것으로 보이므로, 이 역할들이 모두 서로 다른 온라인 키를 사용하도록 요구해도 얻는 이점은 거의 없습니다. 따라서 이 역할들 모두에 하나의 온라인 키를 사용하는 것이 합리적입니다.

온라인 키 관리

타임스탬프, 스냅숏, 그리고 모든 bin-n 역할이 공유하는 온라인 키는 암호화 여부와 관계없이 Python 인프라에 저장할 수 있습니다(MAY). 예를 들어, 키는 자체 호스팅 키 관리 서비스(예: Hashicorp Vault)나 서드파티 키 관리 서비스(예: AWS KMS, Google Cloud KMS 또는 Azure Key Vault)에 보관할 수 있습니다.

이러한 키 관리 서비스 중 일부는 키를 하드웨어 보안 모듈(HSM)에 저장할 수 있게 해 줍니다(예: Hashicorp Vault, AWS CloudHSM, Google Cloud HSM, Azure Key Vault). 이렇게 하면 공격자가 온라인 개인 키를 빼내는 것을 막을 수 있습니다(다만 키를 사용하는 것까지 막지는 못하며, 대신 이제 공격자의 행위를 암호학적으로 감사할 수 있게 될 수 있습니다). 하지만 이를 위해서는 HSM을 지원하도록 TUF 참조 구현을 수정해야 합니다(WIP).

이 온라인 키를 어디에 어떻게 보관하든, 키 사용 내역은 신중하게 기록, 모니터링, 감사해야 하며, 이상적으로는 PyPI를 침해한 공격자가 이러한 기록, 모니터링, 감사를 즉시 끌 수 없는 방식으로 해야 합니다.

오프라인 키 관리

앞 절에서 설명했듯이, 최대한의 보안을 위해 root, targets, bins 역할 키는 반드시 오프라인이어야 합니다. 이 키들은 개인 키를 PyPI에 절대 저장해서는 안 된다는 의미에서 오프라인이며, 다만 그중 일부는 프로젝트의 비공개 인프라에서 온라인 상태일 수 있습니다.

필요할 때(예: 최상위 TUF 역할의 키를 교체할 때) Python 관리자만 개인 키를 읽을 수 있는 방식으로 이 키들을 생성, 백업, 저장하기 위한 오프라인 키 세리머니를 진행해야 합니다. 따라서 키는 가급적 사이드 채널 공격을 걱정하지 않아도 되는 물리적 장소에서 다음을 사용하여 생성해야 합니다:

  1. 진정한 난수 생성기를 갖추고 세리머니 이후 어떠한 데이터도 남지 않는, 신뢰할 수 있는 에어갭 컴퓨터
  2. 신뢰할 수 있는 운영 체제
  3. 신뢰할 수 있는 서드파티 패키지 집합(예: 신뢰할 수 있는 운영 체제가 제공하는 버전이 충분히 최신이 아닌 경우, 암호화 라이브러리나 TUF 참조 구현의 업데이트된 버전)

세리머니 이후 백업 매체 외의 곳에 민감한 데이터(예: 개인 키)가 남지 않도록, 오프라인 키는 강력한 비밀번호로 암호화된 상태로 생성해야 하며, 생성 위치는 (신뢰도가 높은 순서대로) 비공개 HSM(예: YubiHSM), 클라우드 기반 HSM(예: 위에 나열된 것들), 휘발성 메모리(예: RAM), 비휘발성 메모리(예: SSD 또는 microSD) 중 하나입니다. 키를 비휘발성 메모리에서 생성해야 하는 경우, 키를 안전하게 백업한 뒤 해당 메모리를 반드시 복구할 수 없게 파기해야 합니다.

키 암호화에 사용한 비밀번호는 Python 관리자만 접근할 수 있고 오래 보존되며 신뢰할 수 있는 곳에 저장해야 합니다.

세리머니 중 OPSEC 오류를 최소화하려면, 신뢰할 수 있는 키 생성 컴퓨터에서 실행할 스크립트를 작성하여 다음과 같은 번거로운 세리머니 단계를 자동화해야 합니다:

  • 새 키를 생성하고 기존 키를 교체하는 데 필요한 모든 코드와 데이터(이전 TUF 메타데이터와 root 키)를 스니커넷으로 내보내기
  • 방화벽 강화, 보안 취약점을 수정하기 위한 운영 체제 전체 업데이트, 컴퓨터 에어갭 처리
  • 모든 새 TUF 메타데이터와 키를 암호화된 백업 매체로 내보내기. 이 백업은 PyPI TUF 저장소를 복원하는 데 필요한 데이터의 완전한 사본을 제공합니다.
  • 새 TUF 메타데이터와 온라인 키만 암호화된 백업 매체로 내보내기. 이 백업은 PyPI 인프라로 가져올 모든 온라인 데이터를 제공하며, 예를 들어 온라인 데이터를 이전에 보관된 상태로 복원해야 할 때 유용합니다.
  • 새 TUF 메타데이터의 암호학적 해시를 인쇄하고 저장합니다. 이 인쇄본은 추가적인 오프라인 종이 백업 역할을 하며, 침해가 발생했을 때 비교 자료로 사용할 수 있습니다.

targets 및 bins 역할의 일회용 키는 오프라인 키 세리머니 중에 안전하게 생성하고, 사용하고, 삭제할 수 있다는(MAY) 점에 유의하십시오. 또한 root 키는 오프라인 키 세리머니 자체에서 생성하지 않을 수도 있습니다(MAY). 그 대신 앞서 논의한 대로 n명의 Python 관리자 중 임계값 t명이 다른 모든 키를 생성하는 데 사용된 오프라인 키 세리머니 이후에 root 메타데이터에 독립적으로 서명할 수 있습니다(MAY).

메타데이터는 어떻게 생성해야 합니까?

프로젝트 개발자는 자신이 PyPI에 업로드한 배포본을 즉시 다운로드할 수 있기를 기대합니다. 안타깝게도 많은 읽기 주체와 쓰기 주체가 동일한 메타데이터와 타깃 파일에 동시에 접근하면 문제가 발생합니다. 즉, 여러 개발자가 이 파일들을 동시에 변경할 때 메타데이터와 타깃 파일의 일관성을 보장하는 방법이 필요합니다. TUF가 없는 PyPI에도 일관성 문제가 있지만, PyPI에서 사용할 수 있는 파일을 실시간으로 추적해야 하는(MUST) 서명된 메타데이터에서는 문제가 더 심각합니다.

PyPI가 타임스탬프를 제외한 모든 메타데이터의 최신 버전을 나타내는 스냅숏을 버전 1로 생성하고, 클라이언트가 PyPI에 이 스냅숏을 요청한다고 가정해 봅시다. 클라이언트가 이 snapshot을 다운로드하는 동안 PyPI가 이를테면 버전 2의 새 스냅숏에 타임스탬프를 찍습니다. 메타데이터의 일관성을 보장하지 않으면, 클라이언트는 PyPI에서 제공하는 것과 일치하지 않는 스냅숏 사본을 갖게 됩니다. 그 결과는 공격자가 주입한 임의의 메타데이터와 구별할 수 없습니다. 이 문제는 PyPI와 동기화하려는 미러에서도 발생합니다.

일관된 스냅숏

PyPI의 TUF 메타데이터를 변동성이 매우 큰 타깃 파일과 일관되게 유지하려면 일관된 스냅숏을 사용해야 합니다(SHOULD). 각 일관된 스냅숏은 주어진 시점에 알려진 모든 프로젝트의 상태를 담으며, 다른 어떤 스냅숏에도 영향을 주지 않고 다른 스냅숏과 안전하게 공존하거나 독립적으로 삭제될 수 있습니다(MAY).

일관된 스냅숏을 유지하려면 모든 TUF 메타데이터는 디스크에 기록될 때 파일명에 버전 번호를 포함해야 합니다(MUST):

VERSION_NUMBER.ROLENAME.json,
여기서 VERSION_NUMBER는 증가하는 정수이고, ROLENAME은 최상위 메타데이터 역할인 루트, 스냅숏, 타깃 중 하나이거나 위임된 타깃 역할인 bins 또는 bin-n 중 하나입니다.

유일한 예외는 timestamp 메타데이터 파일로, 클라이언트가 업데이트를 수행할 때 이 파일의 버전을 미리 알 수 없기 때문입니다. timestamp 메타데이터는 snapshot 메타데이터의 버전을 나열하며, 이 메타데이터는 다시 targets 및 위임된 타깃 메타데이터의 버전을 나열합니다. 이 모든 것은 주어진 일관된 스냅숏의 일부입니다.

일반적인 사용에서는 버전 번호 오버플로가 발생할 가능성이 낮습니다. 예를 들어 8바이트 정수는 1밀리초마다 한 번씩 증가시켜도 거의 3억 년 동안 사용할 수 있습니다. 공격자가 버전 번호를 임의로 증가시키면, 저장소는 TUF 명세에 설명된 대로 침해된 키를 폐기하고 버전 번호를 재설정하여 복구할 수 있습니다.

targets 또는 위임된 타깃 메타데이터는 앞서 명시한 대로 암호학적 해시를 포함하여 실제 타깃 파일을 참조합니다. 따라서 타깃 파일을 일관된 스냅숏의 일부로 표시하려면, 타깃 파일은 디스크에 기록될 때 파일명에 자신의 해시를 포함해야 합니다(MUST):

HASH.FILENAME
여기서 HASH는 파일 내용 해시의 hex digest이고, FILENAME은 원래 파일명입니다.

즉, 모든 대상 파일마다 위에서 지정한 암호학적 해시 함수 하나당 사본이 하나씩, 여러 개 존재할 수 있습니다.

무한한 디스크 공간, 엄격하게 증가하는 버전 번호, 그리고 해시 충돌 없음을 가정하면, PyPI가 다른 스냅숏을 생성하는 동안에도 클라이언트는 한 스냅숏에서 안전하게 읽을 수 있습니다.

pip과 같이 TUF 프로토콜을 사용하는 클라이언트는 timestamp 메타데이터를 제외한 모든 메타데이터와 대상 파일을 다운로드하도록 반드시 수정되어야 합니다. 이는 파일을 요청할 때 파일 이름에 파일의 버전(메타데이터의 경우)이나 파일의 암호학적 해시(대상 파일의 경우)를 포함하는 방식으로 이루어집니다.

이처럼 간단하지만 효과적인 방식으로, PyPI는 특정 시점의 모든 프로젝트와 관련 메타데이터에 대한 일관된 스냅숏을 확보할 수 있습니다. 다음 하위 절에서는 이 아이디어의 구현 세부 사항을 설명합니다.

참고: 이 PEP는 일관된 스냅숏을 생성하기 위해 고급 파일 시스템이나 도구를 사용하는 것을 금지하지 않습니다. 이 PEP에서 단순한 해결책을 제안하는 데에는 두 가지 중요한 이유가 있습니다. 첫째, 이 해결책은 PyPI가 특정 파일 시스템이나 도구를 사용하도록 강제하지 않습니다. 둘째, 일반적인 파일 시스템 기반 접근 방식을 사용하면 미러가 rsync 같은 기존 파일 전송 도구를 사용하여 PyPI에서 일관된 스냅숏을 효율적으로 전송할 수 있습니다.

일관된 스냅숏 생성

새 배포 파일이 PyPI에 업로드되면, PyPI는 해당 파일을 담당하는 bin-n 메타데이터를 반드시 갱신해야 합니다. 모든 대상 파일은 파일 이름의 해시에 따라 빈(bin)으로 분류된다는 점을 기억하십시오. 또한 PyPI는 갱신된 bin-n 메타데이터를 반영하도록 스냅숏을, 갱신된 스냅숏 메타데이터를 반영하도록 타임스탬프를 반드시(MUST) 갱신해야 합니다. 이러한 갱신은 자동화된 스냅숏 프로세스에서 처리해야(SHOULD) 합니다.

파일 업로드는 병렬로 처리될 수 있지만, 일관된 스냅숏은 반드시 엄격하게 순차적인 방식으로 생성되어야 합니다. 또한 배포 파일이 자기 완결적이기만 하면, 업로드된 파일마다 일관된 스냅숏을 생성할 수 있습니다. 이를 위해 업로드 프로세스는 새 배포 파일을 동시성 안전한 FIFO 큐에 넣고, 스냅숏 프로세스는 그 큐에서 한 번에 파일 하나씩 읽어 다음 작업을 수행합니다:

먼저, 관련 bin-n 메타데이터에 새 파일 경로를 추가하고, 그 버전 번호를 증가시킨 뒤, bin-n 역할 키로 서명하여 VERSION_NUMBER.bin-N.json에 기록합니다.

그런 다음 가장 최근의 snapshot (스냅숏) 메타데이터를 가져와 그 안의 bin-n 메타데이터 버전 번호를 갱신하고, 자신의 버전 번호를 증가시킨 뒤, snapshot (스냅숏) 역할 키로 서명하여 VERSION_NUMBER.snapshot.json에 기록합니다.

마지막으로, 스냅숏 프로세스는 가장 최근의 timestamp 메타데이터를 가져와 그 안의 snapshot 메타데이터 해시와 버전 번호를 갱신하고, 자신의 버전 번호를 증가시키고, 새 만료 시간을 설정한 뒤, timestamp 역할 키로 서명하여 timestamp.json에 기록합니다.

일관된 스냅숏을 위해 bin-n 메타데이터를 갱신할 때, 스냅숏 프로세스는 새로 생겼거나 갱신된 simple 색인 페이지의 해시도 해당 bin-n 메타데이터에 포함해야 합니다. simple 색인 페이지는 API 호출 시 동적으로 생성될 수 있으므로, 일관된 스냅숏이 유효한 동안 그 출력이 안정적으로 유지되는 것이 중요하다는 점에 유의하십시오.

스냅숏 프로세스는 반드시 엄격하게 순차적인 방식으로 일관된 스냅숏을 생성해야 하므로 병목 지점이 됩니다. 다행히 서명 작업은 초당 천 번 이상 수행할 수 있을 만큼 빠릅니다.

또한 PyPI는 해당하는 일관된 스냅숏 메타데이터가 생성되기 전에 클라이언트에 배포 파일을 제공할 수 있습니다. 그런 경우 클라이언트 소프트웨어는 완전한 TUF 보호가 아직 제공되지 않지만 곧 제공될 것임을 사용자에게 알려야 합니다.

PyPI는 감사 목적으로, 그리고 서버 장애 후 오류에서 복구하기 위해 업로드 프로세스와 스냅숏 큐를 기록하는 트랜잭션 로그를 사용해야 합니다.

오래된 메타데이터 정리하기

새로운 일관된 스냅숏이 상수처럼 끊임없이 생성되어 디스크 공간이 부족해지는 것을 방지하기 위해, PyPI는 오래된 일관된 스냅숏, 즉 과거의 적절한 시점(예: 1시간 전)에 더 이상 사용되지 않게 된 메타데이터와 대상 파일을 정기적으로 삭제해야 합니다(SHOULD).

최신 일관된 스냅숏을 보존하기 위해 PyPI는 “마크 앤 스윕(mark-and-sweep)” 알고리즘을 사용할 수 있습니다(MAY). 즉, 최신 일관된 스냅숏의 루트인 timestamp에서 출발하여 snapshot, targets, 위임된 targets를 차례로 거쳐 대상 파일까지 따라가면서 방문한 모든 파일을 표시하고, 표시되지 않은 파일은 모두 삭제합니다. 최근 몇 개의 일관된 스냅숏도 비슷한 방식으로 보존할 수 있습니다.

일관된 스냅숏을 삭제하면, 클라이언트가 그 일관된 스냅숏 안의 파일을 요청할 때 HTTP 404 응답밖에 받지 못합니다. 그러면 클라이언트는 최신 일관된 스냅숏으로 (이전과 같이) 요청을 다시 시도해야 합니다(SHOULD).

root 메타데이터는 버전이 매겨지기는 하지만 어떤 일관된 스냅숏에도 속하지 않는다는 점에 유의하십시오. PyPI는 root 메타데이터의 이전 버전을 삭제해서는 안 됩니다(MUST NOT). 이렇게 하면 클라이언트의 로컬 root 메타데이터가 아무리 오래되었더라도 클라이언트가 최신 root 역할 키로 업데이트할 수 있음이 보장됩니다.

프로젝트와 배포본에 대한 신뢰 철회

때때로 프로젝트나 배포본을 철회해야 할 때가 있습니다. 프로젝트나 배포본에 대한 신뢰를 철회하려면, 연관된 bin-n 역할이 해당 대상을 제거하고 bin-n 메타데이터에 다시 서명하기만 하면 됩니다. 이 작업에는 온라인 bin-n 키를 사용하는 작업만 필요합니다.

키 유출 분석

이 PEP에서는 최소 보안 모델, 배포본의 지속적 전달을 지원하기 위해 추가해야 하는 TUF 역할, 그리고 각 역할의 메타데이터를 생성하고 서명하는 방법을 다루었습니다. 나머지 절에서는 PyPI가 저장소 메타데이터를 어떻게 감사해야 하는지(SHOULD), 그리고 PyPI 침해를 탐지하고 복구하는 데 PyPI가 사용할 수 있는 방법을 논의합니다.

표 4는 (PyPI 역할 중 어느 것에든 속하는) 개인 암호 키가 임계값 개수만큼 유출되었을 때 가능한 몇 가지 공격을 요약합니다. 가장 왼쪽 열은 유출된 역할(또는 역할의 조합)을 나열하며, 그 오른쪽 열들은 유출된 역할로 인해 클라이언트가 악의적인 업데이트, 동결 공격, 메타데이터 불일치 공격에 취약해지는지를 보여 줍니다. timestamp, snapshot(스냅숏), bin-n 역할이 같은 온라인 위치에 저장되어 있다면, 그중 하나가 침해되면 모두가 침해된다는 점에 유의하십시오. 따라서 이 표는 이 역할들을 함께 다룹니다. 이 역할들을 따로 고려한 버전의 표는 PEP 480에 포함되어 있습니다.

Role Compromise Malicious Updates Freeze Attack Metadata Inconsistency Attacks
targets OR bins NO timestamp and snapshot need to cooperate
timestamp AND snapshot AND bin-n YES limited by earliest root, targets, or bins metadata expiry time
root YES

표 4: 특정 역할 키 조합이 유출되었을 때 가능한 공격. 2013년 9월에 (당시) 최신 버전의 pip가 이러한 공격에 어떻게 취약한지, 그리고 TUF가 이러한 공격으로부터 사용자를 어떻게 보호할 수 있는지가 제시되었습니다 [14].

targets나 bins가 유출되더라도 공격자가 곧바로 악의적인 업데이트를 제공할 수 있는 것은 아니라는 점에 유의하십시오. 공격자는 timestamp 역할과 snapshot (스냅숏) 역할도 침해해야 하는데, 이 두 역할은 모두 온라인 상태이므로 침해될 가능성이 더 높습니다. 즉, 어떤 공격이든 실행하려면 중간자(man-in-the-middle) 역할을 할 수 있어야 할 뿐 아니라 timestamp 키도 유출시켜야 합니다(또는 root 키를 유출시켜 새 timestamp 키에 서명해야 합니다). 동결 공격 이외의 공격을 수행하려면 snapshot (스냅숏) 키도 침해해야 합니다. 실제로 이 PEP는 snapshot (스냅숏), timestamp, bin-n 키를 함께 저장하거나, 심지어 이 모든 역할에 같은 키를 사용할 것을 권장합니다. 이 때문에 공격자는 이 단일 서버 하나만 침해하면 위에 나열된 공격 중 어느 것이든 수행할 수 있습니다. 그렇더라도 CDN이나 미러처럼 서명을 하지 않는 인프라가 침해되는 경우에는 클라이언트가 여전히 보호된다는 점에 유의하십시오. 또한 오프라인 root 키가 있으면 저장소는 온라인 키를 철회하여 공격으로부터 복구할 수 있습니다.

최대 보안 모델은 TUF가 종단 간 서명을 위한 역할을 추가로 도입하여 온라인 키 유출의 피해를 어떻게 줄이는지 보여 줍니다. 개발자 키를 생성하고 업로드할 배포판에 서명하는 방법은 PEP 480에 자세히 나와 있습니다.

키가 유출된 경우

키 유출이란 (PyPI의 메타데이터 역할에 속한) 임곗값 이상의 키와 PyPI 인프라가 함께 탈취되어 PyPI에서 새 메타데이터에 서명하는 데 사용된 상황을 뜻합니다.

임곗값 이상의 timestamp, snapshot (스냅숏), targets, bins 또는 bin-n 키가 침해되었다면, PyPI는 다음 단계를 수행해야 합니다(MUST):

  1. root 역할에서 timestamp, snapshot (스냅숏), targets 역할 키를 폐기하십시오. 이는 침해된 timestamp, snapshot (스냅숏), targets 키를 새로 발급한 키로 교체하는 방식으로 이루어집니다.
  2. targets 역할에서 bins 키를 새로 발급한 키로 교체하여 기존 키를 폐기하십시오. 새 targets 역할 메타데이터에 서명한 뒤 새 키를 폐기하십시오(앞서 설명했듯이 이렇게 하면 targets 메타데이터의 보안이 강화되기 때문입니다).
  3. bin-n 역할의 모든 대상은 timestamp, snapshot, bins, bin-n 키 중 어느 것도 유출되었다고 알려지지 않은, 정상으로 확인된 마지막 일관 스냅숏과 비교해야 합니다(SHOULD). 유출된 일관 스냅숏에서 추가, 갱신 또는 삭제된 대상 가운데 정상으로 확인된 마지막 일관 스냅숏과 일치하지 않는 대상은 이전 버전으로 복원할 수 있습니다(MAY). 모든 bin-n 대상의 무결성을 확인한 후에는 bins 메타데이터에서 해당 역할들의 키를 갱신해야 합니다.
  4. bins 및 bin-n 메타데이터는 반드시(MUST) 버전 번호를 올리고, 만료 시간을 적절히 연장하고, 서명을 갱신해야 합니다.
  5. 반드시(MUST) 타임스탬프가 찍힌 새 일관 스냅숏을 발행해야 합니다.

이 단계를 따르면 이 역할 중 하나만 유출되었을 수 있는 경우에도 모든 역할을 선제적으로 보호할 수 있습니다.

임곗값 이상의 root 키가 유출되었다면, PyPI는 반드시(MUST) 위의 단계를 수행하고 root 역할의 모든 root 키도 교체해야 합니다.

또한 PyPI는 보안 공지를 통해 유출 사고를 충분히 문서화할 것을 권장합니다(RECOMMENDED). 이러한 보안 공지는 timestamp, snapshot (스냅숏) 또는 root 역할의 키가 더 이상 유효하지 않아 pip-with-TUF 사용자가 프로젝트를 설치하거나 업데이트할 수 없을 때 가장 유용합니다. 그러면 사용자는 PyPI 웹 사이트를 방문하여 더 이상 설치하거나 업데이트할 수 없는 이유를 설명하는 보안 공지를 참고하고 그에 따라 조치를 취할 수 있습니다. 유출로 인해 폐기된 root 키가 임곗값에 이르지 않았다면, 기존 root 키 중 임곗값 이상의 키로 새 root 메타데이터의 무결성을 보증하는 서명을 하므로 새 root 메타데이터를 안전하게 업데이트할 수 있습니다. TUF 클라이언트는 이전부터 알고 있던 root 키 중 임곗값 이상의 키로 새 root 메타데이터의 무결성을 검증할 수 있습니다. 대부분은 이 경우에 해당합니다. 그렇지 않은 최악의 경우, 즉 유출로 인해 임곗값 이상의 root 키가 폐기된 경우에는 최종 사용자가 out-of-band 메커니즘을 통해 새 root 메타데이터를 업데이트할 수 있습니다.

스냅숏 감사

악의적인 공격자가 PyPI를 침해하면 온라인 키 중 어느 것으로든 임의의 파일에 서명할 수 있습니다. 오프라인 키를 사용하는 역할(즉, root, targets, bins)은 여전히 보호됩니다. 저장소 침해 사고에서 안전하게 복구하려면 스냅숏을 감사하여 파일이 신뢰할 수 있는 버전으로만 복원되도록 해야 합니다.

저장소 침해가 탐지되면 세 가지 유형의 정보에 대해 무결성을 검증해야 합니다:

  1. 저장소의 온라인 키가 유출되었다면, targets 역할이 새 키에 위임하는 새 메타데이터에 서명하게 하여 해당 키를 폐기할 수 있습니다.
  2. 저장소의 역할 메타데이터가 변경되었다면, 이는 온라인 키로 서명된 메타데이터에 영향을 미칩니다. 마지막 기간 이후에 생성된 역할 정보는 모두 폐기해야 합니다. 그 결과, 새 프로젝트의 개발자는 자신의 프로젝트를 다시 등록해야 합니다.
  3. 대상 파일 자체가 변조되었을 가능성이 있다면, 마지막 기간 시점에 존재하던 대상 파일에 대해 저장해 둔 해시 정보를 사용하여 이를 검증할 수 있습니다.

침해가 발생했을 때 스냅숏을 안전하게 복원할 수 있도록, PyPI는 일정에 따라 PyPI 스냅숏을 복사하는 자체 미러를 소수 유지해야 합니다. 이 목적에는 미러링 프로토콜을 즉시 사용할 수 있습니다. 미러는 PyPI 미러링만을 담당하도록 보안 조치를 갖추고 격리되어야 합니다. 미러들을 서로 대조하여 우발적이거나 악의적인 장애를 탐지할 수 있습니다.

또 다른 방법은 snapshot (스냅숏)의 암호학적 해시를 주기적으로 생성하여 트윗하는 것입니다. 어쩌면 어떤 사용자가 실제 메타데이터를 제공하고, 저장소 유지 관리자가 그 메타데이터 파일의 암호학적 해시를 검증할 수도 있습니다. 또는 PyPI가 외부에서 제공된 메타데이터에 의존하는 대신 자체적으로 snapshot (스냅숏)의 버전들을 주기적으로 보관할 수도 있습니다. 이 경우 PyPI는 저장소에 있는 모든 대상 파일의 암호학적 해시를 구하여 이 데이터를 오프라인 장치에 저장해야 합니다. 어떤 대상 파일의 해시라도 변경되었다면, 이는 공격이 있었음을 나타냅니다.

서로 다른 버전의 메타데이터를 제공하거나 배포 패키지를 특정 버전에 고정시키는 공격은 TUF가 암묵적 키 폐기 및 메타데이터 불일치 탐지 같은 기법으로 처리할 수 있습니다 [2].

업데이트 프로세스의 향후 변경 관리

업데이트 프로세스에 호환성을 깨뜨리는 변경이 이루어진다면, PyPI는 기존 클라이언트에 지장을 주지 않고 이러한 변경을 구현해야 합니다. 이를 수행하는 방법에 대한 일반적인 지침은 TAP 저장소에서 진행 중인 논의를 참조하십시오.

이 PEP로 인한 PyPI의 변경 사항은 하위 호환성을 유지한다는 점에 유의하십시오. 이 PEP에서는 대상 파일과 simple 색인의 위치를 변경하지 않으므로, 기존의 모든 PyPI 클라이언트는 여전히 이 파일들을 사용하여 업데이트를 수행할 수 있습니다. 이 PEP는 클라이언트가 TUF 메타데이터를 사용하여 업데이트 프로세스의 보안을 향상할 수 있는 기능을 추가합니다.

해시 알고리즘 전환 계획

대상 파일과 메타데이터 파일의 해시에 사용되는 알고리즘이 취약해지면, 더 강력한 해시 알고리즘으로 교체해야 합니다.

TUF 메타데이터 형식은 서로 다른 해시 알고리즘의 다이제스트를 알고리즘 식별자와 함께 나란히 나열할 수 있게 하므로, 클라이언트는 알고리즘 간에 원활하게 전환할 수 있습니다.

그러나 기존 알고리즘에 대한 지원이 중단되면, 새 알고리즘을 지원하지 않는 클라이언트는 TUF 검증을 비활성화해야만 클라이언트 자체를 포함한 패키지를 설치하거나 업데이트할 수 있습니다. 클라이언트가 TUF의 보안 보장을 일시적으로도 잃지 않고 전환할 수 있도록, 다음 절차를 권장합니다.

  1. Warehouse에 새 알고리즘을 구현합니다.
  2. 만료되지 않은 기존 TUF 메타데이터를 다시 생성하여 기존 알고리즘과 새 알고리즘을 모두 사용한 해시를 포함시킵니다. 앞으로 생성되는 모든 새 메타데이터에는 두 해시 알고리즘을 모두 나열해야 합니다. 대상 파일이나 다른 메타데이터의 해시 다이제스트를 나열하는 TUF 메타데이터, 즉 bin-n, snapshot (스냅숏), timestamp만 갱신하면 된다는 점에 유의하십시오. 따라서 갱신된 메타데이터에 서명하는 데에는 온라인 키만 필요합니다.
  3. Python Discourse의 packaging과 PyPI 변경 사항 메일링 리스트 등 주목도가 높은 채널에 전환을 공지합니다.
  4. pip, bandersnatch 같은 널리 쓰이는 클라이언트가 새 해시 알고리즘을 도입할 기회를 줍니다.
  5. 최종 사용자가 클라이언트를 업데이트할 기회를 줍니다.
  6. PyPI 유지 관리자들로부터 이전 해시 알고리즘을 제거하는 데 대한 대략적인 합의를 얻으십시오.
  7. Warehouse에서 기존 알고리즘 지원을 제거하고 새 알고리즘만 지원합니다.

부록 A: TUF가 방지하는 저장소 공격

  • 임의 소프트웨어 설치: 공격자가 클라이언트 시스템에 원하는 것은 무엇이든 설치합니다. 즉, 공격자는 다운로드 요청에 대한 응답으로 임의의 파일을 제공할 수 있으며, 이 파일은 부정한 것으로 탐지되지 않습니다.
  • 롤백 공격: 공격자가 클라이언트가 이미 본 것보다 오래된 파일을 소프트웨어 업데이트 시스템에 제시합니다. 그 결과 클라이언트는 오래된 파일을 사용하게 됩니다.
  • 무기한 동결 공격: 공격자가 클라이언트가 이미 본 파일과 같은 파일을 소프트웨어 업데이트 시스템에 계속 제시합니다. 그 결과 클라이언트는 새 파일이 있다는 사실을 알지 못합니다.
  • 끝없는 데이터 공격: 공격자가 파일 다운로드 요청에 끝없는 데이터 스트림으로 응답하여 클라이언트에 피해를 줍니다(예: 디스크 파티션이 가득 차거나 메모리가 고갈됨).
  • 느린 검색 공격: 공격자가 매우 느린 데이터 스트림으로 클라이언트에 응답하여, 사실상 클라이언트가 업데이트 과정을 결코 이어 가지 못하게 만듭니다.
  • 불필요한 의존성 공격: 공격자가 클라이언트에게 원하는 소프트웨어를 설치하려면 관련 없는 소프트웨어도 설치해야 한다고 알립니다. 이 관련 없는 소프트웨어는 신뢰할 수 있는 출처에서 온 것일 수 있지만, 공격자가 악용할 수 있는 알려진 취약점을 가지고 있을 수 있습니다.
  • 혼합 공격: 공격자가 저장소에 동시에 함께 존재한 적이 없는 파일들을 포함하는 저장소의 모습을 클라이언트에 제시합니다. 이로 인해 예를 들어 오래된 버전의 의존성이 설치될 수 있습니다.
  • 잘못된 소프트웨어 설치: 공격자가 신뢰할 수 있는 파일이지만 클라이언트가 원한 것이 아닌 파일을 클라이언트에 제공합니다.
  • 업데이트를 방해하는 악성 미러: 저장소 미러 하나를 장악한 공격자가 사용자가 다른 정상 미러에서 업데이트를 받지 못하게 막을 수 있습니다.
  • 키 탈취에 대한 취약성: 키 하나 또는 주어진 임계값보다 적은 수의 키를 탈취할 수 있는 공격자가 클라이언트를 공격할 수 있습니다. 여기에는 SSL로만 보호되는 경우처럼 단일 온라인 키에 의존하거나, 대부분의 소프트웨어 업데이트 시스템이 파일 서명에 사용하는 것처럼 단일 오프라인 키에 의존하는 경우가 포함됩니다.

참고 문헌

감사의 글

이 자료는 미국 국립과학재단(National Science Foundation)의 연구비 지원(Grants No. CNS-1345049 및 CNS-0959138)을 받아 수행된 연구에 기반합니다. 이 자료에 표현된 모든 의견, 결과, 결론 또는 권고 사항은 저자(들)의 것이며, 반드시 미국 국립과학재단의 견해를 반영하는 것은 아닙니다.

TUF를 사용하기 쉽고 효율적인 방식으로 PyPI에 통합하는 방법을 고민하는 데 도움을 준 Alyssa Coghlan, Daniel Holth, Donald Stufft, 그리고 distutils-sig 커뮤니티 전반에 감사드립니다.

Roger Dingledine, Sebastian Hahn, Nick Mathewson, Martin Peck, Justin Samuel은 TUF의 전신인 Tor 프로젝트의 Thandy를 바탕으로 TUF를 설계하는 데 도움을 주었습니다.

TUF 개발에 힘써 준 Konstantin Andrianov, Geremy Condra, Zane Fisher, Justin Samuel, Tian Tian, Santiago Torres, John Ward, Yuyu Zheng의 노고에 감사드립니다.

Vladimir Diaz, Monzur Muhammad, Sai Teja Peddinti, Sumana Harihareswara, Ee Durbin, Dustin Ingram은 이 PEP의 검토를 도와주었습니다.

Zane Fisher는 이 PEP의 검토와 필사를 도와주었습니다.