Всем привет! На связи Александр Синичкин, ведущий архитектор CTCS – CrossTech Container Security – решения для обеспечения комплексной безопасности контейнерных сред: от сканирования образов до контроля запуска рабочих нагрузок и реагирования на инциденты в средах выполнения. Сегодня я расскажу, как и почему мы решили разрабатывать продукт CTCS, с какими сложностями столкнулись и каких ошибок могли бы избежать, куда планируем развиваться.

Почему решили создать собственное решение для контейнерной безопасности? Что стало отправной точкой для создания CTCS?

Главная причина – ситуация на рынке информационной безопасности. Зарубежные решения либо перестали работать на российской территории, либо прекратили пользоваться спросом из-за курса компаний на импортозамещение. Российские продукты не всегда отвечали основным требованиям заказчиков. У многих крупных компаний инфраструктура построена из сетевых сегментов с разной доступностью, причем всегда существует закрытый сегмент. Для выполнения правила по защите закрытого контура, архитектура нашего продукта состоит из двух главных элементов – ядра и агентов. Ядром выступает центральная часть с административным интерфейсом, к которому имеют доступ администраторы безопасности.

Так у некоторых наших заказчиков при обмене сообщениями между агентами и ядром, агент не может отправлять запросы к ядру. Ядро должно быть полностью изолировано, при этом агенты должны контролировать запуск контейнеров в десятках разных кластеров. Ни одно решение в то время не соответствовало таким ограничениям.

В таких условиях мы решили разрабатывать собственный продукт, который бы полностью отвечал требованиям наших клиентов. Необходимо было создать решение, где ядро полностью закрыто, а агенты могут работать в большом количестве разрозненных кластеров, под управлением одной агент-среды.

Как команда справилась с проектом такого масштаба и объема? Что было самое сложное?

В самом начале работы над решением не все члены команды обладали знаниями в области контейнерной безопасности, но являлись профессионалами в своей основной деятельности - написании кода - и имели за плечами большой и разносторонний опыт работы. Сотрудники, которые ранее работали в данной отрасли, проводили внутренние митапы, делились своими знаниями и навыками с коллегами. Благодаря такому обучению   даже frontend-разработчики CTCS знают азы контейнеризации.

Параллельно с развитием экспертизы шла и проработка архитектуры будущего решения. В основу продукта легли два ключевых решения: собственный Admission Controller и WebSocket-туннель между ядром и агентом. Рассмотрим более подробно второе решение. В продукте ядро делает запрос в агентскую часть, обновляет соединение до WebSocket и за счет этого устанавливается туннель между ядром и агентом. Таким образом, при отправке запроса в агента о том, что происходит деплой, агент не сам принимает решение о запуске, а обращался к ядру через закрытый туннель. Ядро проверяет правила и возвращает агенту ответ.

Также мы остановили свой выбор на GraphQL, который позволил объединить все сервисы ядра в единую согласованную схему благодаря механизму GraphQL Federation. Для нас было важно, что весь запрос это json-документ. Это обеспечивает нас удобством обмена данными. На стороне ядра формируется тело запроса, которое по туннелю передаётся агенту, а тот уже обрабатывает его как обычный входящий GraphQL-запрос.

Тогда такой список компонентов был единственным вариантом, который позволял соблюсти требования и успеть в сроки. И именно сроки оказались одним из самых сложных условий реализации решения.

В сочетании с отсутствием конкретного опыта, мы должны были выкатить MVP продукта за три месяца. При этом у нас:

  • не было глубокого опыта работы с несколькими компонентами;

  • не было полностью готовых компонентов;

  • не было понимания, как встроить наше решение в закрытый контур;

  • нужно было укомплектовать команду и разработать архитектуру почти с нуля.

Из-за этого нам приходилось параллельно изучать новые для нас технологии, пробовать, ошибаться и семимильными шагами идти к дедлайну. Таким образом, многие инструменты и сервисы были выбраны не в качестве идеального решения, а как единственно возможные при таких сроках.

После успешного MVP, мы продолжили трудоемкую работу над CTCS – тогда началось развитие вширь.

Какие архитектурные решения пришлось пересмотреть после MVP?

Когда мы только стартовали разработку продукта, многие решения казались очень удобными. Но по мере роста нагрузки и функциональности стало понятно, что часть архитектуры нужно менять кардинально.

И проблемы кроются в нашей первой связке – собственном Admission Controller, WebSocket-туннеле между ядром и агентами, синхронной модели принятия решений в ядре.

