hg-gateway: Supporting multiple Mercurial users on a shared SSH account

500px-Mercurial_logo_cropped.svg

One of the requirements I had of my web hosting service was the ability to host Mercurial repositories and share them out via SSH. My current host Arvixe does offer SSH and did install hg for me. While hunting for a hosting service I did try GoDaddy for a bit and that was an unpleasant experience for various reasons, one of which was that they did not have hg installed and were unwilling to do so for a shared hosting account. If you  have the misfortune of being in this situation, download and compile hg as non-root user (without access to gcc) as detailed here. That works with GoDaddy’s servers.

Once you have hg installed, one way to let collaborators to access repositories on your SSH account is to use the hg-ssh script as detailed here. This works by basically relying on the feature of SSH by which you can restrict SSH for particular public keys by adding a “command=” specification to your authorized_keys file.(If you dont know what the authorized_keys file is, you probably should read some SSH public/private key management tutorial before reading this blog entry.)

Given this general arrangement, I wanted a way to manage “which mercurial users have access to which repositories” on my user account on the shared server and also ensure that I have a view of the repositories that is consistent with those of my collaborators. Hence hg-gateway.

Using hg-gateway

Once hg-getway is setup on your shared remote machine (and I’ll detail the steps below) it lets you do the following. You can add a new user, say user1, to your shared system by typing the following on your local machine:

cat user1.pub | ssh you@server.com hg-gateway adduser user1

Stating the obvious: “user1.pub” is user1′s public key file, “you” is your username and “server.com” is your server. A user added like this doesn’t have access to any repositories on your system yet – you have to explicitly give them access. On the other hand, you as the owner of the account have access to every repo in the system. The owner does not have to be given explicit access to anything. You can give user1 access to some number of repos, say repo1 and repo2, by typing:

ssh you@server.com hg-gateway add user1 repo1 repo2

If you wish to grant someone read-only access to a repo (i.e. disable “push” to the repo) then add the “ro:” prefix to the repo path:

ssh you@server.com hg-gateway add user1 ro:repo3

Once you do this, you can ask your collaborator to clone out repo1 on their own machine where they have their private key setup by typing:

hg clone ssh://you@server.com/repo1

In the above snippet its important to note that the URL they use has your username “you” in it and not their username “user1″. Why? Because they are accessing your user account. The “adduser” subcommand basically adds their public key to your authorized_keys, but in a restricted way. On a shared hosting system you cannot create new user accounts without being root and thats the whole point of using hg-gateway.

They can pull and push and this should work the same way it would with bitbucket.org or some such hg hosting service. They can only access the hg repos that you have given them access to. And finally, they do not have general SSH or SCP access to your account. If a user tries to SSH to you@server.com they will be greeted by a message from hg-gateway and the “hg tip” of each repo that they have been access to. This helps give them a summary of the repos they are part of.

There are also the following commands to help with administration:

Remove access to one or more repos:

ssh you@server.com hg-gateway rm user1 repo1 repo2
ssh you@server.com hg-gateway rm user1 "~/path/to/repo"

List permissions by user name:

ssh you@server.com hg-gateway ls
ssh you@server.com hg-gateway ls user1

List permissions by repo name:

ssh you@server.com hg-gateway lsr
ssh you@server.com hg-gateway lsr repo1

See the “hg tip” repo summary as seen by user1:

ssh you@server.com hg-gateway summary user1

If you ask for the summary without mentioning a particular username, you will see a listing of all repositories (thats are under your $hg_root_dir folder – see below). The listing of all repos  is available only to the owner of the SSH account. Normal users of the system will see only the status of repos they have access to.

Create a Bash script

Also, since typing the above remote commands are tedious, I usually like to have a little bash script say “server-hg-gateway” defined to be:

#!/bin/bash
ssh you@server.com hg-gateway $*

Installing hg-gateway

Installation is straightforward. Copy the hg-gateway Ruby script into some place in your PATH on your remote machine. Edit a few variables defined at the top of the script to suit your environment. The main ones are:

$hg_root_dir = "~/hg"

I like having most of my repos in a directory (say “~/hg”), this variable specifies that directory’s name. Whenever you are dealing with a repo name now, the name is relative to this root dir. Hence repo1 is interpreted as “~/hg/repo1″ in this case.

That’s probably the most important variable. The remaining ones help you setup the name of the file where usernames permission  are stored (i.e. information regarding which users can access which repos), the filename where connection attempts will be logged (logging can also be disabled), a banner message displayed to your users and the path to your authorized_keys file (usually this is standard). Many of these variables can be left unchanged, in the common case. Optionally, if you have the misfortune of being hosted on GoDaddy, you can configure $owner_cmd to “source ~/.my_bashrc”, because GoDaddy’s servers do not source .my_bashrc for some reason when you SSH in and execute a specific command i.e. you are left to deal with the annoyance of not having your environment variables (PATH) set. Read about this here and here.

Outside of editing variables in the hg-gateway script, you need to edit your authorized_keys file and add “command=~/bin/hg-gateway owner” next to your own public key (assuming that “~/bin” is where you installed hg-gateway). Once you are done this should look like:

command="~/bin/hg-gateway owner" ssh-dss AAB...XZ= you@machine

The main point of doing this is that your hg urls will also be interpreted relative to $hg_root_dir from now on, not just those of your users. This gives you and your collaborators the same view of the repositories and makes things a bit easier for you.

So here is the the big secret: hg-gateway intercepts incoming SSH connections by being invoked from authorized_keys. When it is placed in the “command=” clause to each SSH key, it determines what what permissions should be enabled for connections on that key. If the key is marked as “owner” as shown above, the key will have full access to the system. If the key is marked as “login <username>” it will have only the privileges assigned to that user.

Thats it!

Getting hg-gateway : the easy way.

hg-gateway is about 300 lines of Ruby, most of which are comments. You can get it from my public repository at: https://bitbucket.org/roshanjames/hg-gateway/get/tip.tar.gz

 

If you are on Ruby 1.9+, you can use https://bitbucket.org/roshanjames/hg-gateway2/get/tip.tar.gz

Getting hg-gateway: the hardcore way (with benefits)

An alternate means of setting up hg-gateway (for the advanced user), which is strongly recommended, would be to clone the hg-gateway repository and add a symlink to the script to your “~/bin/” directory. Once cloned, make your edits to the script as described above, and then commit your changes locally. For example:

hg clone https://bitbucket.org/roshanjames/hg-gateway/ ~/src/hg-gateway
ln -s ~/src/hg-gateway/hg-gateway ~/bin/hg-gateway
 
cd ~/src/hg-gateway
emacs hg-gateway
hg commit -m "my configuration"

For Ruby 1.9+ use the following repository:

https://bitbucket.org/roshanjames/hg-gateway2/

The huge advantage of doing this is that its very easy to update hg-gateway as new versions are released in the future. You just need to type:

ssh you@server hg-gateway update -f

Here the -f is optional. Essentially this does an “hg fetch” and relies on hg to merge in the new updates correctly. As long as you have edited only a few script variables, this should happen seamlessly. You can always achieve the same effect manually by doing:

cd ~/src/hg-gateway
hg fetch

That way you have easily upgradeable hg-gateway installation .

Why am I sharing this out? So that it will be useful to some of you and more importantly so that if there is vulnerability in some of this, maybe someone will let me know.

Comments are closed.