rss logo

Sécuriser un serveur NFS avec l'authentification et le chiffrement Kerberos

Icône représentant un partage réseau NFS sécurisé par l'authentification Kerberos, comprenant un dossier, une connexion réseau et un ticket symbolique.

J'avais besoin de mettre en place un partage de fichiers sur mon réseau. Étant dans un environnement full GNU/Linux mon choix s'est naturelement porté sur NFS.

Malheuresement, par conception, ce protocole n'est que très peu sécurisé. Le seul élément de protection que l'on peut mettre en place est le filtrage par adresse ip ce qui peut être considéré comme très faible parce qu'il n'y a pas d'authentification et que les flux circulent en clair (pas de chiffrement).

Il existe deux possibilités pour combler ces lacunes : mettre en place un vpn ou utiliser kerberos. Comme je n'ai jamais travaillé dessus j'ai pensé que c'était l'occasion de m'y mettre. J'aurais mieux fait de me couper une cou… C'est très facile!

Architecture réseau

Diagramme illustrant l'architecture NFS et Kerberos avec un serveur central et deux clients (ArchLinux et Debian), montrant les adresses IP, les tickets Kerberos et la structure des répertoires partagés.

Server

Installation

Debian logo
  • Éditer /etc/network/interfaces pour se mettre en ip statique :
allow-hotplug ens192 iface ens192 inet static address 192.168.1.100 netmask 255.255.255.0 gateway 192.168.1.254 dns-nameservers 192.168.1.254
  • Installer les prérequis :
root@debian:~# apt update && apt install nfs-kernel-server nfs-common krb5-user libpam-krb5 krb5-admin-server krb5-kdc

Note : on pourra répondre STD.LOCAL et nfs.std.local aux questions posées. En fait ça n'a pas d'importance puisque les paramètres seront modifiés ultérieurement.

  • Ajouter le groupe nfs :
root@debian:~# groupadd nfs
  • Ajouter l'utilisateur nobody dans le groupe nfs :
root@debian:~# usermod -a -G nfs nobody
  • Créer le dossier qui sera partagé :
root@debian:~# mkdir /nfs
  • Définir les permissions :
root@debian:~# chmod 770 /nfs && chgrp nfs /nfs
  • Éditer le fichier /etc/hosts et ajouter les entrées pour nos trois machines (à noter que cela pourrait se faire via un serveur dns) :
192.168.1.100 nfs.std.local nfs 192.168.1.10 arch.std.local arch 192.168.1.11 debian.std.local debian

Configurer le service NFS

  • Éditer le fichier /etc/exports avec les options suivantes :
    • rw : accès en lecture/écriture
    • sync : respect du protocole NFS, le serveur attend que les données soient correctement écrites sur l'unité de stockage avant de répondre aux requêtes
    • anongid : par défaut exportfs attribue un uid et gid de 65534. Permet de forcer le gid à 1001 (et donc mapper avec le groupe nfs)
    • root_squash : empêche l'utilisateur root local d'avoir les droits root sur le partage distant
    • no_subtree_check : désactiver la vérification des sous-dossiers, paramètre par défaut et recommandé pour des raisons de fiabilité
  • Note :
    • krb5 Utiliser Kerberos pour l'authentification uniquement.
    • krb5i Utiliser Kerberos pour l'authentification et le contrôle d'intégrité. Le traffic n'est pas chiffré.
    • krb5p Utiliser Kerberos pour l'authentification, le contrôle d'intégrité et le chiffrement entre le client et le serveur. C'est l'option la plus sécurisé.
/nfs *(rw,sync,anongid=1001,root_squash,no_subtree_check,sec=krb5p)
  • Ou encore plus sécurisé en ajoutant le filtrage par ip :
/nfs 192.168.1.10(rw,sync,anongid=1001,root_squash,no_subtree_check,sec=krb5p) 192.168.1.11(rw,sync,anongid=1001,root_squash,no_subtree_check,sec=krb5p)
  • Éditer le fichier /etc/default/nfs-kernel-server pour activer le démon svcgssd qui est utilisé par Kerberos :
NEED_SVCGSSD=yes
  • Redémarrer le service NFS et recharger le partage :
root@debian:~# systemctl restart nfs-kernel-server.service && exportfs -arv

