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.