% authentication and authorization in gitolite
This presentation uses HTML Slidy, a simple presentation software from the W3C. Although there's a help button in the footer of each presentation, it's missing some important stuff, so here's a smaller but more complete summary of the keyboard controls.
.#d
@@blue(Navigation)@@
| Next slide: right arrow, page down, space | Prev slide: left arrow, page up
| Down within slide: down arrow | Up within slide: up arrow
| First slide: home | Last slide: end
.#d
@@blue(Display)@@
| Smaller font: "S" or "<" key | Larger font: "B" or ">" key
| Toggle Current versus All slides: "A" key | Toggle Table of Contents popup: "C" key | Toggle footer: "F" key
.#t
@@blue(To search)@@ for stuff in the full document using your browser's Ctrl-F, first view all slides (press the "A" key).
Before we start, we need to be clear about the difference between authentication and authorisation.
.#d width=50% bgcolor=FFF0F0
Authentication
.#d bgcolor=#F0F0FF
Authorisation
.#t
First, we'll recap how git works over plain ssh.
.aa f0.png
User Workstation | Git Server | ----------------------------+------------------------------------------------- | | git clone ---> ssh ---+---> sshd ------> git‐upload‐pack | git fetch ---> ssh ---+---> sshd ------> git‐upload‐pack | | git push ---> ssh ---+---> sshd ------> git‐receive‐pack | |
.end
The client user authenticates herself to the server using any method supported by ssh -- password or ssh public key. It does not matter to either the git client or the git server how that happens; that's entirely between ssh and sshd (the ssh daemon or server).
There is no authorisation in this mode, other than any file system permissions that the OS may enforce.
Gitolite adds an extra layer in between the sshd and the git-receive-pack (or
git-upload-pack, for read operations), to check if the access is allowed and
abort if needed. Gitolite also installs its own update hook (see man
githooks
) in every repository to check branches being pushed.
This means gitolite does two authorisation checks, as you will see.
Gitolite also requires that the authentication must happen using an ssh public key. We'll see why as we go along.
Here's a series of pictures that show how gitolite works. We're using a "git
push" example; the only difference for a fetch or clone is that instead of
git-receive-pack
it'd call git-upload-pack
, and the last check (update
hook) does not apply.
@@blue(Terminology reminder: "ref" in git parlance means "branch or tag". At least in this page that's what we mean :-))@@
The diagrams were originally drawn to be self-contained. On a first pass you can simply ignore the text on each page, and come back to it later for the finer points.
Experts: yes, I know I simplified the ssh part a lot. If you can find fault with these pictures, you're not the target audience :-)
.aa f1.png
User Workstation | Gitolite Server | ----------------------------+------------------------------------------------- | | | | | | | Git client | | | | | | | | calls | | ssh | | | | | | | v | ssh client | | | | | | | |
.end
When the user runs a "git push" command, the git client calls ssh, passing it a command like
git-receive-pack 'reponame'
.aa f2.png
User Workstation | Gitolite Server | ----------------------------+------------------------------------------------- | | | | | | | Git client | | | | | | | | | | | | | | | | | v | ssh client | | | reads | | ssh keypair | | | v | ~/.ssh/id_rsa | ~/.ssh/id_rsa.pub |
.end
The git client, before it starts talking to the server, looks for the user's ssh keys so it can use public key authentication (and not ask for a password).
As you know, for gitolite to work, the user must already have an ssh key pair
and the pub key already sent to the gitolite administrator who should have
added it to gitolite (which in turn means it's part of
~/.ssh/authorized_keys
on the server).
.aa f3.png
User Workstation | Gitolite Server | ----------------------------+------------------------------------------------- | | | | | | | Git client | | | | | | | | | | | | | | | | calls | v server | ssh client -------------+--------> ssh server sends | | pubkey | | | | | v | ~/.ssh/id_rsa | ~/.ssh/id_rsa.pub |
.end
The ssh client sends the public key to the server to identify itself.
.aa f4.png
User Workstation | Gitolite Server | ----------------------------+------------------------------------------------- | | | | | | | Git client | | | | | | | | | | | | | | | | | v | ssh client -------------+--------> ssh server | | | | matches pubkey in | | | authorized keys file | | | v | v ~/.ssh/id_rsa | ~/.ssh/authorized_keys ~/.ssh/id_rsa.pub |
.end
The server looks for the public key in its "list of people who're allowed in"
(~/.ssh/authorized_keys
). If it finds it, things are good.
@@red(If it doesn't find it, it tells the client and the client asks the user for a password. Which is BAD, because Gitolite doesn't work with passwords!)@@
.aa f5.png
User Workstation | Gitolite Server | ----------------------------+------------------------------------------------- | | | | | | | Git client | | | | | | | | | | | gitolite‐shell | | ^ | | | (if found) calls gitolite with | | | username found in authkeys file v | | ssh client -------------+--------> ssh server | | | | | | | | | | v | v ~/.ssh/id_rsa | ~/.ssh/authorized_keys ~/.ssh/id_rsa.pub |
.end
If authentication succeeds, the ssh server calls gitolite-shell, with the username as the first argument. Here's how that happens.
When the administrator adds Alice's pubkey to gitolite, he names it "alice.pub", puts it in the "keydir" directory of a clone of the gitolite-admin repository, and pushes the new file to the server.
When the Gitolite server receives that push, one of the things it does is add
this key to ~/.ssh/authorized_keys
file, but prefixed with a bunch of
options, one of which is a "command" option that looks like this:
command="/home/git/bin/gitolite-shell alice"
It is this "command" that the ssh daemon executes when the offered public key matches the public key in this line. For a different public key, from a different user, the command will have that user's name instead of "alice".
This is how Gitolite distinguishes users from each other, even though they're
all accessing the same (git@host
) account.
This is also why passwords won't work. When a user supplies a password, there's no additional information to help the ssh server distinguish one user from another.
.aa f6.png
User Workstation | Gitolite Server | ----------------------------+------------------------------------------------- | | | | | | | Git client | git‐receive‐pack | | ^ | | | calls git if | | | access allowed | | | | | gitolite‐shell | | ^ | | | | | | v | | ssh client -------------+--------> ssh server | | | | | | | | | | v | v ~/.ssh/id_rsa | ~/.ssh/authorized_keys ~/.ssh/id_rsa.pub |
.end
When the ssh daemon decided to execute /home/git/bin/gitolite-shell alice
instead of the git-receive-pack 'reponame'
command that the user's git
client sent to the user's ssh client, it didn't simply discard the
git-receive-pack command. It put it in an environment variable called
SSH_ORIGINAL_COMMAND
.
Since that variable contains the repository name, and the username is the first argument passed to it, gitolite-shell now has enough information to decide "is this user allowed to write to this repo" (or "read", if the command was git-upload-pack).
Assuming access is allowed, gitolite-shell then calls git-receive-pack (or git-upload-pack, as the case may be).
This is the first authorisation check that gitolite does.
Notice that git-receive-pack and git-upload-pack have NO idea that someone snuck in between them and the client calling them. Neither does the git client know. Gitolite is totally transparent to git clients unless access is denied for some reason.
.aa f7.png
User Workstation | Gitolite Server | ----------------------------+------------------------------------------------- | | | gitolite-installed update hook | ^ | : each ref pushed is checked | | by the update hook | | Git client | git‐receive‐pack | | ^ | | | | | | | | | | | gitolite‐shell | | ^ | | | | | | v | | ssh client -------------+--------> ssh server | | | | | | | | | | v | v ~/.ssh/id_rsa | ~/.ssh/authorized_keys ~/.ssh/id_rsa.pub |
.end
For a read (git-upload-pack), that is the end of the story.
For a write, git will invoke the update hook for each branch or tag pushed.
(See man githooks
for details).
This update hook, of course, has been installed by gitolite, and it proceeds to check if
| - this user (alice, bob, ...) | - is allowed to do this (create, fast-forward push, non-fast-forward push, or delete) | - to this branch or tag (master, refs/tags/v1.0, ...) | - in this repo.
This is the second authorisation check that gitolite does.
If it is not allowed, the update hooks exits with an error, and git will abort the push (for that ref; other refs sent in the same push may be OK).