Novell Home

CVS Server Setup and Configuration

From Developer Community

-Jon Carey

Contents

Abstract

One of the features provided by the Novell Forge system is the hosting of CVS repositories for projects. CVS is the acronym for Concurrent Versions System. It is the most widely used version control system used throughout the open source community. For a thorough discussion on the capabilities of CVS, visit their web site at http://www.cvshome.org.

When registered Forge users create a project, they have the option of having an associated CVS repository created with it as well. If a project has an associated CVS repository, only users that are a member of that project are allowed to access it, unless the project administrator chooses to allow anonymous access. If anonymous access is allow, anyone can read the contents of the CVS repository as the pre-configured anonymous user. This anonymous user accesses the repository with the user name "anonymous". In all cases, only registered members of the project are allowed to write to the CVS repository.

Novell Forge was designed with an extremely loose coupling between the web and CVS environments. In fact, they are only coupled to the extent that they are interested in the same objects in eDirectory. Typically the CVS repositories will be hosted on a separate machine from the web environment. Forge can be configured to host the web and CVS environment on the same machine, but for security reasons, this is not recommended. Additionally, the web environment is implemented in such a way that multiple CVS servers can be used within the system. As projects get created within the web environment, their CVS repositories will be evenly distributed across the available CVS servers.

The remainder of this document describes the process of setting up the CVS environment in a manner that allows it to interoperate with the Novell Forge web environment, while still maintaining a high level of security.

For the sake of discussion, this document assumes the reader has checked out the source code to the Novell Forge system from the appropriate CVS repository and placed it in the directory /nxoops.

LDAP Configuration

As mentioned previously, the coupling between the Forge CVS and web environments is done through shared LDAP data, so the importance of the LDAP configuration should be obvious. It is difficult to describe how the LDAP configuration pertains to the CVS environment without describing how it relates to the entire Forge system.

It is important to note, that not all of the LDAP procedures described in this section need to be performed if you are adding a CVS server to an existing Forge installation. In this case the schema extensions and the initial Forge objects will already be in place, so you would only need to concern yourself with the LDAP procedures that pertain to the CVS server you are bringing online.

EDirectory Requirements

Before even attempting to install Novell Forge, there is some preliminary work that must be done on the eDirectory side. All communication is done with eDirectory through secure LDAP, so any version greater than or equal to 8.6.2 should work fine. Based on my experiences installing eDirectory, LDAP gets installed by default. One thing to pay close attention to is enabling secure LDAP. This usually involves having a key material object associated with the LDAP server object and having the SSL/TLS operations turned on. Once you have all the pieces in place for secure LDAP, you will need to export the trusted root certificate the LDAP server is using for SSL communication. This is can be done within ConsoleOne from the property pages of the key material object associated with the LDAP server. The remainder of this document assumes you have exported this certificate to a file called TrustedRootCert.der in DER format.

Schema Extensions – (Initial Forge Installation Only)

Novell Forge relies on several object classes and attributes that don’t come standard with eDirectory. Some of these are standard POSIX extensions specified in RFC2307 and the rest are object classes and attributes specific to the Forge system. The files that are used for these schema extensions can be found in the directory /nxoops/cvssetup/ldapsetup of the source tree. In order to make the schema extensions, you will need sufficient administrative rights. For the sake of discussion, we will use "cn=admin,o=novell" for the administrative user with a password of "secret". To perform this schema extension process you will need the utilities "ndssch" and "ldapmodify". Both of these are provided by the Novell eDirectory installation. They can probably be acquired separately through Novell’s software download site as well.

In order to make the RFC2307 schema extensions, execute the following command:

ndssch –h myldapserver.dns.name –t my_tree_name admin.novell rfc2307-usergroup.sch

You will be prompted for the admin password when this runs. Upon completion, ndssch will inform you that the modifications have been made.

In order to make the Forge specific schema extensions, execute the following command:

ldapmodify –h myldapserver.dns.name –p 636 –e TrustedRootCert.der –D \
   "cn=admin,o=novell" –w secret –f nforgesch.ldif

This command assumes your LDAP server is listening on port 636 for secure LDAP.

Note the use of the server certificate that was previously exported. This will not only perform the schema extension, but will verify secure LDAP communications are working. If secure LDAP is not working, you should fix it now, since Novell Forge relies on this capability. If for some reason you wanted to do this in clear text (i.e. non-secure LDAP), you would execute the following command instead:

ldapmodify –h myldapserver.dns.name –p 389 –D "cn=admin,o=novell" –w secret –f nforgesch.ldif