Configurer Kerberos

  • Éditer le fichier /etc/krb5.conf :
[libdefaults] default_realm = STD.LOCAL kdc_timesync = 1 ccache_type = 4 forwardable = true proxiable = true fcc-mit-ticketflags = true allow_weak_crypto = false [realms] STD.LOCAL = { kdc = nfs.std.local admin_server = nfs.std.local default_domain = std.local } [domain_realm] .std.local = STD.LOCAL std.local = STD.LOCAL [logging] kdc = SYSLOG:NOTICE admin_server = SYSLOG:NOTICE default = SYSLOG:NOTICE
  • Générer la base de données et définir le mot de passe administrateur kerberos :
root@debian:~# kdb5_util -r STD.LOCAL create -s You will be prompted for the database Master Password. It is important that you NOT FORGET this password. Enter KDC database master key: MasterOfTheDomain:p Re-enter KDC database master key to verify: MasterOfTheDomain:p
  • Éditer le fichier /etc/krb5kdc/kadm5.acl :
#*/admin@STD.LOCAL * -> moins restrictif kdcadmin/admin@STD.LOCAL *
  • Démarrer les services :
root@debian:~# systemctl start krb5-kdc.service root@debian:~# systemctl start krb5-admin-server.service
  • Créer un compte admin pour l'administration :
root@debian:~# kadmin.local Authenticating as principal root/admin@STD.LOCAL with password. kadmin.local: add_principal kdcadmin/admin@STD.LOCAL No policy specified for kdcadmin/admin@STD.LOCAL; defaulting to no policy Enter password for principal "kdcadmin/admin@STD.LOCAL":$superKDC$2000 Re-enter password for principal "kdcadmin/admin@STD.LOCAL":$superKDC$2000 Principal "kdcadmin/admin@STD.LOCAL" created. kadmin: exit
  • Créer un compte nfs pour l'accès au partage :
root@debian:~# kadmin.local Authenticating as principal root/admin@STD.LOCAL with password. kadmin.local: add_principal nfsuser@STD.LOCAL No policy specified for nfsuser@STD.LOCAL; defaulting to no policy Enter password for principal "nfsuser@STD.LOCAL":NFScher$$ Re-enter password for principal "nfsuser@STD.LOCAL":NFScher$$ Principal "nfsuser@STD.LOCAL" created. kadmin: exit
  • Créer des enregistrements pour le service nfs du serveur et des clients :
root@debian:~# kadmin.local kadmin.local: addprinc -randkey nfs/nfs.std.local@STD.LOCAL No policy specified for nfs/nfs.std.local@STD.LOCAL; defaulting to no policy Principal "nfs/nfs.std.local@STD.LOCAL" created. kadmin.local: addprinc -randkey nfs/arch.std.local@STD.LOCAL No policy specified for nfs/arch.std.local@STD.LOCAL; defaulting to no policy Principal "nfs/arch.std.local@STD.LOCAL" created. kadmin.local: addprinc -randkey nfs/debian.std.local@STD.LOCAL No policy specified for nfs/debian.std.local@STD.LOCAL; defaulting to no policy Principal "nfs/debian.std.local@STD.LOCAL" created. kadmin: exit
  • Ajouter les entrées dans le fichier /etc/krb5.keytab :
root@debian:~# kadmin.local kadmin.local: ktadd nfs/nfs.std.local kadmin.local: ktadd nfs/arch.std.local kadmin.local: ktadd nfs/debian.std.local kadmin: exit
  • Redémarrer (important) :
root@debian:~# reboot

Client (ArchLinux)

ArchLinux logo
  • Installer les paquets logiciels :
[root@arch ~]# pacman -S nfs-utils
  • Activer le service nfs pour qu'il se lance au démarrage :
[root@arch ~]# systemctl enable nfs-client.target
  • Démarrer le service nfs :
[root@arch ~]# systemctl start nfs-client.target
  • Ajouter ces entrées dans le fichier /etc/hosts :
192.168.1.100 nfs.std.local nfs 192.168.1.10 arch.std.local arch 192.168.1.11 debian.std.local debian
  • Éditer le fichier /etc/krb5.conf qui est identique à celui du serveur :
