Locking Down rsync Over SSH

Annika Backstrom
in misc, on 21 January 2011. It is tagged #Linux, #openssh, #rsync, and #ssh.

For my most recent backup scheme, I needed to lock down rsync over OpenSSH while allowing scheduled syncing. First, my requirements:

  • Encrypted file transfer
  • Ability to work without interaction
  • Secure (i.e. locked down to specific tools/files)

OpenSSH public/private keypairs and rsync over SSH was a logical starting point, but I was missing a piece to limit the rsync to specific files: the authorized_keys command="command" option.

The Setup

Hostnames have been changed to protect the innocent. For this description:

  • coriander is the server, which has some MySQL files we need to back up
  • penny is the client, pulling files from coriander using rsync

I regularly SSH to the host I wanted to backup, so this setup needed to work around a potential key conflict. Easy enough using ~/.ssh/config on penny:

Host coriander
    HostName coriander.example.com
    User annika
    IdentityFile ~/.ssh/id_dsa

Host mysql-binlog
    HostName coriander.example.com
    User annika
    IdentityFile ~/.ssh/mysql-binlog-key

I run an ssh-agent, so I have to run things through env -i to prevent the rsync from using my normal key.

In this case, I manually copied my key into annika@coriander's ~/.ssh/authorized_keys file:

ssh-rsa AAAAB3NzaC1yc2EAAAA…BIwAAAQEA2GNx7diU== mysql-binlog-key

Next I prepended some options to this key:

command="~/mysql-backup/rsync-control" ssh-rsa AAAAB3NzaC1yc2EAAAA…BIwAAAQEA2GNx7diU== mysql-binlog-key

My wish is your command="command"

At this point, let's digress to look at the SSH command option. Try this style authorized_keys on your server to echo the environment and exit when the user logs in over SSH:

command="env" ssh-rsa AAAAB3NzaC1yc2EAAAA…BIwAAAQEA2GNx7diU== mysql-binlog-key

SSH to the host and see the output:

ambackstrom@fsck:~:0$ env -i ssh mysql-binlog
SSH_CLIENT= 56490 22

Then once more, specifying a command to run on the server:

ambackstrom@fsck:~:0$ env -i ssh mysql-binlog 'ls -lAF'
SSH_CLIENT= 56546 22

Our script becomes the middleman between the client and the requested command, and that command is placed in the $SSH_ORIGINAL_COMMAND environment variable. We can analyze this command and allow, deny, or modify it before execution. Given this rsync command:

env -i rsync -avzP mysql-binlogs:/tmp/ /tmp/test/

We get this remote command:

SSH_ORIGINAL_COMMAND=rsync --server --sender -vlogDtprz . /tmp/

Security via Misdirection

Let's place a new middleman script in our authorized_keys file on coriander:

command="~/rsync-control" ssh-rsa AAAAB3NzaC1yc2EAAAA…BIwAAAQEA2GNx7diU== mysql-binlog-key

Within ~/rsync-control we'll analyze the incoming command and take some action:


if [ "$SSH_ORIGINAL_COMMAND" = "rsync --server --sender -vlogDtprz . logs/" ] ; then
    rsync --server --sender -vlogDtprz . /var/lib/mysql/binlog/
    exit $?

exit 1

What we've actually done here is obfuscated the real binary log directory from the originating command on penny: the client requests the logs/ directory, but we launch an rsync server for /var/lib/mysql/binlog/. If any other remote command is issued, the script exits with an error code.

I'm sure there are some other great uses for the command option. I know of git server like gitolite that take advantage of this feature, and I'd love to find more.