Безпека сокета Docker: один маунт, що рівноцінний root (і безпечніші альтернативи)

Було корисно?

Десь у вашому кластері контейнер має -v /var/run/docker.sock:/var/run/docker.sock, бо «йому треба збирати образи»
або «йому треба інспектувати контейнери». Це працює. Це відправляють у продакшн. І всі перестають про це думати.

До дня, коли непомітний сервіс-акаунт всередині того контейнера виявить, що може запускати привілейовані контейнери, змонтувати файлову систему хоста
і переписати вашу реальність. Один маунт. Контроль на рівні хоста. Це не теоретичне «можливо». Це практичний, відтворюваний шлях ескалації.

Проблема «одного маунта»: чому docker.sock фактично = root

Демон Docker (dockerd) — це довготривалий привілейований процес на хості. Він може:
створювати неймспейси, налаштувати cgroups, конфігурувати мережу, монтувати файлові системи та запускати контейнери з широкими привілеями. Це його робота.
CLI Docker — це лише клієнт, який надсилає запити до демона.

/var/run/docker.sock — це Unix domain socket, де за замовчуванням живе цей API. Якщо процес може спілкуватися з сокетом з достатніми правами,
він може попросити демон робити дії від імені хоста. Демон не знає (і не перевіряє), чи запит надійшов від «довіреного адміністратора в терміналі», чи від «Node.js‑додатка всередині контейнера».
Він просто приймає API‑запити.

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

Практично це означає, що скомпрометований контейнер може:

  • Запустити новий контейнер у режимі --privileged.
  • Змонтувати файлову систему хоста в цей контейнер (наприклад, /:/host).
  • Записувати в шляхи на хості, змінювати конфіги, кидати SSH‑ключі, читати секрети або модифікувати unit‑файли systemd.
  • Налаштувати мережу для прослуховування або перенаправлення трафіку.
  • Завантажувати й запускати довільні образи як вектор виконання.

Якщо це звучить як «root», то так воно і є, тільки з інтерфейсом трохи іншого вигляду.
Сокет — це не «щось Docker‑ове». Це еквівалент системного root‑доступу.

Перша жартівлива ремарка (жарти тримаємо в шерегі): змонтувати docker.sock у контейнер — це як роздавати майстер‑ключі, тільки ключі ще й можуть побудувати нову будівлю.

«Але ми використовуємо це тільки для збірок» — це не пом’якшення

Docker API за замовчуванням не є деталізованим. Якщо ви можете створювати контейнери, ви можете створювати контейнери, що змонтують хост.
Якщо ви можете змонтувати хост — ви можете зробити все. API не зупиниться чемно на «тільки збірка, жодних витівок».

Межа безпеки тут не в контейнері; вона в демонові. А демон працює на хості з правами хоста.
Тому питання стає: «Кому дозволено просити демон робити речі?» Якщо відповідь — «любому процесу в цьому контейнері», ви збільшуєте зону ураження до всіх багів у стеку залежностей цього контейнера.

Кілька фактів і історія, що пояснюють, як ми дійшли сюди

Це не дрібниці заради дрібниць. Вони пояснюють, чому патерни з сокетом Docker такі поширені, чому вони залишаються та чому з’явилися сучасні альтернативи.

  1. Docker починався як інструмент для розробників. На ранньому етапі приймали «працює на моїй машині» більше, ніж суворе регулювання доступу.
  2. Розділення демон/клієнт було від початку. Навіть класичний інструмент docker завжди був віддаленим клієнтом; за замовчуванням просто використовується локальний сокет.
  3. Група docker історично еквівалентна root. На багатьох Linux‑системах доступ до /var/run/docker.sock надається через членство в групі docker, що фактично дозволяє дії рівня root.
  4. Unix‑сокети обрали заради локальної зручності. Вони швидкі, прості й уникають відкриття TCP‑порту за замовчуванням — але не вирішують авторизацію.
  5. Віддалений Docker по TCP з’явився рано і часто неправильно конфігурувався. «Відкрити 2375 у світ» стало повторюваною інцидентною моделлю в 2010‑х.
  6. Інструменти для збірки еволюціонували через біль від сокет‑патерну. BuildKit, rootless‑збірки та «daemonless»‑білдери набули популярності частково, щоб уникнути надання CI‑жонрам контролю над хостом.
  7. Docker‑in‑Docker (DinD) став обходом із власними гострими краями. Він зменшив потребу в шарингу сокета хоста, але приніс потребу в привілеях, складність збереження даних і обмеження вкладеної ізоляції.
  8. Kubernetes збільшив масштаб проблеми. Коли ви монтуєте сокет рантайму в Pod (Docker, containerd, CRI‑сокети), «один поганий Pod» може стати «одним поганим вузлом».