This command assumes your LDAP server is listening on port 389 for non-secure LDAP.

Initial Novell Forge LDAP Objects – (Initial Forge Installation Only)

After the schema extensions are made you can populate LDAP with the objects Forge is expecting. Forge is designed to be contained by a user configurable organization or organizationalUnit. For this example, we will assume the Forge LDAP objects are going to be contained in the "o=novell" container. These changes can be made within ConsoleOne or by creating an LDIF file and processing it with ldapmodify. Since it is easier to document this process using an LDIF file that will be the approach I take.

nforgeobjects.ldif

version: 1

dn: ou=nforge,o=novell
changetype: add
ou: nforge
objectClass: top
objectClass: ndsContainerLoginProperties
objectClass: ndsLoginProperties
objectClass: organizationalUnit

dn: cn=members,ou=nforge,o=novell
changetype: add
gidNumber: 1000
objectClass: groupOfNames
objectClass: top
objectClass: posixGroup
cn: members

dn: ou=cvsservers,ou=nforge,o=novell
changetype: add
ou: cvsservers
objectClass: top
objectClass: ndsContainerLoginProperties
objectClass: ndsLoginProperties
objectClass: organizationalUnit

dn: ou=projects,ou=nforge,o=novell
changetype: add
ou: projects
objectClass: top
objectClass: ndsContainerLoginProperties
objectClass: ndsLoginProperties
objectClass: organizationalUnit

dn: cn=nforgeadmin,ou=nforge,o=novell</font></font>
changetype: add
uid: nforgeadmin
Language: ENGLISH
sn: admin
passwordAllowChange: TRUE
objectClass: top
objectClass: ndsLoginProperties
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
cn: nforgeadmin
userpassword: secret

dn: ou=nforge,o=novell
changetype: modify
add: acl
acl: 63#subtree#cn=nforgeadmin,o=context#[Entry Rights]
acl: 63#subtree#cn=nforgeadmin,ocontext#[All Attributes Rights]
acl: 1#subtree#[Public]#[Entry Rights]
acl: 3#subtree#[Public]#[All Attributes Rights]

We would process this file using the ldapmodify utility with the following command:

ldapmodify –h myldapserver.dns.name –p 636 –e TrustedRootCert.der –D \
   "cn=admin,o=novell" –w secret –f nforgeobjects.ldif

After executing the ldapmodify command given above, you should see a structure that resembles what is shown in figure 1.

Figure 1

Image:Forge_CVS_LDAP_Structure.gif

Following is a description of the objects shown in figure 1:

  • ou=nforge – This is the parent container to all of the objects maintained by the Forge system.
  • cn=nforgeadmin – This is the Forge administrator’s object. It represents the user our CVS servers will bind as, to perform LDAP operations. The web environment will bind as this user for it’s operations as well.
  • cn=members – This is a posixGroup object that will be the primary group for all registered users of the Forge system. It is created with the gidNumber attribute set to 1000. All the LDAP user objects that represent Forge users must have their gid number attributes set to 1000. This effectively makes all Forge users members of the group "members".
  • ou=projects – This is the container for all projects that will be created by the Forge system. When a project is created in the Forge system, a posixGroup object gets created in this container with the same name as the project.
  • ou=cvsservers – This is the container for CVS server objects that are available for repository hosting.

The directory structure constructed in this section is primarily for the objects that will be created by the Forge system. LDAP user objects are never created by the Forge system. LDAP objects for users are expected to exist before the user ever registers with the Forge system. In our case, this is done by the I-Chain application that sits in front of the Forge system, that some call the E-Login environment. The interesting thing to note about these pre-existing users objects is, they don’t all have the posixAccount information. When the web environment encounters a user object without the posixAccount information, it will automatically add the posixAccount auxiliary class to the object. All the CVS and web environments need to know about the user objects is where to find them. You will notice in some of the configurations shown in this document, the parent container to the user objects must be specified.

CVS Server Object

Once the schema extensions and the initial Forge objects are in place, you can create the object in LDAP that represents the CVS server. As mentioned earlier, you could do this through the ConsoleOne utility, but I will use LDIF here for the sake of discussion. We will assume for the remainder of this document, that the DNS name of this CVS server we are setting up is forgecvs1.novell.com. Considering the Forge LDAP structure that was created previously, will construct our LDIF file as follows:

newcvsserver.ldif

version: 1
dn: cn=forgecvs1,ou=context,o=novell
changetype: add
objectClass: top
objectClass: NFORGECVSServer
cn: forgecvs1
NFORGEDNSName: forgeserver1.novell.com
NFORGEprojectCount: 0

To process this LDIF file and create our CVS server LDAP object, we would execute the following command:

ldapmodify –h myldapserver.dns.name –p 636 –e TrustedRootCert.der –D \
  "cn=admin,o=novell" –w secret –f newcvsserver.ldif

Server Certificates

Since the CVS server uses secure LDAP for all directory operations, we will set up certificates to be used by the various LDAP aware applications. These certificates will be used in some of the steps that follow. We are going to configure things on the CVS server to look in /etc/ssl for certificates needed for secure LDAP, so to get started execute the following commands:

mkdir /etc/ssl
chmod 755 /etc/ssl

Previously, we exported the certificate from our LDAP server in DER format. We wrote that file out as TrustedRootCert.der. There are some things done on the CVS server that will use this certificate in DER format, but will expect it to be located in the /etc/ssl directory as nforgeldap.der. Execute the following statements to put this certificate in the desired location:

cp TrustedRootCert.der /etc/ssl/nforgeldap.der
chmod 644 /etc/ssl/nforgeldap.der

Now we need to create the X509 version of this certificate and place it in the same location with the name nforgeldap.cert. To do this, execute the following commands:

openssl x509 -inform DER -in TrustedRootCert.der -out /etc/ssl/nforgeldap.cert
chmod 644 /etc/ssl/nforgeldap.cert

Setting Up Linux Authentication and Authorization

What we are going to do in this section is set up our CVS server so users can authenticate through an LDAP mechanism. Essentially what we will do is configure the machine to associate LDAP posixAccount objects with users that authenticate through a Secure Shell connection. There are few caveats to the approach we take here, so I should probably describe those before we get too far into this. If there is a local user account that has the same user name as a posixAccount LDAP object, the local user account will take precedence over the LDAP object. This same problem exists for posixGroup LDAP objects as well. This is something that should be considered at installation time, because up to this point, LDAP has never been consulted when local users or groups were created. Given the fact that the Forge system is working from a pre-existing pool of user objects, there is definitely the opportunity for a problem to arise. After LDAP is integrated into process, the administrator will not be able to create users on the local machine that conflict with posixAccount LDAP objects, but there is no mechanism in place to keep the E-Login environment from creating these conflicts. Given the problem just described, it’s probably not a bad idea to configure the machine so user and group id numbers are not generated above a certain value. This at least gives us a known boundary to work with in relation to the CVS server. To do this we modify the /etc/login.defs file. The options we need to change in this file are UID_MAX and GID_MAX. Chances are that both of these are set to 60000. Change the value for both to 999. This forces the account and group creation mechanism on the CVS server to never use a value larger than 999 when creating users or groups.

Pluggable Authentication Modules (PAM)

The standard authentication mechanism used on the Linux platform is PAM. PAM is the acronym for Pluggable Authentication Module. PAM is a suite of shared libraries that enable the local system administrator to choose how PAM aware applications authenticate users. In other words, without rewriting and recompiling a PAM aware application, it is possible to change the authentication mechanism it uses. Indeed, one may entirely upgrade the local authentication system without touching the applications themselves. With PAM, just about every aspect of the authentication process can be customized on a per application basis. The administrator can control how the user’s credentials are obtained and stored. There are many PAM modules available today that deal with a diverse set of authentication schemes. The PAM module we need for our CVS server is pam_ldap. If you list the contents of the /lib/security directory, you should see a file called pam_ldap.so. This file will be loaded by PAM when a PAM aware application is configured to use LDAP authentication. The configuration file for pam_ldap is /etc/ldap.conf. By changing the contents of this file, you can control most aspect of pam_ldap’s behavior. Following is the /etc/ldap.conf file we will use for our configuration:

ldap.conf

host host.domain
base o=toplevel
binddn cn=anonymous,o=context
bindpw secret
port 636
scope sub
pam_filter objectclass=posixAccount
pam_member_attribute member
nss_map_attribute uniqueMember member
nss_base_group o=context
ssl on
tls_checkpeer yes
tls_cacertfile /etc/ssl/nforgeldap.cert
pam_password clear

