Шифрование NFS: RPC-with-TLS как альтернатива VPN

Однажды мы задались вопросом, можно ли защитить трафик NFS-протокола. Всем известные способы, такие как VPN-туннели и различные прокси, нас не интересовали. Оказалось, недавно был опубликован RFC 9289, в котором описывается RPC-with-TLS. И мы решили разобраться, что это за зверь.

В этой статье разберёмся, как поднять шифрование для NFS-трафика с помощью RPC-with-TLS, какие есть нюансы и ограничения. Посмотрим, как настроить tls и mtls, что делать с демоном tlshd и почему важно не ставить пробелы в конфиге. Заодно проверим, как всё это работает на практике и что будет, если что-то пойдёт не так. В общем, нырнём с головой в RPC-with-TLS и посмотрим, что из этого выйдет.

eaddaff3a0bb7ea0d6024395043cb7e2.png

Немного разъяснений

Для работы RPC-with-TLS в ядре Linux (далее просто ядро) необходимо включить две опции:

  • CONFIG_TLS, которая позволяет ядру работать с TLS, но для TLS-рукопожатия (далее handshake) требуется демон tlshd;

  • CONFIG_NET_HANDSHAKE, появившаяся в ядре 6.4, но бэкпортированная и включённая в последних rpm-подобных дистрибутивах, таких как AlmaLinux 9 и Rocky Linux 9.

Существуют две политики безопасности для RPC-with-TLS: tls и mtls. Если кратко, то при tls происходит аутентификация только NFS-сервера, а при mtls добавляется аутентификация клиента. Отсюда вытекают следующие требования: если используется tls, то на стороне клиента корневой сертификат (CA) серверного сертификата должен быть в списке доверенных, а если используется mtls, то ещё и на стороне сервера CA клиентского сертификата должен быть в доверенных.

В Common Name (CN) серверного сертификата должно быть имя сервера, которое указывает клиент в хосте монтирования команды mount.nfs. CN клиентского сертификата при использовании mtls для сервера не имеет значения.

Важно правильно настроить владельца и права на сертификаты и на ключи: владелец должен быть только root, права на сертификаты — 644, а на ключи — 600.

Handshake-демон tlshd

Для старта демона tlshd нужен конфигурационный файл, по умолчанию это /etc/tlshd.conf. В этом файле очень важно, чтобы в конце строк с файлами сертификатов и ключей не было пробелов (как по мне, так это прямо «детская болезнь»), иначе демон не будет работать. Например, в ходе тестирования оказалось, что параметр выглядел так: x509.certificate=/root/my_ca/ca1/certificates/worker-1.crt     , из-за этого handshake не происходил.

Что произойдёт на клиенте с доступом в NFS-папку, если после успешного монтирования tlshd упадёт или перезапустится на клиенте или сервере? Ничего не изменится, папка останется доступной на чтение и запись. При этом шифрование трафика сохранится, так как tlshd нужен только в момент монтирования.

Чисто теоретически никто не запрещает запускать на хосте несколько экземпляров этого демона с разными конфигами. При этом в момент монтирования NFS-папки нельзя указать, какой именно экземпляр tlshd использовать для handshake в каждом случае.

Был проведён эксперимент: на клиенте запустили три экземпляра tlshd с разными конфигами (один валидный и два — нет) и стали монтировать NFS-папку с сервера. Монтирование происходило хаотично — из 10 попыток могло сработать две, или одна, или четыре, или ни одной. При каждой попытке монтирования каждый tlshd-демон писал что-то в свой лог. Это говорит о том, что они все были задействованы сразу. Отсюда вытекает ограничение: при использовании mtls может использоваться только один клиентский сертификат. Это ограничение можно обойти, если каждый раз при монтировании запускать экземпляр tlshd с нужным конфигом (читай: с нужным клиентским сертификатом) и после удачного монтирования останавливать tlshd-демон, но это скорее похоже на костыль, чем на хорошее решение.

Тестирование

Мы создали тестовый стенд и провели ручное тестирование монтирования NFS-папок с различными политиками безопасности. В результате мы убедились в шифровании NFS-трафика и посмотрели на ошибки монтирования.

Описание тестового стенда

Три виртуальные машины с Ubuntu 24.04 LTS на борту: два NFS-сервера (server-1 и server-2) с разными CA и один NFS-клиент (worker-1). На каждом NFS-сервере было экспортировано три папки /mnt/nfs_none, /mnt/nfs_tls, /mnt/nfs_mtls с соответствующими политиками безопасности:

echo '/mnt/nfs_none  *(rw,sync,no_subtree_check)'              | sudo tee -a /etc/exports
echo '/mnt/nfs_tls   *(rw,sync,no_subtree_check,xprtsec=tls)'  | sudo tee -a /etc/exports
echo '/mnt/nfs_mtls  *(rw,sync,no_subtree_check,xprtsec=mtls)' | sudo tee -a /etc/exports

Тестирование проводилось для NFSv3, NFSv4.1, NFSv4.2 с самоподписанными сертификатами.

Зашифрованный NFS-трафик

Вот так проверялось шифрование на NFS-сервере: tshark -i eth0 -f 'tcp and port 2049'.

Момент монтирования папки:

    1 0.000000000    10.1.1.13 → 10.1.1.11    TCP 74 674 → 2049 [SYN] Seq=0 Win=64240 Len=0 MSS=1460 SACK_PERM TSval=398680558 TSecr=0 WS=128
    2 0.000028999    10.1.1.11 → 10.1.1.13    TCP 74 2049 → 674 [SYN, ACK] Seq=0 Ack=1 Win=65160 Len=0 MSS=1460 SACK_PERM TSval=2420952483 TSecr=398680558 WS=128
    3 0.000213423    10.1.1.13 → 10.1.1.11    TCP 66 674 → 2049 [ACK] Seq=1 Ack=1 Win=64256 Len=0 TSval=398680559 TSecr=2420952483
    4 0.000213494    10.1.1.13 → 10.1.1.11    NFS 110 V4 NULL Call        
    5 0.000248597    10.1.1.11 → 10.1.1.13    TCP 66 2049 → 674 [ACK] Seq=1 Ack=45 Win=65152 Len=0 TSval=2420952483 TSecr=398680559
    6 0.000331695    10.1.1.11 → 10.1.1.13    NFS 102 V4 NULL Reply (Call In 4)
    7 0.000461213    10.1.1.13 → 10.1.1.11    TCP 66 674 → 2049 [ACK] Seq=45 Ack=37 Win=64256 Len=0 TSval=398680559 TSecr=2420952483
    8 0.015183491    10.1.1.13 → 10.1.1.11    TLSv1 417 Client Hello (SNI=server-1)
    9 0.015853138    10.1.1.11 → 10.1.1.13    TLSv1.3 264 Server Hello, Change Cipher Spec
   10 0.016537570    10.1.1.13 → 10.1.1.11    TLSv1.3 72 Change Cipher Spec
   11 0.029186413    10.1.1.11 → 10.1.1.13    TLSv1.3 7306 Application Data
   12 0.029199606    10.1.1.11 → 10.1.1.13    TCP 7306 2049 → 674 [PSH, ACK] Seq=7475 Ack=402 Win=64896 Len=7240 TSval=2420952512 TSecr=398680575 [TCP segment of a reassembled PDU]
   13 0.029770565    10.1.1.13 → 10.1.1.11    TCP 66 674 → 2049 [ACK] Seq=402 Ack=7475 Win=78592 Len=0 TSval=398680588 TSecr=2420952512
   14 0.029770658    10.1.1.13 → 10.1.1.11    TCP 66 674 → 2049 [ACK] Seq=402 Ack=14715 Win=92160 Len=0 TSval=398680588 TSecr=2420952512
   15 0.029790811    10.1.1.11 → 10.1.1.13    TLSv1.3 2255 Application Data, Application Data, Application Data, Application Data
   16 0.029978228    10.1.1.13 → 10.1.1.11    TCP 66 674 → 2049 [ACK] Seq=402 Ack=16904 Win=96512 Len=0 TSval=398680588 TSecr=2420952512
   17 0.046021972    10.1.1.13 → 10.1.1.11    TLSv1.3 1923 Application Data, Application Data, Application Data
   18 0.046070545    10.1.1.11 → 10.1.1.13    TCP 66 2049 → 674 [ACK] Seq=16904 Ack=2259 Win=63104 Len=0 TSval=2420952529 TSecr=398680604
   19 0.049060919    10.1.1.13 → 10.1.1.11    TLSv1.3 132 Application Data
   20 0.049120160    10.1.1.11 → 10.1.1.13    TLSv1.3 116 Application Data
   21 0.049489753    10.1.1.13 → 10.1.1.11    TLSv1.3 348 Application Data                                                                                                               
   22 0.049569299    10.1.1.11 → 10.1.1.13    TLSv1.3 192 Application Data
