rss logo

Set NFS with Kerberos authentication and encryption

Network share icon with a kerberos ticket

I needed to create a network share on my network. Being in a GNU/Linux environement, my natural choice was NFS.

Unfortunately, by default, this protocol provide no security whatsoever. The only thing the protocol offers is per-ip filtering which remains rather weak, as there is no authentication and no encryption.

To overcome these two weaknesses, we have a choice between setting up a vpn or using the kerberos protocol. Having never worked on the latter, I chose kerberos.

Network Architecture

NFS and kerberos architecture with one server and two hosts.

Server

Install

Debian logo
  • Edit the /etc/network/interfaces and set the static IP address configuration:
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
  • Install the necessary packages:
root@debian:~# apt update && apt install nfs-kernel-server nfs-common krb5-user libpam-krb5 krb5-admin-server krb5-kdc

Note: you can answer STD.LOCAL and nfs.std.local to the questions asked. In fact, it doesn't matter as we'll define these parameters in the configuration files later.

  • Add a nfs group:
root@debian:~# groupadd nfs
  • Add user nobody to group nfs:
root@debian:~# usermod -a -G nfs nobody
  • Create the folder to be shared:
root@debian:~# mkdir /nfs
  • Define permissions:
root@debian:~# chmod 770 /nfs && chgrp nfs /nfs
  • Edit the /etc/hosts file and add these entries:
192.168.1.100 nfs.std.local nfs 192.168.1.10 arch.std.local arch 192.168.1.11 debian.std.local debian

NFS service configuration

  • Edit the /etc/exports file with the following options:
    • rw: read/write access
    • sync: server waits until data is on disk before responding
    • anongid: explicitly defines gid 1001 (nfs group created ealier). Default values for uid and gid are: 65534 which corresponds to user/group nobody.
    • root_squash: prevents remote root users from having root privileges on remote NFS-mounted volumes
    • no_subtree_check: This option disables subtree checking, which has few security implications, but may improve reliability in certain circumstances.
  • Note:
    • krb5: Use Kerberos for authentication only.
    • krb5i: Use Kerberos for authentication, and include a hash with each transaction to ensure integrity. Traffic can still be intercepted and examined, but changes to traffic will be apparent.
    • krb5p: Use Kerberos for authentication, integrity checking and encrypts all traffic between client and server. This is the most secure solution, but also the most CPU-intensive.
/nfs *(rw,sync,anongid=1001,root_squash,no_subtree_check,sec=krb5p)
  • Or even more secure with the addition of IP address filtering:
/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)
  • Edit /etc/default/nfs-kernel-server to enable the svcgssd daemon, which is required by kerberos:
NEED_SVCGSSD=yes
  • Restart and reload/export NFS share:
root@debian:~# systemctl restart nfs-kernel-server.service && exportfs -arv

Configure Kerberos

  • Edit /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
  • Create a new database and define the KDC database master key:
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
  • Edit the file /etc/krb5kdc/kadm5.acl:
#*/admin@STD.LOCAL * kdcadmin/admin@STD.LOCAL *
  • Start services:
root@debian:~# systemctl start krb5-kdc.service root@debian:~# systemctl start krb5-admin-server.service
  • Create a admin principal user for 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
  • Create an nfs user:
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
  • Create nfs service records for server and 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
  • Add nfs entries to /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.local: exit
  • Restart (important):
root@debian:~# reboot

Client (ArchLinux)

ArchLinux logo
  • Install packages:
[root@arch ~]# pacman -S nfs-utils
  • Enable the nfs service to start at boot time:
[root@arch ~]# systemctl enable nfs-client.target
  • Start nfs service:
[root@arch ~]# systemctl start nfs-client.target
  • Add these entries to /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
  • Edit the /etc/krb5.conf file, which is identical to the server one:
