* [RFC] Using sticky directories to control access to branches.
@ 2005-11-17 17:01 Carl Baldwin
2005-11-18 7:55 ` Junio C Hamano
0 siblings, 1 reply; 8+ messages in thread
From: Carl Baldwin @ 2005-11-17 17:01 UTC (permalink / raw)
To: git
I had a thought this morning. I wanted to use file permissions to
control access to push to a particular branch in a repository in order
to implement some sort of per-branch policy. This assumes that there is
a repository setup into which multiple users can push.
My goals were to be able to...
a) allow only one user (or possibly one group) access to modify a
branch. Do this on per-branch basis.
b) Freeze tags so that they *cannot* be accidentally updated by a push.
My plan to accomplish this was to set the sticky bit (chmod +t) on the
refs/heads and refs/tags directories so that users couldn't bypass
file-permissions by replacing the file with their own. Then grant write
permission to the owner (or possibly a group) to allow updates to that
branch.
I started testing this with git v0.99.9i and found that git push
actually creates a new file and replaces the old branch file with the
new one. The consequence for me is that when I set the sticky bit on
the heads directory then all of the branches restrict access solely to
the owner of the file since only the owner will be able to replace it.
This precludes giving a group write access to the branch. It also
precludes leaving most of the branches open to all users by default.
Now, git was probably designed to do this on purpose because it is the
safest way to update a branch in an automic way. But, I wonder if there
is another way. Maybe, git could make a temporary backup of the branch
doing something like this:
% cp refs/heads/master{,.$randomstring}
(on success, go ahead and edit refs/heads/master in place)
(on success, remove refs/heads/master.$randomstring
Something like this could still be pretty safe but would allow
per-branch access restrictions using unix file permissions.
Carl
--
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Carl Baldwin Systems VLSI Laboratory
Hewlett Packard Company
MS 88 work: 970 898-1523
3404 E. Harmony Rd. work: Carl.N.Baldwin@hp.com
Fort Collins, CO 80525 home: Carl@ecBaldwin.net
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [RFC] Using sticky directories to control access to branches.
2005-11-17 17:01 [RFC] Using sticky directories to control access to branches Carl Baldwin
@ 2005-11-18 7:55 ` Junio C Hamano
2005-11-21 18:01 ` Carl Baldwin
0 siblings, 1 reply; 8+ messages in thread
From: Junio C Hamano @ 2005-11-18 7:55 UTC (permalink / raw)
To: Carl Baldwin; +Cc: git
Carl Baldwin <cnb@fc.hp.com> writes:
> Now, git was probably designed to do this on purpose because it is the
> safest way to update a branch in an automic way.
Yes. How about using hooks/update? The documentation for
receive-pack suggests the use of it for generating commit
notification e-mails, but this is more general mechanism.
When your developer runs git-push into the repository,
git-receive-pack is run (either locally or over ssh) as that
developer, so is hooks/update script. Quoting from the relevant
section of the documentation:
Before each ref is updated, if $GIT_DIR/hooks/update file exists
and executable, it is called with three parameters:
$GIT_DIR/hooks/update refname sha1-old sha1-new
The refname parameter is relative to $GIT_DIR; e.g. for the
master head this is "refs/heads/master". Two sha1 are the
object names for the refname before and after the update. Note
that the hook is called before the refname is updated, so either
sha1-old is 0{40} (meaning there is no such ref yet), or it
should match what is recorded in refname.
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
esac
# Is he 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:
refs/heads/master junio
refs/heads/cogito$ pasky
refs/heads/bw/ linus
refs/heads/tmp/ *
refs/tags/v[0-9]* junio
With , 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.
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [RFC] Using sticky directories to control access to branches.
2005-11-18 7:55 ` Junio C Hamano
@ 2005-11-21 18:01 ` Carl Baldwin
2005-11-21 19:29 ` Junio C Hamano
2005-12-01 15:42 ` Carl Baldwin
0 siblings, 2 replies; 8+ messages in thread
From: Carl Baldwin @ 2005-11-21 18:01 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
OK,
To follow-up on this. Here is a final version of this script that was
started by Junio. I polished it and made it work the way I want it.
Hopefully, someone on the list will find it useful.
Here is a basic description. There are two files:
.git/info/allowed-users
.git/info/allowed-groups
The users file is checked, line by line, followed by the groups file.
The first line matching the ref to be updated is used. Each line has
one regular expression to match the ref followed by one or more regular
expressions to match user (or group). Space is the delimeter so the RE
cannot contain a space.
If no line matches the ref then access is denied. For this reason I
tend to include the following line as the last line of allowed-groups as
the default since it will match any ref and any group.
.* .*
Here is the hook script... Thanks to Junio for getting me started. I
rewrote the whole thing using my own style but much of the code is based
on his.
#!/bin/bash
umask 002
verbose=no
# Default shell globbing messes things up later
GLOBIGNORE=*
function grant {
[ "yes" == "$verbose" ] && echo >&2 "-Grant- $1"
exit 0
}
function deny {
[ "yes" == "$verbose" ] && echo >&2 "-Deny- $1"
exit 1
}
function info {
[ "yes" == "$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 "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 "This is not a fast-forward update." ;;
esac
fi
;;
*)
deny "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
3<$allowed_users_file
while read -u 3 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
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
4<$allowed_groups_file
while read -u 4 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
fi
deny "There are no more rules to check. Denying access"
# End script here
Carl
--
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Carl Baldwin Systems VLSI Laboratory
Hewlett Packard Company
MS 88 work: 970 898-1523
3404 E. Harmony Rd. work: Carl.N.Baldwin@hp.com
Fort Collins, CO 80525 home: Carl@ecBaldwin.net
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [RFC] Using sticky directories to control access to branches.
2005-11-21 18:01 ` Carl Baldwin
@ 2005-11-21 19:29 ` Junio C Hamano
2005-12-01 15:42 ` Carl Baldwin
1 sibling, 0 replies; 8+ messages in thread
From: Junio C Hamano @ 2005-11-21 19:29 UTC (permalink / raw)
To: Carl Baldwin; +Cc: git
Carl Baldwin <cnb@fc.hp.com> writes:
> To follow-up on this. Here is a final version of this script that was
> started by Junio. I polished it and made it work the way I want it.
> Hopefully, someone on the list will find it useful.
Looking good. We might want to have collections of user
contributed hooks, maybe a new directory examples/ perhaps?
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [RFC] Using sticky directories to control access to branches.
2005-11-21 18:01 ` Carl Baldwin
2005-11-21 19:29 ` Junio C Hamano
@ 2005-12-01 15:42 ` Carl Baldwin
2005-12-02 1:13 ` Junio C Hamano
1 sibling, 1 reply; 8+ messages in thread
From: Carl Baldwin @ 2005-12-01 15:42 UTC (permalink / raw)
To: Junio C Hamano, git
I have tweaked the script a little. I thought I'd send to the list in
case anyone was following this. If not, then safely ignore this.
#!/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"
On Mon, Nov 21, 2005 at 11:01:33AM -0700, Carl Baldwin wrote:
> OK,
>
> To follow-up on this. Here is a final version of this script that was
> started by Junio. I polished it and made it work the way I want it.
> Hopefully, someone on the list will find it useful.
>
> Here is a basic description. There are two files:
> .git/info/allowed-users
> .git/info/allowed-groups
>
> The users file is checked, line by line, followed by the groups file.
> The first line matching the ref to be updated is used. Each line has
> one regular expression to match the ref followed by one or more regular
> expressions to match user (or group). Space is the delimeter so the RE
> cannot contain a space.
>
> If no line matches the ref then access is denied. For this reason I
> tend to include the following line as the last line of allowed-groups as
> the default since it will match any ref and any group.
>
> .* .*
>
> Here is the hook script... Thanks to Junio for getting me started. I
> rewrote the whole thing using my own style but much of the code is based
> on his.
>
> #!/bin/bash
>
> umask 002
>
> verbose=no
>
> # Default shell globbing messes things up later
> GLOBIGNORE=*
>
> function grant {
> [ "yes" == "$verbose" ] && echo >&2 "-Grant- $1"
> exit 0
> }
>
> function deny {
> [ "yes" == "$verbose" ] && echo >&2 "-Deny- $1"
> exit 1
> }
>
> function info {
> [ "yes" == "$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 "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 "This is not a fast-forward update." ;;
> esac
> fi
> ;;
> *)
> deny "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
> 3<$allowed_users_file
> while read -u 3 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
> 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
> 4<$allowed_groups_file
> while read -u 4 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
> fi
>
> deny "There are no more rules to check. Denying access"
> # End script here
>
> Carl
>
> --
> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
> Carl Baldwin Systems VLSI Laboratory
> Hewlett Packard Company
> MS 88 work: 970 898-1523
> 3404 E. Harmony Rd. work: Carl.N.Baldwin@hp.com
> Fort Collins, CO 80525 home: Carl@ecBaldwin.net
> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
> -
> To unsubscribe from this list: send the line "unsubscribe git" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
--
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Carl Baldwin Systems VLSI Laboratory
Hewlett Packard Company
MS 88 work: 970 898-1523
3404 E. Harmony Rd. work: Carl.N.Baldwin@hp.com
Fort Collins, CO 80525 home: Carl@ecBaldwin.net
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [RFC] Using sticky directories to control access to branches.
2005-12-01 15:42 ` Carl Baldwin
@ 2005-12-02 1:13 ` Junio C Hamano
2005-12-02 9:29 ` Andreas Ericsson
0 siblings, 1 reply; 8+ messages in thread
From: Junio C Hamano @ 2005-12-02 1:13 UTC (permalink / raw)
To: git; +Cc: Carl Baldwin
Carl Baldwin <cnb@fc.hp.com> writes:
> I have tweaked the script a little. I thought I'd send to the list in
> case anyone was following this. If not, then safely ignore this.
It _might_ make sense to have a contrib/examples subdirectory in
git.git project and keep a collection of examples like this.
Anybody else interested?
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [RFC] Using sticky directories to control access to branches.
2005-12-02 1:13 ` Junio C Hamano
@ 2005-12-02 9:29 ` Andreas Ericsson
2005-12-02 9:35 ` Junio C Hamano
0 siblings, 1 reply; 8+ messages in thread
From: Andreas Ericsson @ 2005-12-02 9:29 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Carl Baldwin
Junio C Hamano wrote:
>
> It _might_ make sense to have a contrib/examples subdirectory in
> git.git project and keep a collection of examples like this.
>
> Anybody else interested?
>
I am. Ten thousand brains usually do a better job than one in thinking
up cool and nifty stuff. :)
--
Andreas Ericsson andreas.ericsson@op5.se
OP5 AB www.op5.se
Tel: +46 8-230225 Fax: +46 8-230231
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [RFC] Using sticky directories to control access to branches.
2005-12-02 9:29 ` Andreas Ericsson
@ 2005-12-02 9:35 ` Junio C Hamano
0 siblings, 0 replies; 8+ messages in thread
From: Junio C Hamano @ 2005-12-02 9:35 UTC (permalink / raw)
To: Andreas Ericsson; +Cc: git
Andreas Ericsson <ae@op5.se> writes:
> Junio C Hamano wrote:
>> It _might_ make sense to have a contrib/examples subdirectory in
>> git.git project and keep a collection of examples like this.
>> Anybody else interested?
>>
>
> I am. Ten thousand brains usually do a better job than one in thinking
> up cool and nifty stuff. :)
Good. Another place (and probably better one) is
Documentation/howto/.
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2005-12-02 9:35 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2005-11-17 17:01 [RFC] Using sticky directories to control access to branches Carl Baldwin
2005-11-18 7:55 ` Junio C Hamano
2005-11-21 18:01 ` Carl Baldwin
2005-11-21 19:29 ` Junio C Hamano
2005-12-01 15:42 ` Carl Baldwin
2005-12-02 1:13 ` Junio C Hamano
2005-12-02 9:29 ` Andreas Ericsson
2005-12-02 9:35 ` Junio C Hamano
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).