В прошлом году мы работали с Python Package Index (PyPI) над новой функцией безопасности для экосистемы Python — цифровыми сертификатами, размещёнными в индексе, как указано в PEP 740.

Эти сертификаты превосходят традиционные подписи PGP (которые были отключены в PyPI) благодаря удобству применения ключей, проверяемости индекса, криптографической надёжности и происхождению, что в наших цепочках поставок ПО делает нас на шаг ближе к целостному, криптографически проверяемому происхождению.

Хорошая новость: если вы уже публикуете пакеты на PyPI с помощью Trusted Publishing (доверенной публикации), вам, скорее всего, не придётся ничего менять: официальный рабочий процесс публикации на PyPI имеет встроенную поддержку аттестации, которая включена по умолчанию в версии 1.11.0 и более поздних версий. Другими словами, если вы уже используете (или обновитесь до) pypa/[email protected] или более поздней версии с доверенного издателя, ваши пакеты будут иметь аттестацию по умолчанию!

Включение по умолчанию было ключевым ограничением при разработке: нам нужна была функция аттестации, которая могла бы интегрироваться с существующими идентификаторами публикации, устраняя проблемы, связанные с управлением ключами и идентификаторами, возникающие при использовании традиционных цифровых подписей. Решением этих проблем стал Sigstore: его поддержка подписи без примененния ключей на основе идентификаторов обеспечивает публично проверяемую связь между поддержкой PyPI для доверенной публикации и происхождением пакетов.

Ознакомьтесь с официальной документацией PyPI, чтобы получить практические сведения о том, как создавать и применять аттестации, размещённые в индексах, а также с нашим техническим описанием того, как работают эти аттестации и каким мы видим их будущее!

Прочитайте официальное объявление в блоге PyPI.

Предпосылки: доверенная публикация

В прошлом году мы работали с PyPI, чтобы разработать и внедрить доверенную публикацию — новый, более удобный и безопасный способ загрузки пакетов в PyPI. Благодаря удобству применения за прошедшие 18 месяцев доверенная публикация стала огромным успехом: более 19 000 отдельных проектов зарегистрировали доверенного издателя, и эти проекты в совокупности опубликовали почти полмиллиона файлов в PyPI через доверенную убликацию:

У нас есть отдельная статья о доверенных публикациях и PyPI, но вкратце:

  • Доверенная публикация устраняет необходимость вручную настраивать и ограничивать доступ к токену API.
  • Проекты объявляют доверенных издателей (GitHub, GitLab, Google Cloud Build, ActiveState и т. д.), которые могут загружать новые версии.
  • Чтобы обеспечить подлинность запросов от этих идентификаторов (т. е. рабочих процессов CI/CD, которые выдают себя за них), доверенная публикация использует криптографию с открытым ключом через OpenID Connect (OIDC).
  • Процесс OIDC позволяет доверенному издателю автоматически получать токен API PyPI без вмешательства пользователя, снижая вероятность его ошибок, таких как утечка учётных данных и случайное превышение области действия.
  • Такие токены, выданные через OIDC, имеют ограниченный срок действия и минимальную область действия. Это снижает вероятность того, что злоумышленник сможет сохранить их для дальнейшей эксплуатации или переключаться между разными проектами с помощью одних и тех же учётных данных.

Успех доверенной публикации на PyPI вызвал интерес и в других экосистемах: RubyGems внедрил ее всего несколько месяцев спустя, а у Rust’s crates.io есть для неё открытый RFC!

От доверенной публикации к Sigstore

Доверенная публикация связывает проекты, размещённые на PyPI, с криптографически проверяемыми идентификаторами устройств (такими как release.yml @ github.com/example/example), которые отвечают за публикацию.

Это отличный способ избавиться от необходимости вручную вводить токены API, но, кроме того, он даёт нечто гораздо более фундаментальное — это происхождение!

В частности, в контексте рабочего процесса упаковки на GitHub (или GitLab и т. д.) идентификатор устройства, указанный в учётных данных OIDC, даёт нам что-то вроде «источника публикации»: набор утверждений о репозитории и состоянии рабочего процесса, соответствующих времени публикации пакета на PyPI.

Однако в форме учётных данных OIDC эта информация не представляет ценности для внешних пользователей:

  • PyPI не может делиться учётными данными самими по себе, ведь это секретная информация в принципе. Даже при наличии соответствующих средств контроля (срока действия и фиксированной аудитории) есть слишком большая вероятность раскрытия персональных данных и некорректной работы верификаторов JWT, чтобы рисковать раскрытием данных для внешней (то есть не PyPI) верификации.
  • PyPI мог бы раскрывать сведения из учётных данных, например, публикуя метаданные в формате «проект sampleproject был опубликован с помощью рабочего процесса GitHub pypi-publish.yml в pypa/sampleproject». Это привело бы к модели, где последующие пользователи вынуждены доверять честности PyPI.

Вот тут-то и пригодится Sigstore. У нас есть еще один пост о Sigstore и о том, как он работает, но ключевой для нашей цели частью является то, что Sigstore привязывает краткоживущие ключи подписи к идентификаторам компьютеров через бесплатный, общедоступный, проверяемый центр сертификации (Fulcio).

Fulcio принимает идентификационные данные устройств в виде учётных данных OIDC, а это значит, что процесс доверенной публикации PyPI неявно совместим с подписью Sigstore: всё, что нужно сделать доверенному издателю, — это отправить запрос на подпись сертификата в Fulcio с учётными данными OIDC и получить сертификат подписи для последующего применения.

Fulcio встроит соответствующие требования из учётных данных OIDC в публичный сертификат, предоставив нам публично проверяемый источник происхождения, который не требует раскрытия самих учётных данных или одностороннего доверия к PyPI, чтобы правильно обслуживать их.

За всеми этапами может быть немного сложно уследить, поэтому визуализируем их. Вот «традиционный» процесс доверенной публикации до подключения Sigstore:

И вот — с Sigstore в цикле:

Обратите внимание: несмотря на то, что в потоке есть ещё одна сущность (Sigstore), с точки зрения пользователя ничего не меняется. Всё, что от него требуется, — это одноразовая настройка доверенного издателя, которая используется в исходном потоке.

От Sigstore к аттестации и происхождению

Sigstore сокращает разрыв между доверенной публикацией и происхождением, предоставляя публичные, проверяемые учётные данные (в виде сертификата X.509), которые привязывают краткоживущую пару ключей к идентификатору устройства (например, репозиторию GitHub и рабочему процессу, который публикует данные в PyPI).

Однако остаётся ещё один шаг: сертификат, выданный Sigstore, привязан к идентификатору доверенной публикации, но он сам по себе не подписывает то, что публикуется (то есть сам дистрибутив пакета Python).

Чтобы обеспечить последнее, нам нужно использовать нашу краткоживущую пару ключей для подписания аттестации нашего дистрибутива, криптографически связывая его идентификатор (название и дайджест краткое описание) с источником (репозиторием GitHub или другим источником, который его создал).

Здесь появляется PEP 740. PEP 740 подключает Sigstore и доверенную публикацию к фактическому дистрибутиву пакета с помощью фиксированной полезной нагрузки для аттестации, которая сама определяется в рамках фоеймворка аттестации in-toto.

Вот пример реальной аттестации, созданной для sigstorev3.5.1:

Затем эти аттестации подписываются приватной частью эфемерной пары ключей, которая связана с сертификатом X.509, завершая полную привязку идентификатора дистрибутива (имя файла и краткое поисание) к происхождению (заявлениям OIDC, встроенным в сертификат X.509) таким образом, что это можно проверить с помощью самого PyPI (поскольку требования OIDC соответствуют идентификатору доверенного издателя, зарегистрированному пользователем).

Конечно, недостаточно просто генерировать сертификаты — эти сертификаты также необходимо сохранять, чтобы пользователи могли проверять их самостоятельно! PEP 740 определяет и это: дистрибутивам, загруженным с сертификатами, присваивается ключ provenance в простом JSON API и соответствующий атрибут data-provenance в индексе PEP 503.

Эти поля содержат URL, которые указывают на «объект происхождения», представляющий собой совокупность одного или нескольких объектов аттестации для каждого дистрибутива, а также идентификатор доверенного издателя, который PyPI использовал для проверки этих аттестаций. Можно покопаться в них, чтобы вернуться к исходной полезной нагрузке, сверху:

К чему это нас приводит?

По состоянию на 29 октября сертификаты по умолчанию используются всеми, кто публикует пакеты с помощью экшна публикации PyPA на GitHub. Это означает, что примерно 20 000 пакетов теперь могут подтверждать своё происхождение по умолчанию, без необходимости вносить изменения. Мы ожидаем, что со временем это число будет расти, поскольку всё больше проектов (особенно новых) по умолчанию используют доверенную публикацию как удобную и более безопасную альтернативу ручной настройке токенов API.

