Блог
Заблудата на доверието: Как нашата мулти-tenant Docker конфигурация изтече данни (и как го оправихме)
Научете как наивната Docker конфигурация на един екип доведе до изтичане на данни между клиенти и как стратегията за слоиста изолация го предотврати.
Резюме
Docker контейнерите не са изолирани по подразбиране — те споделят ядрото на хоста, и без целенасочена конфигурация, клиентите могат да си пречат един на друг. Тази статия преминава през реален сценарий, при който мулти-tenant хостинг доставчик открива, че клиентските контейнери могат да достъпват базите данни един на друг поради споделена мрежа и слаби защитни настройки по подразбиране. Показваме стъпка по стъпка промените, които поправиха пробива: дефинирани от потребителя мрежи за всеки клиент, потребители без root права, премахнати възможности, само за четене файлови системи и seccomp профили. Често срещано предположение е, че контейнерите осигуряват силна изолация по подразбиране; ние оспорваме това, като обясняваме защо виртуалните машини все още предлагат по-строга граница и кога да обмислите хибриден подход. Заключението подчертава, че изолацията е наслоена практика, а не единична отметка.
Инцидентът: Когато контейнерите говорят твърде много
Настроили сте Docker на един хост, за да стартирате множество клиентски уебсайтове. Всеки клиент има свой собствен контейнер — спретната, изолирана среда, нали? Това си мислехме и ние. Докато рутинен одит за сигурност не разкри, че контейнерът на Клиент А чете MySQL сокета на контейнера на Клиент Б на същия хост. Те споделяха мрежата по подразбиране bridge. По-лошо, контейнерите работеха като root, така че атакуващ, който компрометира един, можеше да манипулира Docker сокета на хоста или файловата система на друг контейнер. Пробивът не беше сложен експлойт; това беше основна грешна конфигурация. Данните изтекоха. Доверието се разпадна.
Сценарият на провал не е необичаен. Много екипи предполагат, че Docker namespaces и cgroups автоматично изолират клиентите, но подценяват колко много изходни люкове остават отворени по подразбиране. Мрежите по подразбиране bridge не предлагат мрежова изолация между контейнерите. Работата като root дава на контейнера повече мощност от необходимото. И без изрични ресурсни лимити, един шумен съсед може да лиши другите от CPU или памет.
Стъпка 1: Спрете да споделяте една мрежа
Първата ни поправка беше да дадем на всеки клиент своя собствена дефинирана от потребителя Docker мрежа. Това предотвратява контейнерите да достигат един до друг, освен ако изрично не ги свържете. Създадохме скрипт, който за всеки клиент създава специална мрежа и прикачва техния приложен контейнер към нея. Контейнерът с базата данни живее в същата клиентска мрежа, но добавихме и вътрешна мрежа само за комуникация в рамките на клиента. Без повече надникване между клиенти.
Също така изолирахме базите данни, като ги стартирахме в отделни контейнери в същата клиентска мрежа, използвайки отделни томове с данни. Това гарантира, че дори ако атакуващ проникне в приложния контейнер, не може да подуши трафика на базата данни от друг клиент.
За по-задълбочено разглеждане на стратегиите за мрежова изолация, вижте Практически контролен списък за Docker изолация за сигурност при мулти-tenant хостинг.
Стъпка 2: Премахнете ненужните привилегии
По подразбиране Docker контейнерите работят с ограничен набор от Linux възможности, но все още имат повече, отколкото повечето приложения се нуждаят. Нашите контейнери работеха като root, което позволяваше на процесите вътре да извършват действия като монтиране на файлови системи или промяна на параметри на ядрото. Превключихме към стартиране на приложението като потребител без root права вътре в контейнера (използвайки директивата USER в Dockerfile) и премахнахме всички възможности освен абсолютно необходимите. За типично уеб приложение това може да бъде само NET_BIND_SERVICE (за свързване към портове под 1024) и CHOWN (за запис в директории). Добавихме и --security-opt no-new-privileges, за да предотвратим ескалация на привилегии.
Само тази стъпка премахна много често срещани вектори за бягство от контейнери. Атакуващ, който компрометира уеб сървъра, не може да инсталира пакети, да променя системни двоични файлове или да достъпва Docker сокета на хоста, защото процесът няма възможностите CAP_SYS_ADMIN или CAP_DAC_OVERRIDE.
Стъпка 3: Заключете файловата система
Записваемите файлови системи са често срещана атакуема повърхност. Направихме кореновата файлова система само за четене (--read-only) за всички контейнери, и след това монтирахме временни файлови системи (tmpfs) за директории, които се нуждаят от достъп за запис, като /tmp и кеш директорията на приложението. Това предотвратява атакуващ да модифицира кода на приложението или да запази злонамерени двоични файлове.
Освен това използвахме опцията --mount на Docker за bind-mount на чувствителни директории като Docker сокета само когато е абсолютно необходимо — и никога на продукционни контейнери. Принципът: ако контейнерът не трябва да записва в даден път, го направете само за четене.
Стъпка 4: Приложете Seccomp и AppArmor профили
Профилите по подразбиране на seccomp вече блокират много опасни системни извиквания, но ние ги персонализирахме допълнително, за да разрешим само тези, от които нашето приложение действително се нуждае. Това е компромис, защото изисква профилиране на приложението. По-прост подход е да използвате профила по подразбиране на Docker и след това да добавите --security-opt seccomp=path/to/profile.json, ако имате нужда от по-строги правила. По подобен начин, AppArmor профилите могат да ограничат процесите на контейнера до конкретни файлови пътища и възможности. Активирахме AppArmor и използвахме персонализиран профил, който ограничаваше достъпа само до директориите с данни на приложението.
За изчерпателно ръководство за тези стъпки за укрепване, вижте Укрепване на Docker контейнери за мулти-tenant хостинг: Ръководство стъпка по стъпка за изолация.
Противоположният възглед: Понякога се нуждаете от виртуални машини
Колкото и да са укрепени, контейнерите споделят ядрото на хоста. Уязвимост в ядрото може да разруши цялата изолация наведнъж. Ето защо много платформи, ориентирани към сигурността, стартират контейнери в леки виртуални машини — всеки клиент получава свое собствено ядро. Това добавя допълнителен разход, но осигурява граница на ниво хардуер, която контейнерите сами по себе си не могат. Ако вашите клиенти обработват данни за кредитни карти или здравни досиета, хибридният подход (контейнери във виртуални машини) може да е правилното решение. Не предполагайте, че изолацията на контейнерите е достатъчна за вашия заплашителен модел; оценете чувствителността на данните и регулаторните изисквания.
За по-задълбочено сравнение на нивата на изолация, прочетете Проектиране на мулти-tenant Docker архитектура: Избор на правилното ниво на изолация.
Заключение: Изолацията е стек, а не превключвател
Поправката не беше единична промяна — тя беше наслояване: мрежова изолация, ограничени привилегии, файлови системи само за четене и филтриране на системни извиквания. Дори тогава приехме, че перфектна изолация е невъзможна с контейнери, споделящи ядро. За нашите клиенти с най-високи изисквания за сигурност ги преместихме на специални хостове. Урокът: не вярвайте на нищо по подразбиране. Одитирайте своята Docker конфигурация, сякаш пробивът вече е настъпил. Времето за заключване е преди изтичането, а не след него.


