mercurial-serverA set of tools for managing authorization and access control forssh-based Mercurial repositoriesPaul Crowley, paul@lshift.net, 2008This software may be used and distributed according to the termsof the GNU General Public License, incorporated herein by reference.WHAT IT GIVES YOUThese tools make it easier to provide a centralized repository hostwith 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, anddifferent users can have different permissions. We don't use file permissions toachieve that - instead, developers log in as the "hg" user when they connect tothe repository host using ssh, using ssh URLs of the form"ssh://hg@repository-host/repository-name". A restricted shell prevents themfrom using this access for unauthorized purposes. Developers are authenticatedonly using SSH keys; no other form of authentication is supported. To give a user access to the repository, place their key in anappropriately-named subdirectory of "/etc/mercurial-server/keys" and run"/etc/mercurial-server/refresh-auth". You can then control what access they haveto what repositories by editing the control file"/etc/mercurial-server/access.conf", which can match the names of these keysagainst a glob pattern. For convenient remote control of access, you can instead (if you have theprivileges) make changes to a special repository called "hgadmin", whichcontains its own "access.conf" file and "keys" directory. Changes pushed to thisrepository take effect immediately. The two "access.conf" files areconcatenated, and the keys directories merged.QUICK STARTYou and all developers using this system will need an SSH public key, and willalmost certainly want to be running ssh-agent (or its equivalent, eg Pageantunder Windows). If you're not familiar with ssh-agent, you should learn aboutthat before using this.In what follows, certain operations (eg installing mercurial-server itself) haveto be done on the repository server (which we call "repository-host"), but anyoperation that involves checking in or out of Mercurial can be done wherever ismost convenient to you; the most usual arrangment would be that you'd do thesethings at the machine you sit at, and on which you run ssh-agent, which is whatauthenticates 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 setsup the "hg" user.Place your SSH public key in the directory "/etc/mercurial-server/keys/root". Isuggest creating yourself a directory and naming the key after your hostname (iethe file is called something like"/etc/mercurial-server/keys/root/yourname/yourhostname") so that you can easilymanage 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 changeand create repositories on this repository host. CREATING REPOSITORIESTo 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 (asyourself): hg init myproject hg clone myproject ssh://hg@repository-host/myprojectADDING OTHER USERSBecause your key is in the "keys/root" subdirectory, you have the equivalent of"root privileges" over mercurial-server (not the whole computer, justmercurial-server). You can add other root users by putting their keys next toyours, 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 anyrepository (except one - see below) but will not be able to create newrepositories. As always, when you change "/etc/mercurial-server/keys" you needto re-run "/etc/mercurial-server/refresh-auth".USING HGADMINIt can be inconvenient to log on to the repository server, become root, copykeys around, and run "refresh-auth" every time you want to change userprivileges. This is where mercurial-server shines :-) Suppose you have anotheruser'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-levelaccess 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 pushIn 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" arelocked out. Multiple admins can use Mercurial's version control to cooperate oncontrolling 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 sameway - these users will now be able to control hgadmin and create newrepositories just as you can.ACCESS.CONFOut 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 ofinit - allow any operation, including the creation of new repositorieswrite - allow reads and writes to this file in this repositoryread - allow the repo to be read but reject matching writesdeny - deny all requestsA condition is a globpattern matched against a relative path, one of:user=<globpattern> - user's keyrepo=<globpattern> - repo (as the user supplies it)file=<globpattern> - file in the repobranch=<globpattern> - name of the branchThe first rule in the file which has all its conditions satisfied isused to determine whether an action is allowed.Paths cannot contain any special characters except "/"; glob patternscannot contain any special characters except "/" and "*". "*" matcheszero or more characters not including "/" while "**" matches zero ormore characters including "/".Blank lines and lines that start with "#" are ignored.FILE CONDITIONSmercurial-server supports file and branch conditions, which restrict anoperation 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 thenthings 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 fileWhen the first two of these decisions are being made, nothing is knownabout what files might be changed, and so all file conditionsautomatically succeed for the purpose of such decisions. This meansthat doing tricky things with file conditions can havecounterintuitive 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 arepository can read all of it and its full history. Such a rule canonly have the effect of masking a later "write" rule, as in thisexample: read repo=specialrepo file=dontwritethis write repo=specialrepoallows all users to read specialrepo, and to write to all files*except* that any changeset which writes to "dontwritethis" will berejected.- For similar reasons, don't give "init" rules file conditions.- Don't try to deny write access to a particular file on a particularbranch - a developer can write to the file on another branch and thenmerge it in. Either deny all writes to the branch from that user, orallow 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 willeffectively allow these users to write to any file on any branch, bywriting it to "docs" first: write user=docs/* branch=docs write user=docs/* file=docs/* read user=docs/*HOW IT WORKSWhen a developer attempts to connect to a repository via ssh, the SSHdaemon searches for a match for that user's key in~hg/.ssh/authorized_keys. If the developer is authorised to connectto the repository they will have an entry in this file. The entryincludes a "command" prefix which specifies that the restricted shellshould be used; this shell is passed an argument identifying thedeveloper. The shell parses the command the developer is trying toexecute, and consults a rules file to see if that developer is allowedto perform that action on that repository. The bulk of the work ofthe restricted shell is done by the Python program "hg-ssh", but theshell script "hg-ssh-wrapper" sets up some configuration so that youcan 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 andgenerates an entry in authorized_keys for each one, using the name ofthe key file as the identifier for the developer. These keys willlive in the "keys" subdirectory of a repository called "hgadmin". Ahook in this repository re-runs "refresh-auth" on the most recentversion after every push.Finally, a hook in an extension is run for each changeset that isremotely committed, which uses the rules file to determine whether toallow the changeset.LOCKED OUT?Once you're working with "hgadmin", it can be convenient to remove all the keysin "/etc/mercurial-server/keys" and all the entries in"/etc/mercurial-server/access.conf" and use hgadmin to control everything. Ifyou find yourself locked out, you can get back in again by restoring some of theentries you removed from these files - remember,"/etc/mercurial-server/access.conf" takes precedence over the "access.conf" in"hgadmin".THANKSThanks for reading this far. If you use mercurial-server, please tellme about it.Paul Crowley, 2008