Історичний висновок: більшість експозицій сокета не є зловмисними; це випадкова зручність, що затверділа в «стандартну практику».
Продакшн — це місце, де «стандартна практика» проходить аудит.

Модель загроз: що може зробити атакувальник зі сокетом

Припустімо, атакувальник отримав виконання коду всередині контейнера, який має змонтований сокет Docker хоста. Це може статися через:
RCE у вашому додатку, шкідливу залежність, скомпрометовану CI‑джобу або вразливий адмін‑ендпойнт. І що далі?

Шлях ескалації простими кроками

Атакувальник може запускати Docker CLI, якщо він є в контейнері, або використовувати сирий HTTP через Unix‑сокет.
У будь‑якому випадку він може запросити:

  • Створити контейнер з --privileged (або з набором необхідних capability).
  • Змонтувати / хоста в контейнер.
  • Зробити chroot у файлову систему хоста та змінити її.
  • Забезпечити персистентність: systemd‑сервіс, cron, SSH‑ключі, authorized_keys або заміна бінарів.

Це не лише «root»; це також «plane керування»

Навіть без монтажу /, контроль над Docker може:

  • Зупиняти чи перезавантажувати критичні сервіси.
  • Читати змінні оточення з інших контейнерів (часто включно з токенами).
  • Підключатися до запущених контейнерів і витягувати секрети з пам’яті.
  • Створювати мережеві «піводи» через приєднання до мереж контейнерів.
  • Завантажувати образи з реєстрів, використовуючи облікові дані хоста.

«Але контейнер не привілейований» — це нерозуміння

Сам контейнер може бути непривілейованим. Це не має значення. Демон привілейований. Ви просите привілейований процес хоста виконати привілейовану роботу.
Межа не забезпечується демоном, якщо ви не налаштували її спеціально.

Ось корисна операційна формула: docker.sock — це інтерфейс адміністратора. Монтуйте його лише туди, куди ви також дозволили б shell‑доступ root на хості.
Якщо ця фраза викликає у вас занепокоєння — добре, тепер ми можемо це виправити.

Швидкий план діагностики

Коли ви підозрюєте експозицію сокета docker (або реагуєте на момент «чому цей контейнер може це робити?»), швидкість має значення. Перевіряйте в такому порядку:

По‑перше: чи змонтований сокет або чи досяжний він?

  • Перевірте маунти контейнера на наявність /var/run/docker.sock.
  • Перевірте файлову систему на шлях до сокета та права доступу.
  • Підтвердіть, чи процес може з ним спілкуватися (неуспішний API‑виклик теж дає інформацію).

По‑друге: хто може його доступати?

  • Власник і група сокета; перевірте, хто в групі docker.
  • Всередині контейнера: процес працює як root? Чи в групі, яка мапиться на сокет?
  • Чи є сайдкари або агенти з ширшими правами?

По‑третє: що він може робити прямо зараз?

  • Спробуйте читальний виклик (docker ps) і записний (docker run) у контрольованому середовищі.
  • Перевірте конфіг демона: TLS? плагіни авторизації? rootless demon? user namespace remap?
  • Пошукайте CI‑ранери, інструменти деплойменту або «моніторинг» контейнери, що тихо носять сокет.

По‑четверте: зона ураження та перевірки персистентності

  • Пошукайте контейнери, запущені з --privileged, маунтами хоста або host PID/network namespace.
  • Аудитуйте недавні події запуску контейнерів; перевірте незнайомі образи.
  • Перевірте хост на нові systemd‑unit, cron‑записи, SSH‑ключі або змінені бінарі.

Цей план навмисно прямий. Мета — визначити, чи маєте ви справу з «звично ризиковано» чи з «активною компрометацією».
Далі можна уточнювати. Наразі потрібне реальне підтвердження.

Практичні завдання: команди, виводи й рішення

Нижче — реальні завдання, які можна виконати на Linux‑хості або в контейнері (де це доречно). Кожне містить:
команду, приклад виводу, що означає вивід, і рішення на основі результату.

Завдання 1: Перевірити, чи існує Docker‑сокет і як він захищений

cr0x@server:~$ ls -l /var/run/docker.sock
srw-rw---- 1 root docker 0 Jan  3 09:12 /var/run/docker.sock

Значення: Це Unix‑сокет (s), власник root, група docker, група має запис.

Рішення: Розглядайте членство в docker як привілейований доступ. Якщо контейнери можуть мапитися в цю групу, у вас є проблема межі привілеїв.

Завдання 2: Перелічити членів групи docker (на хості)

cr0x@server:~$ getent group docker
docker:x:998:jenkins,deploy,alice

Значення: Ці користувачі ймовірно можуть контролювати Docker на хості.

Рішення: Радикально скоротіть цей список. Якщо «deploy» — спільний акаунт, ви фактично ділитеся root‑доступом.