Изначальная реализация Validating Admission Webhook обслуживала подключенные к ядру кластеры и управляла правилами запуска контейнеров, но обладала рядом ограничений:

  • добавление новых политик было слишком сложно реализуемо;

  • некоторые крупные компании подключают кластеры через разные Admission Controllers и от нас требовалось не заменять их, а управлять ими;

  • на рынке уже существовали подходящие для нас контроллеры.

Поэтому мы решили отказаться от разработки собственного Admission Controller в пользу интеграции готовых решений: Gatekeeper, Kyverno и встроенный kube-admission.

Gatekeeper нам уже удалось внедрить – его можно развернуть как в Kubernetes, так и на голых хостах с Docker (в виде opa-сервера). Следующим этапом будет Kyverno, а после – встроенный kube-admission.

Таким образом мы можем автоматически передавать политику в нужном формате для каждого кластера, управлять большим количеством кластеров с разными правами из административного интерфейса и использовать уже готовые политики, которые заказчики применяют у себя.

Следующим камнем преткновения, который мы заменили, было общение через WebSocket-туннель. Решение было отличным для MVP, но для развития продукта оно не подходило.

Центр принятия решений находился в ядре, из-за чего  был постоянный overhead в виде лишней передачи данных, в то время как Kubernetes требует мгновенного ответа. Агенты пересылают в ядро логи, которые хранятся в нем, по тому же туннелю, что занимает большое количество ресурсов, таким образом про экономически выгодное масштабирование можно было забыть. Падение туннеля ломало работу системы, в последствии мы поняли, что нет ни одного действия, требующего синхронного ответа от агента. Держать открытым туннель постоянно не имеет смысла.

Было принято решение перейти на event-driven архитектуру. Теперь система работает на NATS, брокере сообщений, и базируется на эвентах. На стороне агентов разворачивается NATS сервер и к нему подключается ядро: в открытой части существует сервер, к которому подключена агентская часть, а со стороны ядра идут запросы в AppNodeServer, который не инициализирует соединения. Таким образом выполняется требование по изолированному контуру ядра и асинхронность передачи событий.

Что получилось улучшить в продукте или какие инструменты решили добавить?

После MVP продукт начал активно расти вширь. Мы встроили инструменты, которые закрывают полноценную оценку состояния инфраструктуры.

Одним из ключевых направлений стала работа с файловыми системами и теперь CTCS обладает большим количеством возможностей:

  • сканирование файловых систем нод;

  • проверка кластеров на соответствие CIS Benchmark;

  • поиск уязвимостей не только в кластерах Kubernetes, но и в средах с отдельно стоящими хостами с Docker.

И именно в «голых» хостах появилась отдельная сложность. Архитектура Docker устроена иначе, поэтому CTCS пришлось подключаться через Docker Authorization Plugin. Для команды это стало дополнительным техническим вызовом: плагин требовал иного подхода, нам пришлось фактически повторить весь функционал Kubernetes-контроля.

Параллельно росла функциональность в ядре продукта. Появилось больше административных возможностей: расширенная работа с подключенными реестрами, гибкие настройки расписаний сканирования, больше параметров управления, которые важны при корпоративной эксплуатации.

Каждая небольшая функция сама по себе выглядит незначительной, но в совокупности получается цельный, зрелый и действительно отличный продукт, покрывающий весь спектр задач по обеспечению безопасности контейнерных сред.

Следующим крупным шагом развития CTCS стало подключение Tetragon – инструмента, использующего eBPF для отслеживания событий в Linux. eBPF встраивается в ядро операционной системы и позволяет видеть практически все, что происходит внутри контейнеров.

Tetragon позволяет отслеживать уязвимости и внутри Kubernetes. Раньше система работала только на этапе запуска контейнера. Мы сканировали образ, анализировали параметры запуска до его старта и больше никак не контролировали возможные уязвимости в нем. Теперь CTCS видит, что происходит внутри контейнера после запуска. Мы смотрим на события через административный интерфейс и можем на них реагировать.

Простой пример: у вас есть контейнер с веб-сервером. Он отдает HTML-страницы и больше ничего делать не должен. Если внутри такого контейнера внезапно запускается SSH-демон, то это явный признак атаки. В обычной ситуации узнать об этом почти невозможно, только если вручную разбирать логи.

