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.