...

Момент записи строки в файл, расположенный в NFS-папке:

   82 24.007203648    10.1.1.13 → 10.1.1.11    TLSv1.3 272 Application Data
   83 24.007358680    10.1.1.11 → 10.1.1.13    TLSv1.3 260 Application Data
   84 24.007529719    10.1.1.13 → 10.1.1.11    TCP 66 792 → 2049 [ACK] Seq=8269 Ack=23090 Win=94976 Len=0 TSval=398803883 TSecr=2421075807
   85 24.007641431    10.1.1.13 → 10.1.1.11    TLSv1.3 372 Application Data
   86 24.007724635    10.1.1.11 → 10.1.1.13    TLSv1.3 468 Application Data
   87 24.007999216    10.1.1.13 → 10.1.1.11    TLSv1.3 316 Application Data
   88 24.011385927    10.1.1.11 → 10.1.1.13    TLSv1.3 276 Application Data
   89 24.011745229    10.1.1.13 → 10.1.1.11    TLSv1.3 296 Application Data
   90 24.011833565    10.1.1.11 → 10.1.1.13    TLSv1.3 268 Application Data
   91 24.052731556    10.1.1.13 → 10.1.1.11    TCP 66 792 → 2049 [ACK] Seq=9055 Ack=23904 Win=94976 Len=0 TSval=398803929 TSecr=2421075812

Ошибки монтирования

Если при монтировании у клиента не будет CA серверного сертификата в доверенных, то ошибка будет такой:

# mount.nfs -o xprtsec=mtls,nfsvers=4.2 server-1:/mnt/nfs_mtls /mnt/nfs_mtls
mount.nfs: access denied by server while mounting server-1:/mnt/nfs_mtls

Интересная особенность

На NFS-сервере есть папка, которая экспортируется по mtls. Если клиент при монтировании укажет политику tls и версию NFS-протокола 3, то монтирование произойдёт успешно, но доступа в папку не будет.

Монтируем:

# mount.nfs -o xprtsec=tls,nfsvers=3 server-1:/mnt/nfs_mtls /mnt/nfs_mtls
#

Доступа нет:

# ls -l /mnt/nfs_mtls/
ls: cannot open directory '/mnt/nfs_mtls/': Permission denied

С NFS 4.1 и 4.2 такого поведения нет — папка просто не монтируется:

# for i in 4.1 4.2;do mount.nfs -o xprtsec=tls,nfsvers=$i server-1:/mnt/nfs_mtls /mnt/nfs_mtls ;done
mount.nfs: Operation not permitted for server-1:/mnt/nfs_mtls on /mnt/nfs_mtls
mount.nfs: Operation not permitted for server-1:/mnt/nfs_mtls on /mnt/nfs_mtls

Ограничение для одного NFS-сервера

Если на одном и том же клиенте монтировать несколько NFS-папок по tls или mtls (в примере ниже используется mtls) с одного и того же NFS-сервера, то всё монтируется без проблем:

# for i in 11 22 33;do mount.nfs -o nfsvers=4.2,xprtsec=mtls server-1:/mnt/nfs_mtls/$i /mnt/nfs_mtls/$i ;done
#

