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/debian.std.local with kvno 3, encryption type aes256-cts-hmac-sha1-96 added to keytab FILE:/etc/krb5.keytab.
Entry for principal nfs/debian.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