Завдання 3: Підтвердити, з яким демоном спілкується ваш CLI

cr0x@server:~$ docker context ls
NAME        DESCRIPTION                               DOCKER ENDPOINT               ERROR
default *   Current DOCKER_HOST based configuration   unix:///var/run/docker.sock

Значення: За замовчуванням використовується локальний сокет під root‑власністю.

Рішення: Якщо ви очікували віддалений збірник або rootless‑демон, ви його не використовуєте. Виправте робочий процес, а не історію.

Завдання 4: Знайти контейнери, що монтять docker‑сокет

cr0x@server:~$ docker ps --format '{{.ID}} {{.Names}}' | while read id name; do docker inspect -f '{{.Name}} {{range .Mounts}}{{println .Source "->" .Destination}}{{end}}' "$id"; done | grep -F '/var/run/docker.sock'
/ci-runner /var/run/docker.sock -> /var/run/docker.sock
/portainer /var/run/docker.sock -> /var/run/docker.sock

Значення: Два контейнери мають контроль Docker хоста. CI‑ранер очікуваний; Portainer може бути прийнятним, але обидва — цінні цілі.

Рішення: Для кожного контейнера: виправдайте його наявність, обмежте сферу або видаліть. «Ми завжди так робили» — не аргумент.

Завдання 5: Всередині підозрілого контейнера перевірити, чи сокет доступний

cr0x@server:~$ docker exec -it ci-runner sh -lc 'id && ls -l /var/run/docker.sock'
uid=1000 gid=1000 groups=1000
srw-rw---- 1 root docker 0 Jan  3 09:12 /var/run/docker.sock

Значення: Процес не в групі docker. Це може блокувати доступ — якщо тільки контейнер інколи не запускається як root або груповий ID не відмінно мапиться.

Рішення: Спробуйте безпечний API‑виклик наступним кроком. Не думайте, що ви в безпеці тільки тому, що id виглядає непривілейовано.

Завдання 6: Спроба читального Docker‑виклику зсередини

cr0x@server:~$ docker exec -it ci-runner sh -lc 'docker ps'
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.45/containers/json": dial unix /var/run/docker.sock: connect: permission denied

Значення: Сокет змонтований, але поточний користувач не має до нього доступу.

Рішення: Це все одно ризик: якщо контейнер може стати root (помилка конфігурації, SUID або експлойт), все може стати доступним. Перевірте, чи контейнер інколи запускається як root під час задач.

Завдання 7: Перевірити, чи контейнер працює як root (на хості)

cr0x@server:~$ docker inspect -f '{{.Name}} user={{.Config.User}}' ci-runner
/ci-runner user=

Значення: Порожнє поле user часто означає, що за замовчуванням всередині контейнера працює root.

Рішення: Якщо цей контейнер також має сокет, розглядайте його як «root на хості, що чекає на свій час». Виправте зараз: запускайте як non‑root і приберіть сокет або ізолюйте демон.

Завдання 8: Довести ескалацію (у контрольованій лабораторії), змонтувавши корінь хоста

cr0x@server:~$ docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock alpine sh -lc 'apk add --no-cache docker-cli >/dev/null && docker run --rm -it --privileged -v /:/host alpine sh -lc "ls -l /host/etc/shadow | head -n 1"'
-rw-r-----    1 root     shadow        1251 Jan  3 08:59 /host/etc/shadow

Значення: Контейнер із доступом до сокета створив привілейований контейнер, який може читати чутливі файли хоста.

Рішення: Якщо ви можете це зробити, те саме може зробити атакувальник. Усуньте маунти сокета з загального призначення робочих навантажень.

Завдання 9: Виявити привілейовані контейнери й маунти хоста

cr0x@server:~$ docker ps -q | xargs -r docker inspect -f '{{.Name}} privileged={{.HostConfig.Privileged}} mounts={{range .Mounts}}{{.Source}}:{{.Destination}},{{end}}'
/ci-runner privileged=false mounts=/var/run/docker.sock:/var/run/docker.sock,
/node-exporter privileged=false mounts=/proc:/host/proc,/sys:/host/sys,
/debug-shell privileged=true mounts=/:/host,

Значення: /debug-shell привілейований і монтує корінь хоста. Це аварійний важіль — прийнятно, якщо контрольовано, катастрофічно, якщо забуто.

Рішення: Видаліть або заблокуйте «debug» контейнери. Застосуйте політики, що забороняють поєднання privileged + host mounts поза break‑glass сценаріями.

Завдання 10: Перевірити конфіг прослуховування демона (щоб уникнути випадкової TCP‑експозиції)

cr0x@server:~$ ps aux | grep -E 'dockerd|docker daemon' | grep -v grep
root      1321  0.3  1.4 1332456 118320 ?      Ssl  08:58   0:06 /usr/bin/dockerd -H fd://

Значення: Використовується systemd socket activation (-H fd://), явно не слухає TCP.

Рішення: Добре. Якщо ви бачите -H tcp://0.0.0.0:2375 без TLS — це інцидент.

Завдання 11: Підтвердити, чи Docker API доступний по мережі

cr0x@server:~$ ss -lntp | grep -E '(:2375|:2376)\b' || true

Значення: Немає слухачів на звичних TCP‑портах Docker.

Рішення: Тримайте це так, якщо тільки у вас немає TLS і історії авторизації. «Ми зафайрволимо пізніше» — це як опинитися на понеділковому колі інцидентів.

Завдання 12: Запитати Docker через сирий сокет (корисно, коли docker CLI відсутній)

cr0x@server:~$ curl --unix-socket /var/run/docker.sock http://localhost/version
{"Platform":{"Name":"Docker Engine - Community"},"Components":[{"Name":"Engine","Version":"26.0.0","Details":{"ApiVersion":"1.45","GitCommit":"...","GoVersion":"...","Os":"linux","Arch":"amd64","KernelVersion":"6.5.0"}}],"Version":"26.0.0","ApiVersion":"1.45","MinAPIVersion":"1.24","GitCommit":"...","GoVersion":"...","Os":"linux","Arch":"amd64","KernelVersion":"6.5.0","BuildTime":"..."}

Значення: Якщо це вдається всередині контейнера, то контейнер має адміністративний доступ до Docker, навіть якщо в ньому не встановлено docker CLI.

Рішення: Не обманюйте себе думкою «ми не встановлювали docker». Доступ — це про сокет, а не про бінар.

Завдання 13: Аудит unit‑файлу systemd для прапорів Docker (на хості)

cr0x@server:~$ systemctl cat docker | sed -n '1,120p'
# /lib/systemd/system/docker.service
[Service]
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

Значення: Тут немає явного небезпечного TCP‑лістенера.

Рішення: Якщо потрібно відкрити TCP, робіть це явно з TLS і обмежте клієнтів; інакше тримайте локально.

Завдання 14: Визначити, які образи й контейнери найбільш ймовірно будуть експлуатовані

cr0x@server:~$ docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}' | sed -n '1,15p'
NAMES         IMAGE                 STATUS
ci-runner     company/runner:latest Up 3 hours
portainer     portainer/portainer   Up 7 days
api           company/api:2026.01   Up 2 days

Значення: «Runner» і «admin UI» — це цінні цілі, часто інтернет‑аджасентні й складні.

Рішення: Віддайте пріоритет видаленню маунтів сокета з усього, що парсить недовірений ввід або запускає сторонні джоби.

Завдання 15: Якщо ви використовуєте rootless Docker, підтвердьте це

cr0x@server:~$ docker info --format 'rootless={{.SecurityOptions}}'
rootless=[name=seccomp, name=rootless]

Значення: Демон працює в rootless‑режимі (або принаймні повідомляє про опцію безпеки rootless).

Рішення: Rootless зменшує ризик захоплення хоста, але не усуває його повністю. Оцініть, до чого має доступ rootless‑демон (шляхи збереження, креденшали, мережа).

Завдання 16: Виявити «docker.sock під іншим іменем» (containerd / CRI сокети)

cr0x@server:~$ ls -l /run/containerd/containerd.sock 2>/dev/null || true
srw-rw---- 1 root root 0 Jan  3 08:58 /run/containerd/containerd.sock

Значення: Існує сокет containerd. Монтування цього (або CRI‑сокетів) у контейнер може подібно відкривати контроль над вузлом, залежно від того, що слухає і як це захищено.

Рішення: Не грайте в whack‑a‑mole з іменами файлів. Розглядайте сокети рантайму як привілейовані інтерфейси загалом.

Три міні-історії з реальних інцидентів

Міні‑історія 1: Інцидент через неправильне припущення

Середня компанія мала «утилітний» контейнер на кожному билд‑хості. Він збирає логи збірки, відвантажує артефакти і — через зручність —
мав змонтований Docker‑сокет, щоб позначати контейнери й чистити старі образи.

Хтось припустив, що контейнер мало ризиковий, бо «спілкується тільки з внутрішніми системами». Контейнер також запускав невеликий HTTP‑ендпойнт для health‑checkів
і метрик. Ендпойнт приймав кілька параметрів і записував їх у логи. Під час поспіху в рефакторингу з’явився маленький ін’єкційний баг.

Сканер знайшов ендпойнт. За кілька хвилин атакувальник отримав RCE у утилітному контейнері. Йому не потрібен був експлойт ядра.
Він просто використав сокет, щоб запустити привілейований контейнер із змонтованою файловою системою хоста, а потім вкинув бекдор‑бінар у шлях, який використовував cron.

