raven ioctl

CVS server configuration.

ioctl.org : unix bits and pieces : CVS stuff : CVS and SSH (server)

Setting up a CVS server for ssh clients

Policy and procedure requirements

It's worthwhile beginning by considering what we're trying to achieve with CVS. We have a number of external contributors; we'd like to give them access to our "outward-facing" CVS repositories in order to enable them to collaborate effectively.


External users get CVS write access to modules in the CVS repository as required by their project. They may also get read access to other modules.

Users get ssh-2-protected public-key-authenticated access to CVS only.


Repository configuration

As a basis for implementation of policy, the following users and groups should be created.

The CVS repository should be set up as usual:

# cd ~cvs
# mkdir cvsrep
# mkdir lock
# cd cvsrep
# cvs init

The file ownerships should be set up as shown below (a sample module directory for project1 is shown).

# cd ~cvs && find . -type d -exec ls {} \;
drwxr-x---  4 cvs  cvsusers    512 Mar  9 14:41 .
drwxrwxr-x  6 cvs  cvsusers    512 Mar  9 17:00 ./lock
drwxr-xr-x  6 cvs  cvs         512 Mar  9 17:00 ./cvsrep
drwxrwxr-x  3 cvs  cvs        1024 Mar 10 09:56 ./cvsrep/CVSROOT
drwxrwxr-x  3 cvs  cvsproject1 512 Mar  9 17:05 ./cvsrep/project1

To ~cvs/cvsrep/CVSROOT/config add the line:


This means people with only read privs to a module (see below) can still check it out.

Project configuration and access control

The clue to this is given on the last line of the directory listing above. CVS access control is performed using Unix permissions.

Note that directories should be set up so that subdirs inherit the group of the parent on creation. This is the default on BSD, on SysV/Solaris you'll need to chmod g+s the directories.

Users with commit priviledges to (say) the module project1 should be in the groups: cvsusers and cvsproject1. As you can see here, _anyone_ who can log on can check out project1 - only those with write permission (under normal Unix rules) get commit priviledges.

Now, add your local and external users (more on this below), setting group permissions on module directories as required. The Unix permissions of 775 on a directory (g+s if required) mean all (cvsusers) can read, group only can write. Permissions of 770 (opt g+s) mean those users with commit priviledges to that directory can read and write; nobody else can.

It is possible to get a finer-grained control on systems that support ACLs. In that case, you might create two groups: cvsproject1 and cvreasel. The first gets write priviledges (and is set up as above): give the directory group cvsproject1 and mode 770. Readers of module project1 go into the cvrpeoject1 group, which is ACL-controlled to get read priviledges to the directory only. Unfortuantely, the way you do this varies from Unix to Unix (there is a POSIX standard for it) - on Solaris, it'd be something like this:

# setfacl -m g:cvreasel:r-x easel

ACLs are nonobvious with most standard Unix tools so this is about the highest level of control I'd attempt to exercise.

There is a caveat here: Unix doesn't let people live in an unbounded number of groups. There may be a limit. Nor are the length of ACLs unbounded. So care is needed in this setup.

Furthermore, it is required that external users have read access to ~cvs/cvsrep/CVSROOT (at least), so a pserver passwd file may need additional file protection. (Actually, "don't use pserver" is a simpler answer - see below.) You should not use your "collaborator-facing" CVS for sensitive data.

Configuration for external collaborators

The following discussion assumes that OpenSSH is installed on the server. This procedure can be made to work with SSH.com's server too, with slight modifications as noted below.

The external user should be identified by the person responsible for their project. A username for CVS access is allocated.

The user account is set up as follows: username (eg, "jbloggs"). Default group: cvsusers. Additional group memberships: any cvs* (or cvr* if you're using that) module-access groups (eg, cvsproject1). They get a real home directory (I suggest those live somewhere obvious, like ~cvs/home/joebloggs), a real shell (/bin/sh will do) and a "*" in their /etc/shadow (or equivalent) password field. In other words, they can log in via ssh only (no ftp, no rsh, nothing that uses the crypted password system). Some ssh servers honour the "*LK*" password entry, so don't just usermod their account locked.

In their home directory a minimal config lives:

jbloggs> ls -alR
drwx------  3 jbloggs cvsusers  512 Mar 10 10:28 .
drwxr-xr-x  6 root    wheel     512 Mar  9 14:12 ..
drwx------  2 jbloggs cvsusers  512 Mar  9 16:15 .ssh
drwx------  2 jbloggs cvsusers  512 Mar  9 16:15 .
drwx------  3 jbloggs cvsusers  512 Mar 10 10:28 ..
-rw-------  1 jbloggs cvsusers  772 Mar  9 16:15 authorized_keys2

What goes in authorized_keys2? Well, they need ssh2 capability at their end. They need to generate a DSA keypair (see the collaborator configuration instructions) and send you the public part of it (often called id_dsa.pub).

That file will look like this:

ssh-dss hdskjfhdksjhSOME_CRAP_IN_HEREdksjshfksj= joebloggs@their.host 

Edit this file. All on the same line, prepend the following options so that the file looks like this (note, no spaces between the options - I've wrapped the output at 80 columns for readability):

n/cvs --allow-root=/abs/path/to/cvs/cvsrep server" ssh-dss hdskjfhdksjhSOME_CRAP
_IN_HEREdksjshfksj= joebloggs@their.host 

Just to spell it out, those options are:

This file should be saved as ~jbloggs/.ssh/authorized_keys2.

Group and world readership of everything under the user's home directory can be turned off.

You're done! At the clent end they need to effectively set up the following:


ssh-2 is whatever command (it must be one word with Unix CVS) is required to run their ssh2 client.

The user still needs to give the correct path to the CVS repository. A properly patched CVS server can prevent the user from accessing anything else; this defence can be augmented by ensuring appropriate permissions (yes, that means another set of users and groups) on any other CVS repositories on the machine.

Further considerations

Update.prog and Checkin.prog

The best explanation of this lives on Bugtraq.

Change to server.c:
   REQ_LINE("Sticky", serve_sticky, 0),
-  REQ_LINE("Checkin-prog", serve_checkin_prog, 0),
-  REQ_LINE("Update-prog", serve_update_prog, 0),
   REQ_LINE("Entry", serve_entry, RQ_ESSENTIAL),

Note: cvsadmin group for restricting "cvs admin" commands.

The result

Hey, presto! - external CVS collaboration without giving too much away.

Changes to this procedure for SSH.com's server or IETF SECSH files

The above discussion assumes that the user and the machine hosting the CVS repository are using OpenSSH. If the user is using SSH.com's client software, it's still possible to turn their public key file into one that OpenSSH can read:

# ssh-keygen -i -f sshcom.pub > opennssh.pub

If the server is using SSH.com's implementation of sshd, then the following changes need to be made:

It's not possible to disable X11-forwarding, etc, using this server. However, the server does support the following options in its central configuration file:

You can't turn off pty allocation, though, but we're not really expecting these people to attempt a denial of service attack against the machine.

As a final note, SSH.com's ssh2.4.0 comes with ssh-dummy-shell which implements a very restricted shell, which might also be used for forcing commands. It's trivial to knock up one of these to run the appropriate cvs command.