[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

Add nfs/arch.std.local entry to /etc/krb5.keytab

We can copy the /etc/krb5.keytab file from the server or generate it from client via Kerberos.

  • Add it from the client with 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
  • Restart host:

Note: I got the following error: mount.nfs: Operation not permitted until the machine was completly rebooted.

[root@arch ~]# reboot
  • Mount the nfs share on /mnt:
[root@arch ~]# mkdir /mnt/nfs [root@arch ~]# mount -t nfs -o sec=krb5p nfs.std.local:/nfs /mnt/nfs/

Note: You should now have access to the nfs share from the root user, but get the error: cannot access '/mnt/nfs/': Permission denied from a user account. You need a kerberos ticket for this to work.

  • Request a kerberos ticket:
[user@arch ~]$ kinit nfsuser@STD.LOCAL Password for nfsuser@STD.LOCAL: NFScher$$

You now have access to the nfs share with a simple user account.

  • Check the status of the kerberos ticket:
[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
  • To enable the user to mount /mnt/nfs once authenticated, add this line to /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
  • Install the following packages:
root@debian:~# apt update && apt install nfs-common krb5-user

Note: you can answer STD.LOCAL and nfs.std.local to the questions asked. In fact, it doesn't matter as we w'll define these parameters in the configuration files later.

  • Enable the nfs service to start at at boot time:
root@debian:~# systemctl enable nfs-client.target
  • Start the nfs service:
root@debian:~# systemctl start nfs-client.target
  • Add these entries to /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
  • Edit the /etc/krb5.conf file, which is identical to the server file:
[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

Add nfs/debian.std.local entry to /etc/krb5.keytab

We can copy the /etc/krb5.keytab file from the server or generate it from client via Kerberos.

  • Add from client with 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
  • Restart host:

Note: I got the following error: mount.nfs: an incorrect mount option was specified until the machine was completly rebooted.

[root@arch ~]# reboot
  • Mount the nfs share on /mnt:
[root@arch ~]# mkdir /mnt/nfs [root@arch ~]# mount -t nfs -o sec=krb5p nfs.std.local:/nfs /mnt/nfs/

Note: ou should now have access to the nfs share from the root user, but get the error: cannot open directory '/mnt/nfs/': Stale file handle from a user account. You need a Kerberos ticket for this to work.

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

We now have access to the nfs share from the user.

  • Check Kerberos ticket status:
[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
  • To enable the user to mount /mnt/nfs once authenticated, add this line to /etc/fstab:
nfs.std.local:/nfs /mnt/nfs nfs defaults,timeo=900,retrans=5,_netdev,sec=krb5p,user,noauto 0 0

Misc

Commands

  • Saving a specific key in a keytab:
root@debian:~# ktadd -k krb5.keytab nfs/archtoi.std.local
  • List of cached Kerberos tickets:
root@debian:~# klist
  • Destroy Kerberos tickets:
root@debian:~# kdestroy
  • Renew Kerberos ticket:
root@debian:~# kinit -R
  • List principals:
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
  • Remove principals:
root@debian:~# kadmin.local Authenticating as principal root/admin@STD.LOCAL with password. kadmin.local: delete_principal nfs/arch.std.local@STD.LOCA
  • List the entries in /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)
  • List entries in /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
  • Delete an entry from /etc/krb5.keytab:
root@debian:~# ktutil ktutil: delete_entry nfs/arch.std.local@STD.LOCAL

Firewall Server (Optional)

Here I'll show the nftables rules I use to protect the server.

Ports used

  • The ports used by the NFS and Kerberos services are:
Service TCP UDP
Kerberos 88, 749 88, 749
NFSv4 2049

Disable IPv6

  • Edit /etc/sysctl.conf and add:
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
  • Take changes into account:
root@debian:~# sysctl -p /etc/sysctl.conf

nftables

  • Enable the nftables service:
root@debian:~# systemctl enable nftables.service
  • Edit the file /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 } }
  • Restart the nftables service:
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