Slackware 14.2 NFS+Kerberos

04 February 2017
This article details the setting up of NFSv4 with Kerberos authentication on Slackware 14.2 systems. It was something of an ordeal, but I went through with it because there are basically no Slackware-specific NFS guides out there, and the non-Slackware NFS/Kerberos guides out there were all incomplete in one way or another. Slackware software packages don't deviate much from tarball installations, so this guide should be very generic. And hopefully correct.

My original aim was to make a serious effort at setting up NFS in a way that required passwords in order to access NFS exports, as I am considering whether to re-commission my file-server, and this time round Windows access would not a requirement. I soon concluded that the overheads of NFS+Kerberos were unjustifiably high for my likely requirements, but decided to press ahead with working stuff out. Even in a Linux-only environment Samba is so much easier to setup, and for my typical usage profile NFS has minimal performance advantage.

I did have more than a casual look at Heimdal, but in the end went for the reference MIT implementation of Kerberos. My earlier impression was that Heimdal was a better/cleaner implementation, but more of the documentation material I came across was aimed at the MIT Kerberos.

Baseline setup

In presenting this guide I will assume three VirtuallBox VMs connected together using a host-only network, but in reality I would expect these roles to be combined into a fewer number of physical machines. Naturally all these machines are Slackware 14.2 installations, although not all of them are full installs. The address allocations will be as follows:
krb.vm (192.168.56.21)
The Kerberos server.
nfs.vm (192.168.56.22)
The NFS server.
cli.vm (192.168.56.23)
The client machine.
In practice the NFS and Kerberos servers will probably be the same machine. Even with reverse-lookups supposedly disabled, Kerberos seems unable to operate purely with IP addresses. The workaround is either having all the machines listed in the /etc/hosts files on each machine, or installing a DNS proxy such as dnrd.

Basic NFS

This is a condensing down of the Slackware quick'n'dirty NFS guide but I include it because I want this guide to be self-contained. If getting traditional NFS working is a problem, getting NFS+Kerberos working will be well-off the agenda. This basic NFS setup will also be used a reference further on.

Server

Enable NFS and create some directories to mount:

chmod +x /etc/rc.d/rc.nfsd chmod +x /etc/rc.d/rc.rpc /etc/rc.d/rc.nfsd start /etc/rc.d/rc.rpc start mkdir /mnt/net1 mkdir /mnt/net2 chmod o+w /mnt/net1 chmod o+w /mnt/net2

Declare the export options:

/etc/exports
/mnt/net1 192.168.56.0/24(rw,root_squash,no_subtree_check) /mnt/net2 192.168.56.0/24(rw,root_squash,no_subtree_check)
Export the filesystems and see if export is successful:

# exportfs -arv exporting 192.168.56.0/24:/mnt/net2 exporting 192.168.56.0/24:/mnt/net1 # showmount -e localhost Export list for localhost: /mnt/net2 192.168.56.0/24 /mnt/net1 192.168.56.0/24

Client

Start the RPC daemon (the NFSd daemon is not needed by NFS clients):

# chmod +x /etc/rc.d/rc.rpc # /etc/rc.d/rc.rpc start Starting RPC portmapper: /sbin/rpcbind -l -w Starting RPC NSM (Network Status Monitor): /sbin/rpc.statd

Check the mounts are visible and working:

# showmount -e nfs.vm Export list for nfs.vm: /mnt/net2 192.168.56.0/24 /mnt/net1 192.168.56.0/24 # mount -t nfs -o vers=4 nfs.vm:/mnt/net1 /mnt/tmp/ $ echo test > /mnt/tmp/test.txt $ ls -l /mnt/tmp/ total 4 -rw-r--r-- 1 remy users 5 Jan 29 16:45 test.txt

Kerberos terminology

In Kerberos, a principle is the basic unit of identity, and it consists of 3 parts: The primary, the instance, and the realm. The realm is the authentication domain, whereas the other two are components of an identity within this domain, and how these components relate to each other varies. All three parts are assembled into a global identity as follows:

${primary}/${instance}@${REALM}

