README
author Paul Crowley <paul@lshift.net>
Tue, 24 Feb 2009 09:49:56 +0000
changeset 63 b75177d307e5
parent 62 f1e319d3672a
parent 61 964ac53280cb
child 66 2f0ea1163b9e
permissions -rw-r--r--
merged

mercurial-server

A set of tools for managing authorization and access control for
ssh-based Mercurial repositories

Paul Crowley, paul@lshift.net, 2008-2009

This software may be used and distributed according to the terms
of the GNU General Public License, incorporated herein by reference.

http://hg.opensource.lshift.net/mercurial-server/

WHAT IT GIVES YOU

These tools make it easier to provide a centralized repository host
with read/write access to many repositories for many developers.

All of the repositories controlled by these tools are owned by a single user
(the "hg" user in what follows), but many remote users can act on them, and
different users can have different permissions. We don't use file permissions to
achieve that - instead, developers log in as the "hg" user when they connect to
the repository host using ssh, using ssh URLs of the form
"ssh://hg@repository-host/repository-name". A restricted shell prevents them
from using this access for unauthorized purposes. Developers are authenticated
only using SSH keys; no other form of authentication is supported. 

To give a user access to the repository, place their key in an
appropriately-named subdirectory of "/etc/mercurial-server/keys" and run
"/etc/mercurial-server/refresh-auth". You can then control what access they have
to what repositories by editing the control file
"/etc/mercurial-server/access.conf", which can match the names of these keys
against a glob pattern. 

For convenient remote control of access, you can instead (if you have the
privileges) make changes to a special repository called "hgadmin", which
contains its own "access.conf" file and "keys" directory. Changes pushed to this
repository take effect immediately. The two "access.conf" files are
concatenated, and the keys directories merged.

QUICK START

You and all developers using this system will need an SSH public key, and will
almost certainly want to be running ssh-agent (or its equivalent, eg Pageant
under Windows). If you're not familiar with ssh-agent, you should learn about
that before using this.

In what follows, certain operations (eg installing mercurial-server itself) have
to be done on the repository server (which we call "repository-host"), but any
operation that involves checking in or out of Mercurial can be done wherever is
most convenient to you; the most usual arrangment would be that you'd do these
things at the machine you sit at, and on which you run ssh-agent, which is what
authenticates you when you talk to the repository server.

Ensure there is no user called "hg" on the repository host, and run "./install".
This installs the mercurial-server files and control files, and creates and sets
up the "hg" user.

Place your SSH public key in the directory "/etc/mercurial-server/keys/root". I
suggest creating yourself a directory and naming the key after your hostname (ie
the file is called something like
"/etc/mercurial-server/keys/root/yourname/yourhostname") so that you can easily
manage users who have a different key on each host they use. Then run
"/etc/mercurial-server/refresh-auth".

The repository is now ready to use, and you are now the sole user able to change
and create repositories on this repository host.  

CREATING REPOSITORIES

To create a new repository, you clone a local repository onto the remote server.
So if you want a new empty repository called "myproject", you can do (as
yourself):

    hg init myproject
    hg clone myproject ssh://hg@repository-host/myproject

ADDING OTHER USERS

Because your key is in the "keys/root" subdirectory, you have the equivalent of
"root privileges" over mercurial-server (not the whole computer, just
mercurial-server). You can add other root users by putting their keys next to
yours, or you can make less privileged users by putting their keys in the
"keys/users" subdirectory - these users will be able to read and write to any
repository (except one - see below) but will not be able to create new
repositories. As always, when you change "/etc/mercurial-server/keys" you need
to re-run "/etc/mercurial-server/refresh-auth".

LOGGING

Every push and pull is logged with the key used: see the file .hg/serve-log in
each repository.

USING HGADMIN

It can be inconvenient to log on to the repository server, become root, copy
keys around, and run "refresh-auth" every time you want to change user
privileges. This is where mercurial-server shines :-) Suppose you have another
user's SSH public key in the file "/tmp/theirkey" (on the machine you sit at,
not necessarily the repository server) and you want to give them user-level
access to the repository server. Run these commands:

    hg clone ssh://hg@repository-server/hgadmin
    cd hgadmin
    mkdir keys/user/thatuser
    cp /tmp/theirkey keys/user/thatuser/theirhostname
    hg add
    hg commit -m "Added key for thatuser"
    hg push

In other words, hgadmin is a version controlled version of
"/etc/mercurial-server/keys", and changes to it take effect immediately. Only
"keys/root" users can act on "hgadmin" - those with keys in "keys/users" are
locked out. Multiple admins can use Mercurial's version control to cooperate on
controlling access to the repository server in a natural way. You can also add
"root" users by putting their key in the "keys/root" directory in just the same
way - these users will now be able to control hgadmin and create new
repositories just as you can.

Once you're working with "hgadmin", it can be convenient to remove all the keys
in "/etc/mercurial-server/keys" and all the entries in
"/etc/mercurial-server/access.conf" and use hgadmin to control everything. If
you find yourself locked out, you can get back in again by restoring some of the
entries you removed from these files - remember,
"/etc/mercurial-server/access.conf" takes precedence over the "access.conf" in
"hgadmin".

