Documentation: shared repository management in tutorial.
authorJunio C Hamano <junkio@cox.net>
Mon, 5 Dec 2005 08:57:48 +0000 (00:57 -0800)
committerJunio C Hamano <junkio@cox.net>
Mon, 5 Dec 2005 08:58:23 +0000 (00:58 -0800)
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>
Documentation/howto/update-hook-example.txt
Documentation/tutorial.txt

index dacaf17..3a33696 100644 (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:
-
-       #!/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.
 
+------------
index cf7ba76..db0bf3e 100644 (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
 ---------------------------