Configuring CVS for development

By Arnout J. Kuiper

Introduction

In a lot of (development) environments CVS is not setup properly, which may give problems in the future. Most of these problems are related to security. For example all users can commit to all modules, including CVSROOT. For small projects/teams this might be acceptable, but when a project grows, you might want to restrict access to certain modules.

In this document, a configuration of CVS is shown where the users have only CVS access, and no shell access, and access to modules can be restricted by means of an ACL (Access Control List).

In this document, the following assumptions are made:

Installing CVS

Install the cvs binary and cvs_acls script. This can be done on various ways, using a package, tar, or even compile it yourself. This is not described here. See the documentation belonging to the preferred installation form.

Configuring the cvs pserver

As the system user 'root', add the following line to the '/etc/services' file:

cvspserver       2401/tcp                   # CVS pserver

This assigns a symbolic name to the pserver service.

As the system user 'root', add the following line to the '/etc/inetd.conf' file:

cvspserver  stream  tcp  nowait  root  /usr/local/bin/cvs cvs -f --allow-root=/home/cvs/projects pserver

(this is one line!)

Note the '--allow-root' option which should contain the repository directory.

Creating system users

First create 2 groups in '/etc/group', cvs and cvsadmin.

cvsadmin::97:cvs
cvs::98:

'cvsadmin' is the group which contains the users who are allowed to execute 'cvs admin'  commands. 'cvs' is the group containing all cvs users.

Next create a user 'cvs' in '/etc/passwd', with a home directory, and assign a password to this user. This is the user who 'owns' the repositories. This user should have the 'cvs' group as its primary group, and it should also be added to the 'cvsadmin' group in '/etc/group'.

Repositories are created in the homedirectory of the 'cvs' user.

Creating a(nother) repository

Creating the repository directory

In this homedirectory of the 'cvs' user, create a directory with a name that identifies the new repository (e.g. 'repository' or 'projects'). For now, assume the repository name is 'projects'. Make sure that the directory is owned by the user 'cvs' and the group 'cvs', and that the permissions are drwxrwxr-x:

mkdir /home/cvs/projects
chmod 775 /home/cvs/projects
chown cvs:cvs /home/cvs/projects

Make sure that in the '/etc/inetd.conf' file, the cvs pserver has an option '--allow-root=/home/cvs/projects'.

Initializing the repository

As the system 'cvs' user, initialize the repository by using the following command:

cvs -d :pserver:cvs@localhost:/home/cvs/projects login

Use the system password of the 'cvs' user when asked for a password.

cvs -d :pserver:cvs@localhost:/home/cvs/projects init

Configuring the repository

Setting up initial password file

Go to the CVSROOT directory within the repository directory (e.g. /home/cvs/projects/CVSROOT) and create a file 'passwd' in that directory with the following contents:

cvs:????????

where ???????? is the encrypted cvs password for the 'cvs' user. Use the script from appendix A to encrypt the password.

Setting up initial ACL

In a temporary location, for instance /home/cvs/tmp, do a checkout of CVSROOT:

mkdir /home/cvs/tmp
cd /home/cvs/tmp
cvs -d :pserver:cvs@localhost:/home/cvs/projects login

Use the cvs password of the 'cvs' user when asked for a password.

cvs -d :pserver:cvs@localhost:/home/cvs/projects checkout CVSROOT
cd CVSROOT

In the CVSROOT directory, create a file 'avail' with the following contents:

avail
unavail||CVSROOT
avail|cvs|CVSROOT

This configuration allows every registered cvs user to access all modules in cvs (except for CVSROOT). If the policy is to deny everybody access, unless granted, use the following contents:

unavail
avail|cvs|CVSROOT

Edit the 'commitinfo' file, and add the following line:

DEFAULT       /usr/local/bin/cvs_acls

where /usr/local/bin is the path to the 'cvs_acls' file that comes with the cvs distribution.

Finally add the following line to the file 'checkoutlist':

avail   ACL file could not be checked out

This automates the checkout of the 'avail' file in the $CVSROOT/CVSROOT directory.

Add 'avail' to cvs and commit the changes:

cvs add avail
cvs commit

Block system users

In order to prevent unauthorized acces by users which are not registered as CVS users, it is wise to block system users. This is done by editting the $CVSROOT/CVSROOT/config file.

In a temporary location, for instance /home/cvs/tmp, do a checkout of CVSROOT:

mkdir /home/cvs/tmp
cd /home/cvs/tmp
cvs -d :pserver:cvs@localhost:/home/cvs/projects login

Use the cvs password of the 'cvs' user when asked for a password.

cvs -d :pserver:cvs@localhost:/home/cvs/projects checkout CVSROOT
cd CVSROOT

Edit the 'config' file. Make sure the following line is there:

SystemAuth=no

After that, commit the changes to CVS:

cvs commit

Creating cvs user accounts

Everybody who needs access to CVS, needs a user account on both the CVS and OS level. User accounts are needed on OS level only for ownership and permissions. These users don't have access to the system itself. It is wise to reserve a range of UIDs for cvs  users, and use some sort of naming convention in order to distinguish between actual users and cvs users (e.g. prepend each username with 'cvs').

First add the user to /etc/passwd without assigning a password:

cvsusr1:x:2001:98:CVS User:/:

where cvsusr1 is the username for the cvs user, 2001 is a unique UID from the range of reserved UIDs for cvs, 98 is the group 'cvs', and 'CVS User' is the actual name of the user.

The next thing is to assign the cvs user a password. This is done in the passwd file of the repository he/she needs access to (e.g. /home/cvs/repository/CVSROOT/passwd). Add a line like:

cvsusr1:????????

where ???????? represents the encrypted password. See appendix A for a script which can assist in the encrypting of the password.

Using ACLs

Basically there are two policies regarding ACLs in cvs; deny everybody access by default, or allow everybody access by default.

Default deny policy

The basic ACL looks like this:

unavail
avail|cvs|CVSROOT

To allow a user access to a certain module, add a line like the following:

avail|cvsusr1|module1/src

This line allows the cvs user 'cvsusr1' access to the module1/src directory, and all directories under it. Multiple users may be supplied by comma-separating them. The same holds for the directories.

Default allow policy

The basic ACL looks like this:

avail
unavail||CVSROOT
avail|cvs|CVSROOT

To deny a user access to a certain module, add a line like the following:

unavail|cvsusr1|module1/src

This line denies the cvs user 'cvsusr1' access to the module1/src directory, and all directories under it. Multiple users may be supplied by comma-separating them. The same holds for the directories.

References

Appendix A

Perl script to encrypt a password (encrypt.pl)

#!/usr/bin/perl
$passwd = $ARGV[0];
$salt = join '',('.', '/',0..9,'A'..'Z','a'..'z')[rand 64,rand 64];
$encpasswd = crypt $passwd,$salt;
print "Encrypted password = $encpasswd\n";

About the author

Arnout J. Kuiper is a Java architect within the Sun Java Center in the Netherlands, with a broad interest in Java and web-related technologies.