[libdefaults] default_realm = STD.LOCAL kdc_timesync = 1 ccache_type = 4 forwardable = true proxiable = true fcc-mit-ticketflags = true allow_weak_crypto = false [realms] STD.LOCAL = { kdc = nfs.std.local admin_server = nfs.std.local default_domain = std.local } [domain_realm] .std.local = STD.LOCAL std.local = STD.LOCAL

Ajouter l'entrée nfs/arch.std.local dans le /etc/krb5.keytab

Pour ce faire nous pouvons au choix copier le fichier /etc/krb5.keytab depuis le serveur ou utiliser Kerberos sur le client pour le faire.

  • Ajout depuis le client en utilisant Kerberos :
[root@arch ~]# kadmin -p kdcadmin/admin@STD.LOCAL Authenticating as principal kdcadmin/admin@STD.LOCAL with password. Password for kdcadmin/admin@STD.LOCAL: $superKDC$2000 kadmin: ktadd nfs/arch.std.local Entry for principal nfs/arch.std.local with kvno 3, encryption type aes256-cts-hmac-sha1-96 added to keytab FILE:/etc/krb5.keytab. Entry for principal nfs/arch.std.local with kvno 3, encryption type aes128-cts-hmac-sha1-96 added to keytab FILE:/etc/krb5.keytab. kadmin: exit
  • Redémarrer l'hôte :

Note : si l'on ne redémarre pas l'erreur mount.nfs: Operation not permitted apparaitra lors du montage.

[root@arch ~]# reboot
  • Monter le partage nfs sur /mnt :
[root@arch ~]# mkdir /mnt/nfs [root@arch ~]# mount -t nfs -o sec=krb5p nfs.std.local:/nfs /mnt/nfs/

Note : Ici le partage devrait être accessible depuis l'utilisateur root par contre le message impossible d'ouvrir le répertoire '/mnt/nfs/': Panne d'accès au fichier apparait depuis un simple utilisateur. Il nous faut ici un ticket kerberos.

  • Demander un ticket Kerberos :
[user@arch ~]$ kinit nfsuser@STD.LOCAL Password for nfsuser@STD.LOCAL: NFScher$$

Nous pouvons maintenant avoir accès au partage depuis notre utilisateur.

  • Vérifier l'état de notre ticket Kerberos :
[user@arch ~]$ klist Ticket cache: FILE:/tmp/krb5cc_1000 Default principal: nfsuser@STD.LOCAL Valid starting Expires Service principal 18/09/2022 20:14:38 19/09/2022 06:14:38 krbtgt/STD.LOCAL@STD.LOCAL renew until 19/09/2022 15:14:33
  • Pour permettre à l'utilisateur de monter /mnt/nfs une fois authentifié, ajoutez cette ligne à /etc/fstab:
nfs.std.local:/nfs /mnt/nfs nfs defaults,timeo=900,retrans=5,_netdev,sec=krb5p,user,noauto 0 0

Client (Debian)

logo Debian
  • Installer les paquets logiciels :
root@debian:~# apt update && apt install nfs-common krb5-user

Note : on pourra répondre STD.LOCAL et nfs.std.local aux questions posées. En fait ça n'a pas d'importance puisque les paramètres seront modifiés ultérieurement.

  • Activer le service nfs pour qu'il se lance au démarrage :
root@debian:~# systemctl enable nfs-client.target
  • Démarrer le service nfs :
root@debian:~# systemctl start nfs-client.target
  • Ajouter ces entrées dans le fichier /etc/hosts :
192.168.1.100 nfs.std.local nfs 192.168.1.10 arch.std.local arch 192.168.1.11 debian.std.local debian
  • Éditer le fichier /etc/krb5.conf qui est identique à celui du serveur :
[libdefaults] default_realm = STD.LOCAL kdc_timesync = 1 ccache_type = 4 forwardable = true proxiable = true fcc-mit-ticketflags = true allow_weak_crypto = false [realms] STD.LOCAL = { kdc = nfs.std.local admin_server = nfs.std.local default_domain = std.local } [domain_realm] .std.local = STD.LOCAL std.local = STD.LOCAL

Ajouter l'entrée nfs/debian.std.local dans le /etc/krb5.keytab

