Documentation: shared repository management in tutorial.

The branch policy script I outlined was improved and polished by
Carl and posted on the list twice since then.  It is a shame not
to pick it up, so replace the original outline in
howto/update-hook-example.txt with the latest from Carl.

Also talk about setting up git-shell to allow git-push/git-fetch
only SSH access to a shared repository host in the tutorial.

Signed-off-by: Junio C Hamano <junkio@cox.net>
This commit is contained in:
Junio C Hamano 2005-12-05 00:57:48 -08:00
parent eb0362a467
commit dc5f9239f7
2 changed files with 167 additions and 65 deletions

View file

@ -1,4 +1,4 @@
From: Junio C Hamano <junkio@cox.net>
From: Junio C Hamano <junkio@cox.net> and Carl Baldwin <cnb@fc.hp.com>
Subject: control access to branches.
Date: Thu, 17 Nov 2005 23:55:32 -0800
Message-ID: <7vfypumlu3.fsf@assigned-by-dhcp.cox.net>
@ -26,63 +26,137 @@ section of the documentation:
So if your policy is (1) always require fast-forward push
(i.e. never allow "git-push repo +branch:branch"), (2) you
have a list of users allowed to update each branch, and (3) you
do not let tags to be overwritten, then:
do not let tags to be overwritten, then you can use something
like this as your hooks/update script.
#!/bin/sh
# This is a sample hooks/update script, written by JC
# in his e-mail buffer, so naturally it is not tested
# but hopefully would convey the idea.
[jc: editorial note. This is a much improved version by Carl
since I posted the original outline]
umask 002
case "$1" in
refs/tags/*)
# No overwriting an existing tag
if test -f "$GIT_DIR/$1"
then
exit 1
fi
;;
refs/heads/*)
# No rebasing or rewinding
if expr "$2" : '0*$' >/dev/null
then
# creating a new branch
;
else
# updating -- make sure it is a fast forward
mb=`git-merge-base "$2" "$3"`
case "$mb,$2" in
"$2,$mb")
;; # fast forward -- happy
*)
exit 1 ;; # unhappy
esac
fi
;;
*)
# No funny refs allowed
exit 1
;;
esac
-- >8 -- beginning of script -- >8 --
# Is the user allowed to update it?
me=`id -u -n` ;# e.g. "junio"
while read head_pattern users
do
if expr "$1" : "$head_pattern" >/dev/null
then
case " $users " in
*" $me "*)
exit 0 ;; # happy
' * ')
exit 0 ;; # anybody
esac
fi
done
exit 1
#!/bin/bash
For the sake of simplicity, I assumed that you keep something
like this in $GIT_DIR/info/allowed-pushers file:
umask 002
# If you are having trouble with this access control hook script
# you can try setting this to true. It will tell you exactly
# why a user is being allowed/denied access.
verbose=false
# Default shell globbing messes things up downstream
GLOBIGNORE=*
function grant {
$verbose && echo >&2 "-Grant- $1"
echo grant
exit 0
}
function deny {
$verbose && echo >&2 "-Deny- $1"
echo deny
exit 1
}
function info {
$verbose && echo >&2 "-Info- $1"
}
# Implement generic branch and tag policies.
# - Tags should not be updated once created.
# - Branches should only be fast-forwarded.
case "$1" in
refs/tags/*)
[ -f "$GIT_DIR/$1" ] &&
deny >/dev/null "You can't overwrite an existing tag"
;;
refs/heads/*)
# No rebasing or rewinding
if expr "$2" : '0*$' >/dev/null; then
info "The branch '$1' is new..."
else
# updating -- make sure it is a fast forward
mb=$(git-merge-base "$2" "$3")
case "$mb,$2" in
"$2,$mb") info "Update is fast-forward" ;;
*) deny >/dev/null "This is not a fast-forward update." ;;
esac
fi
;;
*)
deny >/dev/null \
"Branch is not under refs/heads or refs/tags. What are you trying to do?"
;;
esac
# Implement per-branch controls based on username
allowed_users_file=$GIT_DIR/info/allowed-users
username=$(id -u -n)
info "The user is: '$username'"
if [ -f "$allowed_users_file" ]; then
rc=$(cat $allowed_users_file | grep -v '^#' | grep -v '^$' |
while read head_pattern user_patterns; do
matchlen=$(expr "$1" : "$head_pattern")
if [ "$matchlen" == "${#1}" ]; then
info "Found matching head pattern: '$head_pattern'"
for user_pattern in $user_patterns; do
info "Checking user: '$username' against pattern: '$user_pattern'"
matchlen=$(expr "$username" : "$user_pattern")
if [ "$matchlen" == "${#username}" ]; then
grant "Allowing user: '$username' with pattern: '$user_pattern'"
fi
done
deny "The user is not in the access list for this branch"
fi
done
)
case "$rc" in
grant) grant >/dev/null "Granting access based on $allowed_users_file" ;;
deny) deny >/dev/null "Denying access based on $allowed_users_file" ;;
*) ;;
esac
fi
allowed_groups_file=$GIT_DIR/info/allowed-groups
groups=$(id -G -n)
info "The user belongs to the following groups:"
info "'$groups'"
if [ -f "$allowed_groups_file" ]; then
rc=$(cat $allowed_groups_file | grep -v '^#' | grep -v '^$' |
while read head_pattern group_patterns; do
matchlen=$(expr "$1" : "$head_pattern")
if [ "$matchlen" == "${#1}" ]; then
info "Found matching head pattern: '$head_pattern'"
for group_pattern in $group_patterns; do
for groupname in $groups; do
info "Checking group: '$groupname' against pattern: '$group_pattern'"
matchlen=$(expr "$groupname" : "$group_pattern")
if [ "$matchlen" == "${#groupname}" ]; then
grant "Allowing group: '$groupname' with pattern: '$group_pattern'"
fi
done
done
deny "None of the user's groups are in the access list for this branch"
fi
done
)
case "$rc" in
grant) grant >/dev/null "Granting access based on $allowed_groups_file" ;;
deny) deny >/dev/null "Denying access based on $allowed_groups_file" ;;
*) ;;
esac
fi
deny >/dev/null "There are no more rules to check. Denying access"
-- >8 -- end of script -- >8 --
This uses two files, $GIT_DIR/info/allowed-users and
allowed-groups, to describe which heads can be pushed into by
whom. The format of each file would look like this:
refs/heads/master junio
refs/heads/cogito$ pasky
@ -91,15 +165,8 @@ like this in $GIT_DIR/info/allowed-pushers file:
refs/tags/v[0-9]* junio
With this, Linus can push or create "bw/penguin" or "bw/zebra"
or "bw/panda" branches, Pasky can do only "cogito", and I can do
master branch and make versioned tags. And anybody can do
tmp/blah branches. This assumes all the users are in a single
group that can write into $GIT_DIR/ and underneath.
or "bw/panda" branches, Pasky can do only "cogito", and JC can
do master branch and make versioned tags. And anybody can do
tmp/blah branches.
------------

View file

@ -1636,6 +1636,41 @@ fast forward. You need to pull and merge those other changes
back before you push your work when it happens.
Advanced Shared Repository Management
-------------------------------------
Being able to push into a shared repository means being able to
write into it. If your developers are coming over the network,
this means you, as the repository administrator, need to give
each of them an SSH access to the shared repository machine.
In some cases, though, you may not want to give a normal shell
account to them, but want to restrict them to be able to only
do `git push` into the repository and nothing else.
You can achieve this by setting the login shell of your
developers on the shared repository host to `git-shell` program.
[NOTE]
Most likely you would also need to list `git-shell` program in
`/etc/shells` file.
This restricts the set of commands that can be run from incoming
SSH connection for these users to only `receive-pack` and
`upload-pack`, so the only thing they can do are `git fetch` and
`git push`.
You still need to create UNIX user accounts for each developer,
and put them in the same group. Make sure that the repository
shared among these developers is writable by that group.
You can implement finer grained branch policies using update
hooks. There is a document ("control access to branches") in
Documentation/howto by Carl Baldwin and JC outlining how to (1)
limit access to branch per user, (2) forbid overwriting existing
tags.
Bundling your work together
---------------------------