NET_ADMIN — это возможность Linux, позволяющая процессу выполнять сетевую администрацию, и в контейнере она делает именно это внутри того сетевого пространства имён, в котором работает процесс. Это не лицензия на открытие сокета и не замена устройству или маршруту. Интересный вопрос никогда не в том, выдана ли возможность, а в том, какое пространство имён получает изменение.

Что охватывает возможность

Внутри своего пространства имён процесс с NET_ADMIN может поднимать и опускать интерфейсы, назначать адреса, добавлять и удалять маршруты и писать правила межсетевого экрана, то есть группу операций, которую capabilities(7) собирает под этой возможностью. Он также может настраивать параметры уровня интерфейса, управляющие тем, как ядро ставит трафик в очередь и фильтрует его. VPN-клиенту обычно нужны несколько из них при запуске и больше ничего после. Поэтому большинство образов запрашивают более широкий доступ, чем используют.

Операции за пределами этого пространства имён не охватываются, а смежные привилегии отдельны. Чтение и запись необработанных пакетов относится к NET_RAW, а создание устройства tun требует и возможности, и доступа к узлу устройства. Процесс без устройства получает ошибку разрешения, которая выглядит как сетевая проблема, но ею не является.

Пространство имён определяет радиус поражения

Контейнер в собственной мостовой сети достигает своего loopback, своих интерфейсов и своей таблицы маршрутизации. Выдача там NET_ADMIN позволяет VPN-клиенту построить туннель, не затрагивая решения маршрутизации хоста, — именно это расположение предполагает большинство образов контейнеров.

Переключите этот контейнер на сеть хоста, и та же выдача теперь редактирует маршруты хоста и его межсетевой экран, потому что отдельного пространства имён больше не осталось. Клиент, который должен был управлять одним туннелем, может затем перенаправить трафик всей машины, а ошибка в его скрипте демонтажа оставляет на хосте правила, которых никто не писал. В самой возможности ничего не изменилось; изменился контекст. Поэтому один и тот же compose-файл ведёт себя по-разному на двух хостах.

Три предпосылки, сливающиеся в одну

Сбои запуска в контейнерах VPN обычно связаны с одной из трёх отдельных вещей: возможностью, туннельным устройством и поддержкой протокола ядром. Выдача всех трёх сразу работает, но и делает следующий сбой невозможным для отнесения. Минимальный фрагмент compose выдаёт возможность и явно передаёт узел устройства:

services:
  vpn:
    image: example/vpn-client:latest
    cap_add:
      - NET_ADMIN
    devices:
      - /dev/net/tun
    sysctls:
      - net.ipv4.conf.all.src_valid_mark=1

Читайте документацию самого образа о том, какие sysctl ему нужны, потому что sysctl в контейнере может требовать дополнительной привилегии или настройки на уровне хоста. Там, где образ поддерживает только режим пользовательского пространства, устройство вообще не нужно, и список возможностей соответственно сокращается.

Выдавайте узкую операцию, а не режим

Привилегированный режим передаёт каждую возможность и одновременно расширяет доступ к устройствам, поэтому скрывает, какой из трёх предпосылок не хватало. Он также делает режимы отказа контейнера неотличимыми от изменений хоста. Предпочитайте явный список, даже если его дольше писать.

Развёртывания без root и в пространствах имён пользователя меняют практический смысл возможности, поскольку процесс может держать её только внутри отображённого пространства имён. Копирование списка возможностей из примера с root в среду без root и ожидание той же широты — распространённый способ потерять полдня.

Секреты относятся к отдельному решению. Наличие NET_ADMIN ничего не говорит о том, какую идентичность партнёра представляет клиент, какие учётные данные загружает и как они меняются. Сетевая администрация не даёт доверия.

Проверьте изменение и запишите, зачем существует выдача

Прежде чем отлаживать туннель, убедитесь, что контейнер действительно получил. Проверка ниже сообщает добавленные возможности и режим сети, а затем показывает, как список интерфейсов выглядит изнутри:

docker inspect --format '{{.HostConfig.CapAdd}} {{.HostConfig.NetworkMode}} {{.HostConfig.Privileged}}' vpn
docker exec vpn sh -c 'grep CapEff /proc/self/status'
docker exec vpn ip -br addr
docker exec vpn ip route

Если возможность присутствует, а список интерфейсов по-прежнему пуст, клиент отказал ещё до того, как смог что-либо создать, и следующее место для просмотра — журналы контейнера. Если туннель есть, но трафик не идёт, на следующий вопрос отвечает таблица маршрутизации внутри контейнера.

Проверьте и путь демонтажа, потому что клиент, пишущий правила межсетевого экрана, может оставить их после себя. Остановите контейнер и ещё раз проверьте набор правил хоста, если используется сеть хоста. Выдачу, которая нужна только две секунды при запуске, не следует описывать в compose-файле как постоянное требование без примечания, объясняющего это.

Запишите причину рядом с каждой возможностью: какая операция в ней нуждалась и что ломается без неё. Это примечание переживает обновления образа, и именно оно отличает проверенный список привилегий от списка, выросшего копированием и вставкой.

Похожие статьи