--- title: SSH Certificates with Gitolite date: 2015-11-08 --- [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/host1.example.com/puppet-reader.pub` corresponds to the gitolite user `puppet-reader`. The directory `host1.example.com` 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](#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[...] \ rbj@laptop ``` That is, it is like a regular authorized\_keys entry, but with a certain set of options: - The first option, `command="/path/to/gitolite-shell rbj"`, is what makes gitolite work with ssh. What this option does is whenever I log in as the gitolite user using the corresponding private key the gitolite-shell command will be run instead of the original command or a login shell. More on this later. - The other options are all of the form `no-OPTION`. These options further restrict what you can do with the ssh key, for example it disallows the user from forwarding ports or transfering files with sftp. ### 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.](/images/did-you-mean-95f89861f324e4d9768d2a7393f6f8e2.png) 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 - Passing `-n git` only allows the certificate to be used for logging in as the `git` unix user. - The `-O clear` option unsets all options, and is equivalent to a series of `no-FOO` in authorized\_keys. - The `-O force-command` option does exactly the same as `command="[...]"` in authorized\_keys. An example ``` bash ssh-keygen -s /path/to/CA -I test -n git \ -O clear -O force-command="/remote/path/to/gitolte-shell rbj" \ id_rsa.pub ``` ### 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... \ rbj@laptop ``` 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.* #### Conclusion You have to trust the CA to only sign keys for the gitolite-shell command and for the gitolite users you want. # Sample script ``` {#script .bash} #!/bin/sh # Modify these constants to your needs cakey=/root/puppet-ca/puppet_ca # This file contains a single number as a string serial_file=/root/puppet-ca/serial # You probably want to change this gitolite_user=puppet-reader # Update this to the correct path to gitolite-shell # Check ~git/.ssh/authorized_keys if in doubt gitolite_shell=/srv/git/gitolite/src/gitolite-shell usage() { echo Usage: echo "$0 PUBKEY KEYID" echo echo PUBKEY is the key to be signed echo KEYID should be a unique name } if [ "$#" -ne 2 ]; then usage exit 1 fi pubkey=$1 keyid=$2 # 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}" ``` [1]: https://en.wikipedia.org/wiki/Git_(software)#Git_server [Gitolite]: http://gitolite.com/ [OpenSSH certificates]: http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man1/ssh-keygen.1?query=ssh-keygen&sec=1#x434552544946494341544553 [CERTIFICATES]: http://www.openbsd.org/cgi-bin/man.cgi/OpenBSD-current/man1/ssh-keygen.1?query=ssh-keygen&sec=1#x434552544946494341544553