Обнаруження було неприємним: піки CPU, перезапуски контейнерів і хвиля «чому цей хост робить вихідні з’єднання?». Команда спочатку ганялася за проблемами мережі і таймаутами реєстру.
Корінь виявився старим трюком Docker: контейнер контролює Docker.

Виправлення не було «заплатай ін’єкцію». Вони прибрали маунти сокета з усього, що не було суворо контрольованим компонентом збірки, і перенесли очищення на
host‑рівень через systemd‑таймери під явною адмін‑власністю. Команда безпеки була не в захваті, але принаймні межа ураження стала явною.

Міні‑історія 2: Оптимізація, що відкотилася назад

Інша організація мала інтенсивні CI‑навантаження. Збірки були повільні, здебільшого через те, що завантаження базових образів навантажувало реєстр, а кеш був холодний.
Хтось придумав «розумне» рішення: запустити спільний «сервіс кешу збірок» на кожному вузлі, змонтувати docker.sock і мати його заздалегідь завантажувати образи та підгрівати кеш.
Це скоротило час збірки. Люди аплодували. Запит на зміну пройшов, бо «це просто продуктивність».

Проблема вийшла тихо. Сервіс кешу потребував широких дозволів для керування образами між проектами. CI‑джоби почали покладатися на нього опосередковано, і незабаром
кеш‑сервіс став фактичною контрольною площиною для билд‑хостів. Він також став залежністю. Коли він падав — збірки зупинялись.

Потім пройшов огляд безпеки. Рев’ювери поставили нудне питання: «Якщо CI‑джоб буде скомпрометовано, чи може він дістатися до контейнера кешу?»
Відповідь була «так» — та сама мережа, той самий вузол, спільні змінні оточення та багато можливостей для латерального руху.

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

Вони відкотили рішення і замінили його віддаленим BuildKit‑збирачем зі звуженими обліковими даними та кешуванням на боці реєстру. Збірки стали трохи повільніші,
ніж у піковому «хитрому» рішенні, але ризик впав. Продуктивність — це фіча; живучість — продукт.

Міні‑історія 3: Нудна, але правильна практика, що врятувала

Велике підприємство мало просту політику: ніяких маунтів docker.sock у робочих навантаженнях додатків. Винятки вимагали короткої форми й компенсувальних контролів.
Люди бурчали. Звісно. Політика впроваджувалася через правила рев’ю образів і періодичний аудит запущених контейнерів.

Одного дня нова команда розгорнула контейнер агента від вендора «для покращення спостережності». Quickstart вендора використовував маунт сокета для виявлення контейнерів.
Команда запросила виняток, як і очікувалося. Безпека попросила read‑only альтернативу й обґрунтування. Вендор сказав «це безпечно».
Безпека попросила «покажіть нам».

Під час тестування інженер показав, що агент може створювати контейнери й монтувати хост. Це завершило дискусію про «безпеку».
Команда замість цього розгорнула агента з обмеженим джерелом даних (cgroup і procfs‑метрики) і окремий збирач на рівні хоста для метаданих контейнерів.

Через місяць у того вендора виявили вразливість. Її виправили за розкладом, але вразливість не перетворилася на захоплення хоста, бо агент ніколи не мав контролю над хостом.
Результат не був драматичним. Ось у чому суть. Нудні контроли часто саме ті, що скорочують ваш звіт про інцидент.

Безпечніші альтернативи (з компромісами, а не казками)

Правильна заміна залежить від того, навіщо ви спочатку монтували сокет. «Нам потрібен Docker у контейнері» — це не вимога; це симптом.
Нижче наведені патерни, що працюють у реальних системах, включно з їхніми витратами.

1) Не робіть цього: перенесіть операції Docker на хост (systemd‑таймери, контрольовані скрипти)

Якщо єдина причина доступу до сокета — очищення, prune образів, ротація логів або збір метаданих — робіть це на хості.
Використовуйте systemd‑таймер або cron з явною власністю й аудитом.

Компроміси: Трохи більше управління хостами. Але ви повертаєте чіткі межі привілеїв і зменшуєте кількість процесів, які можуть керувати демоном.

2) Rootless Docker демон (або rootless BuildKit) для недовірених навантажень

Rootless Docker запускає демон без прав root. Це змінює зону ураження: доступ до Docker API вже не автоматично прирівнюється до root‑прав хоста.
Він все ще потужний — але обмежений правами користувача.

Компроміси: Деякі мережеві фічі, привілейовані контейнери і kernel‑capabilities не працюватимуть. Продуктивність і сумісність можуть відрізнятися.
Операційно ви тепер управляєте демонами на користувача, шляхами збереження й локаціями сокетів.

3) Віддалені збирачі: BuildKit як сервіс (окрема зона безпеки)

Часте застосування сокета — збірки образів у CI. Безпечніший патерн: CI спілкується з віддаленим збирачем, ізольованим, загартованим і обмеженим.
Ваші робочі навантаження ніколи не бачать runtime‑сокет. CI‑ранери не отримують права адміністрування хоста.

Компроміси: Потрібна мережна доступність і управління креденшалами. Налагодження збірок може перейти від «ssh на хост» до «перегляду логів збирача». Варто того.

4) Daemonless‑збірки: підходи на кшталт Kaniko

Daemonless‑білдери можуть збирати образи без потреби в правах демона Docker. Це усуває мотивацію монтувати docker.sock у CI‑джоби.

Компроміси: Не всі Dockerfile‑фічі поводяться однаково. Кешування шарів і продуктивність можуть відрізнятися. Ви міняєте привілеї демона на фіча‑відмінності інструменту збірки.

5) Docker‑in‑Docker (DinD): кращий за маунт сокета, але все ще гострий

DinD запускає демон Docker всередині контейнера. CI‑джоби говорять із цим внутрішнім демоном, а не з хостовим. Це може зменшити ризик захоплення хоста від коду CI‑джобів.
Але на практиці DinD часто потребує --privileged, щоб працювати коректно, і ізоляція збереження даних швидко ускладнюється.

Компроміси: Ризик привілейованого контейнера, складність вкладених cgroups, витрати продуктивності і тенденція перетворюватися на «тимчасову» інфраструктуру, що ніколи не прибирається.

6) Плагіни авторизації й застосування політик (коли сокет справді треба експонувати)

Якщо є легітимна потреба в контрольованому доступі до Docker API, можна додати шар політик:
плагіни авторизації, TLS‑серти для клієнтів при віддаленому доступі і суворий контроль, які ендпойнти API дозволені.

Компроміси: Тепер ви запускаєте й підтримуєте шар авторизації над рантаймом. Якщо він «відкриється» — ви програли. Якщо «закриється» — ви будете викликані о 3:00 ранку.
Все ж це краще, ніж сподіватися.

7) Замініть «інспекцію Docker» на сигнали тільки для читання

Моніторингові агенти часто просять сокет, щоб перелічувати контейнери та мітки. Альтернативи:
читати /proc, cgroups, node‑exporter, лог‑драйвери; погодитися на менше метаданих; або запускати довірений збирач на рівні хоста, який експортує санітайзені метрики.

Компроміси: Менша деталізація. Але для більшості випадків моніторингу «менше метаданих» краще, ніж «root випадково».

8) Якщо ви на Kubernetes: застосовуйте це політиками admission і замінами PSP

Якщо ви керуєте Kubernetes, не покладайтеся лише на рев’ю. Додайте політики, що відмовляють у маунті сокетів рантайму і hostPath, якщо це явно не дозволено.
Також блокуйте привілейовані pod‑и та hostPID/hostNetwork, окрім відомих системних компонентів.

Компроміси: Ви зламаєте чиїсь «швидкі debug pod‑и». Це нормально. Надати namespace для break‑glass з сильною RBAC і аудитом.

Один вислів, щоб залишатися чесним

Надія — не стратегія. — генерал Gordon R. Sullivan

Вам не потрібно бути параноїком. Потрібно реально оцінювати, що ви змонтували.

Друга жартівлива ремарка (остання, обіцяю): Docker‑сокет — як «тимчасове» правило брандмауера — всі згадують про нього відразу після інциденту.

Типові помилки: симптом → корінь проблеми → виправлення

1) Симптом: «Наш додаток може запускати інші контейнери»

Корінь: Сокет Docker хоста змонтовано в контейнер додатка, або контейнер має доступ до прав групи docker.

Виправлення: Приберіть маунт сокета; переробіть робочий процес (віддалений збирач, автоматизація на хості). Забезпечте, щоб контейнер запускався як non‑root і не мав шляху до групи docker.

2) Симптом: «Ми прибрали docker CLI, але контейнер все одно контролює Docker»

Корінь: API доступний через сокет; інструменти на кшталт curl можуть говорити з ним напряму.

Виправлення: Приберіть доступ до сокета. Безпека — це не «відсутність бінарного клієнта».

3) Симптом: «Тільки CI має docker.sock, отже ми в безпеці»

Корінь: CI виконує недовірений код (PR, залежності, скрипти збірки). Саме там ви не хочете прав адміністратора хоста.

Виправлення: Використовуйте віддалені збирачі або daemonless‑інструменти. Якщо демон необхідний — ізолюйте його по job і звужуйте креденшали.

4) Симптом: «Випадкові контейнери працюють привілейовано, і ніхто не знає чому»

Корінь: Контейнер з доступом до сокета їх запустив, або debug‑робочий процес нормалізувався в продакшні.

