LXD предоставляет контейнеру символьное устройство хоста, добавляя узел устройства, а не обучая гостя создавать его. Если в контейнере нет /dev/net/tun, устройство может быть на хосте, а гость всё равно не сможет его открыть. Запись unix-char — это половина этого пути на стороне менеджера, и она намеренно уже, чем привилегированный контейнер. Поэтому узел хоста и узел контейнера заслуживают отдельных проверок.

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

Что запись unix-char действительно меняет

Устройство unix-char сообщает LXD, что нужно предоставить одно символьное устройство хоста по выбранному пути внутри контейнера. У записи есть три важные части: метка устройства, которую использует менеджер, путь source на хосте и path, который видит гость. Только последние две описывают само устройство. Опечатка в любой из них создаёт узел в месте, которое клиент никогда не открывает.

На хосте устройство обычно /dev/net/tun. Внутри контейнера можно сохранить тот же путь или выбрать другой, но путь должен совпадать с тем, что ожидает клиент VPN. Сохранение /dev/net/tun избавляет от второй проблемы конфигурации, потому что большинство клиентов открывают этот путь не спрашивая.

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

Объявляйте устройство на экземпляре, а не внутри гостя

Добавьте запись из контекста администрирования LXD, назвав экземпляр и метку, которую вы узнаете позже. Для контейнера с именем vpnbox команда такая:

lxc config device add vpnbox tun unix-char source=/dev/net/tun path=/dev/net/tun
lxc config device show vpnbox

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

Редактирование файлов внутри контейнера — неверный уровень. mknod в стартовом сценарии может выглядеть работающим на привилегированном контейнере, а затем исчезнуть при пересоздании или не сработать на менее привилегированном. Набором устройств экземпляра владеет менеджер; пусть он владеет и узлом.

Согласуйте владельца и режим с процессом гостя

Запись unix-char может задать uid, gid и mode для узла внутри контейнера. Используйте их, чтобы соответствовать учётной записи, запускающей клиент VPN, вместо того чтобы открывать устройство всем пользователям или переводить контейнер в привилегированный режим.

Видимость — не то же самое, что разрешение. Узел, принадлежащий root с режимом 600, виден службе, работающей как непривилегированный пользователь, и всё равно не может быть ею открыт. Сначала проверьте учётную запись гостя, затем назначьте владельцем эту запись и держите режим не шире, чем нужно клиенту.

Процессу также нужна способность настраивать туннельный интерфейс, обычно CAP_NET_ADMIN в сетевом пространстве имён контейнера. Передача устройства не даёт этой способности, а предоставление способности не даёт устройства; это независимые требования.

Знайте, чего запись не даёт

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

systemd может добавить ещё одно ограничение. Модуль с DeviceAllow или ужесточённой политикой устройств может запретить доступ к узлу, который предоставил менеджер контейнеров. Проверьте политику устройств модуля, когда узел существует, но служба сообщает об ошибке разрешения, и измените эту политику, а не расширяйте контейнер.

Горячее подключение необязательно и явно. Записи с required=false нужен путь источника, и она может появляться или исчезать при изменении устройства хоста. Такой жизненный цикл подходит для оборудования, которое приходит и уходит; для TUN-устройства, нужного VPN при загрузке, необязательная запись может оставить службу запускающейся раньше, чем появится её предусловие.

Проверяйте видимость, открытие и интерфейс именно в этом порядке

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

lxc exec vpnbox -- ls -l /dev/net/tun
lxc exec vpnbox -- sh -c 'test -c /dev/net/tun && echo char-device-ok'

Затем подтвердите, что учётная запись службы может его открыть. Короткая проверка от этой записи отделяет проблему владения от проблемы службы. Наконец, запустите клиент и подтвердите, что появился туннельный интерфейс; только этот третий шаг доказывает весь путь, потому что первые два могут пройти, пока профиль или способность клиента ещё неверны.

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

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