# HG changeset patch # User Paul Crowley # Date 1257783784 0 # Node ID 5dd3698fad544da00c54b1c180c439248481bb07 # Parent 107906bfe2c67a993bd5b15a6cc690348602cbae# Parent 7b69d1d86254de59ef3eb001cb56389b35077b7b Adapt Debian stuff to new build process diff -r 107906bfe2c6 -r 5dd3698fad54 .hgignore --- a/.hgignore Tue Oct 13 18:32:26 2009 +0100 +++ b/.hgignore Mon Nov 09 16:23:04 2009 +0000 @@ -1,3 +1,4 @@ +^build/ syntax: glob *~ diff -r 107906bfe2c6 -r 5dd3698fad54 Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile Mon Nov 09 16:23:04 2009 +0000 @@ -0,0 +1,58 @@ +#!/usr/bin/env make -f + +TOPDIR= +PREFIX=$(TOPDIR)/usr/local/share +LIBDIR=$(PREFIX)/mercurial-server +DOCDIR=$(PREFIX)/doc/mercurial-server +ETCDIR=$(TOPDIR)/etc/mercurial-server +NEWUSER=hg + +INSTALL=install + +build: build/html/index.html pythonbuild + +setup-adduser: installfiles adduser inituser + +# WARNING: this is experimental +setup-useradd: installfiles useradd inituser + +installetc: + $(INSTALL) -d $(ETCDIR) + $(INSTALL) -m 644 -t $(ETCDIR) \ + src/init/conf/remote-hgrc src/init/conf/access.conf + $(INSTALL) -d $(ETCDIR)/keys/root + $(INSTALL) -d $(ETCDIR)/keys/user + +installdoc: build/html/index.html + $(INSTALL) -d $(DOCDIR) + $(INSTALL) -m 644 -t $(DOCDIR) README build/html/index.html + +build/html/index.html: doc/manual.docbook + xsltproc -o $@ /usr/share/xml/docbook/stylesheet/nwalsh/html/docbook.xsl $^ + +pythonbuild: + python setup.py build + +pythoninstall: + python setup.py install \ + --install-purelib=$(LIBDIR) \ + --install-platlib=$(LIBDIR) \ + --install-scripts=$(LIBDIR) \ + --install-data=$(LIBDIR) + +installfiles: installetc installdoc pythoninstall + +adduser: + adduser --system --shell /bin/sh --group --disabled-password \ + --home /var/lib/mercurial-server \ + --gecos "Mercurial repositories" $(NEWUSER) + +# WARNING: this is experimental +useradd: + useradd --system --shell /bin/sh \ + --home /var/lib/mercurial-server --create-home \ + --comment "Mercurial repositories" $(NEWUSER) + +inituser: + su -l -c "$(LIBDIR)/init/hginit $(LIBDIR)" $(NEWUSER) + diff -r 107906bfe2c6 -r 5dd3698fad54 README --- a/README Tue Oct 13 18:32:26 2009 +0100 +++ b/README Mon Nov 09 16:23:04 2009 +0000 @@ -1,8 +1,6 @@ mercurial-server -mercurial-server makes a group of repositories available to the developers -you choose, identified by ssh keys, with easy key and access management -based on hg. +mercurial-server gives your developers remote read/write access to centralized Mercurial repositories using SSH public key authentication; it provides convenient and fine-grained key management and access control. http://www.lshift.net/mercurial-server.html @@ -22,134 +20,17 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -SUMMARY - -mercurial-server makes a group of repositories available to the developers -you choose, identified by ssh keys, with easy key and access management -based on hg. - -All of the repositories controlled by mercurial-server 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 -"/usr/local/share/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 +Though mercurial-server is currently targeted at Debian-based systems such as Ubuntu, other users have reported success getting it running on other Unix-based systems such as Red Hat. Running it on a non-Unix system such as Windows is not supported. You will need root privileges to install it. -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. +The best way to install mercurial-server is using your package management system. However, there is some provision for installing it directly. On Debian based systems such as Ubuntu, use the command -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 -"/usr/local/share/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): + sudo make setup-adduser - 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 "/usr/local/share/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: +On Red Hat and possibly other variants of Unix, try - hg clone ssh://hg@repository-server/hgadmin - cd hgadmin - mkdir keys/users/thatuser - cp /tmp/theirkey keys/users/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", and changes to it take effect immediately - -"refresh-auth" is run after every push. - -With the default access.conf file (see doc/configuring-access for more -details) only users in "keys/root" can act on "hgadmin" - those with keys in -"keys/users" cannot even read this repository. So multiple admins can use -Mercurial's version control to cooperate on controlling access to the -repository server in a natural way. + sudo make setup-useradd -You can also create an "access.conf" file in hgadmin, and this is appended to -/etc/mercurial-server/access.conf whenever this is read - in other words, -rules in the latter take precedence over those in the former. So 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. - -MORE INFORMATION - -For more on how to use mercurial-server and configure access, see the files in -the doc directory. - -THANKS - -Thanks for reading this far. If you use mercurial-server, please tell me about -it. +See doc/manual.docbook for the rest of the documentation. Paul Crowley, paul@lshift.net, 2009 diff -r 107906bfe2c6 -r 5dd3698fad54 debian/dirs --- a/debian/dirs Tue Oct 13 18:32:26 2009 +0100 +++ b/debian/dirs Mon Nov 09 16:23:04 2009 +0000 @@ -1,5 +1,6 @@ usr/share/mercurial-server usr/share/mercurial-server/init +usr/share/mercurial-server/init/conf usr/share/mercurial-server/mercurialserver usr/share/doc/mercurial-server etc/mercurial-server diff -r 107906bfe2c6 -r 5dd3698fad54 debian/docs --- a/debian/docs Tue Oct 13 18:32:26 2009 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,6 +0,0 @@ -README -doc/configuring-access -doc/file-conditions -doc/how-it-works -doc/security - diff -r 107906bfe2c6 -r 5dd3698fad54 debian/rules --- a/debian/rules Tue Oct 13 18:32:26 2009 +0100 +++ b/debian/rules Mon Nov 09 16:23:04 2009 +0000 @@ -16,7 +16,6 @@ touch configure-stamp - build: build-stamp build-stamp: configure-stamp @@ -43,27 +42,9 @@ dh_installdirs # Add here commands to install the package into debian/mercurial-server. - cp \ - src/hg-ssh \ - src/refresh-auth \ - debian/mercurial-server/usr/share/mercurial-server - cp \ - src/mercurialserver/__init__.py \ - src/mercurialserver/paths.py \ - src/mercurialserver/changes.py \ - src/mercurialserver/access.py \ - src/mercurialserver/servelog.py \ - src/mercurialserver/refreshauth.py \ - src/mercurialserver/ruleset.py \ - debian/mercurial-server/usr/share/mercurial-server/mercurialserver - cp \ - src/init/hginit \ - src/init/hgadmin-hgrc \ - debian/mercurial-server/usr/share/mercurial-server/init - cp \ - src/init/conf/remote-hgrc \ - src/init/conf/access.conf \ - debian/mercurial-server/etc/mercurial-server + $(MAKE) installfiles \ + TOPDIR=debian/mercurial-server \ + PREFIX=debian/mercurial-server/usr/share # Build architecture-independent files here. binary-indep: build install @@ -83,7 +64,7 @@ # dh_installemacsen # dh_installpam # dh_installmime -# dh_python + dh_python # dh_installinit # dh_installcron # dh_installinfo diff -r 107906bfe2c6 -r 5dd3698fad54 doc/configuring-access --- a/doc/configuring-access Tue Oct 13 18:32:26 2009 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,47 +0,0 @@ -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 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. The two most -important conditions are - - user= - user's key - repo= - repo (as the user supplies it) - -The first rule in the file which has all its conditions satisfied is used -to determine whether an action is allowed. If no rule is matched the -request is denied. - -"*" only matches one directory level, where "**" matches as many as you -want. More precisely, "*" matches zero or more characters not including "/" -while "**" matches zero or more characters including "/". - -Blank lines and lines that start with "#" are ignored. - -access.conf ships with the following contents: - - init user=root/** - deny repo=hgadmin - write user=users/** - -This means: keys in "root" can do anything; keys in "users" cannot create -repositories, cannot even read the hgadmin repository, but can read and -write any other repository; no other key has any access. - -More advanced access configuration is covered in file-conditions. - diff -r 107906bfe2c6 -r 5dd3698fad54 doc/file-conditions --- a/doc/file-conditions Tue Oct 13 18:32:26 2009 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,62 +0,0 @@ -FILE CONDITIONS - -Read configuring-access before you read this. - -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. - -File and branch conditions are added to the conditions against which a rule -matches, just like user and repo conditions; they have this form: - - file= - file in the repo - branch= - name of the branch - -However, in order to understand what effect adding these conditions will -have, it helps to understand how and when these rules are applied. - -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/* - - diff -r 107906bfe2c6 -r 5dd3698fad54 doc/how-it-works --- a/doc/how-it-works Tue Oct 13 18:32:26 2009 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,24 +0,0 @@ -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 -"/usr/local/share/mercurial-server/hg-ssh" 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 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. - -When users try to commit new changesets, a hook is run which consults the -rules file to decide whether to allow the changeset into the repository. -This can depend not only on the user and the repository, but also the -branch and files in the changeset. diff -r 107906bfe2c6 -r 5dd3698fad54 doc/manual.docbook --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/manual.docbook Mon Nov 09 16:23:04 2009 +0000 @@ -0,0 +1,527 @@ + +
+ + Sharing Mercurial repositories with mercurial-server + PaulCrowley + 2009Paul Crowley, LShift Ltd + +
+About mercurial-server + +Home page: + + +mercurial-server gives your developers remote read/write access to +centralized Mercurial +repositories using SSH public key authentication; it provides convenient +and fine-grained key management and access control. + + +Though mercurial-server is currently targeted at Debian-based systems such +as Ubuntu, other users have reported success getting it running on other +Unix-based systems such as Red Hat. Running it on a non-Unix system such as +Windows is not supported. You will need root privileges to install it. + +
+
+Step by step + +mercurial-server authenticates users not using passwords but using SSH +public keys; everyone who wants access to a mercurial-server repository +will need such a key. In combination with ssh-agent (or +equivalents such as the Windows program Pageant), +this means that users will not need to type in a password to access the +repository. If you're not familiar with SSH public keys, the OpenSSH Public +Key Authentication tutorial may be helpful. + +
+Installing mercurial-server + +In what follows, we assume that your username is jay, that you usually sit at a machine called +spoon and you wish to +install mercurial-server on jeeves. We assume that you have created your SSH public key, set up your SSH agent with this key, and that this key gives you access to jeeves. + +First install mercurial-server on jeeves: +jay@spoon:~$ scp mercurial-server_0.6.1_amd64.deb jeeves: +mercurial-server_0.6.1_amd64.deb 100% +jay@spoon:~$ ssh -A jeeves +jay@jeeves:~$ sudo dpkg -i mercurial-server_0.6.1_amd64.deb +[sudo] password for jay: +Selecting previously deselected package mercurial-server. +(Reading database ... 144805 files and directories currently installed.) +Unpacking mercurial-server (from .../mercurial-server_0.6.1_amd64.deb) ... +Setting up mercurial-server (0.6.1) ... +jay@jeeves:~$ + +mercurial-server is now installed on the repository host. Next, we need to give you permission to access its repositories. + +jay@jeeves:~$ ssh-add -L > my-key +jay@jeeves:~$ sudo mkdir -p /etc/mercurial-server/keys/root/jay +jay@jeeves:~$ sudo cp my-key /etc/mercurial-server/keys/root/jay/spoon +jay@jeeves:~$ sudo -u hg /usr/share/mercurial-server/refresh-auth +jay@jeeves:~$ exit +Connection to jeeves closed. +jay@spoon:~$ + +You can now create repositories on the remote machine and have complete +read-write access to all of them. + +
+
+Creating repositories + +To store a repository on the server, clone it over. + +jay@spoon:~$ cd myproj +jay@spoon:~/myproj$ hg clone . ssh://hg@jeeves/jays/project +searching for changes +remote: adding changesets +remote: adding manifests +remote: adding file changes +remote: added 119 changesets with 284 changes to 61 files +jay@spoon:~/myproj$ hg pull ssh://hg@jeeves/jays/project +pulling from ssh://hg@jeeves/jays/project +searching for changes +no changes found +jay@spoon:~/myproj$ cd .. +jay@spoon:~$ +
+
+Adding other users + +At this stage, no-one but you has any access to any repositories you +create on this system. In order to give anyone else access, you'll need a +copy of their SSH public key; we'll assume you have that key in +~/sam-saucer-key.pub. To manage access, you make changes to the special hgadmin repository. + +jay@spoon:~$ hg clone ssh://hg@jeeves/hgadmin +destination directory: hgadmin +no changes found +updating working directory +0 files updated, 0 files merged, 0 files removed, 0 files unresolved +jay@spoon:~$ cd hgadmin +jay@spoon:~/hgadmin$ mkdir -p keys/users/sam +jay@spoon:~/hgadmin$ cp ~/sam-saucer-key.pub keys/users/sam/saucer +jay@spoon:~/hgadmin$ hg add +adding keys/users/sam/saucer +jay@spoon:~/hgadmin$ hg commit -m "Add Sam's key" +jay@spoon:~/hgadmin$ hg push +pushing to ssh://hg@jeeves/hgadmin +searching for changes +remote: adding changesets +remote: adding manifests +remote: adding file changes +remote: added 1 changesets with 1 changes to 1 files +jay@spoon:~/hgadmin$ + +Sam can now read and write to your +ssh://hg@jeeves/jays/project repository. +Most other changes to access control can be made simply by making and +pushing changes to hgadmin, and you can use Mercurial to +cooperate with other root users in the normal way. + + +If you prefer, you could give them access by +logging into jeeves, +putting the key in the right place under /etc/mercurial-server/keys, and re-running +sudo -u hg /usr/share/mercurial-server/refresh-auth. +However, using hgadmin is usually more convenient if you need to make more than a very few changes; it also makes it easier to share administration with others and provides a log of all changes. + +
+
+
+Access control + +Out of the box, mercurial-server supports two kinds of users: "root" users and normal users. If you followed the steps above, you are a "root" user because your key is under keys/root, while the other user you gave access to is a normal user since their key is under keys/users. Keys that are not in either of these directories will by default have no access to anything. + + +Root users can edit hgadmin, create new repositories and read and write to existing ones. Normal users cannot access hgadmin or create new repositories, but they can read and write to any other repository. + +
+Using access.conf + +mercurial-server offers much more fine-grained access control than this division into two classes of users. Let's suppose you wish to give Pat access to the widget repository, but no other. We first copy Pat's SSH public key into the keys/pat directory in hgadmin. This tells mercurial-server about Pat's key, but gives Pat no access to anything because the key is not under either keys/root or keys/users. To grant this key access, we must give mercurial-server a new access rule, so we create a file in hgadmin called access.conf, with the following contents: +# Give Pat access to the "widget" repository +write repo=widget user=pat + + +Pat will have read and write access to the widget repository as soon as we add, commit, and push these files. + + +Each line of access.conf has the following syntax: + +rule condition condition... + + +Blank lines and lines that start with # are ignored. Rule is +one of + + + +init: allow reads, writes, and the creation of new repositories + + +write: allow reads and writes + + +read: allow only read operations + + +deny: deny all requests + + + +A condition is a globpattern matched against a relative path. The two most +important conditions are + + + +user=globpattern: path to the user's key + + +repo=globpattern: path to the repository + + + +* only matches one directory level, where ** +matches as many as you want. More precisely, * matches zero or +more characters not including / while ** matches +zero or more characters including /. So +projects/* matches projects/foo but not projects/foo/bar, while +projects/** matches both. + + +When considering a request, mercurial-server steps through all the rules in +/etc/mercurial-server/access.conf and then all the +rules in access.conf in hgadmin +looking for a rule which matches on every condition. The first match +determines whether the request will be allowed; if there is no match in +either file, the request will be denied. + + +By default, /etc/mercurial-server/access.conf has the +following rules: + +init user=root/** +deny repo=hgadmin +write user=users/** + + +These rules ensure that root users can do any operation on any repository, +that no other users can access the hgadmin repository, +and that those with keys in keys/users can read or write to any repository +but not create repositories. Some examples of how these rules work: + + + +User root/jay creates a repository +foo/bar/baz. This matches the first +rule and so will be allowed. + + +User root/jay changes repository +hgadmin. Again, this matches the +first rule and so will be allowed; later rules have no effect. + + +User users/sam tries to read +repository hgadmin. This does not +match the first rule, but matches the second, and so will be denied. + + +User users/sam tries to create +repository sams-project. This does +not match the first two rules, but matches the third; this is a +write rule, which doesn't grant the privilege to create +repositories, so the request will be denied. + + +User users/sam writes to existing +repository projects/main. Again, +this matches the third rule, which allows the request. + + +User pat tries to write to existing +repository widget. Until we change +the access.conf file in hgadmin, this will match no rule, and so will +be denied. + + +Any request from a user whose key not under the keys directory at all will always be denied, +no matter what rules are in effect; because of the way SSH authentication +works, they will be prompted to enter a password, but no password will +work. This can't be changed. + + +
+
+/etc/mercurial-server and hgadmin + +mercurial-server consults two distinct locations to collect information about what to allow: /etc/mercurial-server and its own hgadmin repository. This is useful for several reasons: + + + +Some users may not need the convenience of access control via mercurial; for these users updating /etc/mercurial-server may offer a simpler route. + + +/etc/mercurial-server is suitable +for management with tools such as Puppet + + +If a change to hgadmin leaves you "locked out", /etc/mercurial-server allows you a way back in. + + +At install time, all users are "locked out", and so some mechanism to allow some users in is needed. + + + +Rules in /etc/mercurial-server/access.conf are checked before those in hgadmin, and keys in /etc/mercurial-server/keys will be present no matter how hgadmin changes. + + +We anticipate that once mercurial-server is successfully installed and +working you will usually want to use hgadmin for most +access control tasks. Once you have the right keys and +access.conf set up in hgadmin, you +can delete /etc/mercurial-server/access.conf and all +of /etc/mercurial-server/keys, +turning control entirely over to hgadmin. + + +/etc/mercurial-server/remote-hgrc is in the +HGRCPATH for all remote access to mercurial-server +repositories. This file contains the hooks that mercurial-server uses for +access control and logging. You can add hooks to this file, but obviously +breaking the existing hooks will disable the relevant functionality and +isn't advisable. + +
+
+File and branch 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. + +The way these conditions work is subtle and can be counterintuitive. Unless +you need what they provide, ignore this section, stick to user and repo +conditions, and then things are likely to work the way you would expect. If +you do need what they provide, read what follows very carefully. + + +File and branch conditions are added to the conditions against which a rule +matches, just like user and repo conditions; they have this form: + + + +file=globpattern: file within the repo + + +branch=globpattern: Mercurial branch name + + + +However, in order to understand what effect adding these conditions will +have, it helps to understand how and when these rules are applied. + + +The rules file is used to make three decisions: + + + +Whether to allow a repository to be created + + +Whether to allow any access to a repository + + +Whether to allow a changeset + + + +When the first two of these decisions are being made, nothing is known +about any changsets that might be pushed, and so all file and branch +conditions automatically succeed for the purpose of such decisions. For the +third condition, every file changed in the changeset must be allowed by a +write or init rule for the changeset +to be allowed. + + +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. + +write user=docs/* branch=docs file=docs/* + + +This rule grants users whose keys are in the docs subdirectory the power to push changesets +into any repository only if those changesets are on the +docs branch and they affect only those files directly +under the docs directory. However, +the rules below have more counterintuitive consequences. + +write user=docs/* branch=docs +write user=docs/* file=docs/* +read user=docs/* + + +These rules grant users whose keys are in the docs subdirectory the power to change any file directly under the docs directory, or any file at all in the docs branch. Indirectly, however, this adds up to the power to change any file on any branch, simply by making the change on the docs branch and then merging the change into another branch. + + + +
+
+
+How mercurial-server works + +All of the repositories controlled by mercurial-server are owned by a +single user, the hg user, which is why all URLs for +mercurial-server repositories start with ssh://hg@.... +Each SSH key that has access to the repository has an entry in +~hg/.ssh/authorized_keys; this is how the SSH daemon +knows to give that key access. When the user connects over SSH, their +commands are run in a custom restricted shell; this shell knows which key +was used to connect, determines what the user is trying to do, checks the +access rules to decide whether to allow it, and if allowed invokes +Mercurial internally, without forking. + + +This restricted shell also ensures that certain Mercurial extensions are +loaded when the user acts on a repository; these extensions check the +access control rules for any changeset that the user tries to commit, and +log all pushes and pulls into a per-repository access log. + + +refresh-auth recurses through the /etc/mercurial-server/keys and the keys directory in the +hgadmin repository, creating an entry in +~hg/.ssh/authorized_keys for each one. This is redone +automatically whenever a change is pushed to hgadmin. + +
+
+Security + +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. Any attack on mercurial-server can only be started if the attacker +already has 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 operation itself if access is +allowed, so users can only read and add to history within repositories; +they cannot run any other 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 short, mercurial-server is a fairly +new program and may harbour bugs. Backups are essential! + +
+
+Legalese + +This program is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation; either version 2 of the License, or (at your option) +any later version. + + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +more details. + + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., 51 +Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +
+
+Thanks + +Thanks for reading this far. If you use mercurial-server, please tell me about +it. + + +Paul Crowley, paul@lshift.net, 2009 + +
+
+ diff -r 107906bfe2c6 -r 5dd3698fad54 doc/security --- a/doc/security Tue Oct 13 18:32:26 2009 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ -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 short, mercurial-server is a fairly -new program and may harbour bugs. Backups are essential! diff -r 107906bfe2c6 -r 5dd3698fad54 install --- a/install Tue Oct 13 18:32:26 2009 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,83 +0,0 @@ -#!/usr/bin/env python - -import sys -import shutil -import os -import pwd -import subprocess -import optparse - -oparser = optparse.OptionParser() - -oparser.add_option("--prefix") -oparser.add_option("--root") -oparser.set_defaults(root="", prefix="/usr/local") -(options, args) = oparser.parse_args() - -if len(args) > 0: - oparser.print_help() - sys.exit(-1) - -# This must be run as root, because it must create an hg user. -# Normally the clean thing to do is let it fail with a permission error -# if a non-root user tries to run it, but I don't want anyone thinking -# that they can make it work as non-root by changing install paths. -# Patches for doing this more cleanly welcome of course. - -if os.getgid() != 0: - print >>sys.stderr, "Install must be run as root user" - sys.exit(-1) - - -def installFiles(d, *sources): - d = options.root + d - os.makedirs(d) - for f in sources: - shutil.copy(f, d) - -installFiles(options.prefix + '/share/mercurial-server', - 'src/hg-ssh', - 'src/refresh-auth') -installFiles(options.prefix + '/share/mercurial-server/mercurialserver', - 'src/mercurialserver/__init__.py', - 'src/mercurialserver/paths.py', - 'src/mercurialserver/changes.py', - 'src/mercurialserver/access.py', - 'src/mercurialserver/servelog.py', - 'src/mercurialserver/refreshauth.py', - 'src/mercurialserver/ruleset.py') -installFiles(options.prefix + '/share/mercurial-server/init', - 'src/init/hginit', - 'src/init/hgadmin-hgrc') -installFiles(options.prefix + '/share/doc/mercurial-server', - 'README', - 'doc/configuring-access', - 'doc/file-conditions', - 'doc/how-it-works', - 'doc/security') -installFiles('/etc/mercurial-server', - 'src/init/conf/remote-hgrc', - 'src/init/conf/access.conf') -installFiles('/etc/mercurial-server/keys/root') -installFiles('/etc/mercurial-server/keys/users') - -def becomeFunc(u): - p = pwd.getpwnam(u) - def become(): - os.setgid(p.pw_gid) - os.setegid(p.pw_gid) - os.setuid(p.pw_uid) - os.seteuid(p.pw_uid) - return become - -if options.root == '': - try: - pwd.getpwnam('hg') - except KeyError: - subprocess.check_call( - "adduser --system --shell /bin/sh --group --disabled-password".split() + - ["--gecos", "Mercurial repositories", "hg"]) - subprocess.check_call([options.prefix + '/share/mercurial-server/init/hginit', - options.prefix + '/share/mercurial-server'], - preexec_fn = becomeFunc('hg')) - diff -r 107906bfe2c6 -r 5dd3698fad54 setup.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/setup.py Mon Nov 09 16:23:04 2009 +0000 @@ -0,0 +1,26 @@ +# WARNING: this file is NOT meant to be directly executed, but +# run from the Makefile. + +from distutils.core import setup + +setup( + name="mercurial-server", + description="Centralized Mercurial repository manager", + url="http://www.lshift.net/mercurial-server.html", + version="0.6+", # FIXME: infer this + package_dir = {'': 'src'}, + packages = ["mercurialserver"], + requires = ["mercurial"], # FIXME: what version? + scripts = ['src/hg-ssh', 'src/refresh-auth'], + data_files = [ + ('init', [ + 'src/init/hginit', + 'src/init/dot-mercurial-server', + 'src/init/hgadmin-hgrc' + ]), ('init/conf', [ + 'src/init/conf/remote-hgrc', + 'src/init/conf/access.conf', + ]), + ], +) + diff -r 107906bfe2c6 -r 5dd3698fad54 src/hg-ssh --- a/src/hg-ssh Tue Oct 13 18:32:26 2009 +0100 +++ b/src/hg-ssh Mon Nov 09 16:23:04 2009 +0000 @@ -84,13 +84,11 @@ # Use a different hgrc for remote pulls - this way you can set # up access.py for everything at once without affecting local operations -os.environ['HGRCPATH'] = paths.getEtcPath() + "/remote-hgrc" - -os.chdir('repos') +os.environ['HGRCPATH'] = paths.getHgrcPaths() -for f in [ - paths.getEtcPath() + "/access.conf", - os.getcwd() + "/hgadmin/access.conf"]: +os.chdir(paths.getReposPath()) + +for f in paths.getAccessPaths(): if os.path.isfile(f): ruleset.rules.readfile(f) diff -r 107906bfe2c6 -r 5dd3698fad54 src/init/dot-mercurial-server --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/init/dot-mercurial-server Mon Nov 09 16:23:04 2009 +0000 @@ -0,0 +1,9 @@ +# WARNING: a .mercurial-server file in your home directory means +# that refresh-auth can and will trash your ~/.ssh/authorized_keys file. + +[paths] +repos = ~/repos +keys = /etc/mercurial-server/keys:~/repos/hgadmin/keys +access = /etc/mercurial-server/access.conf:~/repos/hgadmin/access.conf +hgrc = /etc/mercurial-server/remote-hgrc + diff -r 107906bfe2c6 -r 5dd3698fad54 src/init/hginit --- a/src/init/hginit Tue Oct 13 18:32:26 2009 +0100 +++ b/src/init/hginit Mon Nov 09 16:23:04 2009 +0000 @@ -2,8 +2,16 @@ set -e -cd ~hg +cd + +if [ -e .ssh/authorized_keys ] ; then + echo "This user already exists with authorized keys, aborting" + exit -1 +fi + +cp $1/init/dot-mercurial-server .mercurial-server mkdir -p repos/hgadmin .ssh cd repos/hgadmin hg init . cp $1/init/hgadmin-hgrc .hg/hgrc + diff -r 107906bfe2c6 -r 5dd3698fad54 src/mercurialserver/paths.py --- a/src/mercurialserver/paths.py Tue Oct 13 18:32:26 2009 +0100 +++ b/src/mercurialserver/paths.py Mon Nov 09 16:23:04 2009 +0000 @@ -1,10 +1,43 @@ # Copyright 2008-2009 LShift Ltd -# Crude but it will do - import sys import os.path +import mercurial.config +globalconfig = None + +def _getConf(): + global globalconfig + if globalconfig is None: + globalconfig = mercurial.config.config() + globalconfig.read(os.path.expanduser("~/.mercurial-server")) + return globalconfig + +def configExists(): + try: + _getConf() + return True + except: + return False + +def _getPath(name): + return os.path.expanduser(_getConf()["paths"][name]) + +def _getPaths(name): + return [os.path.expanduser(p) + for p in _getConf()["paths"][name].split(":")] + + +def getExePath(): return _getPath("exe") +def getReposPath(): return _getPath("repos") + +def getKeysPaths(): return _getPaths("keys") +def getAccessPaths(): return _getPaths("access") + +# This goes into an env var, so pass it on verbatim. +def getHgrcPaths(): return _getConf()["paths"]["hgrc"] + +# Work out where we are, don't use config. def setExePath(): global _exePath _exePath = os.path.dirname(os.path.abspath(sys.argv[0])) @@ -12,5 +45,3 @@ def getExePath(): return _exePath -def getEtcPath(): - return "/etc/mercurial-server" diff -r 107906bfe2c6 -r 5dd3698fad54 src/mercurialserver/refreshauth.py --- a/src/mercurialserver/refreshauth.py Tue Oct 13 18:32:26 2009 +0100 +++ b/src/mercurialserver/refreshauth.py Mon Nov 09 16:23:04 2009 +0000 @@ -9,16 +9,14 @@ import base64 import os import os.path -import pwd import subprocess from mercurialserver import paths goodkey = re.compile("[/A-Za-z0-9._-]+$") -def refreshAuth(pw_dir): - akeyfile = pw_dir + "/.ssh/authorized_keys" +def refreshAuth(): + akeyfile = os.path.expanduser("~/.ssh/authorized_keys") wrappercommand = paths.getExePath() + "/hg-ssh" - keydirs = [paths.getEtcPath() + "/keys", pw_dir + "/repos/hgadmin/keys"] prefix='no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,command=' if os.path.exists(akeyfile): @@ -31,7 +29,7 @@ f.close() akeys = open(akeyfile + "_new", "w") - for keyroot in keydirs: + for keyroot in paths.getKeysPaths(): kr = keyroot + "/" #print "Processing keyroot", keyroot for root, dirs, files in os.walk(keyroot): @@ -63,6 +61,5 @@ os.rename(akeyfile + "_new", akeyfile) def hook(ui, repo, hooktype, node=None, source=None, **kwargs): - pentry = pwd.getpwuid(os.geteuid()) - refreshAuth(pentry.pw_dir) + refreshAuth() diff -r 107906bfe2c6 -r 5dd3698fad54 src/refresh-auth --- a/src/refresh-auth Tue Oct 13 18:32:26 2009 +0100 +++ b/src/refresh-auth Mon Nov 09 16:23:04 2009 +0000 @@ -8,19 +8,17 @@ import sys import os -import pwd from mercurialserver import refreshauth, paths if len(sys.argv) != 1: sys.stderr.write("refresh-auth: must be called with no arguments (%s)\n" % sys.argv) sys.exit(-1) -pentry = pwd.getpwuid(os.geteuid()) -if pentry.pw_name != "hg": - # FIXME: re-execute +# To protect the authorized_keys file for innocent users, you have to have +# a ~/.mercurial-server file to run this. +if not paths.configExists(): print >>sys.stderr, "Must be run as the 'hg' user" sys.exit(-1) paths.setExePath() - -refreshauth.refreshAuth(pentry.pw_dir) +refreshauth.refreshAuth()