Настройка NFS сервера в привилегированном LXC, подключение сетевой директории и монтирование в unprivileged LXC

Комьюнити, привет.
В продолжение темы по организации сетевого доступа решил поэкспериментировать с протоколом NFS, актуальность которого сложно переоценить в контексте linux окружения selfhosted сервисов на наших хомлабах.

Идея была в том, чтобы для уже существующих сетевых директорий на базе протокола samba настроить параллельный доступ через NFS протокол, тем самым получить преимущества NFS для linux окружения и удобство доступа для остальных ОС.

:clipboard: Краткое содержание:

  1. Настройка директории в Proxmox и монтирование в LXC с NFS-сервером
  2. Настройка NFS-сервера в привилегированном LXC
  3. Настройка подключения сетевой директории к клиентской машине на базе Proxmox c возможностью монтирования в unprivileged LXC контейнер

:hammer_and_wrench: Версии ПО:
PVE 9.0.10
nfs-kernel-server 1:2.8.3-1
nfs-common 1:2.8.3-1

Общая схема того, что будем реализовывать

:fire: Требования к рабочей среде

  1. Установка NFS-server возможна только в хостовую ОС гипервизора или в привилегированный LXC
  2. При установке в привилегированный LXC необходимо внести изменения в конфигурационный файл LXC с хоста PVE /etc/pve/lxc/ID_LXC.conf (ID_LXC - id вашего LXC)
    2.1 Открыть в редакторе:
    nano /etc/pve/lxc/ID_LXC.conf
    2.2 Добавить строку:
    lxc.apparmor.profile: unconfined
    где
    unconfined - отключение системы контроля доступа AppArmor, контейнер получает почти полный доступ к хосту.
    2.3 Разрешить запуск LXC внутри контейнера.
    features: nesting=1
    где
    nesting - обеспечивает возможность создавать пространства имён для обеспечения запуска терминала, без этого терминал работать не будет.

:gear: Настройка директории в Proxmox и монтирование в LXC с NFS-сервером
Исходим из того, что у вас уже создан привилегированный LXC контейнер, подключен диск к гипервизору PVE и создан storage. В моём случае это жёсткий HDD_USB_1Tb смонтированный в качестве Directory, который я полностью отдал под нужды сетевого хранилища (NAS).

  1. Создать директорию
    mkdir -p /mnt/HDD_USB_1Tb/share
  2. Изменить владельца дирекории
    chown 101000:101000 /mnt/HDD_USB_1Tb/share
    где
    101000:101000 - uid и gid соответствующий будущему пользователю nfs 1000:1000 в LXC (uid_proxmox=uid_lxc+100000, gid_proxmox=gid_lxc+100000)
  3. Изменить права дирекории
    chmod 2775 /mnt/HDD_USB_1Tb/share
    где
    2 - это setgid, все новые файлы и подкаталоги, созданные внутри такого каталога, наследуют группу родительского каталога (для порядка и предупреждения возможных проблем)
    775 - полный доступ для владельца директори (первый бит 7) и группы (второй бит 7), а для всех остальных пользователей только чтение (последний бит 5).
  4. Проверить
    ls -lh
    drwxrwsr-x 8 101000 101000 4.0K Nov 4 21:39 share
  5. Смонтировать директорию в LXC с будущим NFS-сервером (выполнить в Proxmox)
    pct set ID_LXC -mp0 /mnt/HDD_USB_1Tb/share,mp=/mnt/share
    где:
    ID_LXC - ID вашего LXC с NFS-сервером
    -mp0 - точка монтирования (если к LXC уже примонтированы другие директории, то указывается любой свободный номер точки монтирования -mp1, -mp2, …)
    При монтировании получите предупреждение:
    explicitly configured lxc.apparmor.profile overrides the following settings: features:nesting
    Его можно игнорировать (профиль apparmor игнорирует опцию nesting, которая установлена для работоспособности консоли в привилегированном LXC).

