Friday, July 22, 2011

using ssh to restrict commands by key

as we all know, if you have ssh access to a box, you can run arbitrary commands via one liners using the -C switch. neat. or not.
but! you can also have the sysadminly joy of restricting what an ssh client connection can do if you do not want said connection to have unfettered shell access.

anyway. here was the probem:
one day i was getting sort of tired of renaming pems and moving them around between systems. believe me, it was a drag. i wanted a one-liner, so i made one.

i plopped an ssh key for the robot account (robot@mordor) via which i wanted to do all these mundane tasks and then prepended its definition in authorized_keys2 on the client box with the a specific command string.

here's a snippet of authorized_keys2:
command="/usr/local/bin/secretcommands $SSH_ORIGINAL_COMMAND" ssh-rsa == robot@mordor

what this does is restrict robot@mordor to only be able to issue that specific command. no interactive shell, no nothing, just the command. but. in my case, i really needed that robot to be able to do more than one thing. so! i made my command a menu of choices - secretcommands. now, robot@mordor can do only what is on the menu of choices - like look at messages or rename things.

to allow robot@mordor to scp things, i needed to also explicitly place the client system's scp command in the same authorized_keys2 file:
command="/root/bin/scp-wrapper" ssh-rsa == robot@mordor

here's secretcommands:
#!/bin/sh
; secretcommands
case "$1" in
 messages)
  /bin/cat /var/log/messages
  ;;
 delicious)
  /usr/local/bin/rename.sh /etc/secretplaceforsecretthings/crl.pem
  ;;
 help)
  echo 'messages and delicious' 1>&2
  exit 1
 ;;
 *)
  echo 'please do not do that' 1>&2
  exit 1
  ;;
esac

here's the scp command per the snail book:
#!/usr/bin/env perl
# from http://www.snailbook.com/faq/restricted-scp.auto.html

# location of the server-side scp we want to run
$scp_server = "/usr/bin/scp";

sub fail {
    my ($msg) = @_;
    print STDERR "$0: ", $msg, "\n";
    exit 1;
}

# This just makes me feel better.

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

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

($command = $ENV{SSH_ORIGINAL_COMMAND})
    || fail "environment variable SSH_ORIGINAL_COMMAND not set";

# Split the command string to make an argument list, and remove the first
# element (the command name; we'll supply our own);

@scp_argv = split /[ \t]+/, $command;

# Complain if the command is not "scp".

fail "account restricted: only scp allowed (\"$scp_argv[0]\")"
    unless $scp_argv[0] eq "scp";

# Wipe the environment as a security precaution.  This might conceivably
# break something, but if it does you can filter the environment more
# selectively here.

%ENV = ();

# Ensure that either -t or -f is on the command line, to enforce running
# scp in server mode.

$ok = $FALSE;
foreach $arg (@scp_argv) {
    if ($arg eq '-t' || $arg eq '-f') {
        $ok = $TRUE;
        last;
    }
}

fail "Restricted; only server mode allowed."
    unless $ok;

# if we're OK, run our desired "scp" with arguments.

shift(@scp_argv);
exec($scp_server, @scp_argv);

the rename script in secretcommands:
#!/bin/sh
# rename.sh
for file in ${@}; do
    if [ -e $file ]; then
        timestamp=`ls -l --time-style=+%Y%M%d-%k%M%S $file |cut -d' ' -f6`
        basename=`echo $file|cut -d'.' -f1`
        ext=`echo $file | cut  -d'.' -f2-`
        mv -v $file $basename.$ext.$timestamp
    fi
done

here's what it would look like in a one liner:
mordor$ /usr/bin/ssh -i /home/robot/.ssh/id_rsa_nothere.secretcommand -l root nothere delicious
delicious.

No comments: