Поддержка Secure boot при сборке на ABF
Содержание
Введение
Secure Boot (далее SB) — это часть стандарта UEFI, описывающая систему доверенной загрузки. Проверка осуществлятся посредством цепочки сертификатов, главные из которых (корневые) находятся внутри специального хранилища в прошивке UEFI-BIOS. При включённом SB прошивка может загружать и исполнять только бинарники, подписанные доверенным сертификатом. Соответственно, чтобы обеспечить загрузку дистрибутива, требуется наличие загрузчика, который подписан одним из ключей, входящих в базовую цепочку доверия. Обычно в этот список входят сертификаты фирмы-производителя оборудования, производителя прошивки, материнской платы, а также сертификат компании Microsoft, каковая и предоставляет услуги по подписыванию сторонних EFI-файлов.
В дистрибутиве ROSA Desktop Fresh в качестве EFI-загрузчика используется grub2, но поскольку при каждом обновлении его требовалось бы переподписывать заново (что может занять несколько недель), то в качестве доверенного загрузчика была выбрана минималистичная заглушка shim от Matthew Garrett. Shim позволяет добавить в цепочку доверия новые ключи, а также (в комплекте с утилитой MokManager) вручную отметить нужные бинарники как доверенные. В итоге последовательность загрузки выглядит следующим образом:
- UEFI загружает shim, который подписан ключом от Microsoft и потому разрешён к загрузке.
- shim ищет в каталоге рядом с собой файл под именем grubx64.efi (имя фиксированное, задаётся при компиляции shim'а) и пытается его загрузить. При этом выполняется проверка, что grubx64.efi подписан либо одним из доверенных сертификатов UEFI, либо одним из сертификатов, публичные ключи которых были добавлены непосредственно внутрь кода shim'а при компиляции. В нашем случае в shim был включён ключ компании ROSA, которым подписываются бинарники grub2. В результате проверка проходит успешно, grub2 загружается. (Если grubx64.efi подменить другим файлом, не подписанным нашим ключом, то, разумеется, проверка выдаст ошибку, и файл загружен не будет.)
- grub2 в свою очередь загружает файл меню, отображает меню для выбора системы, после выбора начинает загружать ядро Linux. При этом также осуществляется проверка подписи при помощи сервисов shim'а, который не выгружается полностью, а оставляет в памяти callback-функции. Если ядро подписано, то выполняется его загрузка. Если же оно не подписано, то grub2 выполняет выход из режима Secure Boot и выполняет загрузку ядра в обычном, небезопасном режиме. Все варианты ядра в ROSA не подписаны, поэтому загруженная система всегда будет небезопасной. Но это нормально, поскольку основной задачей поддержки режима SB в ROSA является не обеспечение доверенного окружения, а возможность беспроблемной загрузки на пользовательских компьютерах.
Чтобы эта схема работала, исполняемые файлы grub2 должны при каждом обновлении переподписываться ключом ROSA. С одной стороны, требуется процедуру подписывания максимально упростить и автоматизировать, с другой стороны, необходимо гарантировать защиту от несанкционированного доступа к подписыванию произвольных файлов. Ниже описана схема инфраструктуры, которую мы реализовали для этих целей, а также приведены инструкции по разворачиванию её с нуля.
Схема инфраструктуры ABF для безопасного подписывания
Здесь описана общая схема работы. Технические подробности о реализации и необходимых взаимосвязях расписаны в следующем разделе.
- В ABF создан аккаунт signer, который управляет всеми подписываемыми проектами. Пароль от него имеют только доверенные лица.
- Выделяется компьютер, который расположен в физически защищённом месте. На компьютере разворачивается приватная ABF-нода, доступная исключительно аккаунту signer.
- В этот компьютер вставляется токен с росовским ключом, ставятся необходимые драйверы и поднимается демон, который обеспечивает автоматическое подписывание всех бинарников, подложенных в определённое место по определённым правилам.
- В ноду добавляется скрипт, который при каждом запуске сборки RPM-пакета внедряет в сборочницу макрос %auto_sign, предоставляющий простой интерфейс для корректного взаимодействия с подписывающим демоном.
- В аккаунт signer переносятся проекты, которые требуется подписывать, в spec-файлы добавляется вызов %auto_sign для нужных EFI-файлов. При необходимости выполняется перенастройка платформы на использование этих перенесённых проектов вместо ранее использовавшихся общедоступных.
- При сборке пакетов из-под аккаунта signer выбирается опция «External nodes: My». Это заставит проект собираться именно на выделенной ноде, привязанной к аккаунту. В противном случае проект будет собран на публичных нодах, которые не содержат макроса %auto_sign и не обеспечивают подписывания файлов (в зависимости от spec-файла это может привести либо к падению сборки, либо к сборке пакета с неподписанными файлами).
Инструкция по разворачиванию инфраструктуры
В этом разделе предполагается, что система ABF как таковая уже развёрнута и работоспособна, что дистрибутивные платформы сконфигурированы и т. д.
- На ABF создаётся аккаунт signer с достаточно сложным паролем. В профиле аккаунта в разделе Settings находится API-токен, который потребуется для настройки ноды. Внимание! Этот токен должен держаться в секрете, как и пароль!
- На выделенной машине устанавливается система, поддерживаемая серверной частью ABF. Рекомендуется Ubuntu или CentOS (на текущий момент для подписывания используется нода под CentOS 7).
- Если в системе используется SELinux, его имеет смысл отключить. В противном случае могут наблюдаться проблемы с доступом подписывающего демона к базе сертификатов.
- Далее разворачивается инфраструктура ABF-ноды, как описано в инструкции. Некоторые детали:
- На вспомогательном компьютере, имеющем беспарольный рутовый SSH-доступ к будущей ноде, устанавливается ansible, клонируется проект abf-ansible.
- В этом проекте создаётся конфигурационный файл abf-worker.hosts на основе примера abf-worker.hosts.example. Для экономии ресурсов рекомендуется оставить только сборочных воркеров, а публикацию и сборку образов отключить.
- В качестве параметра abf_token требуется указать API-токен аккаунта signer.
- В параметре scripts_mdv_external_script требуется прописать путь, по которому на ноде будет располагаться скрипт, автоматически запускающийся в сборочнице для импорта макроса %auto_sign (сам скрипт будет скопирован туда позднее). Пример финального файла конфигурации (API-токен забит иксами):
rpm1-rosa-signer ansible_ssh_port=22 ansible_ssh_host=192.168.1.22 [abf-worker-rpm-rosa-signer] rpm1-rosa-signer abf_token='XXXXXXXXXXXXXXXXXXXX' platform_type='rhel' platform_name='centos' [abf-worker-rpm-rosa-signer:vars] file_store_ip = 195.19.76.233 file_store_domain = file-store.rosalinux.ru abf_ip1 = 195.19.76.241 abf_domain1 = abf.rosalinux.ru abf_ip2 = 195.19.76.241 abf_domain2 = abf.io max_workers_count = 2 vm_x86_64 = 512 vm_i586 = 512 vm_cpus = 2 use_log_server = false use_proxy = false proxy_ip = http://8.8.8.8:8080 scripts_mdv_external_script = '/opt/rosa-signer/auto_sign-macro.args' scripts_rhel_external_script = [abf-worker-rpm:children] abf-worker-rpm-rosa-signer [abf-worker-rpm:vars] log_server_host=0.0.0.0 resque_redis=0.0.0.0:6379 [abf-worker:children] abf-worker-rpm [abf-worker:vars] # ansible_ssh_user=root Comment because remote_user dont rewrite this # our public key airbrake_api_key='3c6209710087bfb53bd9604040a9c6ef' abf_url='https://abf.rosalinux.ru' file_store_url='http://file-store.rosalinux.ru'
- Разворачивание инфраструктуры на ноде выполняется командой:
ansible-playbook -i abf-worker.hosts abf-worker.yml
- Эту команду необходимо выполнять каждый раз после перезагрузки сервера, так как сервисы ABF не являются linux-сервисами и не стартуют автоматически.
- Для обеспечения работы токена на ноду может потребоваться установка дополнительных пакетов. На данный момент используется ключ от DigiCert на токене Aladdin eToken Pro 72k, для которого требуется клиент SafeNet (DigiCert высылает его по запросу). При установке может возникнуть проблема с зависимостью от libhal.so.1, который в современных системах не используется. Для CentOS 7 можно скачать пакет hal-libs от CentOS 6.x и установить его.
- Для работы потребуются следующие утилиты: pkcs11-tool, certutil, modutil, pesign, expect. В CentOS 7, если они отсутствуют, можно установить их командой:
# yum install nss-tools opensc pesign expect
- Выбираем место для хранения подписывающих скриптов и данных для них. Ниже предполагается, что это каталог /opt/rosa-signer/ (если выбран другой путь, нужно поправить его в параметре scripts_mdv_external_script в конфигурации ansible).
- Создаём базу сертификатов и добавляем в неё наш токен:
# mkdir -p /opt/rosa-signer/db # cd /opt/rosa-signer/db # export DBDIR=$PWD # certutil -d $DBDIR -N --empty-password # modutil -dbdir $DBDIR -add etoken -libfile /usr/lib64/libeToken.so -force
- Проверить наличие токена в системе можно командой:
# pkcs11-tool --module /usr/lib64/libeToken.so -L
- В аккаунте signer имеется приватный проект auto-sign, который необходимо скопировать себе через сотрудника, имеющего доступ к аккаунту. Копируем файлы из этого проекта:
# cp rosa-signer.pl sign.expect auto_sign-macro.args /opt/rosa-signer/ # cp rosa-signer.service /lib/systemd/system/
- Необходимо проверить, что путь к файлу auto_sign-macro.args и его имя соответствуют указанному в scripts_mdv_external_script. Также, если используется путь, отличный от /opt/rosa-signer/, нужно будет отредактировать эти файлы, поправив все пути. В файле README детально расписаны все переменные, на которые нужно обратить внимание.
- Редактируем файл sign.expect, прописывая туда название токена, имя сертификата и пароль к токену.
- Обновляем конфигурацию systemd и запускаем созданного демона подписывания:
# systemctl daemon-reload # systemctl enable rosa-signer.service # systemctl start rosa-signer.service
- Система автоподписывания готова к работе.
В spec-файлах подпись реализуется макросом %auto_sign, принимающим в качестве аргументов список файлов, которые нужно подписать. Если проект поддерживается несколькими разработчиками, то для удобства отладки может потребоваться его сборка также и на публичных нодах, но поскольку на них нет этого макроса, сборка завершится ошибкой. Чтобы не править spec-файл, постоянно добавляя-удаляя вызов команды подписывания, удобно использовать следующую конструкцию:
%if %{defined auto_sign} %auto_sign file1.efi file2.efi ... %endif
Тогда при наличии макроса файлы будут подписаны, а при его отсутствии пакет будет собран с неподписанными файлами.
(временно) Альтернативное решение
Этот раздел требует более тщательного исследования, тестирования и причёсывания. Пока что здесь набросаны сумбурные заметки, чтобы просто не забыть.
В последних версиях osslsigncode тоже добавили поддержку работы с токенами. Поскольку osslsigncode, в отличие от pesign, позволяет подписывать с наложением временно́го сертификата, эта утилита является предпочтительной (если заработает). Предварительные заметки о том, как заставить его работать.
- Скомпилировать последнюю версию osslsigncode из репозитория.
- Для работы с токеном потребуется библиотека engine_pkcs11. В CentOS её нет, поэтому её тоже придётся собрать из исходников.
- Вытаскиваем исходники: engine_pkcs11 + libp11
- Сначала компиляем libp11:
cd /home/user/libp11 ./bootstrap ./configure make
- Теперь engine_pkcs11:
cd /home/user/engine_pkcs11 ./bootstrap LDFLAGS='-L/home/user/libp11/src/.libs/' PKG_CONFIG_PATH=/home/user/libp11/src/ CFLAGS='-I/home/user/libp11/src' ./configure make
- Далее, из токена требуется извлечь сертификат (непонятно, зачем, раз он всё равно в ключе есть, но без этого osslsigncode не работает):
pesign --certdir /opt/rosa-signer/db -t "NTC IT ROSA LLC" -c "NTC IT ROSA LLC" --export-cert=/opt/rosa-signer/cert/rosa.der openssl x509 -inform der -in /opt/rosa-signer/cert/rosa.der -out /opt/rosa-signer/cert/rosa.pem
- Можно подписывать:
/home/user/osslsigncode/osslsigncode sign -pkcs11engine /home/user/engine_pkcs11/src/.libs/engine_pkcs11.so -pkcs11module /usr/lib64/libeToken.so -certs /opt/rosa-signer/cert/rosa.pem -h sha256 -t http://timestamp.digicert.com/ /path/to/grub-unsigned.efi /path/to/grub-signed.efi
- Будет выведен запрос вида «PKCS#11 token PIN:», в ответ надо ввести токеновский пароль.
- Всё, файл подписан и закреплён временно́й меткой.
- Проверить:
- Принимает ли такой файл Secure Boot?
- Используется ли эта временна́я метка при проверке? То есть, продолжает ли ключ быть валидным при переводе даты/времени за пределы валидности ключа?
- Используются ли в Secure Boot (и в шиме) сроки действия ключей вообще? То есть, если взять старый граб, без временно́й метки, и перевести дату вперёд, выдаст ли система отказ грузиться?