6.3 KiB
title | date |
---|---|
SSH Certificates with Gitolite | 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 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.
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 thegit
unix user. -
The
-O clear
option unsets all options, and is equivalent to a series ofno-FOO
in authorized_keys. -
The
-O force-command
option does exactly the same ascommand="[...]"
in authorized_keys.
An example
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
#!/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}"