Pour ce faire nous pouvons au choix copier le fichier /etc/krb5.keytab depuis le serveur ou utiliser Kerberos sur le client pour le faire.

  • Ajout depuis le client en utilisant Kerberos :
root@debian:~# kadmin -p kdcadmin/admin@STD.LOCAL Authenticating as principal kdcadmin/admin@STD.LOCAL with password. Password for kdcadmin/admin@STD.LOCAL: $superKDC$2000 kadmin: ktadd nfs/debian.std.local Entry for principal nfs/arch.std.local with kvno 3, encryption type aes256-cts-hmac-sha1-96 added to keytab FILE:/etc/krb5.keytab. Entry for principal nfs/arch.std.local with kvno 3, encryption type aes128-cts-hmac-sha1-96 added to keytab FILE:/etc/krb5.keytab. kadmin: exit
  • Redémarrer l'hôte :

Note : si l'on ne redémarre pas l'erreur mount.nfs: Operation not permitted apparaitra lors du montage.

[root@arch ~]# reboot
  • Monter le partage nfs sur /mnt :
[root@arch ~]# mkdir /mnt/nfs [root@arch ~]# mount -t nfs -o sec=krb5p nfs.std.local:/nfs /mnt/nfs/

Note : Ici le partage devrait être accessible depuis l'utilisateur root par contre le message impossible d'ouvrir le répertoire '/mnt/nfs/': Panne d'accès au fichier apparait depuis un simple utilisateur. Il nous faut ici un ticket kerberos.

  • Demander un ticket Kerberos :
[user@arch ~]$ kinit nfsuser@STD.LOCAL Password for nfsuser@STD.LOCAL: NFScher$$

Nous pouvons maintenant avoir accès au partage depuis notre utilisateur.

  • Vérifier l'état de notre ticket Kerberos :
[user@arch ~]$ klist Ticket cache: FILE:/tmp/krb5cc_1000 Default principal: nfsuser@STD.LOCAL Valid starting Expires Service principal 09/21/2022 18:12:01 09/22/2022 04:12:01 krbtgt/STD.LOCAL@STD.LOCAL renew until 09/22/2022 18:11:39 09/21/2022 18:12:03 09/22/2022 04:12:01 nfs/nfs.std.local@STD.LOCAL renew until 09/22/2022 18:11:39
  • Pour permettre à l'utilisateur de monter /mnt/nfs une fois authentifié, ajoutez cette ligne à /etc/fstab:
nfs.std.local:/nfs /mnt/nfs nfs defaults,timeo=900,retrans=5,_netdev,sec=krb5p,user,noauto 0 0

Autre

Commandes

  • Sauver une clé spécifique dans un keytab :
root@debian:~# ktadd -k krb5.keytab nfs/archtoi.std.local
  • Lister les tickets kerberos présents en cache :
root@debian:~# klist
  • Supprimer des tickets kerberos :
root@debian:~# kdestroy
  • Renouveller un tickets kerberos :
root@debian:~# kinit -R
  • Lister les entrées kerberos :
root@debian:~# kadmin.local Authenticating as principal root/admin@STD.LOCAL with password. kadmin.local: listprincs K/M@STD.LOCAL kadmin/admin@STD.LOCAL kadmin/changepw@STD.LOCAL kadmin/nfskerb@STD.LOCAL kdcadmin/admin@STD.LOCAL kiprop/nfskerb@STD.LOCAL krbtgt/STD.LOCAL@STD.LOCAL nfs/arch.std.local@STD.LOCAL nfs/debian.std.local@STD.LOCAL nfs/nfs.std.local@STD.LOCAL nfsuser@STD.LOCAL
  • Supprimer un compte kerberos :
root@debian:~# kadmin.local Authenticating as principal root/admin@STD.LOCAL with password. kadmin.local: delete_principal nfs/arch.std.local@STD.LOCA
  • Lister les entrées du fichier /etc/krb5.keytab :
root@debian:~# klist -e -k /etc/krb5.keytab Keytab name: FILE:/etc/krb5.keytab KVNO Principal ---- -------------------------------------------------------------------------- 4 nfs/debian.std.local@STD.LOCAL (aes256-cts-hmac-sha1-96) 4 nfs/debian.std.local@STD.LOCAL (aes128-cts-hmac-sha1-96)
  • Lister les entrées du fichier /etc/krb5.keytab (alternative) :
