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 v2.5.5
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. #!/usr/bin/perl $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. ($command = $ENV{SSH_ORIGINAL_COMMAND}) || print SSHOUT ("$now environment variable SSH_ORIGINAL_COMMAND not set"); # 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 allowed\n"); $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 ) { print SSHOUT ("$now RSYNC REQUEST PASSED INSPECTION - INITIATING RSYNC\n"); # 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 was # 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 path shift @rsync_argv; system ("/usr/bin/rsync @rsync_argv"); $now = localtime; print SSHOUT ("$now RSYNC COMPLETE\n\n"); } else { print SSHOUT ("$now RSYNC REQUEST FAILED INSPECTION - SKIPPING 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) [EMAIL PROTECTED] -----Original Message----- From: Bennett Todd [mailto:[EMAIL PROTECTED]] Sent: Wednesday, May 29, 2002 3:01 PM To: [EMAIL PROTECTED] Cc: Brian D. Hamm; [EMAIL PROTECTED] Subject: Re: restricting rsync over ssh On Wed, May 29, 2002 at 11:04:37AM -0600, [EMAIL PROTECTED] wrote: > I don't know ssh well enough to know whether it passes parameters besides > the ones specified in authorized_keys. I think it passes parameters, > though, because rsync over ssh is the basis of the IBM Content Promotion > Tool (along with DCE/DFS), and it is TIGHTLY controlled. It couldn't work > 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 passed. > 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 authorized_keys. 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; perhaps #!/bin/sh logger [args] "$SSH_ORIGINAL_COMMAND" exec $SSH_ORIGINAL_COMMAND (3) You allow a restricted range of commands, by using a wrapper that parses $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 upgrade rsync, you might have to adjust the wired-in knowlege. That's why I wrote: > > What say, rsync developers, any chance that the details of this cmdline > > invocation --- the one rsync runs over rsh or ssh or whatever to establish > > it's connection --- could be formally documented? -Bennett -- To unsubscribe or change options: http://lists.samba.org/mailman/listinfo/rsync Before posting, read: http://www.tuxedo.org/~esr/faqs/smart-questions.html