Seamless login/logout with ZFS Encrypted Home Directories
Installing FreeBSD from scratch is a rare occurrence for me. The installation on my daily driver laptop, a Lenovo X220, might be pushing 10 years. So, when I got a new Framework 16, it was a nice opportunity to start from scratch and try some new things. One of those new things was a native ZFS encrypted home directory. The installer, while a little rough around the edges, makes this relatively straightforward. What does take some manual intervention now, though, is mounting and unmounting the encrypted home directory.
It's inconvenient to do this every time before logging in to the system:# zfs load-key zroot/home/jrm # zfs mount /home/jrm
What I was hoping to accomplish was the same experience as with an unencrypted home directory. That is, at my login manager prompt, I simply enter my username and password and I'm logged in and ready to use the system. Fortunately, ZFS has a pam module called pam_zfs_key. From the man page, "pam_zfs_key is a PAM module that automatically manages encryption keys for ZFS datasets during user authentication and session management. When a user logs in, the module uses their password to unlock their encrypted home directory. When the last session closes, the module unmounts the dataset and unloads the key."
ZFS native encryption supports keyformat=passphrase, where the dataset is protected by a passphrase you supply. pam_zfs_key hooks into PAM and it uses your password to load the ZFS encryption key and mount your home dataset. On logout (when the last session closes), it unmounts the dataset and unloads the key. It uses reference counting under /var/run/pam_zfs_key/ to handle multiple simultaneous logins from the same user gracefully. The key constraint is that your login password must match your ZFS dataset passphrase. The module only works with keyformat=passphrase and keylocation=prompt datasets.
I've been using xdm for decades now, but after an hour of so of tinkering, I couldn't get it working with PAM, so I tried a few other login managers. I settled on LightDM because it just worked.
The module needs two entries in your PAM service file: one for auth to attempt key loading during authentication, and one for session to handle mounting on open and unmounting on close.
LightDM (/usr/local/etc/pam.d/lightdm)# auth auth sufficient pam_zfs_key.so auth include system # account account requisite pam_securetty.so account required pam_nologin.so account include system # session session include system session optional pam_zfs_key.so mount_recursively forceunmount # password password include system
The auth sufficient line means a successful ZFS key load satisfies authentication; if it fails (e.g., the dataset doesn't exist for that user), PAM falls through to the normal system auth stack. The session line handles mount/unmount with mount_recursively (for hierarchical datasets) and forceunmount (to unmount even if something is holding the filesystem busy at logout).
I also added similar configuration to a few other PAM-aware services.
SSH (/etc/pam.d/sshd)# auth auth sufficient pam_zfs_key.so auth required pam_unix.so no_warn try_first_pass # account account required pam_nologin.so account required pam_login_access.so account required pam_unix.so # session session required pam_permit.so session optional pam_zfs_key.so mount_recursively forceunmount # password password required pam_unix.so no_warn try_first_passsu (/etc/pam.d/su)
# auth auth sufficient pam_rootok.so no_warn auth sufficient pam_self.so no_warn auth requisite pam_group.so no_warn group=wheel root_only fail_safe ruser auth sufficient pam_zfs_key.so auth include system # account account include system # session session required pam_permit.so session optional pam_zfs_key.so mount_recursively forceunmountpasswd (/etc/pam.d/passwd)
password optional pam_zfs_key.so password required pam_unix.so no_warn try_first_pass nullok
The password stack in PAM handles password change operations. Adding pam_zfs_key.so there means that when a user changes their login password, the module automatically re-keys the ZFS dataset with the new passphrase, keeping the two in sync automatically. Without it, you'd need to manually run zfs change-key every time you changed your login password, and if you forgot, the automatic unlock would silently stop working at next login.
Any login manager or service that goes through PAM should be able to use pam_zfs_key. It's transparent to the manager itself. What matters is that the service has a corresponding PAM configuration file.
A limitation to watch for with display managers is that the session close hook fires when the display manager's session ends, so any background processes still running in your home directory at logout may prevent an unmount. The forceunmount option handles this, but it's worth being aware of.
pam_zfs_key is a clean solution that requires no additional daemons or wrappers, just PAM configuration and properly prepared datasets. Once it's set up, the encryption is completely transparent in that your home directory appears decrypted the moment you log in and is locked again the moment your last session closes.