From 0d1e05c7e17b248032396509f6537f748d8eb6b6 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Mon, 25 Apr 2011 20:21:37 +0530 Subject: [PATCH] "hooklets" -- play nice with any number of site-local 'update' hooks (yes, I made up the name. Deal with it!) --- contrib/update.detect-dup-pubkeys | 74 ++++++++++++++++++++++++++++ contrib/update.email-check | 4 +- doc/2-admin.mkd | 4 ++ hooks/common/update | 12 ++--- hooks/common/update.secondary.sample | 44 +++++++++++++++++ 5 files changed, 129 insertions(+), 9 deletions(-) create mode 100755 contrib/update.detect-dup-pubkeys create mode 100644 hooks/common/update.secondary.sample diff --git a/contrib/update.detect-dup-pubkeys b/contrib/update.detect-dup-pubkeys new file mode 100755 index 0000000..a32db7e --- /dev/null +++ b/contrib/update.detect-dup-pubkeys @@ -0,0 +1,74 @@ +#!/bin/bash + +# update "hooklet" to detect duplicate public keys + +# This particular hooklet also serves as an example for people writing others. +# [It should be quite easy to figure out what parts apply to any hooklet and +# what parts are specific to *this* hooklet and its function.] + +# see hooks/common/update.secondary.sample for instructions on *enabling* +# hooklets + +# a hooklet is called as follows: +# git-receive-pack --> 'update' --> 'update.secondary' --> this script +# note: the same three arguments that git passes to the update hook are passed +# along to each hooklet. + +# the update hook, and therefore the hooklets, are called for *every* repo out +# there. If you want this hooklet to run only for certain repos, here's how: +[ "$GL_REPO" = "gitolite-admin" ] || exit 0 + +# superfluous, since update.secondary already did it, but I'd like to +# emphasise that all output MUST go to STDERR +exec >&2 + +# ---- + +# the main functionality of the hooklet starts here. In this one (and I +# suspect many others) we want to examine the actual files from the commit +# that was pushed. + +# get the tip commit being pushed +sha=$3 + +# git sets this; and we don't want it at this point... +unset GIT_DIR + +# paranoia +set -e + +# setup the temp area +export TMPDIR=$GL_REPO_BASE_ABS +export tmp=$(mktemp -d -t gl-internal-temp-repo.XXXXXXXXXX); +trap "rm -rf $tmp" EXIT; + +# now get the files into $tmp. + # (note: if your task does not require the actual files, and you can + # manage with "git cat-file -s" and so on, then you may not even need a + # $tmp; you may be able to do it all right in the repo.git directory) + +git archive $sha keydir | tar -C $tmp -xf - + # DO NOT try, say, 'GIT_WORK_TREE=$tmp git checkout $sha'. It'll screw up + # both the 'index' and 'HEAD' of the repo.git. Screwing up the index is + # BAD because now it goes out of sync with $GL_ADMINDIR. Think of a push + # that had a deleted pubkey but failed a hooklet for some reason. A + # subsequent push that fixes the error will now result in a $GL_ADMINDIR + # that still *has* that deleted pubkey!! + + # And this is equally applicable to cases where you're using a + # post-receive or similar hook to live update a web site or something, + # which is a pretty common usage, I am given to understand. + +cd $tmp + +# ---- + +# *finally*, the actual check you need to do in this hook: look for duplicate +# pubkeys and exit 1 if dups are found +for f in `find keydir -name "*.pub"` +do + ssh-keygen -l -f "$f" +done | perl -ane ' + die "$F[2] is a duplicate of $seen{$F[1]}\n" if $seen{$F[1]}; + $seen{$F[1]} = $F[2]; +' diff --git a/contrib/update.email-check b/contrib/update.email-check index 10f6a27..7610444 100755 --- a/contrib/update.email-check +++ b/contrib/update.email-check @@ -4,9 +4,7 @@ # Gitolite specific script to check "author email" field of every commit # pushed and to disallow if this email does not match the email that the - # user pushing is expected to have. Needs to be renamed to - # "update.secondary"; see gitolite docs for how to propagate this to all - # repos. + # user pushing is expected to have. # Use without gitolite is also possible; just substitute your access # control system's notion of "user" for the env var GL_USER in the code diff --git a/doc/2-admin.mkd b/doc/2-admin.mkd index 253e11f..e749d81 100644 --- a/doc/2-admin.mkd +++ b/doc/2-admin.mkd @@ -148,6 +148,10 @@ if such a hook exists. People wishing to do exotic things on the server side when the admin repo is pushed should see doc/shell-games.notes for how to exploit this :-) +In addition, gitolite now contains the basic infrastructure to support +multiple such hooks without having to remember to chain them yourself. You +can look in `hooks/common/update.secondary.sample` for instructions. + Finally, these names (`update.secondary` and `post-update.secondary`) are merely the defaults. You can change them to anything you want; look in conf/example.gitolite.rc for details. diff --git a/hooks/common/update b/hooks/common/update index b8a4ec0..c264d22 100755 --- a/hooks/common/update +++ b/hooks/common/update @@ -86,14 +86,14 @@ if (exists $repos{$ENV{GL_REPO}}{NAME_LIMITS}) { my $log_refex = check_ref(\@allowed_refs, $ENV{GL_REPO}, (shift @refs), $att_acc); check_ref (\@allowed_refs, $ENV{GL_REPO}, $_ , $att_acc) for @refs; -# if we returned at all, all the checks succeeded, so we log the action and exit 0 +# if we returned at all, all the checks succeeded. Check secondary hooks now +$UPDATE_CHAINS_TO ||= 'hooks/update.secondary'; +( -f $UPDATE_CHAINS_TO or -l $UPDATE_CHAINS_TO ) + and system ( $UPDATE_CHAINS_TO, @ARGV ) + and die "$UPDATE_CHAINS_TO died\n"; +# now log it and exit 0 so git can get on with it log_it("", "$att_acc\t" . substr($oldsha, 0, 14) . "\t" . substr($newsha, 0, 14) . "\t$reported_repo\t$ref\t$log_refex"); -# now chain to the local admin defined update hook, if present -$UPDATE_CHAINS_TO ||= 'hooks/update.secondary'; -exec $UPDATE_CHAINS_TO, @ARGV - if -f $UPDATE_CHAINS_TO or -l $UPDATE_CHAINS_TO; - exit 0; diff --git a/hooks/common/update.secondary.sample b/hooks/common/update.secondary.sample new file mode 100644 index 0000000..c7d9f92 --- /dev/null +++ b/hooks/common/update.secondary.sample @@ -0,0 +1,44 @@ +#!/bin/bash + +# driver script to run multiple update "hooklets". Each "hooklet" performs a +# specific (possibly site-local) check, and they *all* have to succeed for the +# push to succeed. + +# HOW TO USE: + +# (1) clone gitolite as you would for an upgrade (or install) +# (2) rename this file to remove the .sample extension +# (3) make the renamed file executable (chmod +x) +# (4) create a directory called update.secondary.d in hooks/common +# (5) copy all the update "hooklets" you want (like update.detect-dup-pubkeys) +# from contrib or wherever, to that directory +# (6) make them also executable (chmod +x) +# (7) install/upgrade gitolite as normal so all these files go to the server + +# rules for writing a hooklet are in the sample hooklet called +# "update.detect-dup-pubkeys" in contrib + +# ---- + +# NOTE: a hooklet runs under the same assumptions as the 'update' hook, so the +# starting directory must be maintained and arguments must be passed on. + +[ -d hooks/update.secondary.d ] || exit 0 + +# all output from these "hooklets" must go to STDERR to avoid confusing the client +exec >&2 + +for i in hooks/update.secondary.d/* +do + [ -x "$i" ] || continue + # call the hooklet with the same arguments we got + "$i" "$@" || { + # hooklet failed; we need to log it... + echo hooklet $i failed + perl -I$GL_BINDIR -Mgitolite -e "log_it('hooklet $i failed')" + # ...and send back some non-zero exit code ;-) + exit 1 + } +done + +exit 0