SecureCronRsyncWithSudo

30th October 2016 at 1:35pm
Bash OpenSSHTipsAndTricks SSH TechnicalNotes

This is an exmaple of how to use rsync in a cron script to securely pull files from one host to another using a passphraseless SSH key. (It can be done reasonably securely even if you inside on not using a passphrase – see UsingSSHAgentWithCron if you want find out how to use cron with ssh-agent).

You can create the SSH key with ssh-keygen:

ssh-keygen -t rsa -b 4096 -P "" \
  -C "$(id -un)@$(hostname -f):/etc/cron.hourly/allin1rsync.cron" \
  -f ~/key_to_append_to_end_of_script

The private part of this key needs to be appended to the end of the script itself.

The public part is then modified to restrict how it can be used by prefixing it with the following:

command="/usr/bin/rsync --server --sender -vlHogDtpAXre.Lsf --copy-unsafe-links . /base/path/to/replicate/",from="10.10.10.10",no-agent-forwarding,no-pty,no-user-rc,no-X11-forwarding 

This restricts the command that can be executed by the client using this key, from where the key can be used and disabling forwarding etc.

We then further secure the connection by connecting as a non-privileged user on the remote host, and using sudo to give us access to the files to replicate. We make the sudo access explicitly restricted to the rsync command in question of course.

#!/bin/bash
#
# This script will recursively replicate directories under the path
# /base/path/to/replicate/ named "mydir1", "mydir2" or "mydir3".
#
# It will be run as /etc/cron.hourly/allin1rsync.cron on the client host
#   root@client.host.fqdn (10.10.10.10)
#
# It will login over SSH as the user "allin1rsync" to
#    allin1rsync@remote.host.fqdn (10.10.10.20)
#
# Permissions elevation happens via sudo on remote.host.fqdn, which requires
# the following sudoers rule to be installed in /etc/sudoers.d/allin1rsync
# remote.host.fqdn (10.10.10.20):
#   allin1rsync ALL=(root) NOPASSWD: /usr/bin/rsync --server --sender -vlHogDtpAXre.Lsf --copy-unsafe-links . /base/path/to/replicate/
#
# Finally, the public SSH key needs to be copied in to place on
# remote.host.fqdn (10.10.10.20). (See the bottom of this file).
# It is restricted to execute only one specific command, and only from the
# source host.
#
# The private part of the SSH key is read directly from the bottom of this cron
# script itself.

exec > >(2>&-;logger -s -t "${0##*/}[$$]" -p user.info 2>&1) \
    2> >(     logger -s -t "${0##*/}[$$]" -p user.error    )
 
set -uexvo pipefail
  
pull_files () {
  declare -x remote_host="$1"; shift
  declare -x remote_user="$1"; shift
  declare -x ssh_private_key="$1"; shift
  eval $(ssh-agent -t 3595)
  ssh-add "$ssh_private_key"
 
  directories_to_includes () {
    for arg in "$@" ; do
      printf -- "--include=%s/**\n" "$arg"
    done
  }
 
  rsync --verbose --progress \
    --rsh="/usr/bin/ssh -o BatchMode=yes" \
    --rsync-path="/usr/bin/sudo /usr/bin/rsync" \
    --acls --xattrs --archive --delete --delete-after --delay-updates \
    --hard-links --copy-unsafe-links --prune-empty-dirs \
    --include="*/" \
    $(directories_to_includes "$@") \
    --exclude="*" \
    ${remote_user}@${remote_host}:/base/path/to/replicate/ \
    /base/path/to/replicate/ \
      || :
 
  ssh-agent -k
}
 
main () {
  pull_files \
    "remote.host.fqdn" \
    "allin1rsync" \
    <(grep -A 999 '^-----BEGIN RSA PRIVATE KEY-----$' "${BASH_SOURCE[1]}") \
    "mydir1" "mydir2" "mydir3" \
      | tee /var/log/allin1rsync.log
}
 
main "$@"
exit $?

command="/usr/bin/rsync --server --sender -vlHogDtpAXre.Lsf --copy-unsafe-links . /base/path/to/replicate/",from="10.10.10.10",no-agent-forwarding,no-pty,no-user-rc,no-X11-forwarding ssh-rsa AAAAYourPublicSSHKeyYouShouldMakeItSomethingNiceLike4096BitsIfItIsRSA== allin1rsync@client.host.fqdn:/etc/cron.hourly/allin1rsync.cron
 
-----BEGIN RSA PRIVATE KEY-----
ZZZZThisIsYourPrivate4096BitRSASSHKeyWhichYouNeedToKeepPrivateAn
aSecureWeHavePutItInThisScriptAsAnAllIn1RsyncExampleBecauseItsCo
goerghjerotwiertGERWTUYBBuwvb3Eu6tbILUGIIg87ty8GIGBJGBkhKCRRTECh
kjOkIJM9867lfjedsOJNg78g3wbnekjbLIGHLKGUewrguherglihewighninvweq
olijhoerwhgfoighreoigho54uhg05493gjerivhnroievmervrujefrejfore==
-----END RSA PRIVATE KEY-----