Однако общее количество пакетов, создающих сертификаты, — это лишь один из аспектов и, возможно, неполный: ценность сертификатов пакета тесно связана с «важностью» этого пакета, то есть с количеством пользователей или зависимых от него проектов. PyPI не знает о зависимостях проекта, но общее количество загрузок — хороший показатель относительной важности проекта в экосистеме.

Чтобы получить представление о последней, мы разработали Are We PEP 740 Yet?, который отслеживает внедрение сертификатов PEP 740 в 360 самых загружаемых с PyPI пакетах:

На данный момент 5% из этих 360 самых скачиваемых пакетов имеют загруженные сертификаты. Но есть один сбивающий с толку фактор: около двух третей самых скачиваемых пакетов с момента включения сертификатов вообще не обновлялись, а это значит, что мы пока не знаем, сколько из них будут иметь сертификаты после выхода новой версии!

Куда мы пойдем?

Во всей этой работе явно не хватает одного: проверки на выходе.

Как указано, PEP 740 касается только самого индекса: он сообщает PyPI, как получать и проверять сертификаты для собственных целей, а также как распространять их на общедоступных конечных точках индекса, но не предписывает (и даже не определяет) процесс проверки для установочных клиентов (например, pip и uv).

На практике это означает, что краткосрочное влияние аттестаций, размещённых в индексах, ограничено: они обеспечивают прозрачность идентификаторов доверенных издателей, используемых в PyPI, но нижестоящим клиентам по-прежнему необходимо доверять самому PyPI, тому, что аттестация была честной.

Это неприемлемо конечным состоянием (криптографические аттестации обладают защитными свойствами только в том случае, если они фактически проверены), поэтому мы ищем способы обеспечить проверку для отдельных клиентов, устанавливающих приложение. В частности, в настоящее время мы работаем над архитектурой плагинов для pip, которая позволит пользователям загружать логику проверки непосредственно в свои потоки pip install.

В долгосрочной перспективе мы можем сделать ещё лучше: выполнение «одноразовых» проверок означает, что клиент не будет помнить, каким идентификаторам следует доверять при установке каких дистрибутивов. Чтобы решить эту проблему, инструментам установки необходимо понятие «доверие при первом использовании» для подписи идентификаторов. Это означает, что последующие установки могут быть остановлены и проверены пользователем, если удостоверяющий идентификатор изменится (или пакет перестанет быть подтверждённым между версиями).

Если вам это кажется проблемой с файлом блокировки, так оно и есть! Мы внимательно следим за PEP 751, ведь он определяет формат метаданных, в котором нам нужно будет хранить ожидаемые идентификаторы дистрибутивов. Как только экосистема Python начнёт использовать стандартизированные файлы блокировки, мы сможем использовать их для хранения и проверки идентификаторов так же, как сегодня хэши используются для проверки целостности дистрибутивов.

В общем, нам ещё многое предстоит сделать, прежде чем общие процессы установки будут проверять сертификаты по умолчанию. Но, в отличие от более ранних попыток применения подписей, размещённых в индексе, у нас есть чёткое представление о том, как этого добиться. Тем временем, как бы то ни было, есть группы пользователей, которые могут воспользоваться преимуществами новых сертификатов, размещённых на PyPI. Это:

  • Исследователи: сертификаты PEP 740 построены поверх Sigstore и обеспечивают ключевую проверяемую недостающую связь между исходными репозиториями и пакетами (в том виде, в котором они представлены на PyPI). Это делает их отличным источником данных для исследований в области безопасности и цепочек поставок!
  • Специалисты по реагированию на инциденты: при наличии подтверждений значительно сокращаются и упрощаются некоторые из наиболее утомительных и подверженных ошибкам этапов расследования инцидентов: отслеживание конкретного артефакта до его источника, выяснение того, когда и как он был создан, и так далее.
  • Пользователи, полностью контролирующие свои системы сборки: если вы работаете над проектом с открытым исходным кодом или профессиональным проектом, где полностью контролируете зависимости пакетов Python (т. е. не используете pip или другой инструмент для разрешения и установки зависимостей), то вы, вероятно, можете интегрировать проверку аттестации напрямую в процесс сборки! Ознакомьтесь с документацией pypi_attestations, чтобы начать работу.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Trail of Bits: Attestations: A new generation of signatures on PyPI

Предыдущая статьяКак мы создавали автоматизированное тестирование с помощью Playwright
Следующая статья15 продвинутых техник React, которые должен знать каждый старший разработчик