If the instance is absent, the slash after the primary is omitted. For users the primary is the username and the instance is usually not defined (it denotes a role if it is). For other cases such as hosts or services, the instance denotes the purpose (e.g. nfs), and the instance gives the server address (normally the FQDN). Canonically realms are meant to be domain names, but this is not strictly necessary.

Rebuilding NFS with Kerberos support

Slackware's stock packages provide no support for Kerberos at all, so if you want Kerberos-enabled NFS several dependencies need to be (re-)built. Thankfully the amount of dependency-rebuilding is shallow, but it still qualifies for the non-trivial category. I will assume that anyone who is actually prepared to use Slackware is of above-average ability with Linux, because this stuff is not for the faint-hearted. If you have been using Linux for less than 10 years, I suggest running away back to Ubuntu or Fedora now.

Since Slackware doesn't include any Kerberos libraries by default, Slackware's NFS libraries are built without gss support. Much of the effort in creating this guide was a combination of working out terminology, and guessing what a working NFS+Kerberos Ubuntu system had that a Slackware system didn't. Below details how to build the missing bits for Slackware.

krb5

Get the krb5 slackbuild and build/install it.

libnfsidmap

This is needed for NFSv4 but does not seem to be anywhere in the usual Slackware software sources. Grab the latest tarball and build it in the traditional way:

wget https://fedorapeople.org/~steved/libnfsidmap/0.27/libnfsidmap-0.27.tar.gz tar -xzvf libnfsidmap-0.27.tar.gz cd libnfsidmap-0.27 ./configure --prefix=/usr make && make install

tirpc

The stock Slackware libtirpc does not include gss support, so grab the source package and rebuild it with gss support enabled:

MIRROR=https://www.mirrorservice.org/sites/ftp.slackware.com PKGPATH=/pub/slackware/slackware-14.2/source/n/libtirpc for file in doinst.sh.gz libtirpc-1.0.1.tar.xz libtirpc.SlackBuild slack-desc; do wget ${MIRROR}${PKGPATH}/$file done chmod +x libtirpc.SlackBuild WITH_GSS=yes ./libtirpc.SlackBuild removepkg libtirpc installpkg /tmp/libtirpc-1.0.1-i586-2.txz

nfs-utils

The stock Slackware nfs-utils has no gss built into it at all, and enabling it involves hacking the source package. First step is to grab the this package:

MIRROR=https://www.mirrorservice.org/sites/ftp.slackware.com PKGPATH=/pub/slackware/slackware-14.2/source/n/nfs-utils for file in doinst.sh.gz ignore_unsupported_address_types_in_nfssvc_setfds.diff.gz \ nfs-utils-1.3.3.tar.sign nfs-utils-1.3.3.tar.xz nfs-utils.SlackBuild \ nfs-utils.lwrap.needs.lnsl.diff.gz rc.nfsd slack-desc; do wget ${MIRROR}${PKGPATH}/$file done

The SlackBuild script nfs-utils.SlackBuild in this source package has gss disabled completely, and the only way to enable it is to modify said script. The following unified diff, which can be piped into patch -p0,will make these changes:

--- nfs-utils.SlackBuild-orig 2017-01-29 21:29:33.146154212 +0000 +++ nfs-utils.SlackBuild 2017-01-29 22:53:35.008929424 +0000 @@ -87,7 +87,8 @@ --with-statedir=/var/lib/nfs \ --enable-mountconfig \ - --enable-nfsv4=no \ + --enable-nfsv4=yes \ - --enable-gss=no \ + --enable-gss=yes \ + --enable-svcgss \ --enable-tirpc=yes \ --program-prefix= \ --program-suffix= \

Finally kick off the build and install the result:

chmod +x ./nfs-utils.SlackBuild ./nfs-utils.SlackBuild removepkg nfs-utils installpkg /tmp/nfs-utils-1.3.3-i586-2.txz

Kerberos server setup

