Posted by & filed under Uncategorized.

Normally, you can add a users public key to an authorized_keys file to allow ssh public key authentication. Installing Gitolite, I learned that there’s an optional way to configure this file that lets you restrict a user to certain commands. Gitolite extends this to allow for multiple virtual users too. That is pretty easy to do, but it requires your application, or the restricted shell itself, to manage the users and permissions.

Adding this to the beginning of the line in authorized_keys will get you started:

command="./restrict-commands.pl",no-port-forwarding,no-x11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAA....

This tells ssh to allow public key authentication and to run the command “restrict-commands.pl” in the home directory of the user.

To make this work for a virtual user, both user1 and user2 will be logging into the same real system user, but public key belonging to user1 will run the restrict-commands.pl with an argument of “user1”, like this:

command="./restrict-commands.pl user1",no-port-forwarding,no-x11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAA....

And, there would be another line in the authorized_keys file with user2’s public key.

Below is a rough example of a restrict-commands.pl script. It should be installed in the home directory of the real system user.


#!/usr/bin/perl
# commands restricted for ssh-key access
#
use strict;
use warnings;
use Capture::Tiny ':all';
my $soc = '';
if (!$ENV{SSH_ORIGINAL_COMMAND}){
        print "Sorry. No shell here for you.\n";
        exit(1);
}
$soc = $ENV{SSH_ORIGINAL_COMMAND};
$soc =~ s/[\n\r]+/<<newline>>/g;
die "Sorry, newlines not allowed in command\n" if $ENV{SSH_ORIGINAL_COMMAND} ne $soc;

# gitolite uses this to create virtual users - these users don't need to be in the password file. 
# They only need to have keys in 
# authorized_keys file with the username specified. They access the system as 
# user "git" with a virtual user specific ssh-key.
# The key determines the user and the application restricts the user to certain commands. 
# The syntax for the authorized key file is:
# command="./restrict-commands.pl virtusername",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty
# To implement something like this, the user provides their key and their 
# "virtual" username is generated from their email address or whatever.
# The virtualusername is added after the command in authorized_keys as in the above example. 
#
my $user = $ARGV[0];
# print "virt user: $user\n";
# then we can control what command(s) user $user can run. 
# todo - find some use for this :-)

# Control (security) can use a whitelist regex to check the contents of $ENV{SSH_ORIGINAL_COMMAND}
# note pipe '|' is not allowed, nor is ; or >< or other dangerous stuff
# This isn't a good fit for rsync or complex commands that need lots of escaping.
# This allows: - / _ : @ space a-zA-Z0-9 

unless ($ENV{SSH_ORIGINAL_COMMAND} =~ /^([-:\@\s\/\w._]+)$/) {
        print "Error: Command not permitted in restricted environment.\n";
        exit (1);
}
# run the command, and capture std error. Clean it up so it doesn't show the name of this script
my $stderr = capture_stderr { system($ENV{SSH_ORIGINAL_COMMAND}) };
$stderr =~ s/\ at .\/restrict-commands\.pl line 38//g; # yes, I know the perl gods hate me now.
if($stderr){ print "$stderr"; }