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 в образі (непривілейований користувач усередині контейнера) - базова практика.
--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, маніфести) і сканерами конфігурації.
За замовчуванням 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 має не з'єднатися - сегментація працює лише тоді, коли її перевірили.