Following is a description of each line of this file:

  1. host – The DNS name of the LDAP server pam_ldap will connect to. In this case the LDAP server is our edir server.
  2. base – The full DN of the LDAP container to start searching from. When pam_ldap needs to find the user object in LDAP, it will start searching from this point.
  3. binddn – The DN of the user pam_ldap will bind as. This must be the DN of a posixAccount object that has the rights to read the posixAccount attributes at and below the base option. In our case this will be cn=anonymous,o=context. If public access were allowed on all posixAccount information in the container specified by base, this option would not be necessary.
  4. bindpw – This option is only necessary if the binddn option is specified. It is used to provide the password pam_ldap will use when binding as the user specified by the binddn option.
  5. scope – This specifies how pam_ldap will search LDAP starting at base. "sub" indicates the search should look in any sub-containers that are contained by base. For examples: If there are posixaccount objects in the container "o=users", the "sub" directive will allow pam_ldap to find them. Other alternatives would be "base" or "one". The "base" directive tells pam_ldap to look at the base object only. Within the context of our situation, this isn't too useful. The "one" directive tells pam_ldap to look at all children of the base object but not to search any containers that are children of base. This directive can be useful if you’re trying to optimize the LDAP search and you know there is nothing of interest in any sub-containers.
  6. port – This is the port number the LDAP server is listening on. Port 636 is the well known port number for secure LDAP communications.
  7. pam_filter – Specifies what object class pam_ldap should look for during the authentication process. In our example, we are only interested in posixAccount objects, which is a requirement for our configuration.
  8. pam_member_attribute – This option specifies what attribute of the LDAP object contains the group membership information. In our case the attribute is "member".
  9. nss_map_attribute – This is used by pam_ldap for attribute mapping. In our case, we are telling pam_ldap to use the "member" attribute as the "uniqueMember" attribute.
  10. nss_base_group – Specifies the LDAP object that is used as the base container for group objects. If not specified the DN specified by base will be used. In our case, we are only interested in posixGroup objects below the ou=nforge,o=novell container.
  11. ssl on – This specifies that pam_ldap will communicate with the LDAP server over an SSL connection.
  12. tls_checkpeer yes – Force pam_ldap to require an X509 certificate for its’ SSL communications with the LDAP server.
  13. tls_cacertfile – Specifies the location of the X509 certificate that will be used when communicating with the server. This is required because the tls_checkpeer option was set to yes. As you can see, we are referencing the X509 certificate we created earlier.
  14. pam_password clear – Send the user’s password as clear test. This is not as insecure as it may seem, since all communications are being done over SSL.

Name Service Switch (NSS)

Access to files and directories on a Linux system are governed by the user’s identifier (uidNumber) and group membership. Most applications running on a Linux system use a well known set of APIs for obtaining all this information about a user. Where this information comes from is somewhat transparent to most applications because of the Name Service Switch. NSS provides a framework for what I’ll call name service providers to plug into. Name service providers are responsible for acquiring this information from their specific data stores. There are name service providers for acquiring this naming information from the local file system (e.g. /etc/passwd, /etc/group), NIS, DNS, LDAP and other data stores.

The name service providers you typically see on Linux system, are file, DNS and NIS. What we want to do is get LDAP involved here as well. The LDAP name service switch is typically provided by the nss_ldap rpm. At this point I will assume this package is installed. The file that provides the LDAP name service is /lib/libnss_ldap.so*. Like pam_ldap, nss_ldap relies on the OpenLDAP client APIs and the configuration specified in the /etc/ldap.conf file. The changes we made to /etc/ldap.conf file to support pam_ldap, are sufficient for nss_ldap as well. However, NSS needs to be configured to use the functionality provided by nss_ldap. The configuration file for NSS is contained in the file /etc/nsswitch.conf. For our CVS server we will use the following configuration:

nsswitch.conf

passwd: files ldap
shadow: files ldap
group: files ldap
hosts: files dns
bootparams: files
ethers: files
netmasks: files
networks: files
protocols: files
rpc: files
services: files
netgroup: files
publickey:
automount: files
aliases: files

Note the specification of "ldap" on lines 1 through 3. This directs NSS to try the local file system for password, shadow and group related operations first and if that fails, then try ldap.

Secure Shell Server (SSHD)

Up to this point, we have configured PAM and NSS to use LDAP if they ever need to, but there are no PAM aware applications that require this functionality. In the case of our CVS server, we are only concerned with the Secure Shell Server. This will be the mechanism by which all CVS commands are sent to the server. The sshd service will be the application responsible for authenticating users when they issue these CVS commands. Fortunately, sshd is a PAM aware application and thus has a PAM configuration file that we change to suite our needs. The PAM configuration for sshd is contained in the /etc/pam.d/sshd file. Here is the PAM configuration we will use for sshd:

/etc/pam.d/sshd

