Блог
Кои клиенти всъщност се нуждаят от собствена VM? План за изолация на Docker на нива
VM за всеки клиент е прекалено. Ето как да решите колко изолация е необходима за всеки наемател — и да автоматизирате решението.
Резюме
Агенциите често се паникьосват, когато клиент попита колко наистина са изолирани данните му от други наематели. Namespaces и cgroups на Docker ви дават реална изолация, но те не са същото като хардуерна граница. Вместо да пускате всеки клиент на VM – или още по-лошо, да третирате всички клиенти еднакво – изградете малък набор от нива на изолация и съпоставете всеки клиент с ниво според чувствителността на данните, доверието и съответствието. Заключен контейнер (не-root, премахнати capabilities, seccomp, само за четене root) покрива повечето сайтове; регулирани или враждебни натоварвания получават VM или хибрид контейнер-в-VM. Тази публикация дава повтаряем процес на вземане на решения, сравнителна таблица и честен поглед върху това кога повече изолация е излишна.
Намирате ли се в момента на разговор за продажба, когато новият клиент каже „ние сме в здравеопазването, покажете ми, че данните ни са изолирани от другите ви клиенти“ и бихте предпочели да говорите за нещо друго?
Това е проблемът на агенциите: не едно перфектно разполагане, а едно и също надеждно разполагане, повторено за дузина клиенти с различни бюджети, рискови профили и изисквания за съответствие. Ето честната версия. Изолацията на Docker е реална, но е специфична. Namespaces дават на всеки контейнер собствен изглед към процесите, мрежата и файловата система; cgroups ограничават процесора, паметта и дисковия I/O, така че наемателите не могат да се изморят взаимно. Това, което не ви купува, е хардуерна стена между контейнера и хостовото ядро. Ако атакуващ избяга от контейнера, той е вътре в единственото ядро, което имате. Останалата част от тази статия превръща този неприятен факт в повтаряемо решение: класифицирайте всеки клиент по чувствителност на данните и доверие, приложете базов профил за укрепване и посягайте към VM само когато цената на пробив е по-висока от цената на VM.
Чакайте, контейнерите не са ли вече изолирани?
Docker работи върху Linux namespaces и cgroups, и тези думи вършат реална работа. Namespaces разделят идентификаторите на процесите, мрежовите стекове, точките на монтиране и потребителите, така че процес в един контейнер не може да види таблицата на процесите на друг. Cgroups задават ограничения: дайте на контейнер 0,5 CPU, 512 MB памет и фиксирано тегло на блоковия I/O и точно това получава. Бягащ цикъл в един наемател се ограничава, вместо да събори съседа. Ако не сте конфигурирали ограничения, сте пропуснали най-основното, за което служат cgroups.
Вземете просто PHP приложение в контейнер A. То вижда своята собствена файлова система, своя собствен мрежов интерфейс, своя собствен PID 1. Контейнер B има същото, но различен изглед. Това са namespaces. Сега си тръгнете и пропуснете ограничението на паметта: контейнер A може да запълни RAM паметта на хоста и да накара контейнер B да пълзи. Точно за това съществуват cgroups. Но два контейнера могат да бъдат изолирани един от друг чрез namespaces и все пак да споделят хостовото ядро, което е частта, за която разказва всяка история за бягство от контейнер. Експлойт, който достигне до ядрото, може потенциално да достигне до всеки наемател на този хост.
„Docker е изолиран“ е полу-вярно изречение. Точната версия е „Docker изолира с namespaces и cgroups, а уязвимостта на ядрото е радиусът на поражение.“ Преди да се доверите на наемател да изпълнява недоверен код, постойте с тази мисъл за минута. Отговорът не е „никога не използвайте контейнери“ – това е лесната паника. Отговорът е система от нива.
Защо тогава някои клиенти се нуждаят от повече от namespaces?
Честният отговор е, че изолацията не е превключвател, а спектър. В единия край имате напълно споделен контейнер, където всички са ефективно в едно приложение. В другия край имате отделна VM за всеки наемател със собствено ядро. Повечето агенционни работи се намират в неудобната среда, а средата не е бинарен избор между „Docker е добре“ и „пуснете VM за всички“.
Какво тласка клиента надясно не е неговият размер. Това са четири въпроса:
- Съхраняват ли регулирани данни? Здравни досиета, данни за платежни карти, всичко, което регулатор би нарекъл чувствително.
- Има ли реалистичен път от пробив в техния наемател до друг наемател? Ако могат да изпълняват произволен код, да.
- Доверявате ли се на кода и на хората, които го разполагат? Клиент, който наема най-евтиния фрийлансър, не е същото ниво на доверие като клиент, чийто екип за разработка познавате.
- Дали договорът им казва „посветен“, „изолиран“ или „частен“? Ако да, вече сте обещали ниво; единствената работа сега е да изберете правилното.
Ако все още не можете да отговорите на тези въпроси, поставете клиента в базово ниво и запишете допусканията. Това не е одит за сигурност; това е проверка за адекватност, която повтаряте при всяко въвеждане.
Как да решавам за всеки клиент, без да извършвам одит за сигурност всеки път?
Направете малка таблица и се придържайте към нея. Не ви трябва матрица с четиридесет клетки. Четири нива ще покрият почти всеки клиент, който агенцията вижда.
| Позиция на клиента | Какво всъщност ги разделя | Използвайте, когато |
|---|---|---|
| Ниво 1: Споделено приложение/контейнер | Само логика на приложението | Вътрешни помощни програми, нискорискови данни, проекти, където всички са изрично в една система за вход |
| Ниво 2: Същият хост, отделни контейнери | Namespaces и cgroups | Повечето маркетингови сайтове, контактни форми, без чувствителни данни |
| Ниво 3: Заключен контейнер | Ниво 2 + не-root, премахнати capabilities, seccomp, само за четене root, мрежова сегментация | Електронна търговия, PII, персонализиран код, на който не се доверявате напълно |
| Ниво 4: VM за наемател | Хипервизор и отделно ядро | Здравеопазване, финанси, документи за съответствие, недоверен код, шумни съседи |
Ето как се проявява това на практика. Клиент пекарна с контактна форма и линк към Instagram отива на Ниво 2: един контейнер на споделен хост, стандартна Docker мрежова конфигурация, ограничения на ресурсите, готово. Онлайн магазин, който съхранява имена на клиенти, адреси и пренасочвания за плащане, отива на Ниво 3: същият споделен хост, но контейнерът работи като не-root потребител, няма допълнителни capabilities на ядрото, използва seccomp профил и излага само порт 443. Медицински портал за прием, който съхранява защитена здравна информация, отива на Ниво 4: VM за наемател, защото цената на пробив не е „ще го почистим“, а „не можем да покажем на клиента, че сме го взели на сериозно“.
Целият трик е, че не преосмисляте архитектурата за всеки клиент. Избирате ред от таблица, за която вече сте се съгласили. Ето как агенция от петима души може да поддържа сто сайта, без сто отделни мании за сигурност. Това също така означава, че следващият клиент не получава отговор, който зависи от това кой член на екипа е вдигнал телефона. За по-задълбочения архитектурен дебат зад тези избори, това ръководство за проектиране на нива на изолация за мулти-тенантност разглежда компромисите по-подробно.
Как всъщност изглежда заключен контейнер?
Нека спрем да казваме „заключен“ и да станем конкретни. Ето какво означава Ниво 3 за типичен WordPress или PHP клиент.
Първо, сменете потребителя. Повечето официални изображения все още работят като root по подразбиране; в Dockerfile създайте не-root потребител и стартирайте приложението като този потребител. Това веднага премахва най-честия начин, по който компрометирането на контейнер става компрометиране на хоста. Второ, премахнете capabilities, от които не се нуждаете. Стартирайте с --cap-drop ALL и добавете обратно само един, обикновено NET_BIND_SERVICE, за да може приложението да слуша на порт 80. Само това е по-голяма промяна, отколкото повечето хора очакват. Трето, направете root файловата система само за четене с --read-only и монтирайте записваеми директории (качвания, директорията с данни на базата данни) като томове или tmpfs. Четвърто, приложете seccomp профил и, ако хостът ви го поддържа, AppArmor или SELinux. Накрая, поставете контейнера в отделна Docker мрежа и изложете само портовете, които наистина трябва да бъдат достъпни.
Нека преминем през пример с WordPress. Базовото изображение вероятно работи като root, така че добавяте стъпка useradd и директива USER. Стартирате контейнера с ограничение на паметта и процесора, така че прилив на трафик от плъгин не наврежда на съседа. Монтирате /var/www/html/wp-content/uploads като записваем том. Задавате --read-only. Прикачвате го към мрежа, която няма флаг --privileged никъде наблизо. Резултатът е контейнер, който преди е бил „WordPress сайт“ и сега е „WordPress сайт, който се случва да е по-заключен от повечето виртуални частни сървъри“.
Ако ръчното изработване на всичко това изглежда крехко, има по-лесен среден път: Подобрената изолация на контейнери на Docker, която използва изолация на потребителските namespace-и и сигурна среда за изпълнение на контейнери. Това е легитимен пряк път, но не е безплатен пропуск да пропуснете не-root или премахването на capabilities. Наемателят все още се нуждае от разумно изображение. Разликата е, че повърхността за атаки към ядрото става по-малка, без да се превръщате в експерт по seccomp за една нощ. Ако искате точната последователност за един наемател, ръководството за укрепване на изолацията стъпка по стъпка превръща този раздел в команди за копиране и поставяне.
Кога спирам да добавям слоеве и просто им давам VM?
Ето противоречивата част: повече изолация не е автоматично по-добре. VM-ите ви дават изолация на ниво хардуер, отделно ядро и много по-малка повърхност за атаки, ако гостуващото ядро падне. Точно това очакват клиентите в здравеопазването и финансите, когато кажат „искаме да сме изолирани“. Но всяка VM добавя разходи за пачове, резервни копия и изчисления и умножава работата по поддържането на флота актуализиран. Ако сложите всеки клиент на VM, защото един клиент веднъж ви е казал, че Docker го плаши, сте купили театър за сигурност с реални пари.
VM е правилният отговор, когато рискът за наемател е по-висок от оперативните разходи за VM за наемател. Това означава регулирани данни, писмени изисквания за съответствие, недоверен код от трети страни или клиент, който иска да бъде премахнат шумен съсед. Това е и правилният отговор, когато договорът на клиента буквално обещава посветена среда, защото „контейнер“ не е това, което си представят, когато подписват „посветен“.
Но VM не извинява небрежен контейнер. Често срещан капан е да поставите клиента в VM и след това да пропуснете укрепването, защото „VM ги защитава“. VM защитава хоста от наемателя, а не наемателя от собственото му лошо изображение. Все още искате не-root, премахнати capabilities и seccomp вътре в тази VM. Хибридният подход — контейнери в VM — често е най-доброто място: VM осигурява границата за разговори за съответствие, а контейнерът ви дава работния процес за разполагане, който вече познавате. Има по-дълга версия на този дебат в Трябва ли всеки наемател да получи собствена VM?, но краткият отговор е, че VM е за договора, а не за страха.
Как да направя това повторяемо за всеки клиент?
Правите го повторяемо, като превърнете системата от нива в шаблон, а не в спомен. Поддържайте директория с Compose файлове, по един за всяко ниво: tier2-baseline, tier3-locked, tier4-vm-hybrid. Когато се появи нов клиент, копирайте шаблона, променете променливите на средата и вече знаете формата на изолация, преди да сте написали ред нова инфраструктура.
След това запишете решението. Не 400-страничен доклад за сигурност, а кратък параграф в хранилището на клиента: какви данни съхраняват, на кое ниво са, защо и какво би ги преместило на по-високо ниво. Този параграф струва повече от сто правила за защитната стена, защото е нещото, което можете да покажете на следващия одитор или на следващия разтревожен клиент. Също така ви спира да се налага да помните защо пекарната получи Ниво 2, а магазинът за електронна търговия — Ниво 3, след като първоначалният разговор за продажба е избледнял.
Автоматизирайте скучните проверки. Накарайте CI да сканира всяко клиентско изображение и да провали изграждането, ако работи като root, ако има всички capabilities или ако се опитва да публикува порт, различен от тези, които нивото позволява. Нищо от това не е екзотично; просто се уверявате, че шаблонът не е случайно счупен от добронамерен разработчик. Ако така или иначе изграждате околния работен процес за хостинг, статията за стратегии за Docker хостинг, готови за продукция покрива частта, която идва след дефинирането на контейнерите.
Нищо от това не е бляскаво. Никоя публикация в блог няма да накара „изолация на наематели“ да звучи толкова вълнуващо, колкото диаграма на архитектура от нулата. Но това е разликата между агенция, която отговаря на „колко сме изолирани?“ с „напълно“ със скръстени пръсти, и такава, която може да покаже ниво, конфигурация и причина. Контейнерите не са магическа стена. VM-ите не са магически куршум. Системата от нива е просто решение, което записвате и използвате отново — и за агенцията, повторяемостта е цялата игра.

