CVS Guest Account

1. Introduction

This document explains how to set up a CVS server with the following properties:

The drawback to the approach presented here is it involves modifying the sources to the cvs server, using a supplied patch that adds an additional command line argument. I will see about getting the change integrated into the mainline CVS distribution.

2. Theory of Operation

As far as I know, there are basically two approaches to remotely accessing a CVS repository: SSH and pserver.

2.1 Problems with SSH

The usual SSH approach involves using the ":ext:" repository access method, and setting the CVS_RSH environment variable to "ssh". This causes the CVS client to use SSH to contact the server, as if logging in for shell access. Once connected, CVS invokes the command "cvs server" on the server, and the CVS client and server proceed to talk over the connection.

I used this method for quite a while. The problem is that each CVS user needs a corresponding local account on the server, and creating those accounts is a hassle. It is also a security risk to a degree, if the account has shell access, which it typically does (locking down each account separately adds to the hassle of creating them).

If all users share one account on the server, then CVS logs (etc.) are marked as if that one account made all the changes, instead of changes being marked as being made by the particular person responsible. That is unacceptable.

2.2 Problems with pserver

Pserver solves the problem of CVS audit trails, because the pserver protocol provides for the client to tell the server what username to use. An associated password file (independent of the system password file) authenticates users' claimed username.

However, pserver has problems of its own:

2.3 Problems with existing pserver+SSH combinations

An obvious response to the above problems is to try combining SSH and pserver. One solution is to use SSH port forwarding. However, as evidenced by the linked page, setup on the client side is somewhat complicated. I would like to avoid this, putting the burden of (one-time!) setup on the server admin instead.

Another nice solution is to use a proxy CVS_RSH program that lets the CVS client talk via the ":ext:" protocol but in fact behind the scenes uses SSL to contact the server and then invoke pserver once there. My problem with this particular approach is that the SSL connection offers privacy but not authenticity, meaning that anyone on the network can talk to pserver (via the SSL tunnel), and I do not trust pserver. True, the first step in pserver's protocol is to establish authenticity via its own password file, but I don't trust it to get even that far without offering a buffer to overflow. Also, there is the matter of clients getting and using this proxy program.

2.4 My Solution

The only problem with SSH alone and a single shared account (as in Section 2.1 above) is that the CVS audit trail is lost. So, I simply modified the CVS server so it could be told which username to use in the logs, instead of consulting getuid.

With this feature in place, I then utilize a feature of SSH servers that allow a given public key to be associated with a specific command to be executed upon connection, rather than executing a shell or a supplied command (a command supplied in the protocol stream is simply ignored). The specific command I choose supplies the right username to CVS, and everything works.

Finally, we lock down the guest account in the usual ways.

Note that the primary security improvement over the the proxy CVS_RSH method of Section 2.3 is that sshd does authentication before "cvs server" is ever run, so arbitrary net users cannot talk to cvs (not even its pserver authentication stage).

3. Modifying the CVS Server

Download a tarball with the CVS sources. I used

Unpack the sources and go into the src subdirectory:

  $ bunzip2 -c cvs-1.11.18.tar.bz2 | tar xvf -
  $ cd cvs-1.11.18
  $ cd src

Apply the patch main.c.patch. The patch adds the -u command line argument that we'll use later.

  $ patch < main.c.patch

Configure, build and install the server:

  $ cd ..
  $ ./configure --prefix=/opt/cvs-1.11.18
  $ make
  # make install    (as root, or a user than can write to chosen prefix)

4. Setting up the Guest Account

Create a new user on the server; I called mine cvsguest. Let $CVSGUEST be its home directory. Do not give it a password, so no that one can log in via password (put a "*" in the password field of /etc/shadow).

Create $CVSGUEST/bin/cvsguest-ssh-command. Make it executable. Modify the script so it will execute the cvs you compiled in Section 3, which may be in a different location than mine is.

The script as supplied maintains a log of each connection, which I intend to use to help identify old keys that can be removed. Feel free to modify it.

Note: The technique of forcing a command assumes that the cvs client is always trying to run "cvs server" when it connects; the script above is then a variation on "cvs server". As far as I can tell, this is indeed what the client always does. However, if there is a client or option that uses some other command, say "cvs some-other-server", then forcing it to use the script above might break it because the script has no way to know what command the client was actually trying to run (sshd drops that on the floor). Caveat emptor.

Note: The next part is specific to the OpenSSH SSH server; you will have to change it if you are using another SSH server, such as the one from

Create $CVSGUEST/.ssh/authorized_keys2. Put a line into it:

command="$CVSGUEST/bin/cvsguest-ssh-command user" keytype key comment
The parts are: It may help to look at an example. Note that I have removed most of my key, even though it is the public key and in theory is not a problem to be published, because I am paranoid.

5. Lock Down the Guest Account

As mentioned above, do not give the guest account a password. That takes care of most of the locking down, since the only access will be by public key and we will give explicit commands in that case (as already explained above). You can pursue additional methods of ensuring limited access, depending on your system's capabilities.

The only remaining thing is to ensure that CVS guests cannot modify the files in the CVSROOT directory of the repository. The way I have done this is:

6. Client Setup

The first step is to create a public key / private key pair, if you do not already have one. This is typically done with the ssh-keygen program. It should put a key pair into $HOME/.ssh or $HOME/.ssh2. Set up your SSH client to use the key pair. See your SSH client's documentation, e.g., man ssh and man ssh-keygen.

Now send the public key to the CVS server admin. The public key does not (in theory) need to be kept private, though authenticity is of course still important. Emailing the key is probably good enough. The admin will associate your name with the key in the authorized_keys2 file.

Contact the CVS server using the ":ext:" access method, ssh as the external program, and cvsguest as the user. On Unix, do something like:

  $ export CVS_RSH=ssh
  $ cvs -d checkout whatever

7. References


Valid HTML 4.01!