SSH Certificates with Gitolite

Written by Reynir Björnsson, Nov 8, 2015

Gitolite is a "an access control layer on top of Git, providing fine access control to Git repositories." It allows you to host several repositories for several Gitolite users using a single unix user.

A typical setup uses ssh for authentication. A new user is added by commiting a public key in keydir/ to the gitolite-admin repository. The gitolite username corresponding to the public key is determined by the filename of the public key commited to gitolite-admin. For example, keydir/ corresponds to the gitolite user puppet-reader. The directory doesn't carry any semantics, and is useful for when a user has more than one SSH key.

This post is about how to add new ssh keys for a user without having to push the new public key to the gitolite-admin repository every time. This can be convenient when automatically generating keys for reading repositories containing e.g. code or configuration. An example script is embedded at the end of the post. First we need to know a little about how Gitolite works with SSH authentication.

How does this work?

When a new key in keydir/ is pushed to the gitolite-admin repository a git hook script is run. This script will read the public key and add it to authorized_keys for the gitolite unix user. The entry will look something like the following:

command="/path/to/gitolite-shell rbj",no-port-forwarding,[...] \
	ssh-rsa AAAAB3N[...] \

That is, it is like a regular authorized_keys entry, but with a certain set of options:

So what does gitolite-shell do?

Well, it does a bunch. It is similar to git-shell, but using gitolite's access control. But basically it checks the user supplied as first argument and $SSH_ORIGINAL_COMMAND against the permissions configured in conf/gitolite.conf. If the user is allowed then the git commands are performed.

Using certificates

This works great. However, it can be a bit inconvenient to add new keys, especially if you do it repeatedly or want to automate it. This is where OpenSSH certificates come to our rescue.

Searching for 'ssh certificates' is notoriously difficult.

An OpenSSH certificate is a signed file containing a public key and some more information. The certificate is signed by a good old ssh key known as the certificate authority (CA). When creating and signing a certificate we can specify what user(s) the certificate is valid for, and a number of ssh options. I recommend reading the CERTIFICATES section of the ssh-keygen man page for more information.

The certificate options

An example

ssh-keygen -s /path/to/CA -I test -n git \
	-O clear -O force-command="/remote/path/to/gitolte-shell rbj" \

Trusting the CA

To trust a CA we simply add the key to authorized_keys with the cert-authority. For example,

cert-authority,no-port-forwarding,[...] ssh-rsa AAAAB3N[...] rbj@laptop

The no-FOO options are redundant if you passed -O clear when signing the key, but is nice to have just in case.

Security caveats

Anyone with the CA key can sign keys that are valid for the git unix user. They cannot get a PTY or start an sftp session, but they will still be able to run any command(!) They can also sign a certificate valid for any gitolite user, of course.

Reading the man page this sentence stood out to me: "If both certificate restrictions and key options are present, the most restrictive union of the two is applied." I figured I could then restrict the cert-authority to one command (one gitolite user) like this:

cert-authority,command="/path/to/gitolite-shell rbj",no-port-forwarding,... \
	ssh-rsa AAAAB3N... \

Unfortunately, OpenSSH seems to prefer the certificate's force-command option over the command option from authorized_keys!

update: After writing this post I found the following in the sshd(8) man page. Also note that this command may be superseded by either a sshd_config(5) ForceCommand directive or a command embedded in a certificate.


You have to trust the CA to only sign keys for the gitolite-shell command and for the gitolite users you want.

Sample script


# Modify these constants to your needs
# This file contains a single number as a string
# You probably want to change this
# Update this to the correct path to gitolite-shell
# Check ~git/.ssh/authorized_keys if in doubt

usage() {
        echo Usage:
	echo "$0 PUBKEY KEYID"
	echo PUBKEY is the key to be signed
	echo KEYID should be a unique name

if [ "$#" -ne 2 ]; then
        exit 1


# Not the most robust code
serial=$(cat "${serial_file}")
echo $((serial+1)) > "${serial_file}"

# You might want to pass a validity period, e.g. -V +52w
ssh-keygen -s "${cakey}" -I "${keyid}" \
        -n 'git' -O clear \
	-O force-command="${gitolite_shell} ${gitolite_user}" \
        -z "${serial}" "${pubkey}"