Python: Хранение проверяемого описания зависимостей и деталей среды сборки как отдельных частей воспроизводимого контракта релиза
Определяйте зависимости среды выполнения в [project.dependencies], а требования к сборке в [build-system.requires] файла pyproject.toml. Используйте отдельный файл зависимостей с точными версиями и sha256-хешами для создания верифицируемого контракта релиза на байтовом уровне.
В этом материале
Основная мысль
Определяйте зависимости среды выполнения в секции [project.dependencies], а требования к сборке в [build-system.requires] внутри файла pyproject.toml. Для создания верифицируемого, воспроизводимого на байтовом уровне контракта релиза зафиксируйте точные версии и добавьте записи --hash=sha256 в отдельный файл требований.
Разделение зависимостей среды выполнения и требований системы сборки в pyproject.toml
Спецификация pyproject.toml определяет два различных уровня зависимостей. Таблица [build-system] объявляет зависимости на уровне Python, необходимые для работы системы сборки проекта, такой как setuptools или flit. Ключ requires является обязательным для этой таблицы и перечисляет пакеты, необходимые на этапе сборки. Таблица [project], введенная в PEP 621, содержит ключ dependencies, в котором перечислены пакеты, необходимые потребителям при установке (зависимости среды выполнения). Они отображаются в записи Requires-Dist в метаданных пакета.
Разделение этих уровней имеет решающее значение для воспроизводимого контракта релиза. Среда сборки должна быть воспроизводимой для создания идентичных артефактов дистрибутива, в то время как среда выполнения должна быть воспроизводимой для обеспечения согласованного поведения после установки. Объявляя оба уровня в pyproject.toml, вы делаете контракт явным и проверяемым: любой может проверить, какие инструменты нужны для сборки и какие библиотеки - для запуска. В спецификации отмечается, что инструменты не должны требовать обязательного наличия таблицы [build-system]; если она отсутствует, используются значения по умолчанию, которые могут различаться. Следовательно, явное указание обоих уровней устраняет двусмысленность.
Почему фиксация версий и проверка хешей решают разные задачи доверия
Фиксация версий пакетов с помощью == в файле требований защищает от изменений версий в вышестоящих источниках, которые могли бы привести к ошибкам или несовместимости. Однако этот метод все еще полагается на доверие к индексу пакетов (например, PyPI) и цепочке сертификатов TLS. Злоумышленник, скомпрометировавший индекс или осуществивший атаку man-in-the-middle, может предоставить вредоносный пакет с тем же номером версии.
Режим проверки хешей, активируемый с помощью --require-hashes и записей --hash=sha256:..., проверяет скачанный артефакт байт за байтом по известному дайджесту. Это защищает от компрометации индекса, атак на цепочку сертификатов и скрытых повторных загрузок пакета без изменения версии (в индексах, которые это позволяют). В документации pip указано, что проверка хешей является менее трудозатратной альтернативой запуску собственного сервера индекса: она избавляет от необходимости загружать пакеты, поддерживать списки управления доступом (ACL) и вести аудит, в то время как система контроля версий (VCS) обеспечивает этот след для файла требований. Обратной стороной является стоимость обслуживания: при каждом изменении версии зависимости хеши должны быть перегенерированы и проверены. Этот подход хорошо подходит для автоматизированного развертывания на серверах, где среда контролируется.
Конкретный пример: контракт релиза с обоими уровнями
Минимальный pyproject.toml объявляет build-system.requires (например, setuptools и wheel) и project.dependencies (например, requests и pydantic). Сопутствующий файл requirements-pinned.txt, созданный с помощью pip-compile или pip freeze, фиксирует каждую транзитивную зависимость с точными версиями и sha256-хешами. Эти два файла вместе образуют верифицируемый контракт.
Для установки выполните команду pip install --require-hashes -r requirements-pinned.txt. Это гарантирует, что каждый скачанный пакет соответствует записанному хешу. Требования системы сборки используются при сборке проекта из исходного кода; если устанавливается только предварительно собранный wheel, они не требуются в момент установки, но остаются частью контракта для любого, кому потребуется пересборка.
Список build-system.requires гарантирует воспроизводимость инструментов сборки; project.dependencies объявляет, что нужно установленному приложению. Сопутствующий файл требований фиксирует точные версии и предоставляет хеши для всех транзитивных зависимостей, формируя верифицируемый контракт релиза при использовании pip install --require-hashes -r requirements-pinned.txt. Показанные хеши являются усеченными заглушками; реальные хеши должны быть сгенерированы с помощью pip-compile или pip freeze, а затем вручную проверены или зафиксированы в системе контроля версий. Разделение ответственности означает, что обновление зависимости среды выполнения не требует изменения спецификации системы сборки, и наоборот. Один лишь pyproject.toml не гарантирует воспроизводимость; именно комбинация с фиксированным файлом требований с хешами создает контракт. Таблица [build-system] обязательна для воспроизводимости, так как она фиксирует инструменты сборки; ее отсутствие заставило бы полагаться на специфичные для инструментов значения по умолчанию, которые могут отличаться между средами, нарушая контракт.
# pyproject.toml
[build-system]
requires = ["setuptools>=68", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "myapp"
version = "1.0.0"
dependencies = ["requests>=2.31", "pydantic>=2.5"]
# requirements-pinned.txt (generated by pip-compile or pip freeze)
requests==2.31.0 --hash=sha256:58cd2187c8b806c9970b3f8a8e7c1d2e4f6a8b9c0d1e2f3a4b5c6d7e8f9a0b1
pydantic==2.5.0 --hash=sha256:7f4f1b1b0d8d4e8c9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2
urllib3==2.1.0 --hash=sha256:a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2
annotated-types==0.6.0 --hash=sha256:b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3
pydantic-core==2.14.1 --hash=sha256:c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4
typing-extensions==4.9.0 --hash=sha256:d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5
# Install command enforcing hash verification:
pip install --require-hashes -r requirements-pinned.txtОграничения этого подхода
Пакеты wheelhouse, содержащие скомпилированные wheel-файлы, зависят от ОС и архитектуры, поэтому они не переносимы между разными машинами. Проверка хешей не гарантирует доступность пакета так, как это сделал бы частный индекс или вендорированная библиотека; если пакет будет удален из индекса, установка завершится ошибкой, даже если хеш известен. Таблица [build-system] является необязательной; при ее отсутствии инструменты откатываются к значениям по умолчанию, которые могут различаться в разных средах, что нарушает воспроизводимость. Фиксация с хешами все равно полагается на первоначальное разрешение зависимостей и не защищает от вредоносного кода внутри самой зафиксированной версии. Наконец, поддержка хешей создает дополнительные накладные расходы: каждое обновление зависимости требует перегенерации и проверки записей хешей.
Условия применения
- Использует ли ваш проект pyproject.toml с таблицами [build-system] и [project]?
- Готовы ли вы перегенерировать записи хешей при каждом изменении версии зависимости?
- Достаточно ли однородна целевая среда, чтобы скомпилированные wheel-файлы (если они есть) были переносимыми, или вы можете пересобрать их на каждой машине?
- Есть ли у вас процесс генерации и проверки фиксированного файла требований (например, pip-compile или pip freeze)?
- Можете ли вы принять затраты на обслуживание по обновлению хешей для патчей безопасности и обновлений?
- Приемлемо ли для ваших требований к доступности отсутствие частного индекса или вендорированных библиотек?
Границы применения
Пакеты wheelhouse, содержащие скомпилированные wheel-файлы, зависят от ОС и архитектуры, поэтому они не переносимы между разными машинами. Проверка хешей не гарантирует доступность пакета так, как это сделал бы частный индекс или вендорированная библиотека. Таблица [build-system] является необязательной; при ее отсутствии инструменты откатываются к значениям по умолчанию, которые могут различаться в разных средах. Фиксация с хешами все равно полагается на первоначальное разрешение зависимостей и не защищает от вредоносного кода внутри самой зафиксированной версии.