hg-admin-tools A set of tools for managing authorization and access control for ssh-based Mercurial repositories Paul Crowley, paul@lshift.net, 2008 This software may be used and distributed according to the terms of the GNU General Public License, incorporated herein by reference. 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. Access control is managed with a special repository on the server called "hgadmin"; pushes to this repository immediately change the rules that are in effect. Inside "hgadmin" is a "keys" directory containing the SSH keys of all developers who have access, and a file "hg-ssh-access.conf" which gives a set of rules defining who can do what to what. 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. 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. QUICK START You will need - "sudo" installed - "sudo" root privileges - an ssh-key set up with ssh-agent Ensure there is no user called "hg" on the repository host, and run "./install" to create them. You are now the sole user able to change and create repositories on this repository host. To give access to others, check out hgadmin - as yourself, and on whichever host is most convenient, but using the ssh-key with which you set up the repository: mkdir ~/hg cd ~/hg hg clone ssh://hg@repository-host/hgadmin cd hgadmin You can now add other users by putting their keys in an appropriate subdirectory of the "keys" directory, and control their access by editing hg-ssh-access.conf. Changes will take effect as soon as you push them to "ssh://hg@repository-host/hgadmin". Users authorized to do so can now also create new repositories on this host with "clone": hg clone . ssh://hg@repository-host/my-project-name HG-SSH-ACCESS.CONF Each line of hg-ssh-access.conf has the following syntax: ... 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= - user's key repo= - repo (as the user supplies it) file= - file in the repo branch= - 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 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 a directory 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 of a repository called "hgadmin". A hook in this repository re-runs "refresh-auth" on the most recent version after every push. Finally, a 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. LOCKING YOURSELF OUT If you find yourself "locked out" - that is, that you no longer have the permissions needed in hgadmin - you can break back in again if you're able to become the "hg" user on the repository host. Once you are that user, delete ~hg/.ssh/authorized_keys (to stop any user who might have access but shouldn't from using the repository while you fix things). Then go into ~hg/repos/hgadmin, do an "hg update", edit things to your satisfaction, and commit the change. Finally, run ~/admin/hg-admin-tools/refresh-auth to regenerate ~hg/.ssh/authorized_keys. THANKS Thanks for reading this far. If you use hg-admin-tools, please tell me about it. Paul Crowley, 2008