Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Senior: питання на співбесіді з теми «Мережі й безпека контейнерів»

Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.

4 питання

/var/run/docker.sock - сокет, через який клієнти керують демоном Docker. Демон працює від root і виконує будь-які команди API: створює контейнери з будь-якими параметрами.

Хто має доступ до сокета - той має root на хості. Наприклад:

docker run -v /:/host --rm -it alpine chroot /host sh

Новий контейнер монтує кореневу файлову систему хоста, і chroot дає повноцінну оболонку root на хості: читання /etc/shadow, SSH-ключів, зміна системних файлів, встановлення бекдора.

Що це означає на практиці:

1. Монтування сокета в контейнер (-v /var/run/docker.sock:/var/run/docker.sock) - популярне для Traefik, Portainer, агентів моніторингу, CI-раннерів, Watchtower. Якщо такий контейнер зламано (вразливість у вебінтерфейсі, ланцюжку постачання), зловмисник отримує весь хост.

2. Група docker на хості - користувач у ній фактично root без sudo. Додавати в неї варто лише тих, кому довіряєте як адміністраторам.

3. TCP-доступ до демона без TLS (-H tcp://0.0.0.0:2375) - відкритий root для всього інтернету. Такі демони масово знаходять і використовують для криптомайнінгу.

Як зменшити ризик:

  • не монтувати сокет без крайньої потреби; якщо інструмент може працювати інакше (файловий провайдер Traefik замість Docker-провайдера) - обрати інший спосіб;
  • проксі сокета з білим списком дозволених викликів API (наприклад, tecnativa/docker-socket-proxy): Traefik отримує лише читання списку контейнерів, а не створення нових;
  • монтування :ro не допомагає - це лише права на файл сокета, а не на дії через API;
  • rootless Docker - демон працює від звичайного користувача, і доступ до сокета дає права цього користувача, а не root;
  • CI-збирання - замість Docker-in-Docker через сокет хоста використовувати ізольовані раннери чи збирання без демона (BuildKit rootless, Kaniko, Buildah);
  • віддалений доступ до демона - лише через SSH (DOCKER_HOST=ssh://...) чи TLS з клієнтськими сертифікатами.

На співбесіді це питання перевіряє розуміння, що Docker API - привілейований інтерфейс, а не «просто інструмент розробника».

Докладніше в документації: Безпека Docker Engine: поверхня атаки демона

У звичайній конфігурації демон Docker працює від root, і root усередині контейнера - це root ядра (UID 0). Вихід з контейнера через вразливість чи помилку конфігурації дає права root на хості. Два механізми зменшують цей ризик.

1. User namespace remapping (userns-remap) - демон лишається від root, але користувачі контейнера відображаються на непривілейований діапазон UID хоста:

// /etc/docker/daemon.json
{ "userns-remap": "default" }

Root (UID 0) у контейнері насправді є, наприклад, UID 100000 на хості. Якщо процес вирветься з контейнера, на хості він - звичайний користувач без прав.

2. Rootless mode - і демон, і контейнери працюють від звичайного користувача:

  • демон не має прав root узагалі - вразливість у самому демоні не дає root на хості;
  • доступ до сокета Docker дає лише права цього користувача (а не «фактично root», як у звичайному режимі);
  • кожен користувач може мати власний Docker.

Обмеження й компроміси:

  • порти нижче 1024 без додаткового налаштування недоступні (net.ipv4.ip_unprivileged_port_start);
  • мережа реалізована в просторі користувача - продуктивність нижча, а справжня адреса клієнта може бути недоступна без додаткового налаштування;
  • обмеження ресурсів через cgroups потребують cgroup v2 і делегування;
  • деякі драйвери сховища й можливості недоступні;
  • права на bind mount: файли, створені в контейнері, на хості належать відображеним UID - треба розуміти відображення, щоб не отримати «чужі» файли;
  • з userns-remap деякі опції (--privileged, спільні простори імен з хостом) не працюють чи потребують вимкнення відображення для конкретного контейнера.

Альтернатива - Podman: працює без демона і за замовчуванням без root; CLI сумісний з Docker.

Коли варто:

  • багатокористувацькі сервери й CI, де контейнери запускають люди чи процеси з різним рівнем довіри;
  • посилена безпека продакшен-хостів, де можна прийняти обмеження;
  • для локальної розробки rootless зазвичай зайвий, а Docker Desktop і так ізолює контейнери у віртуальній машині.

Незалежно від режиму USER в образі (непривілейований користувач усередині контейнера) - базова практика.

Докладніше в документації: Режим rootless

--privileged знімає майже всі обмеження контейнера:

  • надає всі можливості (capabilities), включно з SYS_ADMIN;
  • вимикає профілі seccomp і AppArmor/SELinux;
  • дає доступ до всіх пристроїв хоста (/dev) - дисків, модулів ядра.

Процес у привілейованому контейнері може змонтувати диск хоста й прочитати чи змінити будь-які файли, завантажити модуль ядра - тобто це фактично root на хості. Ізоляція контейнера тоді майже лише формальна.

Коли його застосовують - Docker-in-Docker, деякі системні агенти (керування мережею, сховищем), експерименти. Майже завжди є вужча альтернатива: окремі можливості (--cap-add), конкретні пристрої (--device), зовнішній демон через SSH чи безпечні інструменти збирання.

Seccomp - фільтр системних викликів ядра. Docker за замовчуванням застосовує профіль, що забороняє кілька десятків небезпечних викликів (наприклад, mount, reboot, kexec_load, add_key, створення нових просторів імен у більшості випадків). Більшості застосунків вони не потрібні, а для зловмисника це інструменти виходу з контейнера.

docker run --security-opt seccomp=./custom-profile.json myapp
docker run --security-opt seccomp=unconfined myapp   # вимкнути - лише для налагодження

Власний, вужчий профіль (лише ті виклики, що реально використовує застосунок) - ще сильніший захист, але вимагає ретельного тестування.

AppArmor (Ubuntu/Debian) / SELinux (RHEL/Fedora) - мандатний контроль доступу: обмежує, до яких файлів, мереж і можливостей процес має доступ, незалежно від прав користувача. Docker застосовує профіль docker-default для AppArmor; власні профілі задають через --security-opt apparmor=....

Багатошаровий захист - кожен механізм закриває свою частину:

Механізм Що обмежує
простори імен що процес бачить
cgroups скільки ресурсів використовує
capabilities які привілейовані дії може робити root
seccomp які системні виклики ядра доступні
AppArmor/SELinux до яких ресурсів є доступ
непривілейований користувач права всередині контейнера

Правило для продакшену: жодного --privileged і seccomp=unconfined без задокументованої причини; перевіряти це в рев'ю конфігурацій (Compose, маніфести) і сканерами конфігурації.

Докладніше в документації: Профілі seccomp

За замовчуванням Compose підключає всі сервіси проєкту до однієї мережі default. Кожен контейнер бачить кожен: зворотний проксі може під'єднатися до бази, а сервіс обробки зображень - до Redis із сесіями.

Якщо будь-який сервіс зламано (вразливість у бібліотеці обробки файлів, сторонній образ), зловмисник отримує мережевий доступ до всіх інших.

Сегментація - кілька мереж за принципом «хто з ким має говорити»:

services:
  proxy:
    image: caddy
    ports: ["443:443"]
    networks: [edge]

  app:
    image: myapp
    networks: [edge, data]

  worker:
    image: myapp
    command: php artisan queue:work
    networks: [data, egress]

  postgres:
    image: postgres:18
    networks: [data]

  redis:
    image: redis:8
    networks: [data]

networks:
  edge:
  data:
    internal: true     # без маршруту в інтернет
  egress:              # окрема мережа з виходом назовні

Що це дає:

  • проксі бачить лише застосунок - не базу й не Redis;
  • база й Redis у мережі internal: true - не можуть самі звертатися в інтернет (зламана база не завантажить шкідливий код і не відправить дані назовні);
  • воркер має вихід в інтернет для сторонніх API, але не стоїть «на краю» - до нього не звертаються ззовні.

Додаткові прийоми:

  • без ports для всього, крім точки входу;
  • окремі облікові записи бази для застосунку й воркерів з мінімальними правами - мережа обмежує, хто може під'єднатися, права - що можна зробити після під'єднання;
  • аліаси мереж - сервіс може мати різні імена в різних мережах.

Обмеження: мережі Docker - це сегментація на рівні з'єднань (L3/L4). Вони не замінюють автентифікацію між сервісами: Redis і база мають паролі навіть у внутрішній мережі. Для політик на рівні окремих потоків і шифрування між сервісами - оркестратори з мережевими політиками (Kubernetes NetworkPolicy) чи service mesh.

Перевірка: docker compose exec proxy nc -zv postgres 5432 має не з'єднатися - сегментація працює лише тоді, коли її перевірили.

Докладніше в документації: Мережі в Compose