Блог

Чи повинен кожен орендар мати власну віртуальну машину?

Вибирайте між контейнерами для кожного орендаря, віртуальними машинами та гібридними рішеннями за допомогою ризик-орієнтованої системи прийняття рішень і кроків із посилення безпеки, які роблять кожен варіант виправданим.

Підсумок

Багатоорендарний хостинг змушує вас вирішити, наскільки глибоко орендарі можуть проникати один в одного. Контейнери використовують простори імен Linux і cgroups для ізоляції процесів і ресурсів, але вони спільно використовують ядро хоста. Віртуальні машини додають апаратний кордон, ціною швидкості та операційного навантаження. Гібридний підхід — контейнери всередині віртуальних машин — може дати вам обидві переваги, але подвоює поверхню, яку потрібно оновлювати. Ця стаття проведе вас через прийняття рішень на основі ризику, порівняння один з одним і кроки з посилення безпеки Docker, які мають значення навіть усередині віртуальної машини. Наприкінці ви знатимете, яка модель ізоляції відповідає вашим орендарям і що налаштувати перед запуском.

Ваш багатоорендарний застосунок майже готовий. У вас є файл Docker Compose, який швидко розгортає стек для кожного клієнта. Тут друг, який керує хостинговою компанією, питає: «Чи даєте ви кожному орендарю власну віртуальну машину?» Ви завмираєте. Ви не планували це питання. Ця стаття дає вам спосіб відповісти на нього сьогодні, без команди безпеки. Ви робите це наодинці, тому рішення має бути досить простим, щоб його можна було захистити о 2 ночі.

Перестаньте шукати «найкращу» модель. Почніть із запису того, що станеться, якщо код орендаря захопить ваш хост. Визначте радіус ураження, перш ніж обирати будь-який інструмент. Ця вправа розкаже вам більше, ніж будь-який бенчмарк.

Ядро — це сусід, якого не можна виселити

Контейнери ефективні, тому що вони спільно використовують ядро хоста. Це спільне використання — весь секрет і весь ризик. Простори імен Linux дають кожному контейнеру власне бачення процесів, мережі та файлової системи. Контрольні групи (cgroups) дозволяють обмежувати CPU, пам'ять і дисковий ввід/вивід, щоб один орендар не міг голодувати інших. Але не те, ні інше не створює апаратної стіни.

Думайте про контейнер як про процес із дуже гарним фальшивим посвідченням. Він вважає, що працює на власній машині. Але ядро — це одна копія Linux, що працює на вашому хості. Якщо орендар використовує вразливість ядра, простори імен стають лише метаданими. Зловмисник, який може викликати функції ядра, може отримати доступ до інших просторів імен на тому самому ядрі. Це той самий вихід із контейнера, про який ви постійно чуєте.

Скажімо, ви розміщуєте невеликий інструмент B2B з одним контейнером на кожного клієнта. Клієнт встановлює сумнівний плагін із вразливістю віддаленого виконання коду. З налаштуваннями Docker за замовчуванням цей процес працює як root усередині контейнера. Root у контейнері — це все одно UID 0, і ядро не відрізняє цей UID від root хоста, якщо ви явно не зіставляєте користувачів. Зловмисник може спробувати вирватися назовні, і спільне ядро є його ціллю.

Збій не обов’язково має бути драматичним. Один орендар із витоком пам’яті може перемістити хост у swap, сповільнюючи всіх інших орендарів. Без обмежень cgroup один некоректний цикл є атакою на доступність. З ними це заблокований процес і сповіщення.

Чи означає це, що контейнери небезпечні? Ні. Це означає, що ви повинні ставитися до ядра як до спільної зони довіри. Перед вибором напишіть однопараграфну заяву про ризик: «Якщо контейнер орендаря скомпрометовано, зловмисник може отримати доступ до: [перелік]. Бізнес-витрати становитимуть: [сума або вплив].» Якщо цей параграф вас лякає, ви не параноїк. Ви чесні.

Для глибшого розуміння спектру ізоляції — від спільних контейнерів до повністю окремих стеків — перегляньте наш посібник із проєктування багатоорендарної архітектури Docker.

Три способи поділу (оберіть один перед розгортанням)

Насправді існує три архітектури для багатоорендарної ізоляції. Кожна «найкраща практика» є їхньою комбінацією.

ПідхідБар'єр ізоляціїНайкраще, колиНайважчий недолік
Контейнери для кожного орендаряПростори імен ядра + cgroupsБагато малих орендарів, низький ризик на орендаря, потрібна щільністьОдин експлойт ядра може зламати всіх орендарів на цьому хості
Одна віртуальна машина на орендаряГіпервізор/апаратна віртуалізаціяРегульовані дані, ворожі орендарі, висока цінність на орендаряВажча, повільніше розгортається, ви оновлюєте ОС для кожного орендаря
Контейнери всередині віртуальних машинМежа віртуальної машини навколо контейнерних робочих навантаженьЩільність плюс міцна оболонка між групамиВитрати та операційні накладні витрати майже подвоюються

Контейнери для кожного орендаря. Це стандарт для більшості засновників SaaS. Кожен орендар отримує власний контейнер або невеликий стек Compose. Розгортання миттєве, образи маленькі, CI/CD простий. Обмеження ресурсів не дають галасливим сусідам з’їсти сервер. Компроміс — спільне ядро. Якщо ви можете тримати робочі навантаження непривілейованими та регулярно оновлювати хост, це часто правильний перший крок.

Не розміщуйте двох орендарів в одному контейнері. Це спільне ядро плюс спільне середовище виконання плюс спільна файлова система. Якщо один орендар завантажить файл, який створює процес, інший орендар уже в тій самій таблиці процесів. Контейнер — це одиниця ізоляції; робіть один контейнер на орендаря.

А як щодо бази даних? Якщо кожен орендар підключається до одного екземпляра MongoDB або PostgreSQL з однаковими обліковими даними, ви вже додали величезний спільний компонент. Надайте кожному орендарю окремі облікові дані, а в ідеалі — окрему базу даних або схему. Контейнери ізолюють застосунок; база даних часто є першим витоком, який перевірятиме зловмисник.

Одна віртуальна машина на орендаря. Надайте кожному орендарю повноцінну віртуальну машину. Гіпервізор додає апаратний кордон, який має перетнути експлойт ядра, щоб досягти хоста. Це важливо для регульованих середовищ або коли орендарі ненадійні. Ціна — щільність і час. Тепер ви керуєте парком операційних систем, а не лише контейнерами. Кожна віртуальна машина потребує оновлень, агентів безпеки та моніторингу. Для самотнього засновника це реальна робота.

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

Контейнери всередині віртуальних машин. Цей гібрид рідко обговорюється в підручниках для початківців. Ви розміщуєте невелику віртуальну машину навколо кожного орендаря (або невеликої групи орендарів), а потім запускаєте контейнери всередині цієї віртуальної машини. Віртуальна машина — це контейнер радіусу ураження; контейнери — лише одиниці розгортання. Це дає вам жорсткість віртуалізації та відтворюваність образів. Це коштує більше, оскільки ви платите за накладні витрати віртуалізації та гнучкість контейнерів, але це може бути найрозумніша довгострокова модель, коли ви не можете повністю довіряти орендарям.

Типовий мікро-приклад: орендар запускає Node API і фонового працівника. Замість одного величезного контейнера з обома процесами використовуйте одну віртуальну машину, потім два контейнери з різними обмеженнями ресурсів, спільною мережею та без прямого доступу до інтернету для працівника. Віртуальна машина забезпечує жорсткий кордон; контейнери забезпечують структуру.

Який із них обрати? Таблиця — це ваш короткий список. Наступні розділи роблять рішення конкретним.

Якщо ви обираєте контейнери, зробіть ці шість речей або не варто

Контейнери для кожного орендаря — це нормально, якщо ви ставитеся до кожного контейнера як до потенційного зловмисника. Це починається з конфігурації, а не з сподівань.

0. Обмежте ресурси, перш ніж комусь довіряти. cgroups — це механізм справедливості та захист доступності. Встановіть --memory і --cpus для кожного контейнера. Орендар із витоком пам’яті має впертися у власну межу, а не в межу вашого сервера. Це не кордон безпеки, але галасливий сусід — це атака без жодного рядка коду. Практичний старт: --memory 512m --cpus 0.5. Для працівника почніть з меншого та масштабуйтеся вгору.

1. Запускайте від імені не-root користувача. Ніколи не дозволяйте процесу в контейнері використовувати UID 0, якщо це не абсолютно необхідно. Встановіть користувача у Dockerfile і передайте --user як додатковий захист. Експлойт, який працює від непривілейованого користувача, має значно менше шляхів до ядра. У Dockerfile створіть користувача: RUN useradd -u 10001 app і USER app. Не пропускайте це, щоб заощадити час.

2. Відкиньте всі можливості, які вам не потрібні. 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, зверніть увагу на розширену ізоляцію контейнерів Docker (ECI). Вона використовує ізоляцію просторів імен користувачів і безпечне середовище виконання контейнерів. Root усередині контейнера зіставляється з непривілейованим користувачем на хості, тому навіть контейнер, що працює від root, не отримує привілеїв root на хості. Вона також блокує небезпечні можливості та системні виклики за замовчуванням. Це не те, що можна відтворити кількома прапорцями на звичайному Docker. Якщо ваша платформа підтримує це, увімкніть. Це не усуває потреби в непривілейованих користувачах та обмеженнях ресурсів, але змінює математику ризику.

Частину цього можна наблизити за допомогою перепризначення просторів імен користувачів (userns-remap) у демоні Docker. Це не так повноцінно, як безпечне середовище виконання, але краще, ніж нічого. Якщо ви це використовуєте, перевірте зіставлення UID, перш ніж довіряти.

Хибне уявлення про віртуальні машини: перехід на віртуальні машини — це не посилення безпеки

