(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:
parent
7f203fc020
commit
98a4c79dce
7 changed files with 132 additions and 3 deletions
|
@ -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;
|
||||
|
|
|
@ -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 :-)
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue