Friday, May 6, 2011

Private GIT Repository Over SSH w/ SSH-KEY and git-shell

There's documentation out there on using git, on git over SSH, and a wealth of information on using SSH keys for single-command purposes in Chapter 8 of O'Reilly's SSH book. However, if you're trying to build a private git repository:
  • On a Mac (OS X) server
  • Want the user to maintain a normal shell (IE: don't want to use git-shell as the user's shell)
  • Can't install gitosis
You're going to need to do things slightly different than are documented out there. ***if you read further I presume you have a good reason to forsake gitosis because all things considered it's a good solution: it's very easy to install, customize, or even extend as source code. Once set up it doesn't impose much in the way of dependencies, maintenance, or overhead.

Apple seems to change command-line user addition commands on OS X between minor versions. Existing blog entries thus create silent failures when you follow their instructions. The reason is subtle: failing to create the correct user directory elements for a new user will result in clients attempting git [fetch|pull] commands remotely to receive an error message:

bash-3.2$ fatal: The remote end hung up unexpectedly

The following will be written to the OS X server system.log:

sshd(28534) deny mach-per-user-lookup

I've seen these errors result from the user lacking a home directory, from the git repository not having the correct group, and from other circumstances. Because no further error/debug information is given, this can be very frustrating to debug.

supporting ssh-based access to your private git repository through a single-use key requires work on both client and server. First server. git-shell allows you to support cloning, push, pull, and fetch to and from a server, using an ssh-key, while still supporting normal user login on your sever for other purposes. Additionally, you need not open ports beyond what you've already carved for SSH in your server.

Server Set-up

  1. Change directory to server-user's account:
     [ -d ~/.ssh ] ||  mkdir ~/.ssh; chmod 700 ~/.ssh && cd ~/.ssh/ 
  2. Create new SSH key (*Be sure to set parameters as your needs warrant)
    ssh-keygen -b 2048 -f "`whoami`@`hostname`-GIT-SHELL-`date "+%Y-%m-%d"`"
  3. Add this new key as a 'single-use' key to the authorized keys file:
    [ -e ./authorized_keys ] || touch authorized_keys && chmod 600 authorized_keys
    echo -n "command=\"/usr/bin/git-shell -c \\\"\$SSH_ORIGINAL_COMMAND\\\"\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty >> authorized_keys
    cat ./`whoami`@`hostname`-GIT-SHELL-`date "+%Y-%m-%d".pub` >> authorized_keys

In case you have trouble resolving the escaped characters in the above lines: the output should look something like this:

bash-3.2$ more authorized_keys 
command="/usr/bin/git-shell -c \"$SSH_ORIGINAL_COMMAND\"",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAAB3...KdeQ== bach@Sanguine.local

Client Setup
Remember to use your public key for your authorized keys file server-side, and then securely transport the private key to your client users/machines. This represents a departure from what some administrators are used in other circumstances, and I've seen keys compromised on more than one occasion as a result.

  1. Validate the user's ~/.ssh directory has permissions 700 (IE: drwx------)
    1. If not, chmod 700 ~/.ssh
  2. Copy the private key created above into the user's ~/.ssh directory
  3. Verify the private key has permissions 600 (IE: -rw-------) 
    1. If not,  chmod 600
  4. Create a ~/.ssh/config file, if one doesn't exist:
    1. [ -e ~/.ssh/config ] || touch ~/.ssh/config && chmod 600 ~/.ssh/config
  5.  Create a host configuration: (configure each host parameter to your needs)
    1. host mySource
              Port            22
              User            git
              Compression     yes
              Protocol        2
              IdentityFile    ~/.ssh/
  6. Execute your clone, fetch, or pull commands, using the host specifier as the host name in the URL:
  7. bash-3.2$ git clone ssh://mySource/repository_path/repository.git

The configuration file appears necessary for correct client operation. Specifying the username, host, and port on the command line to a git command such as follows:

bash-3.2$ git clone ssh://git@mySource:22/repository_path/repository.git

does not work, and produces the otherwise silent "Fatal Error" discussed earlier. The git remote set-url or git remote add commands can be used as well, their hostname element of the URL must only match the host specifier in the ~/.ssh/config file (in our case mySource). For example, a user might execute the following command:

git remote add repo ssh://mySource/repository_path/repository.git

so that they can issue commands as follows subsequently:

git fetch repo master

The above solution allows you to allow git-shell access, through a single-use ssh-key to an otherwise normal system user. You can add separate ssh-keys for different client users, though each will adopt the UID of the user under which you install them for local server-side operations.

If you need the capability to do directory-, repository-, or user-based access control, ssh-keys will not provide sufficient fine-grained resolution/control. In this case, you'll want to go directly to gitosis.