root@debian:~# ktutil ktutil: rkt /etc/krb5.keytab ktutil: list slot KVNO Principal ---- ---- --------------------------------------------------------------------- 1 2 nfs/nfs.std.local@STD.LOCAL 2 2 nfs/nfs.std.local@STD.LOCAL 3 2 nfs/arch.std.local@STD.LOCAL 4 2 nfs/arch.std.local@STD.LOCAL 5 2 nfs/debian.std.local@STD.LOCAL 6 2 nfs/debian.std.local@STD.LOCAL
  • Supprimer une entrée du fichier /etc/krb5.keytab :
root@debian:~# ktutil ktutil: delete_entry nfs/arch.std.local@STD.LOCAL

Pare-feu Serveur (Optionnel)

Je vais afficher ici mes règles nftables que j'ai utilisé pour sécuriser mon serveur.

Ports utilisés

  • Les ports utilisés par les services NFS et Kerberos sont listés ci-dessous :
Service TCP UDP
Kerberos 88, 749 88, 749
NFSv4 2049

Désactiver IPv6

  • Éditer le fichier /etc/sysctl.conf et ajouter la ligne :
net.ipv6.conf.all.disable_ipv6=1 net.ipv6.conf.all.autoconf=0 net.ipv6.conf.default.disable_ipv6=1 net.ipv6.conf.default.autoconf=0
  • Prendre en compte la modification :
root@debian:~# sysctl -p /etc/sysctl.conf

nftables

  • Activer le service nft :
root@debian:~# systemctl enable nftables.service
  • Éditer le fichier /etc/nftables.conf :
#!/usr/sbin/nft -f flush ruleset # ----- IPv4 ----- table ip filter { chain INPUT { type filter hook input priority 0; policy drop; #by default, we drop traffic iif lo accept comment "Accept any localhost traffic" ct state invalid counter drop comment "Drop invalid connections" ct state { established, related } counter accept comment "Accept traffic originated from us" iif != lo ip daddr 127.0.0.1/8 counter drop comment "drop connections to loopback not coming from loopback" tcp flags & (fin | syn | rst | psh | ack | urg) > urg counter drop comment "Drop TCP Null" tcp flags & (fin | syn | rst | psh | ack | urg) < fin counter drop comment "Drop TCP XMS" ip protocol icmp icmp type { destination-unreachable, router-solicitation, router-advertisement, time-exceeded, parameter-problem, echo-request } accept comment "Accept ICMP" iif ens192 ip saddr { 192.168.1.10, 192.168.1.11 } tcp dport { ssh, 88, 749, 2049 } counter accept comment "Accept ssh, kerberos tcp and nfsv4" iif ens192 ip saddr { 192.168.1.10, 192.168.1.11 } udp dport { 88, 749 } counter accept comment "Accept kerberos udp" iif ens192 tcp sport { http, https, 53, ntp } counter accept comment "Accept http, https, dns and ntp TCP" iif ens192 udp sport { 53, ntp } counter accept comment "Accept dns and ntp UDP" log counter drop #log (/var/log/kern.log), count and drop any other rules } chain FORWARD { type filter hook forward priority 0; policy drop; counter comment "count dropped packets" } chain OUTPUT { type filter hook output priority 0; policy drop; oif ens192 ip daddr { 192.168.1.10, 192.168.1.11 } tcp sport { ssh, 88, 749, 2049 } counter accept comment "Accept ssh, kerberos tcp and nfsv4" oif ens192 ip daddr { 192.168.1.10, 192.168.1.11 } udp sport { 88, 749 } counter accept comment "Accept kerberos udp" tcp dport { http, https, 53, ntp } counter accept comment "Accept http, https, dns and ntp TCP" udp dport { 53, ntp } counter accept comment "Accept dns and ntp UDP" ip protocol icmp icmp type { destination-unreachable, router-solicitation, router-advertisement, time-exceeded, parameter-problem, echo-request, echo-reply } accept comment "Accept ICMP" log counter drop #log (/var/log/kern.log), count and drop any other rules } }
  • Relancer le service nftables :
root@debian:~# systemctl restart nftables.service

References

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

Contact :

contact mail address