auth sufficient /lib/security/pam_unix.so debug
auth required /lib/security/pam_ldap.so use_first_pass debug
account sufficient /lib/security/pam_unix.so debug
account required /lib/security/pam_ldap.so debug
password sufficient /lib/security/pam_unix.so debug
password required /lib/security/pam_ldap.so debug
session sufficient /lib/security/pam_unix.so debug
session required /lib/security/pam_ldap.so debug

I’m going to be brief in my description of this file in order to avoid a long drawn out discussion of PAM. In this file you can see that there are four distinct management groups: authentication, account, password and session. For all of these management groups we have specified that it is sufficient if the PAM module pam_unix handles the request. If pam_unix is unable to handle the request, it is passed on to the pam_ldap module. Under all circumstances, if pam_ldap fails, the process ends in failure. Take line 1 and 2 for example: When users authenticate, pam_unix first attempts to process the request using the information contained on the local file system (e.g. /etc/passwd,…). If pam_unix succeeds, the user is considered sufficiently authenticated. Otherwise the user’s credentials are passed on to the pam_ldap module. If pam_ldap fails, then the user has failed authentication. It’s important to note that this configuration is for the Secure Shell server only. It has no impact on any other application.

There is actually one last configuration item that needs to be set for sshd. The sshd service is controlled in large part by the configuration options specified in the /etc/ssh/sshd_config file. If we are using openssh version 3.3 or above, all we need to do to this file is append the following line to it:

UsePrivilegeSeparation yes

If this option is already specified in the file, then just make sure it is set to "yes". This option specifies whether sshd separates privileges by creating an unprivileged child process to deal with incoming network traffic. After successful authentication, another process will be created that has the privilege of the authenticated user. The goal of privilege separation is to prevent privilege escalation by containing any corruption within the unprivileged processes. The default is "yes", but we explicitly set it just to be safe.

Privilege separation was introduced as an experimental feature in openssh version 3.2 and didn’t exist before that. If we are using a version of openssh earlier that 3.3 then append the following line to the /etc/ssh/sshd_config file:

PAMAuthenticationViaKbdInt yes

This option specifies whether PAM challenge response authentication is allowed. This allows the use of most PAM challenge response authentication modules.

Restrict the Access

There is obviously a big difference between what we want LDAP users to be able to do on our CVS server and users with a local account. For obvious security reasons, we want to restrict LDAP users to CVS operations only. This can be accomplished by setting the loginShell attribute for all LDAP users to a shell that only allows CVS operations. The web environment will actually do that for us when it adds the posixAccount auxiliary class to an existing object. It sets the loginShell attribute to /bin/cvssh, assuming someone has created this restrictive shell on the CVS server. The /bin/cvssh file is really just a simple bash script that only allows interaction with CVS. Following is what the contents of this file should look like:

/bin/cvssh

#!/bin/bash
if [ "$2" != "cvs server" ]; then
        echo "Access Denied"
        exit 1
fi
cvs server
exit

Once you have created this file, you need to make it executable and restrict access to it with the following command:

chmod 755 /bin/cvssh

Almost There!

We have actually reached a milestone here, though we haven’t completed the configuration of the CVS server as a whole, LDAP users should be able to authenticate to the machine at this point! Usually when I reach this point, I like to address any problems that may exists that are keeping LDAP users from authenticating. If everything we have configured up to this point isn’t working properly, you will not be able to make the changes described in the next section.

CVS Configuration

At this point we have a machine that will authenticate the same users that are recognized by the web environment and only allow them to perform CVS operations on projects they are members of. Now we need to put the components in place that allow the machine to fulfill its CVS role.

For the initial part of this process, it is convenient to have a local user account and group other than root to get things started. In our example we will create the user "devsup" for this purpose. To do this, execute the following command:

adduser devsup

On RedHat Linux, this creates the user "devsup" with a primary group of "devsup".

Directory Setup and CVS Configuration Files

Now we need to create a directory that will serve as the root for all project CVS repositories. For the sake of consistency and simplicity, Forge CVS servers are configured with /cvsroot being that directory. To initialize this directory execute the following commands:

mkdir /cvsroot
cvs –d /cvsroot init
chmod 770 /cvsroot
mkdir /cvsroot/__locks
chmod 770 /cvsroot/__locks
chgrp –R devsup /cvsroot
su – devsup
cd /home/devsup
export CVSROOT=/cvsroot
cvs get CVSROOT

