gitolite/src/gl-auth-command

206 lines
7.7 KiB
Plaintext
Raw Normal View History

2009-08-25 05:14:46 +02:00
#!/usr/bin/perl
2009-08-23 14:54:37 +02:00
use strict;
2009-08-25 05:14:46 +02:00
use warnings;
2009-08-23 14:54:37 +02:00
# === auth-command ===
# the command that GL users actually run
2009-08-26 02:47:27 +02:00
# part of the gitolite (GL) suite
2009-08-23 14:54:37 +02:00
# how run: via sshd, being listed in "command=" in ssh authkeys
# when: every login by a GL user
# input: $1 is GL username, plus $SSH_ORIGINAL_COMMAND
# output:
# security:
# - currently, we just make some basic checks, copied from gitosis
# robustness:
# other notes:
# ----------------------------------------------------------------------------
# common definitions
# ----------------------------------------------------------------------------
# these are set by the "rc" file
our ($GL_LOGT, $GL_CONF_COMPILED, $REPO_BASE, $GIT_PATH, $REPO_UMASK, $GL_ADMINDIR, $RSYNC_BASE, $HTPASSWD_FILE, $GL_WILDREPOS);
# and these are set by gitolite.pm
our ($R_COMMANDS, $W_COMMANDS, $REPONAME_PATT, $REPOPATT_PATT);
2009-08-23 14:54:37 +02:00
our %repos;
# the common setup module is in the same directory as this running program is
my $bindir = $0;
$bindir =~ s/\/[^\/]+$//;
2010-02-09 12:30:01 +01:00
$bindir = "$ENV{PWD}/$bindir" unless $bindir =~ /^\//;
require "$bindir/gitolite.pm";
# ask where the rc file is, get it, and "do" it
&where_is_rc();
die "parse $ENV{GL_RC} failed: " . ($! or $@) unless do $ENV{GL_RC};
2009-08-23 14:54:37 +02:00
# we need to pass GL_ADMINDIR and the bindir to the child hooks
$ENV{GL_ADMINDIR} = $GL_ADMINDIR;
$ENV{GL_BINDIR} = $bindir;
# add a custom path for git binaries, if specified
$ENV{PATH} .= ":$GIT_PATH" if $GIT_PATH;
# set the umask before creating any files
umask($REPO_UMASK);
my $repo_base_abs = ( $REPO_BASE =~ m(^/) ? $REPO_BASE : "$ENV{HOME}/$REPO_BASE" );
2009-08-23 14:54:37 +02:00
# ----------------------------------------------------------------------------
# start...
# ----------------------------------------------------------------------------
# if the first argument is a "-s", this user is allowed to get a shell using
# this key
my $shell_allowed = 0;
if ($ARGV[0] eq '-s') {
$shell_allowed = 1;
shift;
}
2009-08-23 14:54:37 +02:00
# first, fix the biggest gripe I have with gitosis, a 1-line change
my $user=$ENV{GL_USER}=shift; # there; now that's available everywhere!
2010-02-01 12:24:39 +01:00
# ----------------------------------------------------------------------------
# logging, timestamp env vars
# ----------------------------------------------------------------------------
# timestamp
my ($s, $min, $h, $d, $m, $y) = (localtime)[0..5];
$y += 1900; $m++; # usual adjustments
for ($s, $min, $h, $d, $m) {
$_ = "0$_" if $_ < 10;
}
$ENV{GL_TS} = "$y-$m-$d.$h:$min:$s";
# substitute template parameters and set the logfile name
$GL_LOGT =~ s/%y/$y/g;
$GL_LOGT =~ s/%m/$m/g;
$GL_LOGT =~ s/%d/$d/g;
$ENV{GL_LOG} = $GL_LOGT;
2009-08-23 14:54:37 +02:00
# ----------------------------------------------------------------------------
# sanity checks on SSH_ORIGINAL_COMMAND
# ----------------------------------------------------------------------------
# no SSH_ORIGINAL_COMMAND given...
unless ($ENV{SSH_ORIGINAL_COMMAND}) {
# if the user is allowed to use a shell, give him one
if ($shell_allowed) {
my $shell = $ENV{SHELL};
$shell =~ s/.*\//-/; # change "/bin/bash" to "-bash"
exec { $ENV{SHELL} } $shell;
}
# otherwise, pretend he typed in "info" and carry on...
$ENV{SSH_ORIGINAL_COMMAND} = 'info';
}
# ----------------------------------------------------------------------------
# get and set perms for actual repo created by wildcard-autoviv
# ----------------------------------------------------------------------------
my $CUSTOM_COMMANDS=qr/^\s*(expand|(get|set)(perms|desc))\b/;
# note that all the subs called here chdir somewhere else and do not come
# back; they all blithely take advantage of the fact that processing custom
# commands is sort of a dead end for normal (git) processing
if ($ENV{SSH_ORIGINAL_COMMAND} =~ $CUSTOM_COMMANDS) {
die "wildrepos disabled, sorry\n" unless $GL_WILDREPOS;
my $cmd = $ENV{SSH_ORIGINAL_COMMAND};
my ($verb, $repo) = ($cmd =~ /^\s*(\S+)(?:\s+'?\/?(.*?)(?:\.git)?'?)?$/);
# deal with "no argument" cases
$verb eq 'expand' ? $repo = '^' : die "$verb needs an argument\n" unless $repo;
if ($repo =~ $REPONAME_PATT and $verb =~ /getperms|setperms/) {
# with an actual reponame, you can "getperms" or "setperms"
get_set_perms($repo_base_abs, $repo, $verb, $user);
}
elsif ($repo =~ $REPONAME_PATT and $verb =~ /(get|set)desc/) {
# with an actual reponame, you can "getdesc" or "setdesc"
get_set_desc($repo_base_abs, $repo, $verb, $user);
}
elsif ($verb eq 'expand') {
# with a wildcard, you can "expand" it to see what repos actually match
die "$repo has invalid characters" unless "x$repo" =~ $REPOPATT_PATT;
wildrepos: expanded access reporting This feature has *no* warranty, and so no documentation. Not more than this transcript anyway. config file: @prof = u1 @TAs = u2 u3 @students = u4 u5 u6 repo assignments/CREATER/a[0-9][0-9] C = @students RW+ = CREATER RW = WRITERS @TAs R = READERS @prof session: as user "u4": # check your permissions $ ssh git@server PTY allocation request failed on channel 0 hello u4, the gitolite version here is v0.95-31-gbcb14ca you have the following permissions: C assignments/CREATER/a[0-9][0-9] @ @ testing Connection to localhost closed. # autovivify repos for assignment 12 and 24 $ git clone git@server:assignments/u4/a12 a12 Initialized empty Git repository in /home/sitaram/t/a12/.git/ Initialized empty Git repository in /home/gitolite/repositories/assignments/u4/a12.git/ warning: You appear to have cloned an empty repository. $ git clone git@server:assignments/u4/a24 a24 Initialized empty Git repository in /home/sitaram/t/a24/.git/ Initialized empty Git repository in /home/gitolite/repositories/assignments/u4/a24.git/ warning: You appear to have cloned an empty repository. # check what repos you autovivified $ ssh git@server expand assignments/u4/a[0-9][0-9] (u4) assignments/u4/a12 (u4) assignments/u4/a24 as user "u5": # check your basic permissions $ ssh git@server PTY allocation request failed on channel 0 hello u5, the gitolite version here is v0.95-31-gbcb14ca you have the following permissions: C assignments/CREATER/a[0-9][0-9] @ @ testing Connection to localhost closed. # see if you have access to any of u4's repos $ ssh git@server expand assignments/u4/a[0-9][0-9] # (no output produced) as user "u4": # allow "u5" read access to assignment 12 # note you type in "R u5", hit enter, then hit Ctrl-D. Gitolite # then produces a confirmation message starting "New perms are:" $ ssh git@server setperms assignments/u4/a12 R u5 New perms are: R u5 as user "u5": # again see if you have access to any u4 repos $ ssh git@server expand assignments/u4/a[0-9][0-9] (u4) assignments/u4/a12 as user "u4": # check what permissions you gave to assignment 12 $ ssh git@server getperms assignments/u4/a12 R u5 # add RW access to "u6" to assignment 12 # again, type 'em in, then hit Ctrl-D; and note each time you run # this you're starting from scratch -- you can't "add to" the # permissions. Deal with it... $ ssh git@server setperms assignments/u4/a12 R u5 RW u6 New perms are: R u5 RW u6 as user "u6": # check what u4 stuff you have access to $ ssh git@server expand assignments/u4/a[0-9][0-9] (u4) assignments/u4/a12
2009-12-06 10:56:53 +01:00
expand_wild($GL_CONF_COMPILED, $repo_base_abs, $repo, $user);
} else {
die "$cmd doesn't make sense to me\n";
}
exit 0;
}
# ----------------------------------------------------------------------------
# non-git commands
# ----------------------------------------------------------------------------
2009-08-23 14:54:37 +02:00
# if the command does NOT fit the pattern of a normal git command, send it off
# somewhere else...
2009-08-23 14:54:37 +02:00
# side notes on detecting a normal git command: the pattern we check allows
# old style as well as new style ("git-subcommand arg" or "git subcommand
# arg"), just like gitosis does, although I'm not sure how necessary that is.
# Currently, this is how git sends across the command (including the single
# quotes):
# git-receive-pack 'reponame.git'
2009-08-23 14:54:37 +02:00
my ($verb, $repo) = ($ENV{SSH_ORIGINAL_COMMAND} =~ /^\s*(git\s+\S+|\S+)\s+'\/?(.*?)(?:\.git)?'/);
unless ( $verb and ( $verb =~ $R_COMMANDS or $verb =~ $W_COMMANDS ) and $repo and $repo =~ $REPONAME_PATT ) {
# ok, it's not a normal git command; call the special command helper
&special_cmd ($GL_ADMINDIR, $GL_CONF_COMPILED, $shell_allowed, $RSYNC_BASE, $HTPASSWD_FILE);
exit;
}
die "$repo ends with a slash; I don't like that\n" if $repo =~ /\/$/;
die "$repo has two consecutive periods; I don't like that\n" if $repo =~ /\.\./;
2009-08-23 14:54:37 +02:00
# reponame
$ENV{GL_REPO}=$repo;
2009-08-23 14:54:37 +02:00
# ----------------------------------------------------------------------------
# the real git commands (git-receive-pack, etc...)
# ----------------------------------------------------------------------------
2009-08-23 14:54:37 +02:00
# ----------------------------------------------------------------------------
# first level permissions check
# ----------------------------------------------------------------------------
if ( -d "$repo_base_abs/$repo.git" ) {
# existing repo
my ($creater, $user_R, $user_W) = &repo_rights($repo_base_abs, $repo, $user);
&parse_acl($GL_CONF_COMPILED, $repo, $creater, $user_R, $user_W);
} else {
&parse_acl($GL_CONF_COMPILED, $repo, $user, "NOBODY", "NOBODY");
# auto-vivify new repo if you have C access (and wildrepos is on)
if ( $GL_WILDREPOS and $repos{$repo}{C}{$user} || $repos{$repo}{C}{'@all'} ) {
wrap_chdir("$repo_base_abs");
new_repo($repo, "$GL_ADMINDIR/hooks/common", $user);
wrap_chdir($ENV{HOME});
auth, gitolite.pm: do not leak info about repo existence All this is about a user trying to look if a repo exists or not, when he does not have any access to that repo. Ideally, "repo does not exist" should be indistinguishable from "you dont have perms to that repo". (1) if $GL_WILDREPOS is not set, you either get a permissions error, or a "$repo not found in compiled config" death. Fixed. (2) if $GL_WILDREPOS is set, you either get either a permissions error, or a "$repo has no matches" death. Fixed. (3) The following combination leaks info about repo existence: - actual repo doesn't exist - spying user don't have C perms - repo patt doesn't contain CREATER - RW+ = CREATER is specified (as is normal) In such case, the "convenience copy" of the ACL that parse_acl makes, coupled with substituting CREATER for the invoking user means $repos{$actual_repo} has RW+ for the spying user. This means the access denied doesn't happen, and control passes to git, which promptly expresses it unhappiness and angst over being given a repo that 'does not appear to be a git repository' This doesn't happen if all those conditions are not met: - if repo exists, CREATER is set to the real creater, so RW+ = CREATER does not gain spying user anything - if spying user has C perms it just gets created, because he has rights. This is also info leak but we can't prevent it; tighten the config (maybe by including CREATER in repo pattern) if this is not wanted - if repo patt contains CREATER it will never match someone else's repo anyway!
2010-03-28 20:14:37 +02:00
} else {
# repo didn't exist, and you didn't have perms to create it. Delete
# the "convenience" copy of the ACL that parse_acl makes for us
delete $repos{$repo};
}
}
# we know the user and repo; we just need to know what perm he's trying
my $perm = ($verb =~ $R_COMMANDS ? 'R' : 'W');
die "$perm access for $repo DENIED to $user\n"
unless $repos{$repo}{$perm}{$user}
or $repos{'@all'}{$perm}{$user} # new: access to @all repos
or $repos{$repo}{$perm}{'@all'};
2009-08-23 14:54:37 +02:00
# ----------------------------------------------------------------------------
2010-02-01 12:24:39 +01:00
# over to git now
2009-08-23 14:54:37 +02:00
# ----------------------------------------------------------------------------
&log_it("$ENV{GL_TS}\t$ENV{SSH_ORIGINAL_COMMAND}\t$user\n");
2009-08-23 14:54:37 +02:00
$repo = "'$REPO_BASE/$repo.git'";
exec("git", "shell", "-c", "$verb $repo");