Dev Notes

Software Development Resources by David Egan.

Connect Raspberry Pi Backup Server to Remote Server


Linux, Raspberry Pi, Sysadmin
David Egan

This article describes how to provide a local Raspberry Pi access to a remote server in order to automatically download backup files through an encrypted channel. The connection is limited to read-only access of a specified directory on the remote server.

Context

The objective is to hold a local copy of backup files. The solution needed to be fully automatic and secure.

A Raspberry Pi with an external SSD storage drive was used as the local client. Once downloaded, files are accessible across the LAN by SSHing into the Pi.

The Pi acts as an always-on local fileserver - this periodically connects to a remote server (in this case, a Digital Ocean Ubuntu box) via SSH collecting backup data using rsync. The setup is low power, cost-effective, secure and runs automatically.

Because the backup script runs automatically and is unattended, it has to be passwordless. The scope of action on the target machine must be limited to read-only rsync for a specified directory. This limitation is the focus of this article, though I also provide a sample backup script.

Key Generation

A public-private key pair is required, with the public key added to the target server.

If one doesn’t already exist, or you need a unique key for some reason, you can generate an RSA key with a specific name:

ssh-keygen -t rsa

# Outputs this:
Enter file in which to save the key (/home/david/.ssh/id_rsa):

Specify a filename if required. Enter the full path, e.g. /home/username/.ssh/local-fileserver

Comments in the Key File

It can be helpful to have a comment in the key file. When transferred to the target machine, the public key will be appended to the /home/username/.ssh/authorized_keys file. The comment will help distinguish the key.

If the client machine is compromised, it allows the relevant line to be easily removed from the server authorized_keys file. If no comment is added, the public key file has username@hostname appended to the key as a default comment.

To add a comment, just enter text at the end of the public key, separated from the key content by a space.

Specify the Correct Key

If you’re not using the default key file, create an entry in an SSH config file specifying the correct key file for the server that you’re going to connect to.

You may need to create the config file it it doesn’t exist. The following command will do this in any case, with the file being created when you save (ctrl + o in nano):

sudo nano /home/username/.ssh/config

# Enter the following and save
Host 192.168.1.XXX
    IdentityFile ~/.ssh/local-fileserver

Uploading the Key

This can be achieved easily using the ssh-copy-id command - though the server must obviously allow the connection before the key is set up, so you may need to temporarily amend the servers SSH configuration to reflect this.

If no keyfile is specified, this command will copy all public keys from the /home/username/.ssh directory, which may not be what you intend.

The -i flag can be used to set the specific key file that should be transferred:

ssh-copy-id -i ~/.ssh/local-fileserver.pub username@192.168.1.XXX

Once the key has been transferred, set the SSH config to disallow password login (PasswordAuthentication no) and restart SSH.

Security

Using an SSH key without a passphrase makes it possible to automate remote tasks - a user-entered passphrase is not required for local key decryption.

Automation of backup tasks is essential - if backup relies on human intervention, sooner or later it will fail. However, using SSH keys without a passphrase is a security risk - is someone had access to the local backup server, they would be easily able to access the remote server.

Realistically, this is a pretty low risk. The bad guy would have to break in to your office and know his way round a headless Linux box. We don’t get too many roving bands of rogue systems administrators in this neck of the woods, but just in case…

SSH allows restriction of the commands that can be executed by a specific set of keys. This is accomplished by editing the /home/username/.ssh/authorized_keys file on the target server.

A script called rrsync (which stands for restricted rsync) is provided with rsync specifically to ease the restricting keys to be used only for rsync via .ssh/authorized_keys.

On Ubuntu, the script is located here: /usr/share/doc/rsync/scripts/rrsync.gz. The script needs to be unzipped and installed under usr/local/bin:

# Copy the archive rrsync script to /usr/local/bin
sudo cp /usr/share/doc/rsync/scripts/rrsync.gz  /usr/local/bin/

# Unzip the script
sudo gzip -d   /usr/local/bin/rrsync.gz

# Give it correct permissions
sudo chmod 755 /usr/local/bin/rrsync