Виправлення: Аудитуйте джерела запуску контейнерів; приберіть маунти сокета; застосуйте політики, що забороняють privileged контейнери поза контрольованими неймспейсами.

5) Симптом: «Ми відкрили Docker по TCP для зручності»

Корінь: dockerd слухає на tcp://0.0.0.0:2375 (часто без TLS) або правила файрволу надто дозвільні.

Виправлення: Вимкніть TCP‑лістенер; якщо потрібен віддалений доступ, використовуйте TLS на 2376 з клієнтськими сертифікатами і суворими ACL мережі, плюс контролі авторизації.

6) Симптом: «Агент моніторингу вимагає docker.sock, вендор каже, що це потрібно»

Корінь: За замовчуванням зручність вендора. Вони хочуть повні метадані й просять найпростіший інтерфейс.

Виправлення: Використовуйте збирач на рівні хоста; надайте джерела даних тільки для читання; вимагайте зменшеного обсягу; відмовляйтеся від маунту сокета в звичайних агентах.

7) Симптом: «Спробували rootless, але збірки поламались»

Корінь: Деякі Dockerfile‑фічі і привілейовані операції очікують поведінки rootful, або пайплайн припускає пряму хост‑мережу.

Виправлення: Використовуйте віддалений BuildKit зі контрольованими привілеями; розділіть «build» і «run»; переробіть Dockerfile; прийміть, що деякі застарілі припущення треба відмовитись.

8) Симптом: «Ми використали DinD і тепер дискове використання шалене»

Корінь: Вкладене зберігання демона в overlay2 всередині файлової системи контейнера; кеші накопичуються на кожному раннері; prune не зачіпає хост як очікувалось.

Виправлення: Використовуйте зовнішні томи для кешу збирача з управлінням життєвим циклом; або перейдіть на віддалені збирачі/реєстр‑кеш; впровадьте явні політики prune для кожного середовища.

Контрольні списки / поетапний план

Покроково: усунути небезпечні маунти docker.sock без зламу продакшну

  1. Інвентаризація використання сокета.

    • Перелічіть запущені контейнери, що монтують /var/run/docker.sock.
    • Перегляньте маніфести/compose‑файли, що його містять.
    • Класифікуйте за призначенням: CI‑збірки, моніторинг, адмін‑UI, очищення, «різне».
  2. Визначте, що заборонено однозначно.

    • Робочі навантаження додатків, що парсять недовірений ввід: заборонити.
    • Сторонні агенти: за замовчуванням заборона.
    • CI‑ранери: підвищена увага; ймовірна переробка.
  3. Спочатку замініть сценарії «доступ до метаданих».

    • Замініть інспекцію сокета на cgroup/procfs‑метрики де можливо.
    • Використовуйте збирачі на рівні хоста для решти.
  4. Далі виправіть CI‑збірки (найвищий ризик).

    • Обирайте патерн збирача: віддалений BuildKit, daemonless‑збірки або ізольований DinD на job.
    • Обмежте облікові дані реєстру мінімальними правами на потрібні репозиторії.
    • Відокремте секрети збірки від секретів виконання.
  5. Заблокуйте залишені винятки.

    • Запускати як non‑root.
    • Мережна ізоляція й суворі вхідні правила.
    • Додати логування аудиту викликів Docker API, якщо можливо.
    • Документувати обґрунтування й дату видалення.
  6. Застосовуйте, а не випрошуйтесь.

    • Додайте перевірки політик у CI (відхиляти manifest‑и/композе з маунтами сокета).
    • Додайте рантайм‑аудит і оповіщення (періодичне сканування запущених контейнерів).

Операційний чекліст: якщо треба дозволити доступ до сокета (рідко)

  • Контейнер з доступом до сокета розглядається як привілейований агент хоста, а не звичайний додаток.
  • Образ контейнера мінімальний, зафіксований (pinned) і оперативно патчиться.
  • Працює як non‑root, де можливо; без shell, без менеджера пакетів у продакшн‑образах.
  • Мережна експозиція мінімізована; немає вхідних з’єднань з недовірених мереж.
  • Немає спільних секретів, що дозволяють латеральний рух (звужені токени; короткотривалі креденшали).
  • Сильне логування: події запуску контейнерів, pull‑и образів і події демона моніторяться.
  • Ясна відповідальність: хто отримує оповіщення при некоректній поведінці і хто погоджує зміни.

Питання й відповіді (FAQ)

1) Монтування /var/run/docker.sock завжди рівнозначне root?

У rootful‑налаштуваннях Docker — фактично так. Якщо процес може робити Docker API‑виклики, які створюють контейнери, він зазвичай може ескалувати до повного контролю хоста,
запустивши привілейований контейнер і змонтувавши файлову систему хоста. Є винятки (кастомна авторизація, обмежені демони), але не ризикуйте флотом на ставці на винятки.

