Воспроизводимые сборки на примере GNU/Guix
Эта обзорная статья рассматривает концепцию воспроизводимых сборок. Она не затрагивает глубокие технические детали. В качестве демонстрации используется пакетный менеджер GNU/Guix.
Воспроизводимая сборка также называется детерменированной компиляцией 1. Сборка называется воспроизводимой, если исходный код, программное окружение и инструкции сборки будут давать на выходе побитово идентичные копии всех артефактов.
Соответствующее программное окружение, инструкции сборки и исходный код также как и ожидаемый воспроизводимый артефакт определены авторами ПО или дистрибьюторами. Артефакты сборки – это часть сборки, которая является основным ожидаемым результатом.
Термины
- Исходный код
- Берется из системы контроля версий определенной ревизии или архива.
- Соответствующие аттрибуты среды сборки
- Включают в себя зависимости и их версии, флаги конфигурации сборки и переменные окружения.
- Артефакты
- Являются исполняемыми файлами, пакетами дистрибутива или образами файловой системы. Обычно не содержат в себе логов сборки или похожих на логи вещей. Воспроизводимость артефактов достигается путём побитового сравнивания при помощи криптографических хеш-функций 2.
- Авторы и дистрибьюторы
- Лица, которые утверждают воспроизводимость артефактов. Они могут быть авторами ПО, сопровождающими дистрибутива или иным лицом, распространяющим ПО.
Почему воспроизводимые сборки важны
Рис. 1.: Логотип движения за воспроизводимые сборки
Не смотря на то, что каждый может исследовать исходный код свободного и открытого ПО на наличие вредоносных дыр, большая часть этого ПО распространяется в виде собранных исполняемых файлов без методов проверки соответствия с исходным кодом.
Это стимулирует нападки на разработчиков, которые выпускают ПО, не только посредством традиционного использования эксплоитов, но также в форме политического влияния или шантажа.
Это особенно важно для разработчиков, создающих ПО, связанное с приватностью и безопасностью: атака на это ПО, обычно компроментирует политические цели таких как: инакомыслящие, журналисты, информаторы, а также любого, кто желает использовать безопасные от репрессивного режима каналы связи.
Не смотря на то, что отдельно взятые разработчики кажутся естественной мишенью, под удар также попадают инфраструктуры для сборки, которые при успешной атаке предоставляют доступ к большому числу компьютерных систем. Модифицирование собранных исполняемых файлов в сборочной инфраструктуре вместо модифицирования исходного кода делает эти изменения практически невидимыми для оригинальных авторов и пользователей программы.
Целью воспроизводимых сборок является возможность проверки на факт наличия уязвимостей и дыр в безопасности в процессе сборки. Один и тот же вход гарантирет один и тот же выход. Это позволяет понять находится перед нами оригинальная правильная или скомпроментированная неправильная сборка. Это позволяет предотвращать такие угрозы и атаки как можно раньше, так как любое изменение будет быстро обнаружено.
Как добиться воспроизводимости
Достижение воспроизводимости сборок требует сотрудничества нескольких ролей в производстве ПО.
Создание детерменированного окружения сборки
Чтобы ПО могло быть воспроизводимым, исходный код не должен создавать неконтроллируемые вариации в логе вывода.
Будет лучше, если пользователи обнаружат вариации, которые выдают невоспроизводимые исполняемые файлы. Создание протокола для тестов, который бы пересобирал программу в нужной вариации, сильно поможет.
Определение окружения сборки
Разные версии утилит для сборки скорее всего выдадут разный результат, пользователи должны быть в состоянии воссоздать окружение сборки. Это не обозначает, что сами утилиты для сборки должны быть побитово идентичными, вернее результат их работы должен оставаться одинаковым.
Окружение сборки может быть определено, пока ПО только разрабатывается или поверх существующего процесса сборки.
Дистрибуция окружения сборки
Пользователи должны знать какое окружение должно быть воссоздано, чтобы собрать ПО.
Если окружение сборки уже определено или является частью исходного кода, то дальнейшие шаги не требуются.
В остальных же случаях, необходимо поместить описание рядом с исполняемыми файлами о том, как воспроизвести окружение.
Предоставление протокола сравнения
Пользователи должны иметь легкий способ воссоздания окружения сборки, получения исходного кода, проведения процесса сборки и сравнения результата.
В идеале протокол сравнения должен быть прост в сравнении исполняемых файлов. Сравнение байтов и криптографических хешей легко реализовать и воспринимать.
Как проверить воспроизводимость
Техника, используемая в воспроизводимых сборках, называется Diverse Double-Compilation3 и создана она David A. Wheeler.
Алгоритм примерно следующий:
- Собрать в первый раз
- Сохранить результат
- Сделать максимальное количество изменений окружения
- Собрать второй раз
- Сравнить результаты
diffoscope – утилита, которая разработана для обнаружения проблем в сравнении результатов сборки.
Список вариаций:
- Дата и время
- путь сборки
- hostname
- domain name
- Файловая система
- Переменные окружения
- Часовой пояс
- Язык
- Локаль
- Имя пользователя
- ID пользователя
- Группа пользователя
- ID группы
- Версия ядра
- umask,
- Тип CPU
- Количество ядер CPU
disorderfs – утилита, помогающая тестировать файловую систему в воспроизводимой манере.
Воспроизводимые сборки в GNU/Guix
Рис. 2.: Логотип GNU/Guix
GNU/GUIX (произносится гикс ɡiːks) — функциональный пакетный менеджер и операционная система, разработанные Ludovic Courtès и сообществом GNU. Отличительной особенностью является использование воспроизводимых сборок и наличие только свободного ПО. Guix можно поставить на существующий дистрибутив GNU/Linux или в качестве отдельной системы на базе Linux или GNU Hurd. Раньше существовало разделение менеджера пакетов Guix и операционной системы GuixSD.
Определения пакетов описываются на диалекте языка Scheme – GNU/Guile. Большая часть исходников написана на нём же. Система изначально была основана на Nix. Отличиями от Nix(OS) являются язык для описания пакетов и сервисов, система инициализации (GNU Shepherd), использование ядра Linux-Libre (Linux без проприетарных блобов) и отсутствие проприетарных пакетов.
Все пакеты Guix собраны из исходников, включая различные прошивки (firmware), которые имеют тенденцию быть представлены в виде предсобранных исполняемых файлов без предоставления доступа к исходному коду. В Guix возможно получение предсобранных исполняемых файлов в виде подстановок. Эти подстановки представляют из себя программы, собранные на отдельных машинах (build farm), одинаковый результат которых вы получите, если бы собирали их на своей машине.
Guix собирает пакеты в полностью изолированном программном окружении для максимизации воспроизводимости – это важнейшая особенность, унаследованная от пакетного менеджера Nix.
Таким образом возможно очень малое количество вариаций между различными экземплярами окружений сборки; список доступных файлов в окружении, имя хоста, переменные окружения, локаль и так далее полностью под контролем и недоступны для изменения.
Единственное, что может различаться – это ядро и железо. Самый популярный пример того, как детали "железа" могут просочиться в процесс сборки – временная метка (timestamp). К сожалению это очень частое явление, которое используется в выводе сборки.
Подробнее о преимуществах GUIX в статье «Guix: Самая совершенная операционная система»4.
Существует русскоязычная конференция пользователей Guix в Telegram.
Установка Guix
Нам понадобится Guix в качестве пакетного менеджера, он предоставляет все инструменты для воспроизводимых сборок. Я подразумеваю, что вы использует GNU/Linux дистрибутив. Вы можете установить Guix, используя пакетный менеджер вашего дистрибутива:
Если в вашем дистрибутиве нет готового пакета с Guix, то это не проблема. Guix возможно установить на любой дистрибутив GNU/Linux, единственное требование – иметь программы GNU tar и Xz. Для этого необходимо воспользоваться инструкцией «Бинарная установка».
При установке Guix создаст директории в следующих местах:
/gnu/store/
/var/guix/
~/.guix-profile/
В /gnu/
помещаются все собранные программы в так называемый
store
. Если программа предполагается к установке, то сначала она
собирается в store
, а далее линкуется в ~/.guix-profile/
и
помещается в переменную окружения PATH. Много версий программ
может существовать на одной системе без конфликтов и хранится в
store. Неиспользуемые программы могут быть удалены сборщиком
мусора guix gc
.
В отличии от большинства системных пакетных менеджеров (apt, dnf, pacman, portage) guix не требует доступа суперпользователя для функционирования. Guix, будучи функциональным пакетным менеджером, не использует состояние системы, поэтому ему не надо лазать в системные директории, для которых нужны права суперпользователя.
GNU Hello
Рассмотрим программу GNU Hello. Это небольшая hello-world
программа, написанная на языке Си в соответствии со стилем и
практиками проекта GNU. Найдём ее описание при помощи
guix show hello
.
w96k ~$ guix show hello name: hello version: 2.10 outputs: out systems: x86_64-linux i686-linux dependencies: location: gnu/packages/base.scm:73:2 homepage: https://www.gnu.org/software/hello/ license: GPL 3+ synopsis: Hello, GNU world: An example GNU package description: GNU Hello prints the message "Hello, world!" and then exits. It serves as an example of standard GNU coding practices. As such, it + supports command-line arguments, multiple languages, and so on.
Если не знаете название пакета, то используйте команду для поиска guix search
.
Определение пакета
Определение пакета5 можно найти в файле
~/.config/guix/current/share/guile/site/2.2/
+ строка
location (location: gnu/packages/base.scm:73:2
). Для простоты взаимодействия с системой существует мод
для редактора Emacs – emacs-guix. Из него можно проще
навигироваться по системе.
Пакет GNU Hello в Guix определен следующим образом6:
(define-public hello (package (name "hello") (version "2.10") (source (origin (method url-fetch) (uri (string-append "mirror://gnu/hello/hello-" version ".tar.gz")) (sha256 (base32 "0ssi1wpaf7plaswqqjwigppsg5fyh99vdlb9kzl7c9lng89ndq1i")))) (build-system gnu-build-system) (synopsis "Hello, GNU world: An example GNU package") (description "GNU Hello prints the message \"Hello, world!\" and then exits. It serves as an example of standard GNU coding practices. As such, it supports command-line arguments, multiple languages, and so on.") (home-page "https://www.gnu.org/software/hello/") (license gpl3+)))
Это определение написано на языке GNU/Guile, диалекте лиспа Scheme. Вам не нужно понимать тонкости языка, чтобы его использовать в Guix. Как мы видим пакет описан декларативно.
Разберем это определение на составные части.
(package (name "hello") (version "2.10") ...
Имя и версия пакета.
(package ... (source (origin (method url-fetch) (uri (string-append "mirror://gnu/hello/hello-" version ".tar.gz")) (sha256 (base32 "0ssi1wpaf7plaswqqjwigppsg5fyh99vdlb9kzl7c9lng89ndq1i")))) ...
Источник пакета и его хеш-сумма. Источником пакета может являться как гит репозиторий, так и обычная ссылка на архив. Если хеш-сумма не совпадает, то guix не установит пакет. Строка mirror заменяет строку на url доступного зеркала7.
(package ... (build-system gnu-build-system) ...
Система сборки8. В Guix существуют несколько систем сборки,
содержащие инструкции и окружение сборки. В данном случае
подразумевается, что система сборки запустит команды ./configure
&& make && make check && make install
и будет иметь
соответствующие утилиты.
(package ... (synopsis "Hello, GNU world: An example GNU package") (description "GNU Hello prints the message \"Hello, world!\" and then exits. It serves as an example of standard GNU coding practices. As such, it supports command-line arguments, multiple languages, and so on.") (home-page "https://www.gnu.org/software/hello/") (license gpl3+))
Синопсис, описание, домашняя страница и лицензия. Информация, полезная при поиске пакета.
Также определение пакета может содержать зависимости inputs
,
native-inputes
и propogated-inputs
, но в нашем случае пакет
hello не содержит зависимостей. Если бы зависимости были, то они
бы собрались раньше пакета и были бы добавлены в окружение, а
зависимости зависимостей собрались бы еще раньше. Таким образом
образуется дерево зависимостей. Например дерево зависимостей
программы grep
.
Рис. 3.: дерево зависимостей программы grep
Вы можете создать такую же визуализацию командой guix graph
grep | dot -Tpng > grep.png
. Для выполнения нужен установленный
пакет graphviz
.
Загрузка исходного кода
Создадим отдельную директорию для нашей демонстрации и переместимся в нее:
mkdir hello cd hello
Загрузим исходный код при помощи команды guix download
и ссылки
на пакет из определения пакета (необходимо подставить версию):
guix download -o hello.tar.gz mirror://gnu/hello/hello-2.10.tar.gz Starting download of hello.tar.gz From https://ftpmirror.gnu.org/gnu/hello/hello-2.10.tar.gz... following redirection to `https://mirror.tochlab.net/pub/gnu/hello/hello-2.10.tar.gz` 709KiB 5.2MiB/s 00:00 [##################] 100.0% hello.tar.gz 0ssi1wpaf7plaswqqjwigppsg5fyh99vdlb9kzl7c9lng89ndq1i
Мы получили файл hello.tar.gz в текущей директории. Последняя строка вывода консоли – это хеш-сумма, которую мы видели в определении пакета.
Создание детерминированного окружения
Детерминированное окружение одно из требований для создания
воспроизводимых сборок. Для создания изолированного окружения
воспользуемся командой guix environment
:
guix environment hello --container
Флаг --container
использует возможность ядра cgroups, которая
может не поддерживаться вашим процессором. Если это так, то
используется вместо --container
флаг --pure
.
Выполнив данную команду Guix скачивает необходимые программы для окружения сборки и перемещает нас в него. То есть нам доступны только непосредственно программы для сборки (gnu-build-system) + при наличии зависимостей – собранные зависимости.
Строка [env]
говорит о том, что вы находитесь в изолированном
окружении. Чтобы выйти из окружения, используйте сочетание клавиш
C-d
. Для дальнейшей работы оставайтесь в изолированном
окружении.
Сборка
Разархивируем архив при помощи tar
и переместимся в папку с
исходным кодом:
tar -xf hello.tar.gz ls hello-2.10 hello.tar.gz cd hello-2.10
Далее соберем проект из серии команд:
./configure
make
make check
make prefix=./dist install
И так мы получили в директории dist
:
cd dist; cd bin; ./hello Hello, world!
Теперь можно выйти из виртуального окружения, нажав C-d
.
Сборка через пакетный менеджер
Теперь вы примерно понимаете как происходит процесс сборки. Всё
что мы сделали уже реализовано пакетным менеджером, для повторения
всей процедуры используем guix build
:
guix build hello --check
На самом деле данная команда использует дополнительные шаги, но основные – те, что мы уже выполнили.
Установка через пакетный менеджер
Основная команда для взаимодействия с пакетами guix package
9, но
есть удобные алиасы guix install
для установки и guix remove
для удаления. Попробуем установить пакет hello:
guix install hello hello Здравствуй, мир! # Я использую русскую локаль в системе, поэтому выводит на русском. В # окружении сборки переменная LANG не настроена. Поменяем переменную # окружения LANG на en_US LANG=en_US hello Hello, world!
Проверка воспроизводимости
Для проверки существует команда guix challenge
. Она проверяет
совпадает ли хеш, собранной программы с тем, что находится на
сборочной ферме (build farm):
guix challenge hello -v /gnu/store/kg9mirg6xbvzcp0a98v7326n1nvvwgsj-hello-2.10 contents match: local hash: 101vrhsf9zrzkir4yz01934il2b8wp3z30cc8h0aymzdzbwrikq9 https://ci.guix.gnu.org/nar/lzip/kg9mirg6xbvzcp0a98v7326n1nvvwgsj-hello-2.10: 101vrhsf9zrzkir4yz01934il2b8wp3z30cc8h0aymzdzbwrikq9 1 store items were analyzed: - 1 (100.0%) were identical - 0 (0.0%) differed - 0 (0.0%) were inconclusive
Наша сборка побитово идентична, а значит воспроизводима. Таким образом мы рассмотрели воспроизводимые сборки на примере уже существующей программы в репозитории Guix.
Запись действий
Воспроизводимые сборки в других дистрибутивах
NixOS
Рис. 4.: Логотип Nix(OS)
Дистрибутив GNU/Linux, которым вдохновлялся Guix.
1528 out of 1541 (99.16%) paths in the minimal installation image are reproducible! 10
Debian
Рис. 5.: Логотип Debian
Проект Reproducible Debian11 ставит целью достижение воспроизводимости как можно большего числа пакетов в дистрибутиве Debian. Достаточно много пакетов уже воспроизводимы: