Блог

Трябва ли всеки наемател да има собствена VM?

Изберете между контейнери за отделни наематели, виртуални машини и хибридни решения с рамка за оценка на риска и стъпки за укрепване, които правят всяка опция защитима.

Резюме

Многонаемателният хостинг ви принуждава да изберете колко дълбоко наемателите могат да достигнат един до друг. Контейнерите използват Linux namespaces и cgroups, за да изолират процесите и ресурсите, но споделят ядрото на хост системата. Виртуалните машини добавят хардуерна граница, но с цената на скорост и оперативно натоварване. Хибридният подход — контейнери във виртуални машини — може да ви даде и двете, но удвоява повърхността, която трябва да обновявате. Тази статия ви превежда през вземане на решение, базирано на риска, сравнение едно до друго и стъпките за укрепване на Docker, които имат значение дори във виртуална машина. Накрая ще знаете кой модел на изолация подхожда на вашите наематели и какво да конфигурирате преди старта.

Вашето многонаемателно приложение е почти готово. Имате Docker Compose файл, който стартира стек за всеки клиент, и е бърз. Тогава приятел, който управлява хостинг компания, пита: „Даваш ли на всеки наемател собствена виртуална машина?“ Замръзвате. Не сте планирали отговора на този въпрос. Тази статия ви дава начин да отговорите днес, без екип по сигурността. Правите това сами, така че решението трябва да е достатъчно просто, за да го защитите в 2 през нощта.

Спрете да търсите „най-добрия“ модел. Започнете, като запишете какво се случва, ако кодът на наемател превземе вашия хост. Определете радиуса на поражение, преди да изберете какъвто и да е инструмент. Това упражнение ще ви каже повече от всеки бенчмарк.

Ядрото е съквартирантът, когото не можете да изгоните

Контейнерите са ефективни, защото споделят ядрото на хост системата. Това споделяне е целият трик и целият риск. Linux namespaces дават на всеки контейнер собствен изглед на процесите, мрежата и файловата система. Control groups (cgroups) ви позволяват да ограничите CPU, паметта и дисковия I/O, така че един наемател да не може да остави останалите гладни. Но нито едно от двете не създава хардуерна стена.

Мислете за контейнера като за процес с много добър фалшив документ за самоличност. Той вярва, че е на собствена машина. Ядрото обаче е едно копие на Linux, работещо на вашия хост. Ако наемател използва уязвимост в ядрото, namespaces се превръщат в метаданни и нищо повече. Атакуващ, който може да извиква функции на ядрото, може да достигне до други namespaces на същото ядро. Това е бягството от контейнера, за което постоянно чувате.

Да кажем, че хоствате малък B2B инструмент с по един контейнер на клиент. Клиент инсталира съмнителен плъгин с бъг за изпълнение на отдалечен код. При настройките по подразбиране на Docker, този процес работи като root вътре в контейнера. Root в контейнер все още е UID 0 и ядрото не различава този UID от root на хоста, освен ако изрично не съпоставите потребители. Атакуващият може да опита да избяга, а споделеното ядро е неговата цел.

Провалът не е задължително да е драматичен. Един наемател, който изтича памет, може да принуди хоста да използва swap, забавяйки всички останали. Без ограничения на cgroups, един некоректен цикъл е атака на наличността. С тях той е блокиран процес и аларма.

Означава ли това, че контейнерите са небезопасни? Не. Означава, че трябва да третирате ядрото като споделена зона на доверие. Преди да изберете, напишете рисково изявление от един абзац: „Ако контейнерът на наемател е компрометиран, атакуващият може да получи достъп до: [списък]. Бизнес разходите биха били: [сума или влияние].“ Ако този абзац ви плаши, не сте параноични. Вие сте честни.

За по-задълбочен поглед върху спектъра на изолацията, от споделени контейнери до напълно отделни стекове, вижте нашето ръководство за проектиране на многонаемателна Docker архитектура.

Три начина за разделяне (изберете един, преди да внедрите)

Има наистина три архитектури за многонаемателна изолация. Всяка „най-добра практика“ е комбинация от тях.

ПодходИзолационна бариераНай-добре, когатоНай-трудното условие
Контейнери за отделни наемателиKernel namespaces + cgroupsМного малки наематели, нисък риск за наемател, нужда от плътностЕдин експлойт на ядрото може да счупи всички наематели на този хост
Една виртуална машина на наемателHypervisor/хардуерна виртуализацияРегулирани данни, враждебни наематели, висока стойност на наемателПо-тежко, по-бавно за предоставяне, обновявате ОС за всеки наемател
Контейнери във виртуални машиниГраница на виртуалната машина около контейнерните натоварванияПлътност плюс твърда обвивка между групитеРазходите и оперативните натоварвания почти се удвояват

Контейнери за отделни наематели. Това е подразбиращият се избор за повечето основатели на 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. Отхвърлете всички възможности, които не ви трябват. Linux capabilities разделят властта на root на малки части. Повечето уеб приложения не се нуждаят от почти нищо. Започнете с --cap-drop=ALL и добавете обратно само това, което знаете, че ви трябва. Контейнер без CAP_SYS_ADMIN е много по-труден за използване за трикове с namespaces. Ако приложението ви се опитва да свърже привилегирован порт, пуснете го на висок порт и поставете прокси отпред, вместо да давате NET_BIND_SERVICE.

3. Направете файловата система само за четене. Вашето приложение не трябва да записва в собствения си слой на контейнера. Монтирайте tmpfs за състояние. Атакуващ, който не може да записва на диска, има много по-трудно време да постави устойчивост. Компрометирано PHP приложение, опитващо се да запише webshell, ще се провали, когато кореновата файлова система е само за четене. Можете да монтирате именуван том за директория за запис, от която приложението ви наистина се нуждае.

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. Тя използва изолация на потребителските namespaces и сигурна среда за изпълнение на контейнери под капака. Root вътре в контейнер се съпоставя с непривилегирован потребител на хоста, така че дори контейнер, работещ като root, не получава root привилегии на хоста. Тя също блокира опасни capabilities и системни извиквания по подразбиране. Това не е нещо, което можете да пресъздадете с няколко флага на обикновен Docker. Ако вашата платформа го поддържа, включете го. Това не премахва нуждата от непривилегировани потребители и ограничения на ресурсите, но променя математиката на риска.

Можете да приближите част от това с пренасочване на потребителските namespaces (userns-remap) в Docker демона. Това не е толкова пълно, колкото сигурна среда за изпълнение, но е по-добре от нищо. Ако го използвате, проверете дали съпоставянето на UID работи, преди да му се доверите.

Заблудата за виртуалните машини: Преминаването към виртуални машини не е укрепване

Тук е противоречивата част, и това е частта, която повечето хора пропускат. Ако преминете към една виртуална машина на наемател и след това внедрите нормалните си контейнери в нея, не сте премахнали проблема със сигурността на контейнерите. Добавили сте широка клетка. Бягството от контейнера все още работи; атакуващият просто се приземява във виртуалната машина, вместо на хоста. Това е реално подобрение, но все още се нуждаете от шестте стъпки.

Другият капан е да предположите, че самата виртуална машина е безопасна. Образ по подразбиране със слаба SSH парола, необновени базови пакети или отворен управляващ порт е подарък. Границата на хипервизора има значение само ако гостът е укрепен и обновен. В противен случай вашата „сигурна виртуална машина“ е по-бърз път към компрометиране, защото се чувствате безопасни и спирате да проверявате.

Това, което получавате от виртуалната машина, е редуцируем радиус на поражение. Бедствието на един наемател остава в една виртуална машина. Това, което ви струва, е вашето време. Вие ставате системен администратор за толкова операционни системи, колкото наематели имате. Ако сте самотен основател, който пуска продукт, попитайте се дали имате часове да обновявате и наблюдавате флота. Ако да, виртуална машина на наемател може да е правилното решение. Ако не, контейнерите със силно укрепване може да са по-честни.

Също така не забравяйте, че хостът на хипервизора е критична цел. Компрометиран хипервизор може да види всички гости. Обновявайте хоста, не само гостите. Виртуалната машина не ви освобождава от обновяването на хоста; тя увеличава залога за пропускането му.

Предупреждение относно хибрида: не предполагайте, че контейнерите във виртуална машина ви дават „два слоя сигурност“ безплатно. Виртуалната машина добавя граница; контейнерът все още се нуждае от непривилегирован потребител, capabilities и seccomp. В противен случай първият слой е толкова силен, колкото най-слабият контейнер.

Четири въпроса, които решават спора за десет минути

Не оптимизирайте в абстрактното. Задайте си тези четири въпроса в ред. Запишете отговорите.

1. До какво има достъп моят наемател? Ако наемателят може да достигне само до собственото си уеб приложение и база данни, контейнерите за отделни наематели със строги мрежови правила са защитими. Ако данните на наемателя са регулирани или финансово чувствителни, преминете към виртуални машини.

2. Колко би ми струвал компрометирането на един наемател? Съберете загубени клиенти, правни рискове и доверие. Ако числото е по-голямо от разходите за виртуални машини, похарчете парите. Ако не, контейнерите са рационален избор.

3. Колко наематели имам и колко плащат? Много малки абонати: плътността на контейнерите има значение. Няколко големи акаунта: дайте на всеки виртуална машина и таксувайте съответно. Наематели, които плащат по-малко от кафе, не трябва да изискват по една операционна система за управление.

4. Мога ли да обновявам нещата по график? Контейнерите споделят едно ядро на хоста, така че обновяването на хоста защитава всички. Виртуалните машини умножават вашите цели за обновяване. Ако знаете, че ще пропускате обновявания, изберете архитектура с по-малко движещи се части и по-твърди настройки по подразбиране.

Отговорите ви ще се групират. Два или повече отговора, фокусирани върху виртуални машини, означават, че не трябва да се придържате към контейнери за отделни наематели по подразбиране. Три или повече отговора, фокусирани върху контейнери, означават, че виртуалните машини са преждевременни. Един противоречив резултат: наемател с ниски приходи и достъп до чувствителни данни все още се нуждае от виртуална машина, защото регулаторната цена няма нищо общо с това колко плащат.

Пуснете минимума, на който можете да се доверите, след това заслужете повече изолация

Първата ви архитектура не трябва да бъде последната. Започнете с най-строгата настройка, която реално можете да поддържате, след това добавяйте изолация, когато базата от наематели го оправдае. За повечето самотни оператори това означава контейнери за отделни наематели с непривилегирован потребител, ограничени capabilities, файлови системи само за четене, seccomp и мрежова сегментация. За регулирани или високостойностни наематели скочете направо на една виртуална машина на наемател, с контейнери само като слой за опаковане отвътре.

Каквото и да изберете, запишете решението и го преглеждайте тримесечно. Когато получите първия си въпрос „трябва ли да преместим този наемател на виртуална машина?“, ще имате отговор и ще имате контролния списък, който да го подкрепи. Това всъщност означава изолация: компромис, който управлявате, а не технология, която купувате.

Преди старта преминете през нашия практичен контролен списък за сигурност на Docker изолацията — той превръща тези решения в списък, който можете да проверите, преди да покажете страница на клиент.

Sources (5)