Skip to content

Instantly share code, notes, and snippets.

@kyle0r
Last active March 17, 2021 23:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kyle0r/f20e79b65595642506448be0e88a7b78 to your computer and use it in GitHub Desktop.
Save kyle0r/f20e79b65595642506448be0e88a7b78 to your computer and use it in GitHub Desktop.
mirror a root owned path to a remote node, with your non root user

mirror a root owned path to a remote node, with your non root user

Its often undesirable for InfoSec reasons and/or a productivity killer to use root to ssh|scp|rsync to other nodes.

There seem to be a number of sources of knowledge online about rsync'ing when you have sudo rights on the remote dst node aka "the receiver", but I didn't find a good answer when you also want to use sudo on the local src node aka "the sender". Typically once you've sudo rsync on the sender node, the env is changed to the sudo user e.g. root and previous session authentication mechanisms are lost.

For example if you have barrier free ssh and sudo access to your internal systems with your own user because of already satisfying MFA on the perimeter, and you're using ssh keys and/or Kerberos tickets to persist authenticated sessions, it can be a real PITA to be forced to use a different user to ssh|scp|rsync.

In related news root is often restricted for good InfoSec reasons via sshd_config directive PermitRootLogin no which can snafu your plans.

I've used these scripts and also checked them with https://www.shellcheck.net/ but still do your own careful testing.

one liner

In this example, the command sequence rsync mirrors the /etc/letsencrypt folder between two nodes. remove the --dry-run to perform the rsync.

ABS_LOCAL_PATH_TO_MIRROR=/etc/letsencrypt; REMOTE_SSH_USER_HOST=${USER}@node.domain.tld; sudo --preserve-env --shell rsync --dry-run --rsync-path="sudo rsync" --verbose --checksum --archive --itemize-changes --itemize-changes --delete-after ${ABS_LOCAL_PATH_TO_MIRROR}/ ${REMOTE_SSH_USER_HOST}:${ABS_LOCAL_PATH_TO_MIRROR}

why does it work?

My ssh session into the node where I ran this was using agent forwarding, and the rsync dst node has my public key configured for my user. sudo --preserve-env --shell is able to re-use my users env authentication with $REMOTE_SSH_USER_HOST without the need to prompt for credentials. So the prerequisites are:

  1. ssh public key auth is configured and working for your $USER.
  2. ssh agent forwarding is configured and working for your $USER and is used to connect to the node(s) you want to execute on.

script breakdown

NOT syntactically correct. This won't work because of the comments/white-space, its just for illustration. Check the one liner for the correct syntax.

# setup vars
ABS_LOCAL_PATH_TO_MIRROR=/etc/letsencrypt
REMOTE_SSH_USER_HOST=${USER}@node.domain.tld

# sudo with --preserve-env and --shell options, check man sudo
sudo --preserve-env --shell

# rsync with checksum mode, deleting DEST files that don't exist on the SRC.
# --itemize-changes used twice to show unchanged files.
# note the rsync line should be on the same as the sudo line above.
rsync --rsync-path="sudo rsync" --progress --verbose --checksum --archive --itemize-changes --itemize-changes --delete-after ${ABS_LOCAL_PATH_TO_MIRROR}/ ${REMOTE_SSH_USER_HOST}:${ABS_LOCAL_PATH_TO_MIRROR}

alternative with tar, sudo and rsync

Here is an alternative bash snip-it that might help you if sudo is not setup to allow --preserve-env to preserve your environment.

I wrote this rsync-like method to get a root owned path/directory mirrored to a remote with a one liner. Half of the bytes here are info text.

Shortly after writing this I recalled that sudo could preserve env which is a neater solution if it works with your setup.

one liner

Update the first two vars to suit your needs, and if your happy with the results, remove --dry-run from the rsync command.

ABS_LOCAL_PATH_TO_MIRROR=/etc/letsencrypt; REMOTE_SSH_USER_HOST=user@node.domain.tld; sudo tar cf - $ABS_LOCAL_PATH_TO_MIRROR | ssh $REMOTE_SSH_USER_HOST "tmpdir=\$(sudo mktemp --tmpdir=/var/tmp --directory); sudo tar xf - -C \$tmpdir; set -x; sudo rsync --dry-run --progress --verbose --checksum --archive --itemize-changes --itemize-changes --delete-after \$tmpdir/${ABS_LOCAL_PATH_TO_MIRROR}/ $ABS_LOCAL_PATH_TO_MIRROR; set +x; printf '\n################# >>> INFO <<<\n\n%s\n\n%s %s %s\n\n%s %s %s\n' 'non-interactive recursive deletion is extremely dangerous as root.' 'therefore the remote server tmpdir' \$tmpdir 'still exists, you need to clean it up.' 'suggested command: sudo find' \$tmpdir '-depth -and -print ## if it looks good append -and -delete.'" ; tput setaf 1; echo; printf '%s\n\n' '>>> please check the info message and clean up the remote tmpdir when you are done. <<<'; tput sgr0

script breakdown

This should be syntactically correct

#!/bin/bash

# setup vars
# ensure this is an absolute path
ABS_LOCAL_PATH_TO_MIRROR=/etc/letsencrypt
REMOTE_SSH_USER_HOST=user@node.domain.tld

#  create a transinet tar archive with root,
#+ then run ssh with your $USER on the $REMOTE with the transient script.
sudo tar cf - $ABS_LOCAL_PATH_TO_MIRROR | \
ssh $REMOTE_SSH_USER_HOST "
    # make a tmpdir to extract the archive
    tmpdir=\$(sudo mktemp --tmpdir=/var/tmp --directory)
    
    # extract archive
    sudo tar xf - -C \$tmpdir
    
    # echo the rsync cmd on stderr
    set -x 
    
    # rsync the archive with root to the 
    sudo rsync --verbose --progress --checksum --archive \
    --itemize-changes --itemize-changes \
    --delete-after \$tmpdir/${ABS_LOCAL_PATH_TO_MIRROR}/ $ABS_LOCAL_PATH_TO_MIRROR
    set +x
    
    # info print
    printf '\n################# >>> INFO <<<\n\n%s\n\n%s %s %s\n\n%s %s %s\n' \
    'non-interactive recursive deletion is extremely dangerous as root.' \
    'therefore the remote server tmpdir' \
    \$tmpdir \
    'still exists, you need to clean it up.' \
    'suggested command: sudo find' \$tmpdir '-depth -and -print ## if it looks good append -and -delete.'
"

tput setaf 1 # color red
printf '\n%s\n\n' '>>> please check the info message and clean up the remote tmpdir when you are done. <<<'
tput sgr0 # reset
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment