Привет, Хабр!
Представь себе ситуацию:
Пятница. Конец рабочего дня. Релиз готов, пройдены последние тесты. Кнопка «Опубликовать» нажимается, и релиз растекается по серверам обновлений и дальше по серверам клиентов.
Суббота. Саппорт завален тикетами вида: «После вчерашнего обновления при формировании отчета XXX выскакивает картинка Warhammer 40k. Эта фича не была заявлена в обновлениях и в документации не описана».
Воскресенье. Чат продукта после диагностики проблемы.
Тимлид: Женя, ты зачем закоммитил картинку с warhammer40k?!
Женя: Да я вообще в отпуске вторую неделю, ты чего, какой hammer40k?
Тимлид: Ну вот же, смотри, в понедельник от тебя коммит через webide!
Женя: Всю неделю провел в тайге. Ел мох и грибы. Какой коммит, спутниковой связи даже не было!
Тимлид: Похоже, это неЖеня от имени Жени...
Как убедиться в том, что коммиты в продуктовых репозиториях «настоящие», то есть отправлены тем человеком, имя которого указано в коммите? Мы с коллегами из команды DevOps задались целью построить процесс, который будет давать нам полностью прозрачную картинку, и у нас это получилось. Эта статья довольно практическая, и решение, о котором я, Рамазан Ибрагимов, вместе с моим коллегой Александром Паздниковым пишу в этом материале, — лишь часть большой схемы по обеспечению безопасности. В качестве хранилища кода будем опираться на инстанс GitLab On-Premise внутри компании — вендора ПО. Будем рады обсудить, как подобные задачи решаете в своей инфраструктуре вы.
Знакомимся. Злоумышленник и его цели
Итак, перед нами злоумышленник. Он прошел внешний периметр защиты компании, закрепился внутри сети и получил доступы одного из разработчиков. Зачем ему это? Дело в том, что в большинстве случаев исполняемый код попадает в кодовую базу репозиториев продукта через коммиты разработчиков с их рабочих машин.
Злоумышленник хочет:
Добавить вредоносный или открывающий уязвимость код в релиз продукта компании.
Через продукты компании развить атаку на инфраструктуру клиентов.
Реализовать недопустимые события для клиентов.
В итоге получаем крайне негативный веерный сценарий. Такой сценарий трудно отслеживается и детектируется средствами мониторинга, потому что выглядит «настоящим» и нормальным.
Вернемся все же к злоумышленнику, который уже внутри сети компании. Как он сможет (и сможет ли?) влить вредоносный код на корпоративный GitLab On-Premise? Основных сценариев три:
Push нужного ему кода через веб-интерфейс GitLab.
Push нужного ему кода по протоколу HTTPS в репозиторий с использованием выписанного через UI токена.
Push нужного ему кода по протоколу SSH в репозиторий с использованием добавленного через UI ключа.
А что же git? А у git для проверки достоверности коммитов есть механизм подписи коммитов. GitLab умеет задним числом обрабатывать запушенные коммиты и отмечать коммиты с проверенными подписями статусом Verified в своей WebUI. Только это слабо помогает, потому что в процессе CI/CD требуется проверять, что кодовая база отмечена статусом Verified. А что делать, если Verified на деле оказался совсем не Verified?
Таким образом, проверка коммитов на достоверность задним числом, после попадания в GitLab-репозиторий, добавляет кучу головоломок и сценариев на обработку на стороне CI/CD.
Как изначально быть уверенными, что весь код в GitLab-репозиториях гарантированно имеет статус Verified в подписях коммитов разработчиков? Принимать только подписанный код в GitLab-репозитории и отвергать неподписанный.
Как защититься от лжекоммитов
Отвергать прием неподписанных коммитов
Первым приходит на ум каким-то образом обязать разработчиков подписывать код. Для этого будем проверять подписи коммитов в момент пуша в GitLab-репозитории при помощи Git server hooks. По умолчанию такого хука у GitLab нет. Написать самим выглядит посильной задачей. Решаемо.
Блокировать возможность использовать поддельные ключи
Исключаем возможность подделать пары ключей для подписи, выпустив новую пару ключей и прикинувшись разработчиком. Простой механизм GnuPG нам для этого не подходит. Публичная часть находится в профиле разработчика и аналогична добавлению SSH public key для идентификации пользователя при git push через SSH. Можно выпустить новую пару GnuPG, подложить в профиль разработчика и подписывать и пушить код от его имени нелегитимной парой GnuPG. Заметить и задетектить такой коммит крайне сложно.
Зато есть альтернатива GnuPG — это цепочки сертификатов PKI X.509. Развернув свой центр сертификации (CA), мы сможем выписывать сертификаты для конкретных людей и проверять подписи по цепочке доверия до корневого сертификата CA вместо доверия конечной паре ключей подписи. В итоге злоумышленнику нужно выпустить новый сертификат на нашем CA или украсть с рабочей машины разработчика его ключ. И тот и другой сценарий выполнить незаметно на порядок сложнее. Решаемо.
Управлять выпуском сертификатов
Хотим держать под полным контролем небольшой группы людей создание новых пар ключей для подписи коммитов. Для GnuPG придется создать какой-то свой keyring или аналогичное хранилище допустимых публичных частей проверки подписи. А в PKI X.509 это решено из коробки.
При компрометации сертификата легко отозвать сертификат через стандартный CRL в PKI X.509.
Отзывать сертификат ключа подписи коммитов
Для решения задачи контроля выпуска сертификатов и их отзыва хорошо подходит стандартный PKI X.509.
Ну и вишенкой на торте стала фича в GitLab 12.8+ проверки подписей коммитов через корневой сертификат X.509 и отображения статуса Verified для коммитов в GitLab WebUI.
Проверять подписи коммитов
Все нужные нам сценарии по управлению сертификатами решены в техстеке PKI X.509. Что же мы сделали:
Подняли свой CA.
Подготовили скрипты для выписывания сертификатов конечных разработчиков.
Написали глобальный Git server hook для проверки подписей всех коммитов в пуше по стеку PKI X.509 и установили его на инстанс GitLab. Для проверки знаем только корневой сертификат и проверяем дальше сертификаты конечных пользователей в коммитах, а также автоматом и CRL на отзыв сертификата.
Сконфигурировали GitLab нужным образом для проверки коммитов и отображения статуса Verified через корневой сертификат CA.
За управление выписыванием/отзывом сертификатов отвечает небольшая централизованная группа сотрудников в ИТ. Просто так выписать дублирующий сертификат уже не получится.
Общая схема работы
Как мы внедрили выбранное решение
Нужно было раздать под тысячу сертификатов. Разработчики используют различные ОС, иногда достаточно экзотические для нас дистрибутивы Linux. Если Debian/Ubuntu и Windows мы смогли протестировать на себе и отладить большинство нюансов, то с редко встречающимися у нас дистрибутивами Mint, Arch, Fedora и другими помогали разбираться сами пользователи этих систем и сводить в FAQ для таких же пользователей. Всего было выписано уже более двух тысяч сертификатов.
Сначала ввели проверку подписей на месяц в рекомендательном характере. Выявили массу нюансов конкретных ОС и зафиксировали основной FAQ по решению подписи на конкретных версиях ОС.
Затем уже вводили блокирующую проверку подписей, и, в принципе, все прошло достаточно гладко.
Из минусов — потерялась возможность изменений через GitLab Web IDE. Такова плата за безопасность кодовой базы.
Скоро уже будет год, как все коммиты в продуктовые репозитории подписываются сертификатами разработчиков X.509. Мы расширили информационные части самого хука, чтобы было сразу понятна причина отказа и как ее чинить самому пользователю.
Количество обращений с подписями коммитов и сертификатами через 2-3 месяца начало стремиться к нулю (очень редко случается, что коллеги бывают невнимательны, но мы работаем над этим).
Исключения
Да, конечно, нам пришлось ввести исключения в проверки подписей. И эти исключения всегда рассматриваются комитетом безопасности по допустимости рисков. В исключения попадают только репозитории, не участвующие в кодовой базе продуктов, и только если с подписями ну совсем никак не получается работать. Например, для определенных не ИТ-ролей: юристов, финансистов и других, далеких от git clone/commit/push. В коде серверного хука это белый список.
Настройки для вашего GitLab On-Premise
Как видите, пока что проверка подлинности коммитов работает не «из коробки». Когда-нибудь GitLab добавит эту фичу, по меньшей мере запрос на нее уже есть. А пока вы можете взять кодовую базу нашего Commit Signature Verifier и инструкции по его настройке тут. Пользуйтесь на здоровье и делайте свою кодовую базу заведомо безопасной.