Following is a description of what we have done with this sequence of commands:

  1. Create the directory /cvsroot. Ultimately this will be the parent directory of all project CVS repositories hosted on this machine.
  2. Initialize the /cvsroot directory as though it were going to be a CVS repository itself. This creates the directory /cvsroot/CVSROOT and populates it with all the files required for a CVS repository.
  3. Change access to the /cvsroot directory so members of the owning group can use it for read/write/execute operations.
  4. Create the /cvsroot/__locks directory. Later, we will configure CVS to place its’ lock files in this directory rather than directly in each repository.
  5. Temporarily change the group ownership of /cvsroot and everything to the "devsup" group.
  6. Switch to the "devsup" user.
  7. Change to the home directory of the "devsup" user.
  8. Set an environment variable that specifies the root directory for the "devsup" user’s CVS operations.
  9. Check out the contents of the CVSROOT directory.


Now if all the instructions have been followed properly, you should be in the /home/devsup directory and logged in as the user devsup. The directory /home/devsup/CVSROOT should exist and contain a variety of configuration files for the CVS repository. All we need to do is change one of these files and check it in. To do this, change to the /home/devsup/CVSROOT directory and edit the file "config". This file has an option called "LockDir". This option is used to specify where CVS should put lock files during it’s operations. By default CVS puts these directly in the repository. We want to change this so the lock files are written to a world writeable directory. This will allow users with read-only access to a repository to check-out files without CVS getting access errors trying to write the lock files. This option is probably commented out at this point. If it is, then uncomment it by removing the "#" character from the beginning of the line. Change the value of the "LockDir" option to "/cvsroot/__locks" and save the file. Now within the /home/devsup/CVSROOT directory execute "cvs commit" from the command line and enter appropriate comments for the change your checking in. Now remove the /home/devsup/CVSROOT directory and all of its’ contents. User "devsup" has done all it needs for us, so now you can type "exit" at the command prompt to become the root user again. Now the CVS configuration files are in the state we want for all CVS repositories that will be hosted on the machine. Let’s continue with the setup of our CVS environment. At this point execute the following sequence of instructions:

chgrp –R members /cvsroot
mv /cvsroot/CVSROOT /cvsroot/CVSROOT-TEMPLATE
chgrp –R root /cvsroot/CVSROOT-TEMPLATE
chmod 700 /cvsroot/CVSROOT-TEMPLATE
mkdir /cvsroot/bin
chmod 700 /cvsroot/bin
mkdir /cvsroot/lib
chmod 700 /cvsroot/bin


Following is a description of what we have done with this sequence of commands:

  1. Change the group ownership of the /cvsroot directory to "members". Recall "members" is the POSIX group object we created in LDAP that all registered Forge users are a member of.
  2. Rename the CVSROOT directory to CVSROOT-TEMPLATE. This directory will be a template for the CVSROOT directories that get created when CVS repositories are established for Forge projects.
  3. Set the group owner of our CVSROOT-TEMPLATE to root.
  4. Restrict access to our CVSROOT-TEMPLATE directory to only allow root.
  5. Create the /cvsroot/bin directory. We will be putting a couple of our own executable files in here later.
  6. Restrict access to the /cvsroot/bin directory. The executable files that will reside in this directory later are only ran by the root user in a cron job.
  7. Create the /cvsroot/lib directory. One of the executable files that will reside in /cvsroot/bin directory will use libraries we place in here later.
  8. Restrict access to the /cvsroot/lib directory. Only processes owned by the root user will be using the libraries in this directory.

The Cron Job

It is the responsibility of the CVS server to create CVS repositories for projects when they request them and maintain access rights on them. These things are handled by a cron job that we initially configure to run every 15 minutes. From this point on I’ll refer to this cron job as nforgecvs. When nforgecvs runs, it reads the CVS server object associated with this server from LDAP. One of the attributes of this LDAP object is NFORGEPendingProjectActions. This attribute is essentially a queue of tasks that nforgecvs needs to perform on the local machine. There are three types of tasks that are placed in this queue: add project, modify project, delete project. Each task has a project name and time stamp associated with it. Every time nforgecvs runs, it attempts to process every task in this queue. If a task is successfully performed, it is removed from the queue, otherwise it is left in the queue to be processed by the next run of nforgecvs. nforgecvs is a C program that’s primary purpose is to handle the LDAP related activities of this process. Whenever it encounters as task that needs to run, it delegates the rest of the work to a shell script called proccvsproj. This shell script is passed the project name and the project anonymous access flag as parameters. If the CVS repository for the project already exists, proccvsproj assumes the operation is a modification and sets file/directory access rights according to the anonymous access flag parameter. If the project doesn’t exist, it creates a directory with the same name as the project under the /cvsroot directory and copies the /cvsroot/CVSROOT-TEMPLATE file to that directory as CVSROOT. It then modifies file and directory access rights to allow members of the project read-write access to the repository. If the anonymous access flag is set to false on a modification or addition, the read/write/execute bits are turned off for everyone but the project members. If the anonymous access flag is set to true on a modification or addition, the read bit is turned on for users that are not members of the project as well as the execute bit on all related directories. Currently the delete task is not supported. It is implemented on the CVS server but not in the web environment.