Example kdc.conf & krb5.conf config files are located under /usr/share/examples/krb5/.
/etc/krb5.conf
[libdefaults] default_realm = REMYNET dns_lookup_kdc = no dns_lookup_realm = no rdns = false [realms] REMYNET = { admin_server = krb.vm kdc = krb.vm } [logging] kdc = FILE:/var/log/kdc.log admin_server = FILE:/var/log/kadmin.log
/var/krb5kdc/kdc.conf
[kdcdefaults] kdc_ports = 750,88 [realms] REMYNET = { database_name = /var/krb5kdc/principal acl_file = /var/krb5kdc/kadm5.acl key_stash_file = /var/krb5kdc/.k5.REMYNET kdc_ports = 750,88 max_life = 1h 0m 0s max_renewable_life = 1d 0h 0m 0s }
/var/krb5kdc/kadm5.acl
master/admin *

Creating realm database

[root@S142 ~]# kdb5_util create -s Loading random data Initializing database '/var/krb5kdc/principal' for realm 'REMYNET', master key name 'K/M@REMYNET' You will be prompted for the database Master Password. It is important that you NOT FORGET this password. Enter KDC database master key: Re-enter KDC database master key to verify:

You don't need to use -r REMYNET as this will be picked up from the default_realm directive within krb5.conf.

Deleting realm database

If for whatever reason you need to blow away your Kerberos setup and start again from scratch, you can use the followoing:

rm -r /var/krb5kdc/.k5.REMYNET /var/krb5kdc/principal* /var/krb5kdc/kadm5.keytab

Setting up principles

[root@S142 ~]# kadmin.local Authenticating as principal root/admin@REMYNET with password. kadmin.local: xst -k /var/krb5kdc/kadm5.keytab kadmin/admin kadmin/changepw Entry for principal kadmin/admin with kvno 2, encryption type aes256-cts-hmac-sha1-96 added to keytab WRFILE:/var/krb5kdc/kadm5.keytab. Entry for principal kadmin/admin with kvno 2, encryption type aes128-cts-hmac-sha1-96 added to keytab WRFILE:/var/krb5kdc/kadm5.keytab. Entry for principal kadmin/changepw with kvno 2, encryption type aes256-cts-hmac-sha1-96 added to keytab WRFILE:/var/krb5kdc/kadm5.keytab. Entry for principal kadmin/changepw with kvno 2, encryption type aes128-cts-hmac-sha1-96 added to keytab WRFILE:/var/krb5kdc/kadm5.keytab. kadmin.local: ank master/admin WARNING: no policy specified for master/admin@REMYNET; defaulting to no policy Enter password for principal "master/admin@REMYNET": Re-enter password for principal "master/admin@REMYNET": Principal "master/admin@REMYNET" created. kadmin.local: addprinc -randkey nfs/nfs.vm@REMYNET WARNING: no policy specified for nfs/nfs.vm@REMYNET; defaulting to no policy Principal "nfs/nfs.vm@REMYNET" created. kadmin.local: addprinc -randkey nfs/cli.vm@REMYNET WARNING: no policy specified for nfs/cli.vm@REMYNET; defaulting to no policy Principal "nfs/cli.vm@REMYNET" created.

Startup/stopping Kerberos servers

Starting the Keberos admin & keys distribution servers
kadmind && krb5kdc
Signalling daemins to reload
# killall -1 kadmind krb5kdc
Shutting down daemons
# killall kadmind krb5kdc

Testing remote admin

[root@S142 krb5kdc]# kinit master/admin Password for master/admin@REMYNET: [root@S142 krb5kdc]# klist Ticket cache: FILE:/tmp/krb5cc_0 Default principal: master/admin@REMYNET Valid starting Expires Service principal 01/29/2017 13:28:35 01/29/2017 14:28:35 krbtgt/REMYNET@REMYNET renew until 01/30/2017 13:28:35

Setting up a non-root user

[root@S142 krb5kdc]# kadmin -p master/admin Authenticating as principal master/admin with password. Password for master/admin@REMYNET: kadmin: ank remy WARNING: no policy specified for remy@REMYNET; defaulting to no policy Enter password for principal "remy@REMYNET": Re-enter password for principal "remy@REMYNET": Principal "remy@REMYNET" created. kadmin:

Setting up Kerberos-enabled NFS Server

