17 January 2003 Managing Access with CVS Introduction CVS is the Concurrent Versioning System - the most popular free source code manager around. Pretty much every open source or free software developer has at some time or other crossed swords with it. In addition, many companies use CVS as a cheaper alternative to more costly SCMs such as Perforce or ClearCase to manage their internal source code. A source code manager allows you to "remember" old known-good states of your source code, track the changes to your code over time, and allow development by several developers on the same source code at the same time. CVS provides basic versioning functionality, including branching (allowing several versions of the source code to be developer concurrently) and tagging (remembering snapshots of the repository). It has a well-earned reputation as a solid and reliable piece of software. One of the key issues when dealing with source code management is deciding who can get at it. In the open source and free software world, we generally want as many people as possible to have access to our software, but we don't want just anyone changing our copy of it. Nor do we want to allow just anyone access to our machine to do with it as they will. On commercial projects, the group of people who are allowed access to source code is usually as small as possible, even within the company. This article addresses the rarely-addressed issue of the mechanisms available for controlling access to source code stored in CVS repositories. In this article, we look at common project set-ups, and present a scheme adequate to the needs of the project. The access strategy that you use will depend on your situation. In many cases, the most sophisticated scheme requires more administering than a lone developer is required to spend, for example. The goal of this article is to give an idea of the simplest scheme that you will have to use to cater for your particular situation. The Lone Developer So you've just started on a pet project - your code compiles, only just, and you'd like to start adding features and polishing interfaces to the stage where you are happy enough to let other people look at it. You're the only one that will have access to the code, and you are doing all your development in the machine housing the code. First, set aside some space for your repository. You'll need r/w access to the directory (somewhere in /home is fine). For the purposes of the article, we'll assume your user-name is john, and that your project sources are in $HOME/project. john@bolsh:~$ mkdir cvs john@bolsh:~$ ls -l total 8 drwxr-xr-x 2 john john 4096 Dec 26 16:38 cvs drwxr-xr-x 2 john john 4096 Dec 26 16:52 project Then Let CVS know you want to store your sources in that directory, initialise CVS, and import your sources. Et voilą - you're ready to CVS away to your heart's content. john@bolsh:~$ export CVSROOT=/home/john/cvs/ john@bolsh:~$ cvs init john@bolsh:~$ ls cvs CVSROOT john@bolsh:~$ cd project/ john@bolsh:~/project$ cvs import -m "Initialising sources in CVS" project john start N project/main.c N project/logic.h N project/logic.c N project/Makefile No conflicts created by this import john@bolsh:~/project$ cd .. john@bolsh:~$ rm -rf project/ john@bolsh:~$ cvs co project cvs checkout: Updating project U project/Makefile U project/logic.c U project/logic.h U project/main.c john@bolsh:~$ Woohoo! Now, if you make changes to any files in your project, cvs commit captures those changes forever. And you are all set to hack away in security on your project. For the moment, we won't worry too much about how CVS does what it does (later we will have to think a little bit about it) - if you want to go exploring in ~/cvs/project or CVSROOT, feel free. The project grows The thing that you've always dreamed of has happened - your project has become popular, and you're experiencing a bazaar effect - you are being flooded with patches by mail and are spending all your time committing them and not enough time developing yourself. You decide that the time is right to start trusting some other people to check in stuff to your project. There are a few problems, though - they want to work in their own development environment, not on your machine. No problem! You can grant them access to your machine and CVS knows any number of ways to connect. In all these cases, you'll need to create an account on your machine for all the developers you trust. In any case, once we are dealing with multiple developers, we need to know how file permissions issues affect a CVS repository. Letting people in If we have a look at the permissions in our repository, we'll see that every ,v file has permissions 444 (read-only for everyone), and all directories have permissions 776 (that is, read-write for user & group, and readable by anyone else). Those are reasonable permissions, but they imply that all your CVS users belong to the same group as the repository. To allow all trusted CVS users access to the repository, we will create a cvsusers group. To do this, add cvsusers:x:4901:john,jack to /etc/group to create a cvsusers group, and add john and jack to it. Note, 4901 is not special, it's any number not already in use as a group id or user id - I used it because it's the default port for a CVS pserver, which creates a nice link between the two. Now, we should to change the group of all files in the repository to cvsusers (we only need to change the group of the directories, but we may as well do it for all files to be consistent). john@bolsh:~$ find cvs/ -exec chgrp cvsusers {} \; This still leaves us with a sticky problem. The easiest way to see the problem is with an example. John has a friend Jack that he's decided to trust to write to the repository. So Jack gets an account on John's machine, logs in, sets CVSROOT to point at the repository and checks out the project to work away on it. After making some changes, he checks them in. Let's see what happens... Before Jack checks anything in, the files in $CVSROOT/project look like this... john@bolsh:~/cvs/project$ ls -l total 16 -r--r--r-- 1 john cvsusers 538 Dec 26 16:56 Makefile,v -r--r--r-- 1 jack cvsusers 709 Dec 27 21:47 logic.c,v -r--r--r-- 1 john cvsusers 606 Dec 28 16:55 logic.h,v -r--r--r-- 1 john cvsusers 541 Dec 26 16:56 main.c,v Now Jack makes a change, and checks it in... jack@bolsh:~/project$ cvs update cvs server: Updating . M logic.h jack@bolsh:~/project$ cvs commit -m "made a change" cvs commit: Examining . Checking in logic.h; /home/john/cvs/project/logic.h,v <-- logic.h new revision: 1.3; previous revision: 1.2 done jack@bolsh:~/project$ And let's look at those permissions now... john@bolsh:~/cvs/project$ ls -l total 16 -r--r--r-- 1 john cvsusers 538 Dec 26 16:56 Makefile,v -r--r--r-- 1 jack cvsusers 709 Dec 27 21:47 logic.c,v -r--r--r-- 1 jack jack 737 Dec 28 17:23 logic.h,v -r--r--r-- 1 john cvsusers 541 Dec 26 16:56 main.c,v john@bolsh:~/cvs/project$ But we just changed the file to be john:cvsusers - so what's going on? Well, CVS is running as jack, and jack's default group is jack. So when jack commits his change, and cvs reconstructed the ,v file, naturally the file's ownership gets changed too. In this case, that's fine - every user can still read the file and the directory has the same permissions. But what if Jack wants to add some files to a new subdirectory? jack@bolsh:~/project$ cvs add subdir ? subdir/test Directory /home/john/cvs/project/subdir added to the repository jack@bolsh:~/project$ cvs add subdir/test cvs server: scheduling file `subdir/test' for addition cvs server: use 'cvs commit' to add this file permanently jack@bolsh:~/project$ cvs commit -m "adding new file in subdir" cvs commit: Examining . cvs commit: Examining subdir RCS file: /home/john/cvs/project/subdir/test,v done Checking in subdir/test; /home/john/cvs/project/subdir/test,v <-- test initial revision: 1.1 done jack@bolsh:~/project$ And now in the repository, we have... john@bolsh:~/cvs/project$ ls -l total 20 -r--r--r-- 1 john cvsusers 538 Dec 26 16:56 Makefile,v -r--r--r-- 1 jack cvsusers 709 Dec 27 21:47 logic.c,v -r--r--r-- 1 jack jack 737 Dec 28 17:23 logic.h,v -r--r--r-- 1 john cvsusers 541 Dec 26 16:56 main.c,v drwxrwxr-x 2 jack jack 4096 Dec 28 17:29 subdir As expected, subdir is owned by the user jack, and the group jack. But what does this mean? To see, let's try and update John's working copy of the repository now. john@bolsh:~/project$ cvs update -d cvs update: Updating . U logic.c U logic.h cvs update: Updating subdir cvs update: failed to create lock directory for `/home/john/cvs/project/subdir' (/home/john/cvs/project/subdir/#cvs.lock): Permission denied cvs update: failed to obtain dir lock in repository `/home/john/cvs/project/subdir' cvs [update aborted]: read lock failed - giving up Gah! John no longer has even read-only access to the subdirectory. We can fix that, but whatever happens he will definitely not have r/w access. We need to ensure that anyone writing to the repository does so using the cvsusers group. The way we do this is by setting the setgid bit on the parent directories. This means that when someone "executes" the directory (by changing directory into it), their group automatically becomes the same as the parent directory. By running the command *find $CVSROOT -type d -exec chmod g+s {} \;* we ensure that files get created as we want them. Let's rewind a little, and (after running the command above) add that subdir and file again. john@bolsh:~/cvs/project$ ls -al total 28 drwxrwsr-x 3 john cvsusers 4096 Dec 28 17:39 . drwxrwsr-x 5 john cvsusers 4096 Dec 27 21:45 .. -r--r--r-- 1 john cvsusers 538 Dec 26 16:56 Makefile,v -r--r--r-- 1 jack cvsusers 709 Dec 27 21:47 logic.c,v -r--r--r-- 1 jack cvsusers 737 Dec 28 17:23 logic.h,v -r--r--r-- 1 john cvsusers 541 Dec 26 16:56 main.c,v drwxrwsr-x 2 jack cvsusers 4096 Dec 28 17:39 subdir Note that the permissions on the repository root, on the project directory (.. and . respectively), as well as on the newly created directory are all g+s - a listing of subdir shows that all files and directories are created with cvsusers as the group owners too. Lock files Earlier when John couldn't even check out a read-only copy of the repository, I said "we can fix that" - here's how. When a CVS client is trying to check out or update a repository, it creates lockfiles in the repository to let otehr CVS processes know that it's currently reading. These lock files aren't blocking - unlike when you are checking in a file - but CVS needs to create them anyway for its internal data structures. To create these lock files somewhere other than in the same directory as the RCS files, uncomment the folowing line in CVSROOT/config, LockDir=/var/lock/cvs and set it to point to somewhere innocuous that all cvs users can write to - I use $CVSROOT/.lock with permissions 777. Now even if we don't have write access to the directory containing the ,v files, we can check out the sources. We're now all set for multiple users - in fact multiple local users can already use the repository as it is. We're now going to look at the various ways to connect to a CVS server from remote machines - which is what makes CVS really useful, after all. Connecting via rsh CVS can connect over rsh to your machine, and then communicate with a remote cvs process it starts when it gets there. One of the problems is that to be able to do this, you need to disable password authentication for the rsh connection method. Another is that rsh needs to be enabled in /etc/inetd.conf. And a third is that using this method all CVS users need a shell account on the server, which you might not want to do. With rsh, to disable password checking from a trusted remote machine, we add a line to ~/.rhosts - for a remote user jack on his machine pitbull.terrier.net, that means that jack has to add the following line to his ~/.rhosts on john's machine: pitbull.terrier.net jack To enable the rsh (or sheel) service, we uncomment this line in /etc/inetd.conf (happily, this is usually disabled by default, being quite insecure) shell stream tcp nowait root /usr/sbin/tcpd /usr/sbin/in.rshd We also need to ensure that the shell service is listed in /etc/services (although this isn't usually a concern) - it is on port 514 by default. Now when Jack runs "rsh -l jack john.test.org 'which cvs'" he will see the path to cvs on your machine, without being asked for a password. To take advantage of the method, we set the CVSROOT to let CVS know how to connect to the server, make sure that the CVS repository is r/w for Jack, and we're all set jack@pitbull:~$ export CVSROOT=:ext:jack@bolsh:/home/john/cvs jack@pitbull:~$ cvs co project cvs server: Updating project U project/Makefile U project/logic.c U project/logic.h U project/main.c jack@pitbull:~$ There is one other big disadvantage with this method I didn't mention - all network communications over rsh are unencrypted, which means that any packet sniffer can get hold of your source code, as well as monitor the authentication conversations that the server and client have. While this might not be a major issue with open source projects, the commercial take-up of CVS might be slowed a little if there were no alternative. Securing communications (connecting over ssh) Rather than rsh, we can use ssh, which is a secure protocol, and also supports compressed communication streams. To use ssh rather than rsh for source transfer, we set the environment variable CVS_RSH on the client side to ssh and on the server side we still need to disable password authentication. With ssh, we do that by creating a public/private key-pair on the client, and on the server we add the public key to the list of authorized client keys, which is stored in ~/.ssh/authorized_keys or ~/.ssh/authorized_keys2 (depending on whether ssh is using protocol version 1 or 2). jack@pitbull:~$ ssh-keygen -t dsa Generating public/private dsa key pair. Enter file in which to save the key (/home/jack/.ssh/id_dsa): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /home/jack/.ssh/id_dsa. Your public key has been saved in /home/jack/.ssh/id_dsa.pub. The key fingerprint is: 47:83:5a:24:ed:1c:3b:c8:d7:ea:00:7d:a6:eb:9a:d1 jack@pitbull.terrier.net jack@pitbull:~$ scp .ssh/id_dsa.pub jack@bolsh:~/.ssh/authorized_keys2 Password: jack@pitbull:~$ export CVS_RSH=ssh jack@pitbull:~$ echo $CVSROOT :ext:jack@bolsh:/home/john/cvs jack@pitbull:~$ cvs co project Enter passphrase for key '/home/jack/.ssh/id_dsa': cvs server: Updating project U project/Makefile U project/logic.c U project/logic.h U project/main.c jack@pitbull:~$ There are lots of good things about using ssh - there's passphrase authentication for the client private key, and all communications are encrypted. However, there is no requirement to use a passphrase, and many people don't bother. So your machine may be exposed if a client machine is exploited. And since clients need a shell account on your machine, you are exposing yourself a little to malicious attacks. If, however, you trust everyone who you ever let at your source code, this is a good scheme. This is the set-up that many companies use for CVS. Using CVS as a client/server Now your project has grown pretty big, and a nightmare scenario happened - a black-hat cracker got onto a machine of one of your trusted developers, and noticed that CVSROOT started with ext - he logged straight onto your machine without even being asked for a password, and has wreaked havoc. You've learnt your lesson, and have decided that untrusted CVS users shouldn't get shell accounts on your machine. You also insist on some kind of password authentication on the server side. CVS has it's own in-built client-server abilities, which offer several advantages over rsh based methods, namely 1. Password authentication. 2. Server-side compression of streams. 3. Users don't need shell access on the server. 4. Remote users can map to local users for the duration of the connection. 5. Remote users can have a CVS password different to the system password. This means that you have some protection in the event of someone cracking a client side computer. Nevertheless, read/write access to a CVS repository allows you to execute more or less arbitrary commands on the server anyway, so don't get too lulled into a false sense of security. Because of the little problem above, the CVS server process doesn't run as root. Well, it does for a second, while it finds out what local user your cvs username maps onto, and then it does a setuid and setgid. This can be a source of security flaws if there are problems with the cvs server. We also restrict r/w access to the CVSROOT directory to be similar to those on /etc - since administrative files housed there may be used maliciously. To enable CVS as a server, we need to add the service to /etc/services first (the default CVS port is 2401) and enable the server in the inetd (or xinetd) superserver. In /etc/services, add cvspserver 2401/tcp in /etc/inetd.conf, add cvspserver stream tcp nowait root /usr/bin/cvs cvs -f --allow-root=/home/john/cvs pserver In brief, this means that when a connection to the server arrives on port 2401, it will be passed onto a cvs pserver process running as root. The process needs to run as root to setuid and setgid of the CVS user after connectng to the server. Now if we set our CVSROOT on a remote machine to the following: jack@pitbull:~$ export CVSROOT=:pserver:jack@bolsh:/home/john/cvs jack@pitbull:~$ cvs login Logging in to :pserver:jack@bolsh.wanadoo.fr:2401/home/john/cvs CVS password: jack@pitbull:~$ cvs co project cvs server: Updating project U project/Makefile U project/logic.c U project/logic.h U project/main.c jack@pitbull:~$ Cool! Our pserver is up and running. But there's a problem - CVS stores your CVS passwords locally (by default in a file called .cvspass in your home directory), and we're using system authorisation to check passwords - that means that anyone can fairly easily get at jack's password for the CVS server. To avoid this problem, we will set up a password system to have password authentication done internally by CVS. CVS users will now have their system password, and their CVS password. The CVS passwords are stored in CVSROOT/passwd under the CVS root node. In $CVSROOT/CVSROOT, there are a number of administrative files - in the repository we created above, the list is as follows... john@bolsh:~/cvs$ ls CVSROOT/ Emptydir config editinfo,v modules,v rcsinfo,v verifymsg,v checkoutlist config,v history notify taginfo checkoutlist,v cvswrappers loginfo notify,v taginfo,v commitinfo cvswrappers,v loginfo,v passwd val-tags commitinfo,v editinfo modules rcsinfo verifymsg The ,v files are the RCS files which go to make up our versioning information - this means that the administrative database is versionned too and can be checked out just like any other source by anyone with access to the repository. We don't want people to be able to see the passwd file that easily, so we are not going to add it to the repository - it is just a flat text file with line in the format cvsusername:[password][:localusername] If we leave an empty password, then any password will authorise entry. If we leave out a local username that our CVS user will map to, we need to have a user of that name created locally. The following script, which is in the CVS book (published by Coriolis, and partly available free online at http://cvsbook.red-bean.com/), permits you to generate passwords for users. The passwords are encoded with the standard unix crypt() function. jack@pitbull:~$ cat /usr/local/bin/genpasswd #!/usr/bin/perl srand (time()); my $randletter = "(int (rand (26)) + (int (rand (1) + .5) % 2 ? 65 : 97))"; my $salt = sprintf ("%c%c", eval $randletter, eval $randletter); my $plaintext = shift; my $crypttext = crypt ($plaintext, $salt); print "${crypttext}\n"; jack@pitbull:~$ This is an extremely simple script - run it as follows... jack@pitbull:~$ genpasswd hairylegs cpmoD6UteRVDM jack@pitbull:~$ Then mail the password to the repository admin, and ask him to add the line jack:cpmoD6UteRVDM to the CVSROOT/passwd entry for you. Then next time you do a cvs login, enter "hairylegs" and you're in. By default, CVS will look in the system password file for a match if it doesn't find one in CVSROOT/passwd - to stop it from doing this, we check out the administrative database, and uncomment a line in CVSROOT/config. john@bolsh:~$ cvs co CVSROOT cvs checkout: Updating CVSROOT U CVSROOT/checkoutlist U CVSROOT/commitinfo U CVSROOT/config U CVSROOT/cvswrappers U CVSROOT/editinfo U CVSROOT/loginfo U CVSROOT/modules U CVSROOT/notify U CVSROOT/rcsinfo U CVSROOT/taginfo U CVSROOT/verifymsg john@bolsh:~$ Now in CVSROOT/config, uncomment the line (or add it) SysthemAuth=no john@bolsh:~/CVSROOT$ cvs update cvs update: Updating . M config john@bolsh:~/CVSROOT$ cvs commit -m "set SystemAuth=no to disable system password check" cvs commit: Examining . Checking in config; /home/john/cvs/CVSROOT/config,v <-- config new revision: 1.2; previous revision: 1.1 done cvs commit: Rebuilding administrative file database john@bolsh:~/CVSROOT$ Now we have the capacity to add users who *only* have cvs access and can't otherwise log onto the machine at all. To add a CVS only user, first add the user locally as normal, and set his password in /etc/passwd (or /etc/shadow) to *. Then add an entry with his CVS password to CVSROOT/passwd. He will be able to connect to the CVS repository, but will never be able to log in. Adding read-only users This is really easy - the CVSROOT/readers file contains a list of all CVS users with read-only permissions on the repository. We create a local user called anonymous (and set the password to * in /etc/passwd), and add anonymous: to CVSROOT/passwd to allow him to log on without a password, and finally we add just anonymous to the file CVSROOT/readers. Often, rather than do this, bigger software projects maintain two repositories - a read/write copy for development, and a read-only copy which is a few hours behind for people who just want the latest sources. This system relieves a lot of stress from the server if there are lots of read-only users, but usually traffic is low enough that there isn't really a need to do that. It's worth explaining how the administrative files database works. When we checked in the config file earlier, we saw the line "cvs commit: Rebuilding administrative file database" at the end. Since the files in CVSROOT need to be read by CVS processes running on the server, the files themselves are created there, alongside their ,v partners. We saw this earlier. But with newer files which aren't created by default (such as the readers file above), if we want to keep versioning information on the files, we must add them to the repository with cvs add & cvs commit. However, the files are not automatically generated from the ,v files by default - we need to instruct CVS which files we would like to have automatically constructed. The file in which we do that is CVSROOT/checkoutlist (don't ask me, I didn't name it). We can add our readers file to checkoutlist, cvs add it to the repository, and it will be available to anyone who can check out CVSROOT. Module-level control So you've become a regular industry - your machine is hosting dozens of different projects, and they're not necessarily related. Using cvs.gnome.org as an example, you have people working on galeon, and people working on the gimp. And they all have permission to check in anything they want wherever they want. You have been receiving a lot of complaints from module owners about unauthorised check-ins, and they want you to do something about it. There are two ways you can address the idea of module-level permissions in CVS. One is a little more risky, but requires little or no administration. The other requires some work and often demands more time from the CVS person that they're willing to spend. The pointy stick approach Two weeks after getting CVS checkin permissions on her favourite project, jill is browsing through the sources of another project housed on the same server when she sees something that just has to be a bug - feelng friendly, she makes a change to "fix" the bug, commits the change and goes back to her favourite project again. Two days later all hell breaks loose - the other project is hopelessly broken, and several development users have reported data loss. It turns out that Jill in her eagerness removed an essential cross-check and caused the bug. The module owner sends Jill a big flame, and CCs you baying for blood. Anyone can make a mistake once, so you send Jill a polite mail telling her that for all modules other than the one she's approved for, she should mail a patch to the developpers like everyone else. She assures you it won't happen again. A couple of months later, she's browsing again, and finds a project which seems idle, and which doesn't conform to the coding standards of the project. She runs all the sources through indent, and checks them in. The next day, the developer of the module reports to you that he's just found out that he has a load of work resolving conflicts for every single change he's made to the code in a major re-write that he's spent a month working on. By doing things like adding or removing witespace, Jill has caused all of his source to conflict with the repository, so he has to manually go through each source file resolving the conflicts. What makes matters worse is that he is the only person who is supposed to be checking any code into the module. This time you send a much firmer mail to Jill. You tell her that as this is the second time this has happened, she is now getting her last chance. In addition, you send a mail to everyone with commit access pointing out what Jill has done as an example of behaviour which is unacceptable. Jill gets a little bit annoyed at being picked on, but she says she sees the light and won't do it again. She sends a heartfelt apology to the module owner she's messed up, and all is well again. Six months later, when the heat has died down, Jill is again potterring about when she again checks in code which breaks stuff into a module that she isn't supposed to be writing to. You hear about it from the module owner, and send her a polite mail telling her that her commit priveleges have been indefinitely suspended. The "three strikes and your out" approach works very well - usually people get the message after the first time that just because they can check things in doesn't mean they should. But sometimes it's not a feasible approach - if you are a commercial developer, for example, it's not reasonable to cut off your commit priveleges completely. The ideal would be for us to have a way to control who can check in code on a repository by repository basis. To every module its group We have seen earlier how creating a cvsusers group helped with the coordination of the work of several developers. We can extend this approach to permit directory level check-in restrictions. In our example, let's say that the module "cujo" is to be r/w for jack and john, and the module "carrie" is r/w for john and jill. We will create two groups, g_cujo and g_carrie, and add the appropriate users to each - in /etc/group we add g_cujo:x:3200:john,jack g_carrie:x:3201:john,jill Now in the repository (as root), run find $CVSROOT/cujo -exec chgrp g_cujo {} \; find $CVSROOT/carrie -exec chgrp g_carrie {} \; ensuring, as before, that all directories have the gid bit set. Now if we have a look in the repository... john@bolsh:~/cvs$ ls -l total 16 drwxrwsr-x 3 john cvsadmin 4096 Dec 28 19:42 CVSROOT drwxrwsr-x 2 john g_carrie 4096 Dec 28 19:35 carrie drwxrwsr-x 2 john g_cujo 4096 Dec 28 19:40 cujo and if Jack tries to commit a change to carrie... jack@bolsh:~/carrie$ cvs update cvs server: Updating . M test jack@bolsh:~/carrie$ cvs commit -m "test" cvs commit: Examining . Checking in test; /home/john/cvs/carrie/test,v <-- test new revision: 1.2; previous revision: 1.1 cvs [server aborted]: could not open lock file `/home/john/cvs/carrie/,test,': Permission denied jack@bolsh:~/carrie$ But in cujo, there is no problem. jack@bolsh:~/cujo$ cvs update cvs server: Updating . M test jack@bolsh:~/cujo$ cvs commit -m "Updating test" cvs commit: Examining . Checking in test; /home/john/cvs/cujo/test,v <-- test new revision: 1.2; previous revision: 1.1 done jack@bolsh:~/cujo$ The procedure for adding a user is now a little more complicated that it might be. To create a new CVS user, we have to create a system user, add them to the groups corresponding to the modules they may write to, and (if you're using a pserver method) generate a password for them, and add an entry to CVSROOT/passwd. To add a project, we need to create a group, import the sources, change the groups on all the files in the repository and make sure the set gid on execution bit is set on all directories inside the module, and add the relevant users to the group. There is undoubtedly more administration needed to do all this than when we jab people with a pointy stick. In that method, we never have to add a system user or a group or change the groups on directories - all that is taken care of once we set up the repository. This means that an unpriveleged user can be the CVS admin without ever having root priveleges on the machine. Summary In this article, we've presented a number of ways that we can use CVS with the Unix permissions system to grant & restrict access to source code in a repository. The factors we need to consider when doing this are often in conflict - for example between ease of administration and granularity of control, or ease of use and server security. I haven't covered all of the security methods available to a CVS adminitrator - but these are certainly the most common. For more information on CVS administration and use, the definitive guide is the Cederqvist manual (info cvs or http://www.cvshome.org/docs/manual/cvs.html ) which is the officially maintained documentation. There is also the most excellent red-bean book, published by Coriolis, part of which is available online ( http://cvsbook.red-bean.com - Open Source Development with CVS by Karl Fogel) which presents many real-life situations and helpful hints for repository administration and CVS use. Useful links The official CVS manual ("Cederqvist") Open Source Development with CVS Chrooted tunnelled read-write CVS server Copyright David Neary, 2002