Once rrsync is in place, we can lock down access for our SSH key by amending the /home/backupuser/.ssh/authorized_keys file on the target server.

Open the public key for the backup user on the target server. This will constitute a single line of text:

sudo nano /home/backupuser/.ssh/authorized_keys

Prepend the key data with the following:

command="/usr/local/bin/rrsync -ro /home/path-to/backup/",no-agent-forwarding,no-port-forwarding,no-pty,no-user-rc,no-X11-forwarding
  • The command="/usr/local/bin/rrsync ..." restricts access of that particular public key - only the given command can be executed
  • The ...-ro /home/path-to/backup/" gives read-only access to the specified directory
  • The no-* options further restrict what actions can be carried out with the public key.
  • Note that the full path is need to reference rrsync

If you try and SSH into the Production machine from the Backup server you should now see a message like this:

PTY allocation request failed on channel 0
/usr/local/bin/rrsync: Not invoked via sshd
Use 'command="/usr/local/bin/rrsync [-ro] SUBDIR"' in front of lines in /home/backupuser/.ssh/authorized_keys
Connection to 123.45.67.89 closed.

Your script however will be able to gain read-only access, which is good enough to pull-down backup files.

rsync Command rsync from the Client

Note: After the above amendment to the authorized_keys file, the final path from the Backup server’s point of view is now /

Sample rsync command from the local server:

rsync --log-file=$HOME/.rsyncd.log --progress -az -H -e "ssh -p 1234" backupuser@123.456.789.0:/ ~/backup-target-directory

This specifies:

  • -a: archive mode; equals -rlptgoD (no -H,-A,-X)
  • -z: Compress file data during the transfer
  • -H: Maintain hardlinks (important due to our incremental backup)
  • -e “ssh -p 1234”: connect via SSH on port 1234
  • See rsync man page

Connect Via a Scheduled Script

Save the following script in /usr/local/sbin (or create an appropriate symlink:

#!/bin/bash
#
# On remote, run `rrsync` and prepend this command to the relevant line in `/home/username/.ssh/authorized_keys`:
# `command="/usr/local/bin/rrsync -ro /home/path/to/backup/",no-agent-forwarding,no-port-forwarding,no-pty,no-user-rc,no-X11-forwarding`
#
# Run this file from the root crontab on the client machine.

# Name of the current script
NAME=$(basename "$0")
# The remote server username@ip address, appended with `:/`
SRC="remote-username@111.222.111.222:/"
TRG="/mnt/usb1/server-backups/local-backups-dir"
LOGFILE="/mnt/usb1/server-backups/pull-backups.log"
TIMESTAMP=$(date '+%Y-%m-%d at %H:%M:%S')
ERRORLOG="/mnt/usb1/server-backups/pull-backups-error.log"

# ------------------------------------------------------------------------------
# rsync options: follow the symlinks to make a hard backup. Note the custom port
# specified for SSH which needs to be configured on the remote server.
# ------------------------------------------------------------------------------
OPT=(-az -H --exclude='*.zip' --exclude='debian.cnf' -e 'ssh -p 3333')

# ------------------------------------------------------------------------------
# Execute the backup
# ------------------------------------------------------------------------------
rsync "${OPT[@]}" $SRC $TRG 2> $ERRORLOG

if [[ $? -eq 0 ]]
then
  ENDTIME=$(date '+%H:%M:%S')
  printf -v MSG "SUCCESS! %s started on: %s, successfully finished at: %s" "$NAME" "$TIMESTAMP" "$ENDTIME" echo "$MSG" >> "$LOGFILE"
else
  ENDTIME=$(date '+%H:%M:%S')
  printf -v MSG "ERROR! There was a problem running %s. Started on: %s, ended at: %s. See: %s" "$NAME" "$TIMESTAMP" "$ENDTIME" "$ERRORLOG"
  echo "$MSG" >> "$LOGFILE"
fi

Edit the crontab:

sudo crontab -l

Enter the following on a separate line:

# m h  dom mon dow   command

# Run script `my-backup-script` every day at 03:30 am
30 03 * * * /usr/local/sbin/my-backup-script

References

Ubuntu Manpage for ssh-copy-id


comments powered by Disqus