Tetragon фиксирует события такого рода мгновенно. Благодаря eBPF мы можем задать политику, которая запрещает запуск не доверенных процессов. Например, если хоть в одном контейнере запускается SSH, то мы сразу знаем об этом и можем заблокировать действие превентивно перед тем, как оно запустится. 

В итоге подключение Tetragon стало большим шагом вперед, но и принесло немало испытаний для всей архитектуры. Как только мы включили eBPF-мониторинг на тестовой среде и начали собирать события, то увидели, что объем данных измеряется не сотнями или тысячами, а миллионами. CTCS должен получить все эти события, сохранить, обработать, показать пользователю в удобной форме – это означает постоянный поток высокочастотных записей. И именно здесь начались трудности. Сеть, к счастью, выдержала нагрузку, но база данных явно не справлялась. Она не была рассчитана на такой объем телеметрии и начала быстро «задыхаться».

Ситуация моментально превратилась в инженерную задачу: нам нужно хранилище, рассчитанное на потоковые события и большие объемы данных. Так родилось решение перейти с PostgreSQL на ClickHouse.

Какие дальнейшие планы?

CTCS уже перешел в стадию зрелого продукта, но он не стоит на месте и у команды   запланированы шаги для его дальнейшего развития. 

Одна из первоочередных задач – научиться работать в CI, чтобы понимать соответствие образа политикам не на этапе его выгрузки из реестра, а сильно раньше – уже на стадии его билда. Нам важно давать разработчикам и DevOps’ам мгновенную обратную связь и настраивать правила пропуска или блокировки пайплайна.

Такое внедрение подразумевает появление CLI-утилиты, которая позволит интегрировать CTCS с любыми CI-системами и работать напрямую из терминала.

Следующее важное направление – расширение источников данных об инцидентах информационной безопасности и уязвимостях.

Команда планирует «подружить» текущий механизм с базами данных уязвимостей ФСТЭК, автоматическим обновлением этих баз и доставкой обновлений клиентам. Это позволит CTCS соответствовать требованиям российского рынка и использовать официальные регуляторные источники.

Прямо сейчас CTCS уже использует NATS как канал сообщения между ядром и агентами, но следующая цель – сделать NATS центром всей внутренней коммуникации и использовать события для всех внутренних сервисов ядра, чтобы получить гибкое масштабирование и устойчивость в закрытых контурах.

Также в планах расширенная интеграция с различными реестрами. Сегодня одни заказчики предпочитают Nexus, вторые Harbor, а третьи – приватный реестр в составе CI/CD. Задача CTCS уметь подключаться к большинству из них без ручной настройки.

На ранних этапах CTCS использовал примитивную модель прав и простые уведомления. Но корпоративные клиенты требуют гибкости и точечной настройки доступа, поэтому сейчас мы будет переходить к полноценной ролевой модели, а также планируем интегрировать различные почтовые системы.

Если бы, обладая текущим опытом, команда начинала разработку продукта заново, что было бы сделано иначе? Каких ошибок удалось бы избежать?

Опыт разработки CTCS оказался настолько насыщенным, что, возвращаясь назад, можно было бы принять другие решения – что-то изменить, от чего-то отказаться. Но при этом именно благодаря этим решениям продукт стал тем, чем он является сейчас.

Изначально мы выбрали GraphQL как универсальный инструмент для общения между сервисами. Бэкендерам удобно – пишешь схему, он к этой схеме генерирует код, но фронтам было особенно тяжело, до сих пор меня не любят. Стандартный REST для UI и GRPC для микросервисов, вероятно, был бы более рациональным выбором. Но со временем и бэкендеры, и аналитики, и тестировщики, и даже фронтендеры привыкли к GraphQL.

Работа с корпоративной авторизацией оказалась значительно сложнее ожидаемого. У клиентов разные и часто нестандартные схемы интеграции, поэтому приходилось разбираться в моменте, решая нестандартные ситуации.

На раннем этапе мы слишком упрощали интеграции и решили, что можно разрешить пользователям использовать незащищенные соединения. Казалось, что это ускорит внедрение. На практике получилось наоборот. Нам пришлось поддерживать несколько режимов работы: защищенное, незащищенное соединение. Каждый вариант требовал своей логики обработки и проверок.

Несмотря на все технические сложности, хочу подчеркнуть: если бы мы не прошли этот путь с «набитыми шишками», продукт не стал бы таким полным и зрелым. Каждый выбор привел по итогу к лучшему решению, а каждая сложность позволила улучшить компетенции и увеличить экспертность каждого члена команды.

Комментарии (0)