(read this in full) access control for non-git commands running over ssh

This is actually a pretty big deal, and I am seriously starting wonder
if calling this "gito*lite*" is justified anymore.

Anyway, in for a penny, in for a pound...

This patch implements a generic way to allow access control for external
commands, as long as they are invoked via ssh and present a server-side
command that contains enough information to make an access control
decision.

The first (and only, so far) such command implemented is rsync.

Please read the changes in this commit (at least the ones in conf/ and
doc/) carefully.
This commit is contained in:
Sitaram Chamarty 2010-01-31 20:24:36 +05:30 committed by Sitaram Chamarty
parent 7f203fc020
commit 98a4c79dce
7 changed files with 132 additions and 3 deletions

View file

@ -156,4 +156,82 @@ sub report_basic
print "$perm\t$r\n\r" if $perm;
}
}
# ----------------------------------------------------------------------------
# E X T E R N A L C O M M A N D H E L P E R S
# ----------------------------------------------------------------------------
sub ext_cmd
{
my ($GL_CONF_COMPILED, $RSYNC_BASE, $cmd) = @_;
# check each external command we know about and call it if enabled
if ($RSYNC_BASE and $cmd =~ /^rsync /) {
&ext_cmd_rsync($GL_CONF_COMPILED, $RSYNC_BASE, $cmd);
} else {
die "bad command: $cmd\n";
}
}
# ----------------------------------------------------------------------------
# generic check access routine
# ----------------------------------------------------------------------------
sub check_access
{
my ($GL_CONF_COMPILED, $repo, $path, $perm) = @_;
my $ref = "NAME/$path";
&parse_acl($GL_CONF_COMPILED);
# until I do some major refactoring (which will bloat the update hook a
# bit, sadly), this code duplicates stuff in the current update hook.
my @allowed_refs;
# we want specific perms to override @all, so they come first
push @allowed_refs, @ { $repos{$repo}{$ENV{GL_USER}} || [] };
push @allowed_refs, @ { $repos{$repo}{'@all'} || [] };
for my $ar (@allowed_refs) {
my $refex = (keys %$ar)[0];
next unless $ref =~ /^$refex/;
die "$perm $ref $ENV{GL_USER} DENIED by $refex\n" if $ar->{$refex} eq '-';
return if ($ar->{$refex} =~ /\Q$perm/);
}
die "$perm $ref $ENV{GL_REPO} $ENV{GL_USER} DENIED by fallthru\n";
}
# ----------------------------------------------------------------------------
# external command helper: rsync
# ----------------------------------------------------------------------------
sub ext_cmd_rsync
{
my ($GL_CONF_COMPILED, $RSYNC_BASE, $cmd) = @_;
# test the command patterns; reject if they don't fit. Rsync sends
# commands that looks like one of these to the server (the first one is
# for a read, the second for a write)
# rsync --server --sender -some.flags . some/path
# rsync --server -some.flags . some/path
die "bad rsync command: $cmd"
unless $cmd =~ /^rsync --server( --sender)? -[\w.]+ \. (\S+)$/;
my $perm = "W";
$perm = "R" if $1;
my $path = $2;
die "I dont like absolute paths in $cmd\n" if $path =~ /^\//;
die "I dont like '..' paths in $cmd\n" if $path =~ /\.\./;
# ok now check if we're permitted to execute a $perm action on $path
# (taken as a refex) using rsync.
&check_access($GL_CONF_COMPILED, 'EXTCMD/rsync', $path, $perm);
# that should "die" if there's a problem
wrap_chdir($RSYNC_BASE);
exec $ENV{SHELL}, "-c", $ENV{SSH_ORIGINAL_COMMAND};
}
1;

View file

@ -24,7 +24,7 @@ use warnings;
# ----------------------------------------------------------------------------
# these are set by the "rc" file
our ($GL_LOGT, $GL_CONF_COMPILED, $REPO_BASE, $GIT_PATH, $REPO_UMASK, $GL_ADMINDIR);
our ($GL_LOGT, $GL_CONF_COMPILED, $REPO_BASE, $GIT_PATH, $REPO_UMASK, $GL_ADMINDIR, $RSYNC_BASE);
# and these are set by gitolite.pm
our ($R_COMMANDS, $W_COMMANDS, $REPONAME_PATT);
our %repos;
@ -99,8 +99,9 @@ my ($verb, $repo) = ($cmd =~ /^\s*(git\s+\S+|\S+)\s+'\/?(.*?)(?:\.git)?'/);
unless ( $verb and ( $verb =~ $R_COMMANDS or $verb =~ $W_COMMANDS ) and $repo and $repo =~ $REPONAME_PATT ) {
# if the user is allowed a shell, just run the command
exec $ENV{SHELL}, "-c", $ENV{SSH_ORIGINAL_COMMAND} if $shell_allowed;
# otherwise, whine
die "bad command: $cmd\n";
# otherwise, call the external command helper
&ext_cmd($GL_CONF_COMPILED, $RSYNC_BASE, $cmd);
exit; # in case the external command helper forgot :-)
}
# ----------------------------------------------------------------------------

View file

@ -355,6 +355,7 @@ my $repo_base_abs = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE"
wrap_chdir("$repo_base_abs");
for my $repo (sort keys %repos) {
next if $repo =~ m(^EXTCMD/); # these are not real repos
unless (-d "$repo.git") {
new_repo($repo, "$GL_ADMINDIR/src/hooks");
# new_repo would have chdir'd us away; come back