Блог

Хибність довіри: Як наша багатокористувацька конфігурація Docker призвела до витоку даних (і як ми це виправили)

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

Підсумок

Контейнери Docker не є ізольованими за замовчуванням — вони спільно використовують ядро хосту, і без ретельної конфігурації користувачі можуть заважати один одному. Ця стаття описує реальний сценарій, де багатокористувацький хостинг-провайдер виявив, що клієнтські контейнери можуть отримувати доступ до баз даних один одного через спільну мережу та слабкі налаштування безпеки за замовчуванням. Ми показуємо покрокові зміни, які виправили порушення: визначені користувачем мережі для кожного користувача, користувачі без root-прав, видалені можливості, файлові системи лише для читання та профілі seccomp. Поширене припущення, що контейнери за своєю суттю забезпечують сильну ізоляцію; ми заперечуємо це, пояснюючи, чому віртуальні машини все ще пропонують більш жорсткий кордон і коли варто розглянути гібридний підхід. Висновок підкреслює, що ізоляція — це багаторівнева вправа, а не єдина галочка.

Інцидент: Коли контейнери забагато говорять

Ви налаштували Docker на одному хості для запуску кількох сайтів клієнтів. Кожен клієнт має власний контейнер — акуратне ізольоване середовище, чи не так? Саме так ми думали. Поки планова аудит безпеки не виявив, що контейнер клієнта A читав MySQL-сокет контейнера клієнта B на тому ж хості. Вони використовували спільну мережу bridge за замовчуванням. Ще гірше, контейнери працювали від root, тому атакуючий, який отримав доступ до одного, міг втручатися в Docker-сокет хосту або файлову систему іншого контейнера. Порушення не було складним експлойтом; це була базова неправильна конфігурація. Дані витекли. Довіра зникла.

Сценарій невдачі не є рідкісним. Багато команд припускають, що простори імен (namespaces) та cgroups Docker автоматично відокремлюють користувачів, але вони недооцінюють, скільки лазівок залишаються відкритими за замовчуванням. Мережі мосту за замовчуванням не забезпечують ізоляцію мережі між контейнерами. Робота від root надає контейнеру більше потужностей, ніж потрібно. І без явних обмежень ресурсів один гучний сусід може позбавити інших CPU або пам'яті.

Крок 1: Припиніть використовувати спільну мережу

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

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

Для глибшого занурення в стратегії ізоляції мережі див. Практичний чек-лист ізоляції Docker для багатокористувацького хостингу.

Крок 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 для монтування чутливих директорій, таких як Docker-сокет, лише коли це абсолютно необхідно — і ніколи в продуктивних контейнерах. Принцип: якщо контейнеру не потрібно записувати в шлях, зробіть його лише для читання.

Крок 4: Застосуйте профілі Seccomp та AppArmor

Типові профілі seccomp вже блокують багато небезпечних системних викликів, але ми налаштували їх додатково, дозволивши лише ті системні виклики, які дійсно потрібні нашому застосунку. Це компроміс, оскільки вимагає профілювання застосунку. Простіший підхід — використовувати типовий профіль seccomp Docker, а потім додати --security-opt seccomp=path/to/profile.json, якщо потрібні суворіші правила. Аналогічно, профілі AppArmor можуть обмежувати процеси контейнера до конкретних шляхів файлів і можливостей. Ми увімкнули AppArmor і використовували власний профіль, який обмежував доступ лише до директорій даних застосунку.

Для повного посібника з цих кроків посилення безпеки див. Посилення безпеки контейнерів Docker для багатокористувацького хостингу: покроковий посібник з ізоляції.

Протилежна думка: Іноді потрібні віртуальні машини

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

Для глибшого порівняння рівнів ізоляції прочитайте Проектування багатокористувацької архітектури Docker: вибір правильного рівня ізоляції.

Висновок: Ізоляція — це стек, а не перемикач

Виправлення не було єдиною зміною — це було нашарування: ізоляція мережі, обмежені привілеї, файлові системи лише для читання та фільтрація системних викликів. Навіть тоді ми визнали, що ідеальна ізоляція неможлива з контейнерами, що спільно використовують ядро. Для наших найбільш захищених користувачів ми перемістили їх на виділені хости. Урок: не довіряйте налаштуванням за замовчуванням. Аудит вашої конфігурації Docker так, ніби порушення вже сталося. Час посилювати безпеку — до витоку, а не після.

Sources (5)