Ось суперечлива частина, і саме її більшість пропускає. Якщо ви переходите на одну віртуальну машину для орендаря, а потім розгортаєте всередині неї звичайні контейнери, ви не усунули проблему безпеки контейнерів. Ви додали широку клітку. Вихід із контейнера все ще працює; зловмисник просто потрапляє у віртуальну машину, а не на хост. Це справжнє покращення, але вам усе ще потрібні шість кроків.

Інша пастка — припускати, що сама віртуальна машина безпечна. Образ за замовчуванням із слабким паролем SSH, невиправленими базовими пакетами або відкритим портом керування — це подарунок. Кордон гіпервізора має значення лише тоді, коли гостьова система посилена та оновлена. Інакше ваша «безпечна віртуальна машина» — це швидший шлях до компрометації, тому що ви почуваєтеся в безпеці та перестаєте перевіряти.

Що ви отримуєте від віртуальної машини, це зменшуваний радіус ураження. Лихо одного орендаря залишається в одній віртуальній машині. Що це вам коштує, це ваш час. Ви стаєте системним адміністратором для стількох операційних систем, скільки у вас орендарів. Якщо ви самотній засновник, який випускає продукт, спитайте себе, чи маєте ви години на виправлення та моніторинг парку. Якщо так, віртуальна машина на орендаря може бути правильним рішенням. Якщо ні, контейнери з потужним посиленням безпеки можуть бути чеснішими.

Також пам’ятайте, що хост гіпервізора є критичною ціллю. Скомпрометований гіпервізор може бачити всіх гостьових систем. Виправляйте хост, а не лише гостьових систем. Віртуальна машина не звільняє вас від виправлення хоста; вона підвищує ставки за його пропуск.

Застереження щодо гібрида: не припускайте, що контейнери всередині віртуальної машини дають вам «два рівні безпеки» безкоштовно. Віртуальна машина додає кордон; контейнер все одно потребує непривілейованого користувача, обмеження можливостей і seccomp. Інакше перший рівень настільки ж міцний, наскільки міцний найслабший контейнер.

Чотири питання, які вирішують суперечку за десять хвилин

Не оптимізуйте абстрактно. Поставте собі ці чотири питання по порядку. Запишіть відповіді.

1. До чого має доступ мій орендар? Якщо орендар може отримати доступ лише до власного вебзастосунку та бази даних, контейнери для кожного орендаря зі строгими правилами мережі є виправданими. Якщо дані орендаря регулюються або є фінансово чутливими, переходьте до віртуальних машин.

2. Скільки мені коштуватиме компрометація одного орендаря? Підрахуйте втрачених клієнтів, юридичні ризики та довіру. Якщо сума більша за вартість запуску віртуальних машин, витратьте гроші. Якщо ні, контейнери є раціональним вибором.

3. Скільки в мене орендарів і скільки вони платять? Багато малих підписників: важлива щільність контейнерів. Жменька великих акаунтів: надайте кожному віртуальну машину та виставляйте рахунки відповідно. Орендарі, які платять менше, ніж чашка кави, не повинні вимагати окремої операційної системи для керування.

4. Чи можу я виправляти все за розкладом? Контейнери спільно використовують ядро хоста, тому виправлення хоста захищає всіх. Віртуальні машини збільшують кількість цілей для виправлення. Якщо ви знаєте, що пропускатимете оновлення, оберіть архітектуру з меншою кількістю рухомих частин і жорсткішими налаштуваннями за замовчуванням.

Ваші відповіді згрупуються. Дві або більше відповідей, орієнтованих на віртуальні машини, означають, що вам не слід за замовчуванням використовувати контейнери для кожного орендаря. Три або більше відповідей, орієнтованих на контейнери, означають, що віртуальні машини передчасні. Один неочевидний результат: орендар із низьким доходом, який має доступ до чутливих даних, усе одно потребує віртуальної машини, оскільки регуляторні витрати не мають нічого спільного з тим, скільки він платить.

Запускайте найменше, чому ви можете довіряти, а потім заробляйте більше ізоляції

Ваша перша архітектура не обов’язково має бути остаточною. Почніть із найщільнішої конфігурації, яку ви можете реально підтримувати, а потім додавайте ізоляцію, коли ваша база орендарів це виправдає. Для більшості соло-операторів це означає контейнери для кожного орендаря з непривілейованим користувачем, обмеженими можливостями, файловими системами лише для читання, seccomp і сегментацією мережі. Для регульованих або високоцінних орендарів переходьте одразу до однієї віртуальної машини на орендаря, використовуючи контейнери лише як пакувальний шар усередині.

Що б ви не обрали, запишіть рішення та переглядайте його щокварталу. Коли ви отримаєте перше питання «чи варто переводити цього орендаря на віртуальну машину?», у вас буде відповідь і контрольний список, щоб її підтвердити. Це насправді означає ізоляцію: компроміс, яким ви керуєте, а не технологія, яку купуєте.

Перед запуском пройдіться нашим практичним контрольним списком безпеки ізоляції Docker — він перетворює ці рішення на список, який ви можете перевірити, перш ніж показати сторінку клієнту.

Sources (5)