2) Що якщо сокет змонтовано тільки для читання?

Read‑only‑маунт впливає на операції запису в вузол сокета у файловій системі, але не на здатність відправляти API‑запити через нього. Якщо ви можете відкрити сокет, ви можете з ним говорити.
«Read‑only docker.sock» — здебільшого театралізація безпеки.

3) А якщо контейнер працює як non‑root?

Краще, але недостатньо. Якщо non‑root користувач має доступ до сокета (через групову мапу або дозволи), ви все одно у зоні ризику.
Навіть якщо зараз доступу немає, ескалації всередині контейнера стають набагато ціннішими наявністю сокета.

4) Docker уже ізольований неймспейсами, хіба ні?

Контейнери ізольовані від хоста неймспейсами й cgroups. Демон Docker — не контейнер; це процес хоста з правами хоста.
Надаючи контейнеризованому коду доступ до демона, ви даєте йому адміністративний API вашого шару ізоляції.

5) Наш постачальник вимагає docker.sock для моніторингу. Яка практична альтернатива?

Запустіть збирач на рівні хоста (system service), щоб збирати метадані контейнерів і експортувати санітайзені метрики.
Або прийміть зменшений набір метаданих через cgroup/procfs. Якщо вендор наполягає на сокеті, розглядайте їх агента як привілейований компонент і ізолюйте його відповідно.

6) Чи безпечніше відкривати Docker по TCP з TLS, ніж монтувати сокет?

Може бути, якщо ви дійсно застосуєте автентифікацію клієнта (mutual TLS), обмежите мережний доступ і бажано накладете авторизаційну політику.
Але це простіше неправильно конфігурувати. Локальний сокет принаймні за замовчуванням не доступний з інтернету.

7) Чи вирішує rootless Docker проблему повністю?

Rootless зменшує аспект «root хоста», бо демон працює як непривілейований користувач. Але це не робить експозицію Docker API безпечною сама по собі.
Атакувальник все ще може контролювати збірки, тягнути образи, ексфільтрувати креденшали, доступні цьому користувачу, і порушувати роботу. Це пом’якшення, а не вільний квиток.

8) Який найчистіший підхід для CI‑збірок зараз?

Віддалений BuildKit‑збирач або daemonless‑білдер зазвичай найчистіший варіант: CI‑джоби надсилають збірки, отримують артефакти і ніколи не отримують контролю рантайму хоста.
Конкретний вибір залежить від потреб у кешуванні, фіч Dockerfile і управління секретами.

9) Як переконати зацікавлених сторін, яким важливі лише релізи?

Подайте це як питання безпеки продакшну: маунт сокета перетворює будь‑який RCE додатка на компрометацію хоста. Це змінює тяжкість інциденту, час відновлення
і відповідальність по відповідності вимогам. Запропонуйте план міграції з вимірними кроками (інвентаризація, заміна моніторингу, заміна CI‑збірок, впровадження політик).

10) Якщо ми приберемо сокет, як тоді робити cleanup і prune контейнерів?

Виконуйте очищення на хості через systemd‑таймери або нативні механізми оркестратора. Якщо потрібно виконувати це з контейнера — робіть це через відокремлений,
загартований агент хоста з документованим винятком і суворою мережею ізоляцією. Але надавайте перевагу host‑керованому очищенню.

Висновок: практичні наступні кроки

Docker‑сокет — це інтерфейс адміністратора. Ставтеся до нього відповідно. Якщо ваша позиція за замовчуванням — «монтувати його скрізь», ви створили шлях ескалації привілеїв у вашій платформі.
Виправлення — це не один прапорець; це рішення: відділити «робочі навантаження» від «контрольних площин».

Наступні кроки, які ви можете виконати цього тижня:

  1. Інвентаризуйте кожен маунт сокета і кожного користувача в групі docker.
  2. Спочатку приберіть маунти сокета з робочих навантажень додатків. Ніяких дебатів, ніяких винятків за замовчуванням.
  3. Переробіть CI‑збірки на віддалені збирачі або daemonless‑інструменти.
  4. Замість використання сокета моніторингом використовуйте збирачі на рівні хоста або сигнали тільки для читання.
  5. Впровадьте політики й рантайм‑аудит, щоб це не повернулося через шість місяців.

Продакшн‑системи ламаються не тому, що хтось не знав. Вони ламаються, коли ризикова зручність стає невидимою.
Зробіть її видимою. Потім видаліть.

← Попередня
Docker: Правила маршрутів Traefik, що мовчки не працюють — як правильно виправити мітки
Наступна →
Ubuntu 24.04: Swap на SSD — робіть це безпечно (і коли цього не слід) (випадок №50)

Залишити коментар