:gear: Настройка NFS-сервера в привилегированном LXC
:clipboard:Справка
Если требуется контроль доступа к сетевой директории посредством владельца и группы, то на NFS-сервере должны быть созданы 1 к 1 (uid, main gid, secondary gid) все пользователи, которые будут подключаться со стороны клиентов. Таким образом NFS-сервер будет сверять подключившегося клиента со списком известных ему пользователей и предоставлять соответствующий доступ. В противном случае NFS-сервер отнесёт любого клиента в категорию other, даже если у него будет членство в группе ( :warning:в случае, если группа добавлена, как вторичная группа).
Пример:
Разрешения сетевой NFS-дирекории:
drwxrwsr-x 8 101000 101000 4.0K Nov 4 21:39 share

  1. Неизвестный NFS-серверу клиент получит разрешения на чтение и запись, соответствующие группе 101000(owner):
    uid=3000(test) gid=101000(owner) groups=101000(owner),3000(test)
    :small_orange_diamond:Эксперименты показали, что сервер может сопоставить основную (main gid) группу неизвестного ему пользователя с группой в директории и в случае совпадения будут применены соответствующие разрешения.
  2. Неизвестный NFS-серверу клиент будет отнесён в other и получит разрешение только на чтение, т.к. на NFS-сервере не создан точно такой же пользователь с идентичным набором uid и gid:
    uid=3000(test) gid=3000(test) groups=3000(test),101000(owner)
    :small_orange_diamond:Эксперименты показали, что сервер не может сопоставить вторичные группы для несуществующего на сервере пользователя.
    Для наглядности визуализировал, как это работает:

Настройка

  1. Установить NFS-сервер в LXC:
    apt install nfs-kernel-server -y
  2. Проверить статус службы nfs-server:
    systemctl status nfs-server
    :small_orange_diamond:Если возникает ошибка nfsdctl: lockd configuration failure, то это указывает на то, что не запущен вспомогательный демон блокировок (rpc.statd или nfs-lockd), который используется для NFSv3-блокировок.
    Для NFSv4 он не критичен, можно проигнорировать, но лучше включить сервис для совместимости:
    systemctl enable --now nfs-blkmap.service rpc-statd.service
  3. Настроить конфигурацию NFS-сервера /etc/exports
    3.1 Открыть в редакторе
    nano /etc/exports
    3.1 добавить строку
/mnt/share 192.168.1.0/24(rw,sync,no_subtree_check,root_squash,fsid=1)

где
192.168.1.0/24 - сетевой доступ для всей подсети (если ваши сетевые настройки отличаются, то укажите актуальную для вас подсеть), при необходимости можно ограничить и указать IP конкретного клиента (доступ с других адресов будет запрещён)
rw - разрешение на запись и чтение (при наличии соответствующих разрешений у сетевой директории и прав у пользователя)
sync - сервер записывает изменения на диск немедленно (без кэша). Безопаснее, но чуть медленнее.
no_subtree_check - отключает проверку “подкаталогов”. Ускоряет работу и снижает ошибки при доступе к поддиректориям.
root_squash - преобразует root-клиента в nobody UID (65534). Защита от суперпользователя.
fsid=1 - File System ID, уникальный ID для каждого экспортирумой дирекории (обязательно при NFSv4 без общего корня). Для каждого нового экспорта fsid должен быть уникальным (fsid=2, fsid=n..).
3.2 Вариант настройки с общим доступом для всех пользователей
/mnt/share

192.168.1.0/24(rw,sync,no_subtree_check,root_squash,all_squash,anonuid=101000,anongid=101000,fsid=2)

где
all_squash — все пользователи на клиентах маппятся в одного анонимного пользователя
anonuid=101000, anongid=101000 — UID и GID, от имени которых будут работать все пользователи (:fire: критично наличие на NFS-сервере пользователя с uid 101000 и основной группой gid 101000, которые должны совпадать с uid и gid директории 2775 101000:101000)
4. Применить настройки
exportfs -ra
5. Перезагрузить службу
systemctl restart nfs-server
6. Проверить текущий список экспортируемых каталогов
exportfs -v

:gear: Настройка подключения сетевой директории к клиентской машине на базе Proxmox c возможностью монтирования в unprivileged LXC контейнер

:clipboard:Справка:

  1. В качестве клиента будет выступать другой сервер Proxmox
  2. Конечный клиент - непривилегированный LXC

:green_circle: Настройка в Proxmox

  1. Создать директорию
    mkdir -p /mnt/share
  2. Установить NFS-клиент:
    apt install nfs-common -y
  3. Проверить наличе NFS-клиента
    dpkg -l | grep nfs-common
ii  nfs-common                   1:2.8.3-1                            amd64        NFS support files common to client and server
  1. Смонтировать сетевую NFS-директорию через /etc/fstab
    4.1 Открыть в редакторе
    nano /etc/fstab
    4.2 Добавить строку
IP_NFS_SERVER:/mnt/share  /mnt/share  nfs4  rw,vers=4.2,hard,timeo=600,retrans=2,rsize=1048576,wsize=1048576,_netdev,x-systemd.automount  0  0

где
IP_NFS_SERVER - IP-адрес NFS-сервера
rw - разрешение на запись и чтение (при наличи прав соответствующих прав у пользователя)
vers=4.2 - используется NFS версии 4.2
hard - при потере соединения клиент ждёт восстановления сервера, не выдаёт ошибку (безопасно для данных, но процессы зависнут - не запустится lxc или повиснет терминал при попытке доступа к директории. При необходимости может быть заменено на soft, но есть риск потери данных, если в момент записи данных nfs-директория станет недоступной)
timeo=600 - (600×0.1=60 сек) время ожидания перед повтором запроса (чтение/запись)
retrans=2 - gосле 2 неудачных попыток выдаёт сообщение об ошибке, но при hard не размыкает монтирование
rsize=1048576 - размер блока чтения 1 МБ, увеличивает пропускную способность сети при чтении (оптимально для современных клиентов и сетей ≥ 1 Gbps)
wsize=1048576 - размер блока записи 1 МБ, аналогично для записи
_netdev - Systemd подождёт поднятия сети перед монтированием
x-systemd.automount - точка монтируется по первому обращению, не блокируя загрузку системы
0 0 - не проверять fsck, не включать в dump
6. Смонтировать
mount -a
7. Смонтировать директорию в непривилегированный LXC
pct set ID_LXC -mp0 /mnt/share,mp=/mnt/share
где
ID_LXC - ID вашего непривилегированного LXC контейнера

:green_circle: Настройка непривилегированного LXC

  1. Создать пользовтеля с одноимённой основной группой
    useradd -M -u 1000 nfs
    где
    -M - не создавать домашний каталог

$ whoami
nfs
$ id nfs
uid=1000(nfs) gid=1000(nfs) groups=1000(nfs)
  1. Проверить возможность записи в директорию /mnt/share
    4.1 Заменить текущего root-пользователя в LXC на созданного nfs
    su - nfs
    4.2 Создать тестовый файл и директорию в директории share
    mkdir -p /mnt/share/nfs_test
    touch /mnt/share/nfs_test.txt
    4.3 Проверить
    ls -n /mnt/share
total 4
drwxr-sr-x 2 1000 1000 4096 Nov  6 23:48 nfs_test
-rw-r--r-- 1 1000 1000    0 Nov  6 23:48 nfs_test.txt

:small_orange_diamond:Пользователь nfs имеет uid и gid равные аналогичным uid владельца и gid группы, соответственно имеет полные права (2775)

Для эксперимента попробуем создать файл из под пользователя root в LXC

root@nfs-test:~# touch /mnt/share/nfs_test.txt
touch: cannot touch '/mnt/share/nfs_test.txt': Permission denied

Пробуем прочитать директорию из под root-пользователя

root@nfs-test:/mnt# ls -n /mnt/share/
total 4
drwxr-sr-x 2 1000 1000 4096 Nov  6 23:48 nfs_test
-rw-r--r-- 1 1000 1000    0 Nov  6 23:48 nfs_test.txt

