diff --git a/conf/example.gitolite.rc b/conf/example.gitolite.rc index cf7fe3d..bcb281f 100644 --- a/conf/example.gitolite.rc +++ b/conf/example.gitolite.rc @@ -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 # # base path of all the files that are accessible via rsync. Must be an diff --git a/doc/3-faq-tips-etc.mkd b/doc/3-faq-tips-etc.mkd index 67ef956..6723000 100644 --- a/doc/3-faq-tips-etc.mkd +++ b/doc/3-faq-tips-etc.mkd @@ -331,9 +331,25 @@ This requires that: * the HTTP auth should use the same username (like "sitaram") as used in the gitolite config (for the corresponding user) -Once that is done, it's easy. Gitweb allows you to specify a subroutine to -decide on access. We use that feature and tie it to gitolite. Sample code -(untested, munged from something I saw [here][leho]) is given below. +Normally a superuser sets up passwords for users using the "htpasswd" command, +but this is an administrative chore. + +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 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/'; 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; # ---------- @@ -359,10 +375,11 @@ already done and we just use it! 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 - # 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 { 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\/?//; # ...and the end, to get the repo name as it is specified in gitolite conf $reponame =~ s/\.git$//; diff --git a/src/gitolite.pm b/src/gitolite.pm index c6d75de..a8127e9 100644 --- a/src/gitolite.pm +++ b/src/gitolite.pm @@ -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 - if ($RSYNC_BASE and $cmd =~ /^rsync /) { + my $cmd = $ENV{SSH_ORIGINAL_COMMAND}; + 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); } 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"; } } @@ -234,5 +245,30 @@ sub ext_cmd_rsync 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 <; + $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; diff --git a/src/gl-auth-command b/src/gl-auth-command index 8fae9f5..8aa2f65 100755 --- a/src/gl-auth-command +++ b/src/gl-auth-command @@ -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, $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 our ($R_COMMANDS, $W_COMMANDS, $REPONAME_PATT); our %repos; @@ -97,31 +97,31 @@ unless ($ENV{SSH_ORIGINAL_COMMAND}) { $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 -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; -} +# ---------------------------------------------------------------------------- +# non-git commands +# ---------------------------------------------------------------------------- -# split into command and arguments; the pattern 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 -# -# keep in mind this is how git sends across the command: -# git-receive-pack 'reponame.git' -# including the single quotes +# if the command does NOT fit the pattern of a normal git command, send it off +# somewhere else... -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 ) { - # if the user is allowed a shell, just run the command - exec $ENV{SHELL}, "-c", $ENV{SSH_ORIGINAL_COMMAND} if $shell_allowed; - # otherwise, call the external command helper - &ext_cmd($GL_CONF_COMPILED, $RSYNC_BASE, $cmd); - exit; # in case the external command helper forgot :-) + # ok, it's not a normal git command; call the special command helper + &special_cmd ($GL_ADMINDIR, $GL_CONF_COMPILED, $RSYNC_BASE, $HTPASSWD_FILE); + exit; } +# ---------------------------------------------------------------------------- +# the real git commands (git-receive-pack, etc...) +# ---------------------------------------------------------------------------- + # ---------------------------------------------------------------------------- # first level permissions check # ----------------------------------------------------------------------------