Enabling git access via password authentication with gitolite

March 4, 2011 3 Comments by Ben

We recently started using gitolite at my workplace. Our previous git hosting setup involved manually managing linux users and groups on the Ubuntu server, which was needlessly time consuming and painful. There were times when file permissions got out of sync because the repo was deployed by user X from his workstation (where group permissions were not setup correctly for a shared git repository), and other headaches.

We needed something easier. We did not want to go to a workflow where there is one git user on the system, every employee authenticates with their own public key to that user, and everyone with a key has access to everything. We wanted more granular control, but also an easier management workflow. While gitolite does use a single system user under the covers, it provides per-user access control (governed by public keys), easy repo management, and a couple other nice features like wildcard repositories and branch-level access restrictions.

However, one of our developers is in the habit of accessing a random server, cloning a repo (using his password), and pushing from environments other than his development environment. Obviously, his private key is not going to be on each of the machines he does this from. Gitolite does not allow password-based access for obvious reasons — it needs to know who you are to determine permissions — so gitolite was cramping his style.

There are a couple ways around this:

  1. Add a public key for each system he pushes changes from. Since he often logs in as “administrator” or “root” on individual systems, this obviously is not a good solution.
  2. Have the developer enable SSH Agent Forwarding from his machine. This works well, as long as the user does not chain sessions (connect to server A, from there to server B, and clone on B) or is willing / able / allowed to enable Agent Forwarding on each system in the chain. (NOTE: on Mac OS X Snow Leopard, enabling SSH Agent Forwarding is as simple as running ssh-add and adding two lines to your ~/.ssh/config file:
    # add key to agent (I do this in ~/.profile)
    ssh-add $HOME/.ssh/id_rsa
    
    # ~/.ssh/config
    Host *
        ForwardAgent yes
    
  3. Come up with something else.

What we really wanted was a way to allow password-based access to the repositories. Since gitolite does not support this itself, we had to come up with another way. As it turns out, doing so is not too bad, though it does involve a little bash shell black magic.

What we came up with is creating a system user on the git server for each developer who wants password access to git repositories, using a custom script as their defined shell (instead of /bin/sh or /bin/bash). Don’t forget to make your new script executable.

This script needs to look at what the user is trying to do (git action, gitolite ADC, some random shell command, nothing…) and act accordingly. Here’s the script we came up with, which will be dissected below to explain what each part does in more detail than the inline doc (gist).

#!/bin/bash
shift # get rid of -c

# if no commands, just open a shell
if [[ $# -eq 0 ]]; then
        /bin/bash -l

# if the first arg is a git- command, that means it is something like git-push, etc... so forward it
elif [[ $1 == git-* ]]; then
        ssh -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no [email protected] $*

# if the first arg is SET_ENV_ONLY, we sourced this file in order to set up the gitolite function
elif [[ $1 == "SET_ENV_ONLY" ]]; then
        gitolite () {
                ssh -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no [email protected] $*
        }

# if there is at least one non-git command, source this file in a new shell and create the 'gitolite' function
else
        /bin/bash -c "source $0 shiftme SET_ENV_ONLY; $*"
fi

If the user is not trying to do anything (just wants to open a shell), then there are no arguments and we should just open a bash prompt:

if [[ $# -eq 0 ]]; then
        /bin/bash -l

Okay, the simple bit is out of the way. Now on to the black magic.

If the user is trying to perform a git command (via git push, etc.) then we create a new SSH session to localhost (remember, this is running on the same server as gitolite) and pass along the commands ($*). Since the user has already logged in, this ssh command will run from their account using their private / public key. The git commands are things like git-receive-pack, git-upload-pack, etc, so we match the first script argument against “git-“.

Finally, we don’t care about validating the host’s fingerprint (we are already on the host) and there can be no man in the middle (since we are going to localhost), so we can turn off StrictHostKeyChecking. We also don’t want to print out the various fingerprint messages that SSH will generate when you ignore host keys, so we make the new SSH connection quiet. That’s it for this block:

elif [[ $1 == git-* ]]; then
        ssh -q -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no [email protected] $*

I am going to go over the last two pieces in reverse order, because it is easier to follow.

If the user sent some SSH commands that were not native git commands, we need to process those commands. So, we will execute the remaining commands in a new bash process.

BUT, what if the user is trying to perform a gitolite command (such as expand or an ADC), what should we do? We can’t just trust that all commands the user sends will either be a git command or a gitolite command, so we need to set up some more black magic to handle that for us. So the first thing we will do when we open a new bash process to handle the remaining commands is source this custom script ($0) with a custom value we made up (SET_ENV_ONLY). That way, if this script executes again we will know it was done by ourselves and we can set up some more magic.

else
        /bin/bash -c "source $0 shiftme SET_ENV_ONLY; $*"

Finally for the last block.

If our new script is being executed, and the first argument is SET_ENV_ONLY (after shifting), then it is because we sourced it from our new bash process from the previous step. What we do now is set up a gitolite command which does something very similar to what happens if the user is performing a native git command: it opens an SSH connection to [email protected] and performs the gitolite commands.

After this has been done, “gitolite expand” is the same as “ssh [email protected] expand”. This makes it so the developer can still run gitolite commands and ADCs, he just has to prefix each command with gitolite. Something like this:

$ ssh [DEVELOPER]@git.company.com "gitolite expand; echo $PATH; gitolite info"

is equivalent to this:

$ ssh [email protected] expand
$ ssh [DEVELOPER]@git.company.com echo $PATH
$ ssh [email protected] info

Now you just need to create accounts that use this script as their shell. Each user can add whatever public keys they want to their authorized_keys file, or they can authenticate solely with their password. The only requirement is that their server account has a private / public key generated, and that the public key is added to gitolite.

$ ssh [email protected]
$ useradd -s /usr/local/bin/custom_gitbash.sh -m [DEVELOPER]
$ su [DEVELOPER]
$ ssh-keygen -t rsa
$ cp ~/.ssh/id_rsa.pub ~/[DEVELOPER]@git.company.com.pub
# add the new public key to the gitolite keydir

2 Comments

  1. Theolein
    6 years ago

    Good to see you still around and giving excellent tips. 

    –Theolein from the McNN days.

  2. INRaj
    6 years ago

    Good Article. Million dollar info ! thanks

One Trackback

  1. […] Another option is to create each user on the system and hook the authentication as described in Enabling git access via password authentication with gitolite. The only difference in my script was adding an extra check for “virt-shell” group. […]

Post a Comment

Your email is never published or shared. Required fields are marked *