При этом handshake произойдёт только один раз — при монтировании первой папки. В логе tlshd это видно так:

tlshd[7776]: handshake with server-1 (10.1.1.11) was successful

После этого нельзя будет смонтировать с того же NFS-сервера другую папку по tls (или mtls, если первый раз монтирование было по tls).

# for i in 11 22 33;do mount.nfs -o nfsvers=4.2,xprtsec=tls server-1:/mnt/nfs_tls/$i /mnt/nfs_tls/$i ;done
mount.nfs: Operation not permitted for server-1:/mnt/nfs_tls/11 on /mnt/nfs_tls/11
mount.nfs: Operation not permitted for server-1:/mnt/nfs_tls/22 on /mnt/nfs_tls/22
mount.nfs: Operation not permitted for server-1:/mnt/nfs_tls/33 on /mnt/nfs_tls/33

Самое интересное, что при этом handshake пройдёт успешно:

tlshd[7807]: handshake with server-1 (10.1.1.11) was successful
tlshd[7815]: handshake with server-1 (10.1.1.11) was successful
tlshd[7820]: handshake with server-1 (10.1.1.11) was successful

А вот монтировать папки без RPC-with-TLS можно без проблем:

# for i in 11 22 33;do mount.nfs -o nfsvers=4.2 server-1:/mnt/nfs_none/$i /mnt/nfs_none/$i ;done
#

Но если изменить порядок монтирования — сначала без RPC-with-TLS, а потом с любыми политиками RPC-with-TLS, то монтирование с RPC-with-TLS не произойдёт:

# for i in 11 22 33;do mount.nfs -o nfsvers=4.2 server-1:/mnt/nfs_none/$i /mnt/nfs_none/$i ;done
# for i in 11 22 33;do mount.nfs -o nfsvers=4.2,xprtsec=tls server-1:/mnt/nfs_tls/$i /mnt/nfs_tls/$i ;done
mount.nfs: Operation not permitted for server-1:/mnt/nfs_tls/11 on /mnt/nfs_tls/11
mount.nfs: Operation not permitted for server-1:/mnt/nfs_tls/22 on /mnt/nfs_tls/22
mount.nfs: Operation not permitted for server-1:/mnt/nfs_tls/33 on /mnt/nfs_tls/33
# for i in 11 22 33;do mount.nfs -o nfsvers=4.2,xprtsec=mtls server-1:/mnt/nfs_mtls/$i /mnt/nfs_mtls/$i ;done
mount.nfs: Operation not permitted for server-1:/mnt/nfs_mtls/11 on /mnt/nfs_mtls/11
mount.nfs: Operation not permitted for server-1:/mnt/nfs_mtls/22 on /mnt/nfs_mtls/22
mount.nfs: Operation not permitted for server-1:/mnt/nfs_mtls/33 on /mnt/nfs_mtls/33

Отсюда получается ограничение: с одного и того же NFS-сервера нельзя одновременно монтировать папки, перемешивая политики безопасности tls, mtls и без шифрования.

Заключение

RPC-with-TLS — это интересный способ защиты трафика NFS, но со своими ограничениями. Например, нельзя одновременно монтировать папки с разными политиками безопасности (tls, mtls и без шифрования) с одного сервера. А ещё при использовании mtls можно задействовать только один клиентский сертификат. Тем не менее шифрование работает стабильно, и даже если демон tlshd упадёт после монтирования, доступ к папке и защита трафика сохранятся.

Единственная нативная альтернатива — это Kerberos в режиме krb5p, но у него есть свои минусы: сложная настройка аутентификации пользователей и сильное падение производительности.

Кстати, в Deckhouse Kubernetes Platform в модуле csi-nfs, начиная с версии v0.2.0, уже есть поддержка RPC-with-TLS.

P. S.

Читайте также в нашем блоге:

  • Извлекаем файлы из образа повреждённого диска: ddrescue, losetup и немного магии

  • Как Linux готовится ко сну

  • Как собрать Linux-контейнер с нуля и без Docker

Habrahabr.ru прочитано 4144 раза