The setup presented here is the “whole package”, because I concluded that showing a more gradual building up of a complete setup would be too much of a headache to check. In this setup differences in UPD/GID between servers & client ought not to matter, but this is a detail I did not check. Firstly, the configuration files:
/etc/krb5.conf
[libdefaults] default_realm = REMYNET rdns = false [realms] REMYNET = { admin_server = krb.vm kdc = krb.vm } [logging] default = STDERR
/etc/exports
/mnt/net1 192.168.56.0/24(rw,root_squash,no_subtree_check,sec=krb5) /mnt/net2 192.168.56.0/24(rw,root_squash,no_subtree_check)
/etc/fstab
/dev/sda2 swap swap defaults 0 0 /dev/sda1 / ext4 defaults 1 1 #/dev/cdrom /mnt/cdrom auto noauto,owner,ro,comment=x-gvfs-show 0 0 /dev/fd0 /mnt/floppy auto noauto,owner 0 0 devpts /dev/pts devpts gid=5,mode=620 0 0 proc /proc proc defaults 0 0 tmpfs /dev/shm tmpfs defaults 0 0 rpc_pipefs /var/lib/nfs/rpc_pipefs rpc_pipefs defaults 0 0
/etc/idmapd.conf
[General] Verbosity = 0 Pipefs-Directory = /var/lib/nfs/rpc_pipefs Domain = vm Local-Realms = REMYNET [Mapping] Nobody-User = nobody Nobody-Group = nogroup [Translation] Method = nsswitch,static [Static] remy@REMYNET = remy

Testing connection to Kerberos server

[remy@Pyre ~]$ kinit master/admin Password for master/admin@REMYNET: [remy@Pyre ~]$ klist Ticket cache: FILE:/tmp/krb5cc_1000 Default principal: master/admin@REMYNET Valid starting Expires Service principal 01/29/2017 14:10:30 01/29/2017 15:10:30 krbtgt/REMYNET@REMYNET [remy@Pyre ~]$ kadmin -p master/admin Authenticating as principal master/admin with password. Password for master/admin@REMYNET: kadmin:

Exporting keytab

# kadmin -p master/admin Authenticating as principal master/admin with password. Password for master/admin@REMYNET: kadmin: ktadd nfs/nfs.vm@REMYNET Entry for principal nfs/nfs.vm@REMYNET with kvno 2, encryption type aes256-cts-hmac-sha1-96 added to keytab FILE:/etc/krb5.keytab. Entry for principal nfs/nfs.vm@REMYNET with kvno 2, encryption type aes128-cts-hmac-sha1-96 added to keytab FILE:/etc/krb5.keytab.

Setting up rpc_pipefs mount-point

This one was a bit of a surprise, and I found out about it because its absence was the cause of the following error:

# rpc.gssd -f -vvv ERROR: opendir(/var/lib/nfs/rpc_pipefs) failed: No such file or directory

The rpc_pipefs mount-point is required by NFS4, and it has something to do with kernel communication for UID/GID mapping purposes. Either way, rpc.gssd wants it. The following line needs to be added to /etc/fstab:

rpc_pipefs /var/lib/nfs/rpc_pipefs rpc_pipefs defaults 0 0

..and then the mount-point needs to be created & used:

mkdir /var/lib/nfs/rpc_pipefs mount rpc_pipefs

Starting NFS w/Kerberos up

# echo N > /sys/module/nfs/parameters/nfs4_disable_idmapping # /etc/rc.d/rc.nfsd start Starting RPC portmapper: /sbin/rpcbind -l -w Starting RPC NSM (Network Status Monitor): /sbin/rpc.statd Starting NFS server daemons: /usr/sbin/exportfs -r /usr/sbin/rpc.rquotad /usr/sbin/rpc.nfsd 8 /usr/sbin/rpc.mountd # /etc/rc.d/rc.rpc start # rpc.gssd # rpc.svcgssd # rpc.idmapd

Most of the rpc daemons can be run in foreground mode with debug output using -f -vv parameters, which is usually quite good for diagnosing Kerberos issues such as missing keytab entries.

