auth: new subcommand "htpasswd"

great idea by Robin Smidsrød: since users are already capable of
authenticating themselves to gitolite via ssh keys, use that to let them
set or change their own HTTP passwords (ie, run the "htpasswd" command
with the correct parameters on behalf of the "git" user on the server)

code, rc para, and documentation.  In fact everything except... ahem...
testing ;-)

and while we're about it, we also reorganised the way these helper
commands (including the venerable "info" are called)
This commit is contained in:
Sitaram Chamarty 2010-02-01 15:37:35 +05:30
parent 0a7fa6c6b5
commit 67c10a34fe
4 changed files with 94 additions and 32 deletions

View file

@ -108,6 +108,15 @@ $GIT_PATH="";
# -------------------------------------- # --------------------------------------
# if you want to enable the "htpasswd" command, give this the absolute path to
# whatever file apache (etc) expect to find the passwords in.
$HTPASSWD_FILE = "";
# Look in doc/3 ("easier to link gitweb authorisation with gitolite" section)
# for more details on using this feature.
# --------------------------------------
# EXTERNAL COMMAND HELPER -- RSYNC # EXTERNAL COMMAND HELPER -- RSYNC
# #
# base path of all the files that are accessible via rsync. Must be an # base path of all the files that are accessible via rsync. Must be an

View file

@ -331,9 +331,25 @@ This requires that:
* the HTTP auth should use the same username (like "sitaram") as used in the * the HTTP auth should use the same username (like "sitaram") as used in the
gitolite config (for the corresponding user) gitolite config (for the corresponding user)
Once that is done, it's easy. Gitweb allows you to specify a subroutine to Normally a superuser sets up passwords for users using the "htpasswd" command,
decide on access. We use that feature and tie it to gitolite. Sample code but this is an administrative chore.
(untested, munged from something I saw [here][leho]) is given below.
Robin Smidsrød had the *great* idea that, since each user already has pubkey
access to `git@server`, this gives us a very neat way of using gitolite to let
the users *manage their own HTTP passwords*. Here's how:
* setup apache so that the htaccess file it looks for is owned by the "git"
user
* in the `~/.gitolite.rc` file, look for the variable `$HTPASSWD_FILE` and
point it to this file
* tell your users to type in `ssh git@server htpasswd` to set or change
their HTTP passwords
Here's the rest of how it hangs together.
Gitweb allows you to specify a subroutine to decide on access. We use that
feature and tie it to gitolite. Sample code (untested by me, but others do
use it, munged from something I saw [here][leho]) is given below.
Note the **utter simplicity** of the actual check (just 1 line!). This is an Note the **utter simplicity** of the actual check (just 1 line!). This is an
unexpected piece of luck coming from the decision to keep the config parse unexpected piece of luck coming from the decision to keep the config parse
@ -349,7 +365,7 @@ already done and we just use it!
$projectroot = '/home/git/repositories/'; $projectroot = '/home/git/repositories/';
my $gl_conf_compiled = '/home/git/.gitolite/conf/gitolite.conf-compiled.pm'; my $gl_conf_compiled = '/home/git/.gitolite/conf/gitolite.conf-compiled.pm';
# I assume this gives us the HTTP auth username # I am told this gives us the HTTP auth username
my $username = $cgi->remote_user; my $username = $cgi->remote_user;
# ---------- # ----------
@ -359,10 +375,11 @@ already done and we just use it!
die "parse $gl_conf_compiled failed: " . ($! or $@) unless do $gl_conf_compiled; die "parse $gl_conf_compiled failed: " . ($! or $@) unless do $gl_conf_compiled;
# this is gitweb's mechanism; it calls whatever sub is pointed at by this # this is gitweb's mechanism; it calls whatever sub is pointed at by this
# variable to decide access yes/no # variable to decide access yes/no. Gitweb calls it with one argument
# containing the full path of the repo being accessed
$export_auth_hook = sub { $export_auth_hook = sub {
my $reponame = shift; my $reponame = shift;
# gitweb passes us the full repo path; so we strip the beginning... # take the full path provided, strip the beginning...
$reponame =~ s/\Q$projectroot\E\/?//; $reponame =~ s/\Q$projectroot\E\/?//;
# ...and the end, to get the repo name as it is specified in gitolite conf # ...and the end, to get the repo name as it is specified in gitolite conf
$reponame =~ s/\.git$//; $reponame =~ s/\.git$//;

View file

@ -158,17 +158,28 @@ sub report_basic
} }
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
# E X T E R N A L C O M M A N D H E L P E R S # S P E C I A L C O M M A N D S
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
sub ext_cmd sub special_cmd
{ {
my ($GL_CONF_COMPILED, $RSYNC_BASE, $cmd) = @_; my ($GL_ADMINDIR, $GL_CONF_COMPILED, $RSYNC_BASE, $HTPASSWD_FILE) = @_;
# check each external command we know about and call it if enabled my $cmd = $ENV{SSH_ORIGINAL_COMMAND};
if ($RSYNC_BASE and $cmd =~ /^rsync /) { my $user = $ENV{GL_USER};
# check each special command we know about and call it if enabled
if ($cmd eq 'info') {
&report_basic($GL_ADMINDIR, $GL_CONF_COMPILED, $user);
print "you also have shell access\n\r" if $shell_allowed;
} elsif ($HTPASSWD_FILE and $cmd eq 'htpasswd') {
&ext_cmd_htpasswd($HTPASSWD_FILE);
} elsif ($RSYNC_BASE and $cmd =~ /^rsync /) {
&ext_cmd_rsync($GL_CONF_COMPILED, $RSYNC_BASE, $cmd); &ext_cmd_rsync($GL_CONF_COMPILED, $RSYNC_BASE, $cmd);
} else { } else {
# if the user is allowed a shell, just run the command
exec $ENV{SHELL}, "-c", $cmd if $shell_allowed;
die "bad command: $cmd\n"; die "bad command: $cmd\n";
} }
} }
@ -234,5 +245,30 @@ sub ext_cmd_rsync
exec $ENV{SHELL}, "-c", $ENV{SSH_ORIGINAL_COMMAND}; exec $ENV{SHELL}, "-c", $ENV{SSH_ORIGINAL_COMMAND};
} }
# ----------------------------------------------------------------------------
# external command helper: htpasswd
# ----------------------------------------------------------------------------
sub ext_cmd_htpasswd
{
my $HTPASSWD_FILE = shift;
die "$HTPASSWD_FILE doesn't exist or is not writable\n" unless -w $HTPASSWD_FILE;
$|++;
print <<EOFhtp;
Please type in your new htpasswd at the prompt. You only have to type it once.
NOTE THAT THE PASSWORD WILL BE ECHOED, so please make sure no one is
shoulder-surfing, and make sure you clear your screen as well as scrollback
history after you're done (or close your terminal instance).
EOFhtp
print "new htpasswd:";
my $password = <>;
$password =~ s/[\n\r]*$//;
my $rc = system("htpasswd", "-b", $HTPASSWD_FILE, $ENV{GL_USER}, $password);
die "htpasswd command seems to have failed with $rc return code...\n" if $rc;
}
1; 1;

View file

@ -24,7 +24,7 @@ use warnings;
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
# these are set by the "rc" file # these are set by the "rc" file
our ($GL_LOGT, $GL_CONF_COMPILED, $REPO_BASE, $GIT_PATH, $REPO_UMASK, $GL_ADMINDIR, $RSYNC_BASE); our ($GL_LOGT, $GL_CONF_COMPILED, $REPO_BASE, $GIT_PATH, $REPO_UMASK, $GL_ADMINDIR, $RSYNC_BASE, $HTPASSWD_FILE);
# and these are set by gitolite.pm # and these are set by gitolite.pm
our ($R_COMMANDS, $W_COMMANDS, $REPONAME_PATT); our ($R_COMMANDS, $W_COMMANDS, $REPONAME_PATT);
our %repos; our %repos;
@ -97,31 +97,31 @@ unless ($ENV{SSH_ORIGINAL_COMMAND}) {
$ENV{SSH_ORIGINAL_COMMAND} = 'info'; $ENV{SSH_ORIGINAL_COMMAND} = 'info';
} }
my $cmd = $ENV{SSH_ORIGINAL_COMMAND}; # ----------------------------------------------------------------------------
# people allowed to get a shell can get basic access info by asking nicely # non-git commands
if ($cmd eq 'info') { # ----------------------------------------------------------------------------
&report_basic($GL_ADMINDIR, $GL_CONF_COMPILED, $user);
print "you also have shell access\n\r" if $shell_allowed;
exit 1;
}
# split into command and arguments; the pattern allows old style as well as # if the command does NOT fit the pattern of a normal git command, send it off
# new style: "git-subcommand arg" or "git subcommand arg", just like gitosis # somewhere else...
# does, although I'm not sure how necessary that is
#
# keep in mind this is how git sends across the command:
# git-receive-pack 'reponame.git'
# including the single quotes
my ($verb, $repo) = ($cmd =~ /^\s*(git\s+\S+|\S+)\s+'\/?(.*?)(?:\.git)?'/); # 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'
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 ) { 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 # ok, it's not a normal git command; call the special command helper
exec $ENV{SHELL}, "-c", $ENV{SSH_ORIGINAL_COMMAND} if $shell_allowed; &special_cmd ($GL_ADMINDIR, $GL_CONF_COMPILED, $RSYNC_BASE, $HTPASSWD_FILE);
# otherwise, call the external command helper exit;
&ext_cmd($GL_CONF_COMPILED, $RSYNC_BASE, $cmd);
exit; # in case the external command helper forgot :-)
} }
# ----------------------------------------------------------------------------
# the real git commands (git-receive-pack, etc...)
# ----------------------------------------------------------------------------
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
# first level permissions check # first level permissions check
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------