Comments 14
Очень интересно, спасибо за статью.
Следующий шаг – отказаться от ядра и можно будет с чистой совестью сказать, что история сделала круг :)
Хотя мне в общих чертах импонирует подобный подход, когда у нас есть какая-то минимальная система без всего лишнего (желательно "атомарная" как FedorаCore), а на ней все упаковано в контейнеры с самодостаточными приложениями, но на практике это не всегда рационально особенно, когда нужно взаимодействовать с каким-то сторонним ПО.
Очень толковый материал, если стоит задача по максимуму облегчить и утилизировать ресурсы и не закапываться с кишки то очень интересный вариант ! Спасибо большое !
Если завязываться на достаточно старую libc, руки будут более развязаны с выбором языков.
Собственно, сама идея статической линковки уже давно была признана устаревшей в связи с набором недостатков.
в статье как раз про то как избавиться от всего что кладут в "традиционные" дистрибутивы, включая libc (не линкуясь с libc статически). runtime Go (или другого языка который умеет также) заменяет libc, выполняя те функции, которые заложены в libc, например открыть файл, записать в него, закрыть. runtime golang имплементирует это сам, делая syscall-ы в ядро, поэтому у runtime golang есть определенные требования к минимальной версии ядра Linux (и они очень мягкие, т.е. поддерживаются очень древние ядра): https://go.dev/wiki/MinimumRequirements . с точки зрения безопасности, это позволяет вам не следить за уязвимостями libc (и, возможно, других библиотек), а следить только за CVE в Go (или другого языка, умеющего тоже самое)
понятно что сделано, но зачем переписывать уже проверенное?
Традиционное user-space окружение дистрибутивов Linux это куча библиотек и программ, большая часть которых написано на C (и на, на языке на котором большинство уязвимостей возникают не из-за ошибок бизнес-логики, а из-за неправильной работы с памятью (потому что сам язык это никак не предотвращает, а программисты постоянно ошибаются). эти уязвимости там могут сидеть годами, а иногда десятилетиями. Сейчас дистростроители в принципе пытаются с этим что-то делать, например внедрение sudo-rs и rust-coreutils в ubuntu, но это долгий путь и неизвестно чем закончится
Другой путь (используемый в этой статье) это минимизировать количество зависимостей. Кроме безопасности, минимизация числа зависимостей снимает и другие головные боли - упрощается сборка (особенно кросс-платформенная), отслеживание deprectated-функций во всех зависимостях и изменений API.
Еще бы ы понять зачем все это? Вот конкретные примеры, а главное с обоснованием. И вишенка как это все дебажить без обвязки.
С точки зрения ИБ это сокращение трудозатрат на отслеживание и обновление "базового" образа. Допустим вы взяли какой-то дистр, добавили туда свое приложение, запустили. Потом (через какое-то время) сканер безопасности в очередной раз проверил образ и нашел там кучу уязвимостей, (как правило) большинство из которых к вам не применимы (в каких-то либах/бинарях которые вы вообще не используете или вы их используете, но не вызываете уязвимую функцию или не выполнены еще какие-то условия экспуатации). Вам надо тратить время на то чтобы проанализировать это всё или обновить базовый образ. В случае с таким подходом Go (из сторонних зависимостей) вам надо следить только за уязвимостями в его runtime (и сторонних библиотек на Go если используете) и не надо думать о том что там опять нашли в glibc/musl или вообще в busybox/shell, который вы и не используете. Называется это сокращение поверхности атаки.
В целом, плюсы и минусы такого подхода довольно близки к distroless containers, например тут описаны https://docs.docker.com/dhi/core-concepts/distroless/
Относительно дебага подходы могут быть разные - с локальной отладкой все более-менее понятно, ведь приложение без зависимостей можно запускать и на локальной машине/VM используя стандартные инструменты дебага. Отладка на железе - если с точки зрения самого приложения, то можно собирать образы с удаленной отладкой (, также (в случае go приложения), можно использовать u-root интегрируясь с ним и получить тот же ssh и прочие стандартные тулы типа ping, cat, cp и т.п.
Если речь про то как отладить первоначальный запуск на железе, то обычно используете UART (serial) чтобы разобраться почему не грузится ядро, почему не монтируется ФС и что еще случается когда деплоишься на bare-metal.
Откуда в образе ping tcpdump итд? Если их доставлять это же опять зависимости которые надо проверять с точки зрения ИБ нет?
собираете (руками/через CI) образ с такой же версией приложения как проблемная + добавляете актуальный инструментарий для отладки, чтобы блюсти ИБ. в этом нет особых сложностей, но в целом да, debug это одно из основных возражений для distroless. как это всегда бывает, это tradeoff между безопасностью (и рядом других преимуществ) и удобством
Distroless приложения (VM/bare-metal)