Привет! Проблема управления безопасностью зависимостей — supply chain security — в настоящее время как никогда актуальна. В качестве примера можно привести историю компании SolarWinds: исходный код разрабатываемой ею утилиты был скомпрометирован и среди клиентов компании распространилось вредоносное ПО. Также возникла тенденция по внедрению деструктивного кода разработчиками open-source проектов, широко используемых коммерческими компаниями.
Поделюсь с вами нашим видением некоторых аспектов обеспечения безопасности сборки программного обеспечения и управления внешними зависимостями.
Фреймворк SLSA
Давайте рассмотрим цепочку поставок программного обеспечения, предлагаемую фреймворком SLSA (Supply-chain Levels for Software Artifacts). Он представляет собой перечень стандартов и руководств для предотвращения несанкционированного доступа, обеспечения целостности и безопасности пакетов и инфраструктуры проектов.
Создание и использование любого программного обеспечения может повлечь появление уязвимостей в цепочке поставок, и, по мере того, как создаваемая система усложняется, необходимо иметь сформулированные методы и процессы, гарантирующие безопасность и целостность программных сборок.
SLSA предлагает визуализацию угроз цепочки поставок, в которой:
-
Угрозы для исходного кода проекта:
(A) пропуск вредоносного кода в обход аудита;
(В) компрометация системы контроля и хранения исходного кода.
-
Угрозы на этапе сборки проекта:
(С) модификация кода после аудита;
(D) компрометация платформы сборки;
(F) публикация результата сборки в обход систем CI/CD;
(G) компрометация репозитория пакета;
(H) использование скомпрометированного пакета.
-
Угрозы при использовании зависимостей:
(Е) использование вредоносной зависимости.
Далее рассмотрим способы защиты от угрозы использования вредоносных зависимостей (Е).
Защита от загрузки недоверенных версий внешних зависимостей на этапе сборки
Если вам необходимо повысить безопасность сборки продуктовых проектов при загрузке внешних зависимостей, то универсальным способом будет отключение системы сборки от интернета. Но тогда возникает другая проблема: мы не сможем устанавливать внешние зависимости. Предлагаю два метода решения:
организовать локальное хранилище зависимостей;
создать прокси, имитирующее хранилище и позволяющее жестко управлять версиями зависимостей.
Метод первый: организация локального хранилища пакетов
Речь о создании полного зеркала требуемого удалённого хранилища. Для этого требуется возможность:
организовать и настроить локальное хранилище пакетов, сторонних библиотек и зависимостей;
актуализировать версии.
Такое решение предполагает создание процесса вендоринга зависимостей — самостоятельной поддержки стороннего кода: своевременных слияний удалённых версий с локальными, проведения аудита безопасности кода новых версий.
Отмечу, что помимо стороннего кода необходимо хранить и поддерживать его зависимости, зависимости зависимостей и так далее. Это накладывает такие ограничения:
большой расход дискового пространства;
выделение дополнительных ресурсов для управления и поддержки локального хранилища.
Зато у вас будет полный контроль версионности и безопасности всех внешних зависимостей.
Метод второй: проксирование трафика со сборщиков в интернет
Здесь я имею в виду использование возможности проксирования в некоторых решениях по локальному хранению пакетов, то есть это не простой SOCKS или HTTP-прокси в интернет. Такие функции существуют во многих системах локального хранения пакетов, например, в J-Frog Artifactory и Sonatype Nexus. Они позволяют указать путь до удалённого хранилища или репозитория и по запросу пользователя или системы сборки загружать и кешировать пакеты зависимостей требуемых версий.
Этот метод освобождает нас от необходимости хранить зеркало хранилища у себя на дисках и выделять ресурсы под вендоринг, но добавляет следующие проблемы:
зависимости всё равно доставляются через интернет;
мы не можем следить за рекурсивными зависимостями и теряем контроль над цепочкой поставки.
Какое решение выбрать?
Мы считаем, что необходимо объединить лучшее из двух методов и реализовать гибкое локальное хранилище, фиксирующее версии пакетов и запрещающее обновлять их до последних версий. Можно использовать всё те же локальные хранилища, но с условием упреждающей загрузки требуемых версий зависимостей и отключения обновления зависимостей из удалённых хранилищ или репозиториев. Если же дискового пространства недостаточно, то нужна возможность управлять версионностью внешних зависимостей: необходимо создать правила загрузки или блокировки версий для хранилища с доступом в интернет.
Преимущества подхода:
уменьшение расходов дискового пространства по сравнению с полным зеркалом;
полный контроль над версиями зависимостей и рекурсивных зависимостей;
ресурсов на поддержку необходимо меньше, чем для полного зеркала.
Также не нужен вендоринг зависимостей. Предполагается, что, помимо блокировки зависимостей, для конкретных версий можно выбрать дату и считать безопасными все опубликованные до неё версии.
Это отличный подход к обеспечению безопасности, и из него можно заимствовать некоторые части.
Защита от загрузки недоверенных версий внешних зависимостей на уровне пакетного менеджера
PHP
Для фиксации зависимостей в PHP можно использовать менеджер зависимостей Composer, генерирующий lock-файл composer.lock
с информацией об установленных зависимостях. Этот файл должен храниться в репозитории и позволять однозначно воспроизводить на любой другой площадке набор пакетов, использованных программистом при разработке.
Чтобы понять, как используется composer.lock
, необходимо обратиться к командам composer install
и composer update
:
composer install
проверяет существованиеcomposer.lock
: при отсутствии — определяет версии зависимостей и создаёт его, а при наличии — устанавливает версии, указанные в нём;composer update
проверяетcomposer.json
, определяет и устанавливает последние версии зависимостей на основе указанных в этом файле, обновляетcomposer.lock
в соответствии с установленными версиями зависимостей.
Публикация и использование composer.lock
— хорошее решение для фиксации версий зависимостей. А если этого файла нет в репозитории, то предлагаю фиксировать версии так:
Определить последнюю версию зависимости, которой можно доверять или которая прошла аудит безопасности кода, например, версия 1.7.8.
Зафиксировать зависимость в
composer.json
, прописав или изменив её версию на целевую. Если этот репозиторий является подключаемым модулем к другому проекту, то можно указать версию как <=1.7.8.
Также можно использовать другие указатели или выражения нечёткого версионирования, если вы уверены и точно можете таким способом определить последнюю доверенную версию.
Примечание: применяя файлы composer.lock
учитывайте, что при наличии в проекте библиотек, использующих более новые версии тех же зависимостей, фиксация зависимостей будет работать некорректно.
JavaScript
В качестве менеджера пакетов в проектах, написанных на JS, в основном используется NPM. Принцип его работы во многом похож на Composer в PHP.
В Composer для хранения списка зависимостей проекта используется файл composer.json
, и в NPM есть такой же по назначению файл package.json
. Аналогом composer.lock
будет package-lock.json
. Команды npm install
и npm update
аналогичны командам Composer’a, которые обсуждались выше.
Для фиксации версий зависимостей можно использовать такой же подход, как и в PHP, только с поправкой на использование или генерацию файла package-lock.json
и на синтаксис semver для определения последних доверенных версий зависимостей.
Go
В отличие от других пакетных менеджеров проекты на Go не имеют отдельных списков ограничений и lock-файлов для фиксации зависимостей. Начиная с Go 1.16 версии зависимостей, используемых в проекте, всегда строго определеныяются в файле go.mod
. То есть команды go build
,go test
,go install
иgo run
не исполнятся, если файл go.mod
содержит неполное описание зависимостей — не использует синтаксис semver.
Команды, которые могут изменить go.mod
— это go get
и go mod tidy
. Команда go get
добавляет зависимости в проект, причём для неё можно указать конкретную версию пакета, которую необходимо установить, например: go get github.com/a/b@1.2.3
. Также этой командой можно установить последнюю версию зависимости, указав вместо конкретной версии слово @latest@latest
. Тогда добавление рекурсивных зависимостей выполняется строго по версиям, указанным в файле go.mod
устанавливаемой зависимости, то есть указание @latest
не распространяется на зависимости зависимостей.
Примечание: не рекомендуется использовать ключ -U
в сочетании с командой go get
, так как это влечёт за собой обновление версий зависимости до последних.
Команда go mod tidy
проходит по исходному коду проекта, загружает главный модуль и все модули, которые он импортирует, удаляет ненужные и добавляет необходимые зависимости для них. Команда добавит последнюю версию каждой необходимой зависимости, отсутствующей в файле go.mod
.
Примечание: go mod tidy
не рекомендуется использовать в процессе production-сборок. Стоит направлять изменения или добавления зависимостей через эту команду в стандартный аудит кода, чтобы удостоверится в том, что зависимости не будут самостоятельно обновляться при сборке и в неё не попадут недоверенные версии.
Такой подход не требует каких-либо действий по фиксации версий или проверке дополнительных скриптов, поэтому достаточно обратить внимание на примечания, чтобы обезопасить сборку проектов на Go.
Проверка фиксации зависимостей
Проверять фиксацию зависимостей при большом количестве репозиториев можно с помощью такого скрипта:
он проходит по репозиториям и проверяет наличие файлов
composer.lock/package-lock.json/go.mod
;проверяет правильность фиксации версий, например, <=1.7.8, и отсутствие
@latest
;обнаружив отклонения от правил фиксации версий, скрипт может написать в файл README.md о необходимости внесения изменений в проект или оповестить любым другим удобным способом.
Заключение
Все описанные методы не являются идеальными способами обеспечения безопасности сборки ПО и управления внешними зависимостями. Но из них можно почерпнуть информацию, которая может лечь в основу вашего процесса обеспечения безопасности цепочки поставок.
jsirex
https://reproducible-builds.org/
https://guix.gnu.org/