Mount something

Since the NFS server and client are the same machine, it is possible to write a file via the NFS mount-point and then see it via the physical mount-point:

# mount -t nfs -o vers=4,sec=krb5 nfs.vm:/mnt/net1 /mnt/tmp/ # echo "root-access" > /mnt/tmp/root.txt # ls /mnt/tmp root.txt # ls /mnt/net1 root.txt # cat /mnt/net/root.txt root-access

The trust needed to access the NFS share is due to the machine itself being authorised by having the “password” for nfs/nfs.vm@REMYNET (and later on, nfs/cli.vm@REMYNET) present in the machine's keytab. For non-root users to get access, a further access token is required that lets the server work out what UID/GID should be served:

# su remy $ ls -l /mnt/tmp/ ls: cannot access 'mnt.tmp': Permission denied $ kinit remy Password for remy@REMYNET: $ ls -l /mnt/tmp/ total 5 -rw-r--r-- 1 nobody nogroup 5 Jan 31 17:52 root.txt

Setting up independent NFS client

For a standalone NFS client the krb5.conf and idmapd.conf from the server is required, although the latter will only need the General and Mapping sections, as well as setting up the rpc_pipefs mountpoint. Then the client machine will also needs its own keytab entry:

# kadmin -p master/admin Authenticating as principal master/admin with password. Password for master/admin@REMYNET: kadmin: ktadd nfs/cli.vm@REMYNET Entry for principal nfs/cli.vm@REMYNET with kvno 2, encryption type aes256-cts-hmac-sha1-96 added to keytab FILE:/etc/krb5.keytab. Entry for principal nfs/cli.vm@REMYNET with kvno 2, encryption type aes128-cts-hmac-sha1-96 added to keytab FILE:/etc/krb5.keytab.

Once this is done start the required daemons:

# echo N > /sys/module/nfs/parameters/nfs4_disable_idmapping # /etc/rc.d/rc.rpc start # rpc.gssd # rpc.idmapd

Now the client should be able to mount the shares.

UIDs &GIDs

In NFSv3 it is up to the connecting client to have the same UID/GID so that permissions work as intended. The use-case NFS was designed for is workstations that all use centralised logins, so UIDs/GIDs are the same on all machines. In NFSv4 user & group names are used instead, with mapping to UID/GID handled by idmapd. I'm not entirely sure whether this daemon is required for proper operation of NFSv4, but my suspicion is that it is. It is certainly required on the server side to resolve Kerberos principles to server-side user identities.

When accessing a share as root, access is granted on the basis of the machine's trust, so root's UID of zero goes through with no further questions asked. In practice the permissions that come with such a UID are by default neutered by root_squash which instead gives the connection a UID/GID of nobody. For non-root users the primary within the principle is used by the NFS server as a user-name, and if this does not correspond to a valid user, it is also assumed to be nobody. If it does exist, the matching user account is used.

Appendix: kadmin command reference

This is pulled straight out of the kadmin help screen, and for convenience includes the commands that are likely to be of use for basic Kerberos setup. One of the annoyances with Kerberos is that there are several aliases for the same action, and to make matters worse some Kerberos implementations have their own unique ones. I have seen guides that have addpriv as an alternative for adding principles, and for good measure the
Debian guide seems to have mixed up principles and privileges.
add_principal, addprinc, ank
Add principal
delete_principal, delprinc
Delete principal
modify_principal, modprinc
Modify principal
rename_principal, renprinc
Rename principal
change_password, cpw
Change password
list_principals, listprincs, get_principals, getprincs
List principals
get_privs, getprivs
Get privileges
ktadd, xst
Add entry(s) to a keytab
ktremove, ktrem
Remove entry(s) from a keytab
lock
Lock database exclusively (use with extreme caution!)
unlock
Release exclusive database lock
purgekeys
Purge previously retained old keys from a principal
get_strings, getstrs
Show string attributes on a principal
set_string, setstr
Set a string attribute on a principal
del_string, delstr
Delete a string attribute on a principal
list_requests, lr, ?
List available requests.
quit, exit, q
Exit program.