As part of my transition from using a combination of Linux and FreeBSD for our home servers to being exclusively FreeBSD, I wanted to update how I did backups from my public server, bree, to the internal storage server, rivendell. Previously, I had done this with a home grown script which used rsync to transfer updates to the storage server overnight. This solution worked just fine, but was not the most efficient (see: rsync.net: ZFS Replication to the cloud is finally here-and it’s fast). While I didn’t intend to replicate to rsync.net I wanted to leverage ZFS since I am now going FreeBSD to FreeBSD.
There are numerous articles about using zxfer to perform backups but there was one big hiccup that I couldn’t get over. Quoting the man page:
zxfer -dFkPv -o copies=2,compression=lzjb -T root@192.168.123.1 -R storage backup01/pools
Having to open up the root
account on my storage server, no matter how I restricted it to IP address, keys, whatever, makes me really uncomfortable and a show-stopper for me. But I thought I could do better. I have limited experience using restricted-shells to limit access to servers before and I knew that ZFS allows for delegating permissions to non-root users so I decided to give it a shot.
TL;DR: It can work.
The configuration had a few phases to it:
- Create a new restricted user account on my backup server and configure the commands that
zxfer
needs access to in the restricted shell - Create the destination zfs filesystem to receive the mirror and configure the delegated permissions for the backup user
- Set up access to the backup server from the source server via SSH
- Make slight modification to
zxfer
to allow it to runzfs
command from thePATH
instead of hardcoding the path in the script
Setting up the restricted user
I created a new user on the backup system named zbackup
that would be my restricted user for receiving the backups. The goal was for this user to be as limited as possible. It should only be allowed to run the commands necessary for zxfer
to do its job. I landed on using rzsh
as the restricted shell as it was the first one I got working with the correct environment. I set up a directory to hold binaries that the zbackup
user was allowed to use.
root@storage$ mkdir /usr/local/restricted_bin
root@storage$ ln -s /sbin/zfs /usr/local/restricted_bin/zfs
root@storage$ ln -s /usr/bin/uname /usr/local/restricted_bin/uname
I then set up the .zshenv
file for the zbackup
user to restrict the user to that directory for executables.
export PATH=/usr/local/restricted_bin
Setting up the destination zfs filesystem
I already had a zfs filesystem that was devoted to backups so I made a new zfs filesystem underneath it to hold these new backups and be a point where I could set delegation points for permissions. Then, through trial and error, I figured out all the permissions I had to delegate to the zbackup
user on the filesystem to allow zxfer
to work
root@storage$ zfs create nas/backup/bree-zxfer
root@storage$ chown zbackup:zbackup /nas/backup/bree-zxfer
root@storage$ zfs allow -u zbackup atime,canmount,casesensitivity,checksum,compression,copies,create,
dedup,destroy,exec,filesystem_count,filesystem_limit,jailed,logbias,mount,
normalization,quota,readonly,receive,recordsize,redundant_metadata,
refquota,refreservation,reservation,setuid,sharenfs,sharesmb,snapdir,
snapshot_count,snapshot_limit,sync,userprop,utf8only,volmode nas/backup/bree-zxfer
(I figured out the list of actions and properties that I needed to delegate by having zxfer
dump the zfs create
command it was trying to run on the backup system when it failed.)
Update: I forgot 1 thing that is critical to making this work. You need to ensure that non-root users are allowed to mount filesystems. This can be accomplished by adding the following line to your /etc/sysctl.conf
and rebooting:
vfs.usermount=1
Remote access to the backup server
Nothing fancy here. On my source server, I created a new SSH keypair for the root
user (no problem with running the source zfs
command as root
). I then copied the public half of that key to the authorized_keys
file of the zbackup
user on the backup server. At this point, I could ssh from my source server to the backup server as the zbackup
user. But when logged in to the backup server, the only commands that could be run are those in the /usr/local/restricted_bin
directory (zfs
and uname
).
Tweak zxfer script to remove hard coded path in zfs commands
One of the limitations (intentional) of a restricted shell is that the restricted user is not allowed to specify a full pathname for any commands. Only commands located in their PATH
can be run. Unfortunately, while the zbackup
user has the zfs
command in their PATH
, it is referenced as /sbin/zfs
in the zxfer
script. To work around this, I modified the zxfer
script to not use the path of zfs
directly and assume that zfs
will be in the path. This was only in 2 places of the script. If you do a quick search for /sbin/zfs
you will find them.
Moment of truth!
After all this, I was now able to run any number of commands to mirror my source servers zfs
filesystems (with snapshots) to my backup server.
root@source$ zxfer -dFPv -T zbackup@storage -N zroot/git nas/backup/bree-zxfer
root@source$ zxfer -dFPv -T zbackup@storage -R zroot/var nas/backup/bree-zxfer
And best of all, the storage server does not have SSH enabled for root. Success.