Это check-list к моему докладу на конференции TechLead Conf X 2025.
Тема доклада: "Безопасное исполнение ненадёжного кода" (5 июня 2025).
Выбираем между стабильностью получаемых результатов и скоростью запуска и взаимодействия с ненадёжным кодом.
-
Тот же процесс. Исполнение кода в адресном пространстве процесса приложения. Минимальная изоляция. Примеры: интерпретация скриптов, визуальные языки программирования, плагины. Возможные решения: самоизоляция, статические анализаторы, централизованный реестр плагинов.
-
Отдельный процесс. Исполнение кода в отдельном процессе, на машине приложения. Средняя степень изоляции, максимально быстрое взаимодействие. Запуск кода производить от имени пользователя с ограниченным набором прав на доступ к ресурсам ОС.
-
Отдельный узел (песочница). Исполнение кода в отдельном процессе, на отдельной машине. Максимальная степень изоляции, медленное взаимодействие, подверженное сетевым сбоям.
-
Пересоздание или переиспользование. Пересоздание, если требуется особое окружение для каждой исполняемой задачи или идентичность этого окружения перед каждым запуском. Вариант решения: при небольшом потоке задач пул песочницы можно создавать заранее. Выбираем между стабильностью получаемых результатов и скоростью запуска.
-
Последовательное или параллельное исполнение. Последовательное, если важен порядок исполнения задач, нужен эксклюзивный доступ к некоторому ресурсу или задачи слишком ресурсоёмки. Важно: параллельное исполнение увеличивает время выполнения отдельных задач и может привести к перегрузке песочницы. Выбираем между стабильностью получаемых результатов и высокой пропускной способностью.
-
Оркестрация. Приложение совмещает две роли: оркестратор исполнения и оператор песочниц. Оркестратор координирует процесс исполнения кода; оператор отслеживает доступные песочницы и их состояние. Оператор может быть выделен в отдельный discovery-сервис, либо может быть реализован с использованием Kubernetes API. Основной плюс: прямолинейность алгоритма. Минусы: синхронное взаимодействие с песочницами, отслеживание доступности песочниц и балансировка нагрузки на них, неопределенность результата при отсутствии ответа, плохая масштабируемость.
-
Самоорганизация. Приложение публикует задачи на исполнение в очередь задач и принимает результаты исполнения из очереди результатов. Агент - мини-сервис, работающий в песочнице, получает задачи из очереди задач, координирует процесс исполнения кода, публикует результаты исполнения в очередь результатов. Основной минус: распределенная обработка. Плюсы: скорость и стабильность взаимодействия, контролируемая нагрузка на песочницы, хорошая масштабируемость.
-
Ограничение прав. Процесс должен запускаться от имени пользователя с максимально ограниченным набором прав на доступ к ресурсам ОС.
-
Ограничение ресурсов. Лимитирование ресурсов процесса средствами ОС (системный API), с использованием cgroup или специализированных rootless-инструментов, например, ProcessSandbox или Bubblewrap. Выбор зависит от требований к скорости запуска процесса. Важно: использование cgroup в Docker/Kubernetes требует эскалации привилегий контейнера, что небезопасно само по себе!
-
Анализ поведения и результатов. Почти всегда требуется программирование с учётом особенностей проекта.
Ограничение ресурсов с использованием инфраструктуры (например, Kubernetes Limits), cgroup или средств ОС задаёт жесткие границы, которые приводят к принудительному завершению процесса. Это осложняет анализ поведения и результатов. В этом случае желательно использовать подход с "программной мягкой границей" (watchdog). Пример реализации подхода и его описание - ProcessSandbox.
-
Заменить init-процесс на tini. Это позволит избежать проблему утечки PIDs в случае, когда много процессов завершаются принудительно. Утечка PIDs может незаметно остановить работоспособность системы.
-
Установить разумный лимит на количество PIDs. Лимит на PIDs задаётся для пользователя, а не для запускаемого процесса! По этой причине он должен быть разумно большим.
Приложение и песочница могут работать в разных контейнерах одного Kubernetes-пода. Это избавит от проблем сетевого взаимодействия между приложением и песочницей, т.к. контейнеры Kubernetes-пода будут работать на одном и том же узле. Недостатки такой схемы:
-
Плохая масштабируемость. Соотношение "приложение-песочница" 1:1.
-
Плохая утилизация ресурсов. Сможет ли приложение достаточно нагрузить песочницу?
-
Возможность перегрузки песочницы. Песочница только одна, справится ли она с потоком?
-
Риск нарушить работоспособность приложения. Например, через Shared Volumes.
-
Заменить init-процесс на tini. Нужно добавить пару строк в Dockerfile, см. инструкцию на странице проекта.
-
Создать непривилегированного пользователя. От его имени будет исполняться ненадёжный код. Ограничить ему всё, что можно.
-
Обеспечить быстрый (пере-)запуск песочниц. Если это Kubernetes, то настроить Image Pull Policy и никогда не использовать тег
latest. -
Установить разумный лимит на количество PIDs. Лимит на PIDs задаётся для пользователя, а не для запускаемого процесса! По этой причине он должен быть разумно большим. Важно: в Kubernetes этот лимит разделяется между всеми контейнерами узла.
-
Для каждой песочницы устанавливаем лимиты на ресурсы. Как минимум, лимиты на использование CPU, память и I/O.
-
Задача исполнения ненадёжного кода решается в комплексе: разработка + DevOps.
-
Вынуждены разменивать безопасность на скорость исполнения или наоборот.
-
Ограничиваем всё, что можно и нужно.
Дополнительные материалы по данной тематике (и не только) доступны в моём Telegram-канале
"Архитектоника в ИТ" (@arch_and_dev).