From dc5f9239f7cc577b7d503017583dcc26ad5991f0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 5 Dec 2005 00:57:48 -0800 Subject: 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 diff --git a/Documentation/howto/update-hook-example.txt b/Documentation/howto/update-hook-example.txt index dacaf17..3a33696 100644 --- a/Documentation/howto/update-hook-example.txt +++ b/Documentation/howto/update-hook-example.txt @@ -1,4 +1,4 @@ -From: Junio C Hamano +From: Junio C Hamano and Carl Baldwin 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: - - #!/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. - - 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 - - # 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 - -For the sake of simplicity, I assumed that you keep something -like this in $GIT_DIR/info/allowed-pushers file: +do not let tags to be overwritten, then you can use something +like this as your hooks/update script. + +[jc: editorial note. This is a much improved version by Carl +since I posted the original outline] + +-- >8 -- beginning of script -- >8 -- + +#!/bin/bash + +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. +------------ diff --git a/Documentation/tutorial.txt b/Documentation/tutorial.txt index cf7ba76..db0bf3e 100644 --- a/Documentation/tutorial.txt +++ b/Documentation/tutorial.txt @@ -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 --------------------------- -- cgit v0.10.2-6-g49f6