restricting rsync over ssh

Brian D. Hamm bdhamm at
Wed May 29 12:59:37 EST 2002

Here is the rsync_wrapper I assembled to meet our needs.  This locks
down access by evaluating each ARG individually and only allowing
specific rsync commands against this account.  I understand that this
may need to change with versions of rsync but the way it is written
makes it easy to debug and modify. OS: Solaris 8, Perl v5.6.1, RSYNC

No harsh programming statements please this was my first crack at Perl

Pointing out any obvious security holes would be welcome, since that is
was the script was trying to eliminate.


$TRUE  = (0 == 0);
$FALSE = (0 == 1);

open (SSHOUT, "+>>/home/admin/ssh.out");

$now = localtime;

# Since this script is called as a forced command, need to get the
# original rsync command given by the client.

 || print SSHOUT ("$now environment variable SSH_ORIGINAL_COMMAND not

# Log the command for tracking and debugging purposes
print SSHOUT ("$now EVALUATING: $command\n");

# Split the command string to make an argument list
# Evaluate each argument separately for exactness
# this will allow easy addition of future rsync calls
# See ARG[3] test for testing multiple possibilities per ARG
# Accepted rsync calls are as follows:
# rsync --server --sender -vvvulogDtprz . /var/log/*.mmddyy.gz
# rsync --server --sender -ulogDtprz . /var/log/*.mmddyy.gz

@rsync_argv = split /[ \t]+/, $command;
$ok = $TRUE;

# ARG[0] Complain if the command is not "rsync".
unless ($rsync_argv[0] eq 'rsync') {
   print SSHOUT ("ssh authorized_key account restricted: only rsync
   $ok = $FALSE;

# ARG[1] Complain if this arg is not --server
unless ($rsync_argv[1] eq '--server') {
   print SSHOUT ("ARG[1] Failure\n");
   $ok = $FALSE;

# ARG[2] Complain if this arg is not --sender
unless ($rsync_argv[2] eq '--sender') {
   print SSHOUT ("ARG[2] Failure\n");
   $ok = $FALSE;

# ARG[3] Complain if this arg is not -vvvulogDtprz or -ulogDtprz
unless (($rsync_argv[3] eq '-vvvulogDtprz') ||
        ($rsync_argv[3] eq '-ulogDtprz')) {
   print SSHOUT ("ARG[3] Failure\n");
   $ok = $FALSE;

# ARG[4] Complain if this arg is not .
unless ($rsync_argv[4] eq '.') {
   print SSHOUT ("ARG[4] Failure\n");
   $ok = $FALSE;

# ARG[5] Complain if this arg does not begin with /var/log/
# SECURITY ISSUE: need to lock down further, /var/log/../../otherdir
would succeed
$log_substr = substr ("$rsync_argv[5]", 0, 16);
unless ($log_substr eq '/var/log/rotate/') {
   print SSHOUT ("ARG[5] Failure\n");
   $ok = $FALSE;

#print SSHOUT ("ARG0 = $rsync_argv[0]\n");
#print SSHOUT ("ARG1 = $rsync_argv[1]\n");
#print SSHOUT ("ARG2 = $rsync_argv[2]\n");
#print SSHOUT ("ARG3 = $rsync_argv[3]\n");
#print SSHOUT ("ARG4 = $rsync_argv[4]\n");
#print SSHOUT ("ARG5 = $rsync_argv[5]\n");

# If we're OK, run the rsync
$now = localtime;
if ( $ok ) {

  # Interesting issue here, printing is queued until file is closed
  # if rsync fails and exits out of the script earlier input would never
  # be seen. In fact 'exec' call was replaced with 'system' call for the
  # reason that exec did not return to the shell and the print output
  # never seen because the close was never reached.

  # close and reopen output file to empty print queue to this point
        close (SSHOUT); open (SSHOUT, "+>>/home/admin/ssh.out");

#remove the first argument which is the rsync command and use absolute
        shift @rsync_argv;
        system ("/usr/bin/rsync @rsync_argv");

        $now = localtime;
        print SSHOUT ("$now RSYNC COMPLETE\n\n"); 
} else {
RSYNC\n\n"); }

close (SSHOUT);

Brian D. Hamm, CISSP, CCNA
Network Design & Implementation
(o) 727-939-3080
(c) 727-424-4384
(f) 240-266-7185
(e) bdhamm at

-----Original Message-----
From: Bennett Todd [mailto:bet at] 
Sent: Wednesday, May 29, 2002 3:01 PM
To: tim.conway at
Cc: Brian D. Hamm; rsync at
Subject: Re: restricting rsync over ssh

On Wed, May 29, 2002 at 11:04:37AM -0600, tim.conway at wrote:
> I don't know ssh well enough to know whether it passes parameters
> the ones specified in authorized_keys.  I think it passes parameters, 
> though, because rsync over ssh is the basis of the IBM Content
> Tool (along with DCE/DFS), and it is TIGHTLY controlled.  It couldn't
> if parameters like "--server -lWHogDtprRz --bwlimit=128 --force . 
> /wan/pri-tools1/big1/cadappl1/hpux/iclibs/CMOS12/PcCMOS12xcorelib" (an

> example from currently running stuff on one of my systems)can't be
>  You don't want to try to preparse the args.  They will change in the 
> future.

What you place in .ssh/authorized_keys is the _full_ commandline.
Command and
all arguments. Neither the original command (if any) nor any additional
arguments are passed to the command when you use command= in

Instead, the full original command is passed in the environment variable
SSH_ORIGINAL_COMMAND. Since it's passed as a string, any quoting is
lost, as
far as I know.

This means there are three reasonable choices:

(1) You can allow only one single invocation of rsync, one cmdline; you
    hardwire that into authorized_keys. This is one I like for backups.

(2) You allow any command, and just use a wrapper to e.g. log it;

    	logger [args] "$SSH_ORIGINAL_COMMAND"

(3) You allow a restricted range of commands, by using a wrapper that
    $SSH_ORIGINAL_COMMAND, and decides whether to allow or not.

This mechanism cannot be used to restrict rsync invocations without
wiring in
knowlege of the cmdline. In practice this means that if you want to
rsync, you might have to adjust the wired-in knowlege. That's why I

> > What say, rsync developers, any chance that the details of this
> > invocation --- the one rsync runs over rsh or ssh or whatever to
> > it's connection --- could be formally documented?


More information about the rsync mailing list