Paul's Internet Landfill/ 2015/ Locking Down Rsync for Multiple Directories

Locking Down Rsync for Multiple Directories

As a Joey Hess fanboy, for years I have been following his article locking down ssh authorized keys to make rsync automated backups more secure. The problem is that by default entries in ~/.ssh/authorized_keys will allow access for any reason, and we often want to limit access to rsync backups. I am particularly interested in this because I have been burned before.

Synchronizing one folder

Say I have two computers: src and dest. src has some files I would like to mirror via rsync, using the following commmand:

rsync -av --delete ~/Documents myuser@dest:/opt/backups/Docs

If I only want to back up this folder, Hess says I should follow these instructions, which I will outline below:

First, create a passwordless SSH keypair on src:

ssh-keygen -f ~/.ssh/backup_dest

Next, upload the public key to dest

ssh-copy-id -i backup_dest.pub myuser@dest

(I have never actually used the ssh-copy-id command. Usually I just scp the key over and add it to the authorized_keys file.)

The entry in authorized_keys in dest will have an entry that represents your public key. It will look something like the following (with linebreaks added for clarity):

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDU5/ae75cnamjnegGyk8gj64xkD4dV kUHlrnKdwz6bFIJnH8jPgBYyS5vmBS/QkhjTTwdjhwesdahgaRX11+DpbNUoC9TeJVtf 20y3gZ+m741i0RmfEMTn1U/IPmziwH+7QECFI1r48wBF0ltDdzGbKeC9vnrOM07m4Zty UT2BNUU7BhxW7i1u8ftT1J4ENK+4ZRTwJ8sTaaaTh0NL8S8kuN3SoXhJCpJQ5T2X3VTA TSX9F+FMf4ODfB5H15URoe8OqD9+ugfqI7y5C+jftZArrCLI+XzcVa/AH80z80QK6opc hIWoRF7yaaPC+9kmBGfFR5mVeXzWYC8zvFLPu7Br myuser@src

At this point, you can use this entry to log in from src to dest without a password, which is convenient but insecure. You can partially secure the key by adding the following magic incantation to the beginning of the key:

no-x11-forwarding,no-user-rc,no-pty,no-agent-forwarding,no-port-forwarding

For our key this looks like:

no-x11-forwarding,no-user-rc,no-pty,no-agent-forwarding, no-port-forwarding ssh-rsa AAAAB3NzaC1yc 2EAAAADAQABAAABAQDU5/ae75cnamjnegGyk8gj64xkD4dVkUHlrnKdwz6bFIJnH8jPg BYyS5vmBS/QkhjTTwdjhwesdahgaRX11+DpbNUoC9TeJVtf20y3gZ+m741i0RmfEMTn1 U/IPmziwH+7QECFI1r48wBF0ltDdzGbKeC9vnrOM07m4ZtyUT2BNUU7BhxW7i1u8ftT1 J4ENK+4ZRTwJ8sTaaaTh0NL8S8kuN3SoXhJCpJQ5T2X3VTATSX9F+FMf4ODfB5H15URo e8OqD9+ugfqI7y5C+jftZArrCLI+XzcVa/AH80z80QK6opchIWoRF7yaaPC+9kmBGfFR 5mVeXzWYC8zvFLPu7Br myuser@src

but we would like to add the further restriction that only the rsync command can be used. To do this, we can add a command entry for the key. The authorized_keys manpage says that this command will be run on dest regardless of the command that was sent over the SSH tunnel by src !

Ordinarily rsync will send a mysterious command over the tunnel. It might look something like:

rsync --server -vlogDtpr --delete . /opt/backups/Docs

How do you find this command out? The linked page suggests using the ssh -v option on src to see what is going on. There is an easier way, using the $SSH_ORIGINAL_COMMAND variable created by the SSH connection. Add the following command to the authorized_keys entry:

command="echo $SSH_ORIGINAL_COMMAND >> /tmp/rsync_commands"

In our case, this means:

command="echo $SSH_ORIGINAL_COMMAND >> /tmp/rsync_commands", no-x11-forwarding,no-user-rc,no-pty, no-agent-forwarding,no-port-forwarding ssh-rsa AAAAB3NzaC1yc2EAAAAD AQABAAABAQDU5/ae75cnamjnegGyk8gj64xkD4dVkUHlrnKdwz6bFIJnH8jPgBYyS5v mBS/QkhjTTwdjhwesdahgaRX11+DpbNUoC9TeJVtf20y3gZ+m741i0RmfEMTn1U/IPm ziwH+7QECFI1r48wBF0ltDdzGbKeC9vnrOM07m4ZtyUT2BNUU7BhxW7i1u8ftT1J4EN K+4ZRTwJ8sTaaaTh0NL8S8kuN3SoXhJCpJQ5T2X3VTATSX9F+FMf4ODfB5H15URoe8O qD9+ugfqI7y5C+jftZArrCLI+XzcVa/AH80z80QK6opchIWoRF7yaaPC+9kmBGfFR5m VeXzWYC8zvFLPu7Br myuser@src

Now run the rsync command from src, and look at the file /tmp/rsync_commands on dest. This is the magic. dest will not execute the command sent to it by src. Rather, it will execute the echo command. The $SSH_ORIGINAL_COMMAND variable tells you what the src server wanted to send to dest.

You can then alter your authorized_keys entry by including the mysterious command with the key:

command="rsync --server -vlogDtpr --delete . /opt/backups/Docs`", no-x11-forwarding,no-user-rc,no-pty, no-agent-forwarding,no-port-forwarding ssh-rsa AAAAB3NzaC1yc2EAAAAD AQABAAABAQDU5/ae75cnamjnegGyk8gj64xkD4dVkUHlrnKdwz6bFIJnH8jPgBYyS5v mBS/QkhjTTwdjhwesdahgaRX11+DpbNUoC9TeJVtf20y3gZ+m741i0RmfEMTn1U/IPm ziwH+7QECFI1r48wBF0ltDdzGbKeC9vnrOM07m4ZtyUT2BNUU7BhxW7i1u8ftT1J4EN K+4ZRTwJ8sTaaaTh0NL8S8kuN3SoXhJCpJQ5T2X3VTATSX9F+FMf4ODfB5H15URoe8O qD9+ugfqI7y5C+jftZArrCLI+XzcVa/AH80z80QK6opchIWoRF7yaaPC+9kmBGfFR5m VeXzWYC8zvFLPu7Br myuser@src

At this point you are done.

Securing multiple folders

This is the harder part. Joey Hess writes:

If you need to rsync multiple separate directories, it's easy to find several documents involving a validate-rsync.sh. Do not use, it is insecure -- it allows rsync to be run with any parameters. Including parameters that allow the remote system to rsync in a new ~/.ssh/authorized_keys. Oops. (You can probably also trick validate-rsync.sh into running other arbitrary commands.) To be secure, you have to check the rsync parameters against some form of whitelist.

Until recently, I interpreted this paragraph to mean that using rsync on multiple folders was too difficult for me. I did not even bother looking at the validate-rsync.sh script linked to from Hess's article. But (as far as I can tell) creating a whitelist is actually not that hard. Here is how I did it.

Let's assume that I now want to back up multiple folders to different destinations:

rsync -av --delete ~/Documents myuser@dest:/opt/backups/Docs
rsync -av --delete ~/podcasts myuser@dest:/opt/largefiles

Start with the version of authorized_keys that echoes the $SSH_ORIGINAL_COMMAND to a file:

command="echo $SSH_ORIGINAL_COMMAND >> /tmp/rsync_commands",no-pty, no-agent-forwarding,no-port-forwarding ssh-rsa AAAAB3NzaC1yc2EAAAAD AQABAAABAQDU5/ae75cnamjnegGyk8gj64xkD4dVkUHlrnKdwz6bFIJnH8jPgBYyS5v mBS/QkhjTTwdjhwesdahgaRX11+DpbNUoC9TeJVtf20y3gZ+m741i0RmfEMTn1U/IPm ziwH+7QECFI1r48wBF0ltDdzGbKeC9vnrOM07m4ZtyUT2BNUU7BhxW7i1u8ftT1J4EN K+4ZRTwJ8sTaaaTh0NL8S8kuN3SoXhJCpJQ5T2X3VTATSX9F+FMf4ODfB5H15URoe8O qD9+ugfqI7y5C+jftZArrCLI+XzcVa/AH80z80QK6opchIWoRF7yaaPC+9kmBGfFR5m VeXzWYC8zvFLPu7Br myuser@src

This will produce a file with multiple entries in /tmp/rsync_commands -- one per rsync call:

rsync --server -vlogDtpr --delete . /opt/backups/Docs
rsync --server -vlogDtpr --delete . /opt/largefiles

I then wrote a hacky python script (whitelist-rsync.py) which checks these commands against the $SSH_ORIGINAL_COMMAND variable:

#!/usr/bin/python
import os

allowed_commands = [
    'rsync --server -vlogDtpr --delete . /opt/backups/Docs',
    'rsync --server -vlogDtpr --delete . /opt/largefiles',
    ]


orig_cmd = os.environ['SSH_ORIGINAL_COMMAND']

if orig_cmd in allowed_commands:
    # Yikes! What am I doing???
    os.system(orig_cmd)

Note how I put each of the allowable rsync invocations into a list. Probably you could read it from a file or something, but I am bad at programming.

Finally, change the authorized_keys entry to call this whitelist-rsync.py command when the key is used:

command="/usr/local/bin/whitelist-rsync.py",no-pty, no-agent-forwarding,no-port-forwarding ssh-rsa AAAAB3NzaC1yc2EAAAAD AQABAAABAQDU5/ae75cnamjnegGyk8gj64xkD4dVkUHlrnKdwz6bFIJnH8jPgBYyS5v mBS/QkhjTTwdjhwesdahgaRX11+DpbNUoC9TeJVtf20y3gZ+m741i0RmfEMTn1U/IPm ziwH+7QECFI1r48wBF0ltDdzGbKeC9vnrOM07m4ZtyUT2BNUU7BhxW7i1u8ftT1J4EN K+4ZRTwJ8sTaaaTh0NL8S8kuN3SoXhJCpJQ5T2X3VTATSX9F+FMf4ODfB5H15URoe8O qD9+ugfqI7y5C+jftZArrCLI+XzcVa/AH80z80QK6opchIWoRF7yaaPC+9kmBGfFR5m VeXzWYC8zvFLPu7Br myuser@src

One big security consideration is that src should NOT be able to write to the folder containing whitelist-rsync.py, because then it could overwrite the whitelist script with something insecure. Other than this I don't know all the security problems I have introduced. I feel that this is about as secure as the solution linked above, but I am bad at security and you should not trust what I say. Use this solution at your own risk, and if you see security weaknesses in this scheme please let me know about them.