ACCESS.CONF

Out of the box, there are just two kinds of users: the ones with keys in
"keys/root" and those in "keys/users". However, you can change this by editing
"access.conf". There are two "access.conf" files, one in "/etc/mercurial-server"
and one in "hgadmin"; the two are simply concatenated before being read.

Each line of access.conf has the following syntax:

<rule> <condition> <condition> ...

Rule is one of

init - allow any operation, including the creation of new repositories
write - allow reads and writes to this file in this repository
read - allow the repo to be read but reject matching writes
deny - deny all requests

A condition is a globpattern matched against a relative path, one of:

user=<globpattern> - user's key
repo=<globpattern> - repo (as the user supplies it)
file=<globpattern> - file in the repo
branch=<globpattern> - name of the branch

The first rule in the file which has all its conditions satisfied is used to
determine whether an action is allowed.

Paths cannot contain any special characters except "/"; glob patterns cannot
contain any special characters except "/" and "*". "*" matches zero or more
characters not including "/" while "**" matches zero or more characters
including "/".

Blank lines and lines that start with "#" are ignored.

FILE CONDITIONS

mercurial-server supports file and branch conditions, which restrict an
operation depending on what files it modifies and what branch the work is on.
However, the way these conditions work is subtle and can be counterintuitive -
if you want to keep things simple, stick to user and repo conditions, and then
things are likely to work the way you would expect.

The rules file is used to make four decisions:

- Whether to allow a repository to be created
- Whether to allow access to a repository
- Whether to allow a changeset on a particular branch at all
- Whether to allow a changeset to change a particular file

When the first two of these decisions are being made, nothing is known about
what files might be changed, and so all file conditions automatically succeed
for the purpose of such decisions. This means that doing tricky things with file
conditions can have counterintuitive consequences:

- You cannot limit read access to a subset of a repository with a "read" rule
and a file condition: any user who has access to a repository can read all of it
and its full history. Such a rule can only have the effect of masking a later
"write" rule, as in this example:

   read repo=specialrepo file=dontwritethis
   write repo=specialrepo

allows all users to read specialrepo, and to write to all files *except* that
any changeset which writes to "dontwritethis" will be rejected.

- For similar reasons, don't give "init" rules file conditions.

- Don't try to deny write access to a particular file on a particular branch - a
developer can write to the file on another branch and then merge it in. Either
deny all writes to the branch from that user, or allow them to write to all the
files they can write to on any branch. In other words, something like this will
have the intended effect:

  write user=docs/* branch=docs file=docs/*

But something like this will not have the intended effect; it will effectively
allow these users to write to any file on any branch, by writing it to "docs"
first:

  write user=docs/* branch=docs
  write user=docs/* file=docs/*
  read user=docs/*

HOW IT WORKS

When a developer attempts to connect to a repository via ssh, the SSH daemon
searches for a match for that user's key in ~hg/.ssh/authorized_keys. If the
developer is authorised to connect to the repository they will have an entry in
this file. The entry includes a "command" prefix which specifies that the
restricted shell should be used; this shell is passed an argument identifying
the developer. The shell parses the command the developer is trying to execute,
and consults a rules file to see if that developer is allowed to perform that
action on that repository. The bulk of the work of the restricted shell is done
by the Python program "hg-ssh", but the shell script "hg-ssh-wrapper" sets up
some configuration so that you can change it to suit your local installation.

The file ~hg/.ssh/authorized_keys is generated by "refresh-auth", which recurses
through two directories of files containing SSH keys and generates an entry in
authorized_keys for each one, using the name of the key file as the identifier
for the developer. These keys will live in the "keys" subdirectory
"/etc/mercurial-server" and the "keys" subdirectory of a repository called
"hgadmin". A hook in this repository re-runs "refresh-auth" on the most recent
version after every push.

Finally, hook in an extension is run for each changeset that is remotely
committed, which uses the rules file to determine whether to allow the
changeset.

SECURITY OF MERCURIAL-SERVER

mercurial-server relies entirely on sshd to grant access to remote users. As a
result, it runs no daemons, installs no setuid programs, and no part of it runs
as root except the install process: all programs run as the user hg. And any
attack on mercurial-server can only be started if the Bad Guys already have a
public key in ~hg/.ssh/authorized_keys, otherwise sshd will bar the way. No
matter what command the user tries to run on the remote system via ssh,
mercurial-server is run. 

It parses the command line the user asked for, and interprets and runs the
corresponding hg operation itself if access is allowed, so users can only read
and add to history within repositories; they cannot run any other hg command. In
addition, every push and pull is logged with a datestamp, changeset ID and the
key that performed the operation.

However, while the first paragraph holds no matter what bugs mercurial-server
contains, the second depends on the relevant code being correct; though the
entire codebase is currently only about twice as long as this README,
mercurial-server is a fairly new program and may harbour bugs. Backups are
essential!

THANKS

Thanks for reading this far. If you use mercurial-server, please tell me about
it.

Paul Crowley, 2009