Блог
Должен ли каждый арендатор иметь собственную виртуальную машину?
Выбирайте между контейнерами для каждого арендатора, виртуальными машинами и гибридными решениями, используя систему принятия решений на основе рисков и шаги по усилению безопасности, которые делают каждый вариант обоснованным.
Резюме
Мультитенантный хостинг заставляет вас выбирать, насколько далеко арендаторы могут проникать друг к другу. Контейнеры используют пространства имен Linux и cgroups для изоляции процессов и ресурсов, но они разделяют ядро хоста. Виртуальные машины добавляют аппаратную границу за счет скорости и эксплуатационной нагрузки. Гибридный подход — контейнеры внутри виртуальных машин — может дать вам и то, и другое, но удваивает поверхность, которую необходимо обновлять. Эта статья проведет вас через принятие решения на основе рисков, сравнительный анализ и шаги по усилению Docker, которые важны даже внутри виртуальной машины. К концу вы будете знать, какая модель изоляции подходит вашим арендаторам и что настроить перед запуском.
Ваше мультитенантное приложение почти готово. У вас есть файл Docker Compose, который запускает стек для каждого клиента, и это быстро. Затем друг, который управляет хостинговой компанией, спрашивает: «Вы даёте каждому арендатору собственную виртуальную машину?» Вы замираете. Вы не планировали такой вопрос. Эта статья дает вам способ ответить на него сегодня, без команды безопасности. Вы делаете это в одиночку, поэтому решение должно быть достаточно простым, чтобы его можно было защитить в 2 часа ночи.
Перестаньте искать «лучшую» модель. Начните с записи того, что произойдет, если код арендатора захватит ваш хост. Определите радиус поражения, прежде чем выбирать любой инструмент. Это упражнение расскажет вам больше, чем любой бенчмарк.
Ядро — это сосед, которого нельзя выселить
Контейнеры эффективны, потому что они разделяют ядро хоста. Это разделение — весь трюк и весь риск. Пространства имен Linux дают каждому контейнеру своё представление о процессах, сети и файловой системе. Контрольные группы (cgroups) позволяют ограничить CPU, память и дисковый ввод-вывод, чтобы один арендатор не мог лишить ресурсов других. Но ни то, ни другое не создает аппаратную стену.
Думайте о контейнере как о процессе с очень хорошим поддельным удостоверением личности. Он верит, что находится на своей собственной машине. Однако ядро — это одна копия Linux, работающая на вашем хосте. Если арендатор использует уязвимость ядра, пространства имен становятся метаданными и не более того. Злоумышленник, который может вызывать функции ядра, может получить доступ к другим пространствам имен на том же ядре. Это тот самый побег из контейнера, о котором вы постоянно слышите.
Предположим, вы размещаете небольшой B2B-инструмент с одним контейнером на клиента. Клиент устанавливает сомнительный плагин с ошибкой удаленного выполнения кода. При настройках Docker по умолчанию этот процесс работает как root внутри контейнера. Root в контейнере по-прежнему UID 0, и ядро не отличает этот UID от root хоста, если вы явно не сопоставили пользователей. Злоумышленник может попытаться вырваться наружу, и общее ядро — их цель.
Сбой не обязательно должен быть драматичным. Один арендатор, у которого утекает память, может перевести хост в своп, замедляя всех остальных арендаторов. Без ограничений cgroups один некорректный цикл — это атака на доступность. С ними это заблокированный процесс и предупреждение.
Означает ли это, что контейнеры небезопасны? Нет. Это означает, что вы должны рассматривать ядро как общую доверительную зону. Прежде чем выбирать, напишите абзац о риске: «Если контейнер арендатора скомпрометирован, злоумышленник может получить доступ: [список]. Стоимость для бизнеса составит: [сумма или влияние]». Если этот абзац вас пугает, вы не параноик. Вы честны.
Чтобы глубже изучить спектр изоляции, от общих контейнеров до полностью раздельных стеков, обратитесь к нашему руководству по проектированию мультитенантной архитектуры Docker.
Три способа разделить это (выберите один перед развертыванием)
Существует три архитектуры для мультитенантной изоляции. Каждая «лучшая практика» — это комбинация этих вариантов.
| Подход | Изоляционный барьер | Лучше всего подходит, когда | Самый сложный нюанс |
|---|---|---|---|
| Контейнеры на арендатора | Пространства имен ядра + cgroups | Много мелких арендаторов, низкий риск на арендатора, нужна плотность | Одна эксплойт ядра может сломать всех арендаторов на этом хосте |
| Одна ВМ на арендатора | Гипервизор/аппаратная виртуализация | Регулируемые данные, враждебные арендаторы, высокая ценность на арендатора | Тяжелее, медленнее предоставление, вы обновляете ОС для каждого арендатора |
| Контейнеры внутри ВМ | Граница ВМ вокруг контейнерных нагрузок | Плотность плюс жесткая оболочка между группами | Затраты и эксплуатационные расходы почти удваиваются |
Контейнеры на арендатора. Это вариант по умолчанию для большинства основателей SaaS. Каждый арендатор получает свой собственный контейнер или небольшой стек Compose. Предоставление происходит мгновенно, образы небольшие, CI/CD понятен. Ограничения ресурсов не позволяют шумным соседям съедать сервер. Компромисс — общее ядро. Если вы можете сохранять нагрузки непривилегированными и регулярно обновлять хост, это часто правильный первый шаг.
Не помещайте двух арендаторов в один контейнер. Это общее ядро плюс общая среда выполнения плюс общая файловая система. Если один арендатор загружает файл, который создает процесс, другой арендатор уже находится в той же таблице процессов. Контейнер — это ваша единица изоляции; делайте один арендатор на контейнер.
А что насчет базы данных? Если каждый арендатор подключается к одному экземпляру MongoDB или PostgreSQL с одними и теми же учетными данными, вы уже добавили огромный общий компонент. Дайте каждому арендатору отдельные учетные данные и в идеале отдельную базу данных или схему. Контейнеры изолируют приложение; база данных часто является первой утечкой, которую будет проверять злоумышленник.
Одна ВМ на арендатора. Дайте каждому арендатору полную виртуальную машину. Гипервизор добавляет аппаратную границу, которую эксплойт ядра должен пересечь, чтобы достичь хоста. Это важно для регулируемых сред или когда арендаторы не заслуживают доверия. Цена — плотность и время. Теперь вы управляете парком операционных систем, а не только контейнерами. Каждая ВМ требует обновлений, агентов безопасности и мониторинга. Для соло-основателя это настоящая работа.
Паттерны, которые работают на этом уровне: используйте инфраструктуру как код для создания ВМ из одного базового образа, встраивайте обновления в новые образы вместо обновления работающих систем и отключайте рабочие нагрузки, которые вы не распознаете. Держите порт управления ВМ закрытым для интернета.
Контейнеры внутри ВМ. Этот гибрид редко обсуждается в учебниках для начинающих. Вы помещаете небольшую ВМ вокруг каждого арендатора (или небольшой группы арендаторов), а затем запускаете контейнеры внутри этой ВМ. ВМ — это контейнер радиуса поражения; контейнеры — просто развертываемые единицы. Это дает вам жесткую границу виртуализации и воспроизводимость образов. Это стоит дороже, потому что вы платите за накладные расходы на виртуализацию и гибкость контейнеров, но это может быть самая разумная долгосрочная модель, когда вы не можете полностью доверять арендаторам.
Один распространенный микро-пример: арендатор запускает Node API и фоновый воркер. Вместо одного огромного контейнера с обоими процессами используйте одну ВМ, затем два контейнера с разными ограничениями ресурсов, общей сетью и без прямого доступа к интернету для воркера. ВМ обеспечивает жесткую границу; контейнеры обеспечивают структуру.
Какой из них выбрать? Таблица — ваш короткий список. Следующие разделы делают решение конкретным.
Если вы выбираете контейнеры, сделайте эти шесть вещей или не беритесь
Контейнеры на арендатора хороши, если вы относитесь к каждому контейнеру как к потенциальному злоумышленнику. Это начинается с конфигурации, а не с благих пожеланий.
0. Ограничьте ресурсы, прежде чем доверять кому-либо. Cgroups — это механизм справедливости и защита доступности. Установите --memory и --cpus для каждого контейнера. Арендатор, у которого утекает память, должен достигнуть своего собственного лимита, а не вашего сервера. Это не граница безопасности, но шумный сосед — это атака без единой строки кода. Практическое начало: --memory 512m --cpus 0.5. Для воркер-процесса начните с меньшего и увеличивайте.
1. Запускайте от непривилегированного пользователя. Никогда не позволяйте процессу контейнера использовать UID 0, если вам это абсолютно не необходимо. Установите пользователя в Dockerfile и передайте --user в качестве дополнительной защиты. Эксплойт, работающий от непривилегированного пользователя, имеет гораздо меньше путей к ядру. В вашем Dockerfile создайте пользователя: RUN useradd -u 10001 app и USER app. Не пропускайте это, чтобы сэкономить время.
2. Откажитесь от всех capabilities, которые вам не нужны. Linux capabilities разделяют власть root на мелкие части. Большинству веб-приложений они почти не нужны. Начните с --cap-drop=ALL и верните только то, что точно нужно. Контейнер без CAP_SYS_ADMIN гораздо сложнее использовать для трюков с пространствами имен. Если ваше приложение пытается привязаться к привилегированному порту, запустите его на высоком порту и поставьте прокси перед ним, вместо того чтобы предоставлять NET_BIND_SERVICE.
3. Сделайте файловую систему только для чтения. Ваше приложение не должно записывать в свой собственный слой контейнера. Подмонтируйте tmpfs для состояния. Злоумышленнику, который не может записывать на диск, гораздо сложнее внедрить механизм постоянства. Скомпрометированное PHP-приложение, пытающееся записать веб-шелл, потерпит неудачу, когда корневая файловая система доступна только для чтения. Вы можете смонтировать именованный том для каталога с возможностью записи, который действительно нужен вашему приложению.
4. Примените seccomp и AppArmor или SELinux. Они отправляют опасные системные вызовы в утиль. Docker поставляется с профилем seccomp по умолчанию; используйте его. Добавьте профиль AppArmor для дополнительного уровня. Вам не нужно разбираться в каждом системном вызове. Вам нужно запретить то, что обычный веб-воркер никогда не требует. Никогда не запускайте с --privileged. Этот флаг отключает почти все защиты, которые вы только что настроили.
5. Сегментируйте сеть. Не давайте каждому контейнеру маршрут к каждому другому контейнеру. Запрет по умолчанию, затем открывайте только нужные порты. Скомпрометированный контейнер базы данных не должен иметь возможность сканировать вашу админ-панель. Если арендаторы в отдельных сетях, взлом одной сети не может распространяться латерально.
Практическое начало:
docker run --user 10001 --cap-drop=ALL --security-opt no-new-privileges --read-only --tmpfs /tmp:rw,size=64M --security-opt seccomp=default.json --memory 512m --cpus 0.5 myimage
Поместите те же флаги в файл Compose и примените их к каждому арендатору. Это не полно, но это гораздо более сильный вариант по умолчанию, чем то, что docker run дает вам из коробки.
Для более подробного руководства воспользуйтесь нашим пошаговым руководством по усилению безопасности контейнеров Docker в мультитенантном хостинге.
Расширенная изоляция контейнеров Docker — исключение, о котором вам стоит знать
Если вы работаете в управляемой среде Docker, обратите внимание на Enhanced Container Isolation (ECI) от Docker. Он использует изоляцию пользовательских пространств имен и защищенную среду выполнения контейнеров под капотом. Root внутри контейнера сопоставляется с непривилегированным пользователем на хосте, поэтому даже контейнер, работающий от root, не получает привилегий root хоста. Он также блокирует опасные capabilities и системные вызовы по умолчанию. Это не то, что можно воссоздать с помощью нескольких флагов на стандартном Docker. Если ваша платформа это поддерживает, включите его. Это не устраняет необходимость в непривилегированных пользователях и ограничениях ресурсов, но меняет математику риска.
Вы можете приблизиться к этому частично с помощью переназначения пользовательских пространств имен (userns-remap) в демоне Docker. Это не так полно, как защищенная среда выполнения, но лучше, чем ничего. Если вы используете это, проверьте, что сопоставление UID работает, прежде чем доверять ему.
Заблуждение о ВМ: переход на виртуальные машины не является усилением безопасности
Вот та противоречивая часть, которую большинство пропускает. Если вы перейдете на одну ВМ на арендатора и развернете внутри нее свои обычные контейнеры, вы не устранили проблему безопасности контейнеров. Вы добавили широкую клетку. Побег из контейнера по-прежнему работает; злоумышленник просто попадает в ВМ, а не на хост. Это реальное улучшение, но вам все еще нужны шесть шагов.
Другая ловушка — предположение, что сама ВМ безопасна. Образ по умолчанию со слабым SSH-паролем, необновленными базовыми пакетами или открытым портом управления — это подарок. Граница гипервизора имеет значение только если гостевая система усилена и обновлена. В противном случае ваша «безопасная ВМ» — более быстрый путь к компрометации, потому что вы чувствуете себя в безопасности и перестаете проверять.
Что дает вам ВМ — это уменьшаемый радиус поражения. Чрезвычайная ситуация одного арендатора остается в одной ВМ. Что она вам стоит — это ваше время. Вы становитесь системным администратором для стольких операционных систем, сколько у вас арендаторов. Если вы соло-основатель, выпускающий продукт, спросите, есть ли у вас часы на обновление и мониторинг парка. Если да, то ВМ на арендатора может быть правильным решением. Если нет, возможно, более честным будет выбор контейнеров с сильной защитой.
Также помните, что ваш хост-гипервизор — критическая цель. Скомпрометированный гипервизор может видеть всех гостей. Обновляйте хост, а не только гостей. ВМ не освобождает вас от обновления хоста; она повышает ставки за его пропуск.
Предостережение о гибриде: не предполагайте, что контейнеры внутри ВМ дают вам «два уровня безопасности» бесплатно. ВМ добавляет границу; контейнеру все еще нужны непривилегированный пользователь, capabilities и seccomp. В противном случае первый уровень настолько же силен, насколько силен самый слабый контейнер.
Четыре вопроса, которые решают спор за десять минут
Не оптимизируйте абстрактно. Задайте себе эти четыре вопроса по порядку. Запишите ответы.
1. К чему имеет доступ мой арендатор? Если арендатор может получить доступ только к своему веб-приложению и базе данных, контейнеры на арендатора со строгими сетевыми правилами защитимы. Если данные арендатора регулируются или являются финансово чувствительными, двигайтесь к ВМ.
2. Сколько будет стоить мне компрометация одного арендатора? Сложите потерянных клиентов, юридические риски и доверие. Если сумма больше стоимости использования ВМ, потратьте деньги. Если нет, контейнеры — рациональный выбор.
3. Сколько у меня арендаторов и сколько они платят? Много мелких подписчиков: плотность контейнеров важна. Несколько крупных аккаунтов: дайте каждому ВМ и выставляйте счета соответственно. Арендаторы, которые платят вам меньше, чем чашка кофе, не должны требовать отдельной ОС для управления.
4. Могу ли я выполнять обновления по расписанию? Контейнеры разделяют одно ядро хоста, поэтому обновление хоста защищает всех. ВМ умножают ваши цели для обновлений. Если вы знаете, что будете пропускать обновления, выберите архитектуру с меньшим количеством движущихся частей и более безопасными настройками по умолчанию.
Ваши ответы сгруппируются. Два или более ответов в сторону ВМ означают, что вам не следует по умолчанию выбирать контейнеры на арендатора. Три или более ответов в сторону контейнеров означают, что ВМ преждевременны. Один нелогичный результат: арендатор с низким доходом, имеющий доступ к чувствительным данным, все равно нуждается в ВМ, потому что нормативные издержки не зависят от того, сколько они платят.
Запускайте минимально доверенное, затем зарабатывайте больше изоляции
Ваша первая архитектура не обязана быть окончательной. Начните с самой строгой настройки, которую вы реально можете поддерживать, затем добавляйте изоляцию по мере того, как ваша база арендаторов это оправдывает. Для большинства соло-операторов это означает контейнеры на арендатора с непривилегированным пользователем, ограниченными capabilities, файловыми системами только для чтения, seccomp и сегментацией сети. Для регулируемых или высокоценных арендаторов переходите сразу к одной ВМ на арендатора, с контейнерами только как слоем упаковки внутри.
Независимо от выбора, запишите решение и пересматривайте его ежеквартально. Когда вы получите первый вопрос «стоит ли переводить этого арендатора на ВМ?», у вас будет ответ и список для его подтверждения. Именно это на самом деле означает изоляция: компромисс, которым вы управляете, а не технология, которую вы покупаете.
Перед запуском пройдитесь по нашему практическому чек-листу безопасности изоляции Docker — он превращает эти решения в список, который вы можете проверить, прежде чем показать страницу клиенту.