Setting Up the Cron Job

The first thing we need to do to setup the cron job is to build the nforgecvs program. This is done by simply changing to the /nxoops/cvssetup/nforgecvs directory and typing ./makenforgecvs at the command line. This should build the nforgecvs binary. Next execute the following sequence of commands:

cp /nxoops/cvssetup/nforgecvs /cvsroot/bin
cp /nxoops/cvssetup/proccvsproj /cvsroot/bin
cp /nxoops/cvssetup/cldapsdk/lib/* /cvsroot/lib
chmod 700 /cvsroot/bin/*
chmod 700 /cvsroot/lib/*
echo "/cvsroot/lib" >> /etc/ld.so.conf
ldconfig

Following is a description of what we have done with this sequence of commands:

  1. Copy the nforgecvs binary to the /cvsroot/bin directory.
  2. Copy the proccvsproj shell script to the /cvsroot/bin directory.
  3. Copy all libraries needed to support nforgecvs to the /cvsroot/lib directory.
  4. Allow only the root user to run /cvsroot/bin/nforgecvs and /cvsroot/bin/proccvsproj.
  5. Allow only root access to the supporting libraries for nforgecvs.
  6. Add the path to the supporting libraries for nforgecvs to the dynamic linker configuration file. This will allow the libraries to be found at runtime.
  7. Configure the dynamic linker run-time bindings. This will reload the settings in the /etc/ld.so.conf file and create the necessary symbolic links for the shared libraries we put in the /cvsroot/lib directory.

Well now nforgecvs has everything it needs to run except a configuration file to tell it where to look for things. The configuration for nforgecvs is kept in the file /etc/nforgecvs.conf. Following is an example configuration for nforgecvs and a description of the various options:

/etc/nforgecvs.conf

nforgecvs.cvsroot = /cvsroot
nforgecvs.ldap_server = host.domain.com
nforgecvs.ldap_server_port = 636
nforgecvs.ldap_certificate = /etc/ssl/nforgeldap.der
nforgecvs.bind_name = cn=nforgeadmin,o=context
nforgecvs.bind_passwd = secret
nforgecvs.ldap_root = ou=nforge,o=context
#nforgecvs.dns_name = alternate.dnsname.com

Following is a description of each of these configuration options:

  1. nforgecvs.cvsroot - The parent directory for all project CVS repositories.
  2. nforgecvs.ldap_server - The DNS name of the LDAP server nforgecvs will communicate with.
  3. nforgecvs.ldap_server_port - The port number the LDAP server is listening on for secure LDAP communications.
  4. nforgecvs.ldap_certificate - The location of the certificate file nforgecvs will use when communicating with the LDAP server.
  5. nforgecvs.bind_name - The DN of the LDAP object nforgecvs will bind as to make the LDAP requests.
  6. nforgecvs.bind_passwd - The password nforgecvs will use when it binds as the object specified by the nforgecvs.bind_name option.
  7. nforgecvs.ldap_root - The DN of the parent LDAP container for the Novell Forge System.
  8. nforgecvs.dns_name - This line should not be required in most cases. It must be used if the CVS server is known by a DNS name that is different than what is reported on the local machine. This would also mean that the LDAP object associated with this CVS server has its NFORGEDNSName attribute set to this alternate DNS name.

The last thing we need to do to tie up the configuration of the cron job is to register it with cron. This can be done by executing the following sequence of commands:

echo ‘*/15 * * * * root /cvsroot/bin/nforgecvs’ > /etc/cron.d/nforgepoll
chmod 755 /etc/cron.d/nforgepoll
/etc/rc.d/init.d/crond restart

The first thing we do here is create a file called /etc/cron.d/nforgepoll with a single cron configuration line in it. This line tells cron to execute /cvsroot/bin/nforgecvs every 15 minutes. The next line just restricts the access to the file and sets the executable bits on it. The last line just restarts the crond daemon. I’m not sure if this is necessary, but it couldn’t hurt. That wraps up the configuration of the cron job for the CVS server. The only thing we have left is the installation and configuration of the viewCVS application.

