Короткий ответ
UNIX-сокет /var/run/docker.sock с группой docker даёт полный root на хосте: можно запустить --privileged и смонтировать /. Пользователь admin для SSH/sudo — не то же самое, что прикладной deploy или разработчик на прод-сервере. Уберите лишних из группы docker, запретите chmod 666 на сокет, закройте TCP 2375. Деплой — CI по SSH с узким sudo или rootless Docker / Podman в своём namespace. Хост host.example (10.0.20.10).
Симптомы и как отличить
groups deploy→docker;ls -l /var/run/docker.sock→srw-rw---- root dockerили хужеsrw-rw-rw-;ss -tlnp | grep 2375;- в wiki «добавь себя в docker чтобы не вводить пароль».
| Доступ | Эквивалент |
|---|---|
| группа docker | root |
| sudo docker ALL | root, хотя бы с паролем sudo |
| 2375 без TLS | root по сети |
| rootless sock в XDG | не хостовый root |
Отличие от широкого sudo: тот же итог, другой файл. sudoers. Публикация портов: лишние порты.
Возможные причины
- Постинструкция Docker Official:
usermod -aG docker $USERна проде. - Панель Portainer с сокетом и паролем admin/admin.
DOCKER_HOST=tcp://0.0.0.0:2375«на час».- Jenkins-агент под
jenkinsв группе docker. - snap docker vs docker.io — два сокета, забыли один.
- 666 «чтобы php запускал контейнеры».
Диагностика
getent group docker
awk -F: '$1=="docker" {print}' /etc/group
ls -l /var/run/docker.sock /run/docker.sock 2>/dev/null
ss -tlnp | grep -E '2375|2376'
systemctl status docker --no-pager | headКто уже пользуется:
ps -ef | grep -E 'docker|containerd' | grep -v grep
sudo grep -R docker /etc/sudoers /etc/sudoers.d/ 2>/dev/nullПроверка, что сокет = root (на изолированной копии, не как «эксплойт-инструкция»; здесь только факт API):
# от пользователя в группе docker
docker info >/dev/null && echo 'this user can control docker engine'Если docker info работает без sudo — считайте uid 0.
TCP:
curl -sS --max-time 2 http://127.0.0.1:2375/version || trueОтвет JSON с WAN — инцидент. Закрывать bind, не «оставить для IDE».
Решение
Сценарий A. Забрать группу у людей и приложений
sudo gpasswd -d deploy docker
sudo gpasswd -d www-data docker
getent group dockerОставьте минимум: учётки, которым политика явно даёт админ Docker (часто только admin и то лучше через sudo). Сессии group membership: пользователю перелогиниться.
Права сокета должны остаться 660 root:docker (пакет). Не:
# НЕ делать
# chmod 666 /var/run/docker.sockСценарий B. Админу нужен docker CLI
Узкий sudo вместо группы:
admin ALL=(root) NOPASSWD: /usr/bin/dockerВсё ещё root-эквивалент, но есть след sudo и нет постоянного gid. Ещё лучше: без NOPASSWD. Ещё лучше: админить Docker с jump-хоста, на app-сервере socket только root.
Сценарий C. Закрыть 2375
В /etc/docker/daemon.json не должно быть "hosts": ["tcp://0.0.0.0:2375"]. Override systemd docker.service с -H fd:// только unix. systemctl cat docker. После правки:
sudo systemctl daemon-reload
sudo systemctl restart docker
ss -tlnp | grep docker2376+TLS — отдельный проект с клиентскими сертификатами, не «почти 2375». Firewall не выключать.
Сценарий D. CI
Варианты без хостового сокета приложению: отдельный runner-хост; Kaniko/buildah в k8s; rootless Docker (dockerd-rootless) под пользователем CI, без группы docker хоста. Не монтировать хостовый sock в job-контейнер.
Секреты из docker inspect: секреты. Процесс демона root: ожидаемо для классического Docker; не плодите ещё User=root приложения на хосте: сервис.
Инвентаризация членов группы после чистки. getent group docker должен содержать только согласованные админские UID. Автоматизация: ansible assert docker_users == ['admin'] или пусто. Cloud-init groups: [docker] в user-data — уберите для прикладных user.
TCP API и systemd override: некоторые инструкции предлагают /etc/systemd/system/docker.service.d/override.conf с ExecStart= пустым и новым -H tcp://. Ищите такие drop-in:
systemctl cat docker.service
ls /etc/systemd/system/docker.service.d/Пустой ExecStart= обязателен при переопределении, иначе два -H. Цель — не держать tcp://0.0.0.0 без TLS.
Portainer, Yacht, Dockge с bind-монтом сокета: UI = docker root. Ограничьте listen 127.0.0.1 + SSH tunnel, SSO, не публикуйте :9000 в мир. Пароль по умолчанию смените в день установки.
Rootless: dockerd-rootless-setuptool.sh под пользователем CI, DOCKER_HOST=unix:///run/user/UID/docker.sock. Не добавляйте этого пользователя в группу docker хоста. Проверьте, что он не может ls /root.
Секреты: любой, кто говорит с сокетом, делает docker exec и читает env. После изъятия gid ротируйте секреты контейнеров, если gid был у посторонних.
AppArmor docker-default не отменяет gid. Не aa-complain docker-default чтобы «собрать образ».
Как проверить, что проблема устранена
getent group docker
sudo -u deploy docker info 2>&1 | head
ls -l /var/run/docker.sock
ss -tlnp | grep -E '2375|2376' || echo 'no docker tcp'deploy получает permission denied. Сокет не 666. TCP API нет. Контейнеры приложения живы. admin администрирует согласованным способом (sudo/консоль).
Если не помогло
- Сокет снова 666: unit или скрипт в cron. Найдите
chmod.*docker.sock. - snap docker:
/var/snap/docker/common/run/docker.sock. - Пользователь в
lxdтоже root-подобный — инвентаризация групп. - Portainer с сокетом: ограничьте UI CIDR, сильный пароль, всё равно = root.
- AppArmor docker-default не замена изъятию группы.
Профилактика
- Запрет
usermod -aG dockerна прод в регламенте. - Мониторинг
getent group dockerи listen 2375. - Compose без sock mount.
- Baseline.
FAQ
Почему официальный док предлагает группу docker?
Для рабочей станции разработчика. Прод-сервер — не рабочая станция.
rootless полностью безопасен?
Нет, но это не хостовый uid 0. Уменьшает удар. Нужны cgroup/uid map. Всё равно патчи и firewall.
Podman + sudo
Тот же класс задач. Сокет/socket activation смотрите отдельно, не копируйте docker.sock 666.
Нужен ли docker для одного nginx
Часто нет. Бинарник пакета + User=www-data проще для MAC и сокета.
Может ли AppArmor закрыть сокет?
Профиль может ограничить контейнер, не отменяет gid docker на хосте. Не aa-disable.