:small_orange_diamond:Как и ожидалось root пользователь может только читать, т.к. не является владельцем директории и не состоит в группе, т.е. является пользователем other и имеет права только на чтение (2775)

Несколько уточнений

  1. В случае внесения изменений в конфигурацию /etc/exports на NFS-сервере на клиентских машинах может возникнуть ошибка вида:
    -bash: cd: share: Stale file handle
    Для устранения необходимо перемонтировать директорию:
    1.1 Отмонтировать
    umount share
    1.2 Смонтировать заново (монтирование через fstab)
    mount -a
  2. В выводе команды ls -n /mnt/share/ созданная директория nfs_test имеет разрешения 755, а файл nfs_test.txt 644. Эти разрешения задаются в операционной системе клиента через umask (маска прав по умолчанию). При необходимости можно скорректировать.
    Детальная информация под спойлером
Спойлер

:green_circle: Для всех пользователей

  1. Создать отдельный файл в /etc/profile.d/
    nano /etc/profile.d/umask.sh
  2. Вставить содержимое
# /etc/profile.d/umask.sh
# Устанавливает umask для всех интерактивных пользователей
if [ "$(id -u)" -eq 0 ]; then
    umask 022   # root: файлы 644, каталоги 755
else
    umask 002   # пользователи: файлы 664, каталоги 775
fi
  1. Установить строиге права на файл
    chmod 644 /etc/profile.d/umask.sh
  2. Перезагрузить систему
    reboot

:small_orange_diamond: Этот скрипт автоматически подхватывается в конце /etc/profile (запускает все .sh из /etc/profile.d)

:green_circle: Для текущего пользователя

  1. Раскоментировать строку в файле nano ~/.bashrc
    umask 022
  2. Скорретировать на нужное значение, например
    umask 002
  3. Перезагрузить систему
    reboot

:small_orange_diamond: При создании файла 666 вычитается umask: 666-002=664
При создании директории 777 вычитается umask: 777-002=775

Таким образом получаем сетевой доступ к директории через протокол nfs, которая ранее была расшарена по протоколу samba.

Для наглядности смонтировал nfs-директорию share в LXC контейнер с File Browser, где эта же директория (share_write) в рамках предыдущей статьи была смонитрована по протоколу samba.




:white_check_mark: Общие рекомендации

  • Для обычной файловой шары с доступом для всех пользователей сети исользуйте настройки NFS-сервера all_squash,anonuid,anongid (см. п.3.2 Настройка NFS-сервера в привилегированном LXC). Это поможет избежать проблем с разными uid и gid на клиентских машинах (дублирование, разный набор вторичных групп и т.д.)
  • Для дирекорий предназначенных только для чтения явно указывайте это в разрешениях на директорию и используйте параметр ro (вместо rw) в настройках экспорта /etc/exports на NFS-сервере
  • Для директорий с которыми будет работать конкретный сервис из под фиксированного пользователя (uid) и группы (gid) необходимо создать аналогичного пользователя на NFS-сервере, создать директорию с такими же uid и gid, а так же в экспорте /etc/exports на NFS-сервере прописать IP адрес этого сервиса. Таким образом только этот сервис сможет читать и писать в данную директорию.
  • Каждому новому экспорту присваивайте свой уникальный fsid через /etc/exports.
  • Разрешения на директории, списки uid и gid настраивайте под ваши нужды, в данной статье приведён всего лишь пример одной из рабочих схем.
2 лайка

Вот тут как раз таки не ожидалось т.к. root имеет все права и ближе к владельцу, нежели к other

Тут мы столкнулись с маппингом uid:gid в непривилигированных lxc контейнерах. Фактический ID пользователя будет не 0, а 100000, ну и id пользователя nfs будет не 1000, а 1001000 (со сдвигом могу напутать)

Спасибо за уточнение.
Дополнительно, если бы запрос от root шёл из привилегированного lxc (idmaping 1 к 1) , то в таком случае отрабатывала бы настройка сервера:
root_squash - преобразует root-клиента в nobody UID (65534). Защита от суперпользователя.