Setting Up ViewCVS

Many projects created in the Novell Forge system allow anonymous users read only access to their CVS repositories. For these projects, Novell Forge also provides a web interface into their CVS repository. This is done through the ViewCVS application. ViewCVS is typically run as a CGI script on a machine that already has an HTTP server running. In situations where there is no HTTP server running, ViewCVS can run in standalone mode, providing its own HTTP interface. This is how the CVS servers uses ViewCVS.


The ViewCVS application is written in the Python programming language, so you should ensure the Python package is installed on the machine. During a standard RedHat Linux installation, Python version 1.5.x is installed. I have had problems with ViewCVS running on this older version of Python, so Python version 2.1 or higher must be installed. It is fine to have both versions running on the same machine, but any application looking for Python, will end up using version 1.5.x. To take care of this problem, I changed the "#!/usr/bin/pyhon" line at the beginning of all of ViewCVS’s Python files to "#!/usr/bin/python2" and created the symbolic link /usr/bin/python2 that points to /usr/bin/python2.1. Assuming the file /usr/bin/python2.1 exists, this symbolic link can be created with the following command:

ln –s /usr/bin/python2.1 /usr/bin/python2

The ViewCVS application was also modified in others ways to make it more suitable for the Novell Forge CVS environment. This modified version can be found in the file /nxoops/cvssetup/viewcvs.tar.gz. The first thing we need to do is put the ViewCVS application in an appropriate location. That would be in a directory below our /cvsroot directory. You can do that with the following commands:

cd /cvsroot
tar zxvf /nxoops/cvssetup/viewcvs.tar.gz


The directory /cvsroot/viewcvs should now be present on your system, which will contain all the files necessary for ViewCVS to run.

In order to keep things as secure as possible, we can’t have the ViewCVS process run as the root user. What we need is a user that owns all of the ViewCVS related files, but still has access to CVS repositories that allow anonymous access. To do this we will create a local user that has the same primary group as the Novell Forge users, which is the "members" group. You can create this user with the following command:

adduser –g members –r –s /bin/false viewcvs

This creates a system account with the user name "viewcvs" with the primary group set to 1000(members) and the login shell set to /bin/false. With the login shell set to /bin/false, the viewcvs user is not allowed to login. Now we need to know what the numeric user id is for the viewcvs user. To determine this, type the following command:

id –u viewcvs

This should print the uid for the viewcvs user. Take note of this number, because you are going to need it to configure the ViewCVS daemon. Now we need to change the ownership of the viewcvs directory to the viewcvs user and restrict access to only the viewcvs user. You can do this with the following sequence of commands:

cd /cvsroot
chmod 700 viewcvs
chgrp –R viewcvs viewcvs

Now we are going to make adjustments to the ViewCVS configuration file for our specific CVS server. Open the /cvsroot/viewcvs/viewcvs.conf file in your editor and make the following changes:

  • Set the value of the "daemon_uid" option to the numeric user id of the viewcvs user.
  • Set the value of the "address" option to the email address of the CVS Server administrator.

Now we need to make changes to the script that runs ViewCVS as a daemon when the system starts up. Open the /cvsroot/viewcvs/viewcvs file. Search for the value "~CVSSERVERDNS~" and change it to the DNS name of your CVS server (e.g. mydns.provo.novell.com). Save this file and execute the following commands:

cp /cvsroot/viewcvs/viewcvs /etc/rc.d/init.d
cd /etc/rc.d/init.d
chmod +x viewcvs
chkconfig --add viewcvs
./viewcvs start

Through the above instructions, we have set up ViewCVS to run as a daemon when the machine is started. We also started the ViewCVS daemon. You can verify that it is running by typing the following command:

ps –C standalone u

If everything has gone well, you should see something print out that is similar to the following:

USER    PID %CPU %MEM VSZ  RSS  TTY STAT START TIME COMMAND
viewcvs 820 0.0  0.3  5436 3896  ?   S   Mar14 0:01 /usr/bin/python2 /cvsroot/viewcvs/standalone.py -h …

At this point you should be able to point your web browser at http://thecvsserver.dns.name:8080/ and see the ViewCVS interface. Of course you shouldn’t see any project repositories yet since none have been imported yet.

That concludes the setup and configuration of a Novell Forge CVS server. Now that wasn’t hard, was it?

Novell® Making IT Work As One

© 2009 Novell, Inc. All Rights Reserved.