Securing CVS by pserver Port Forwarding using an SSH Tunnel

Summary

The Concurrent Versions System, in its "pserver" client/server mode, and secured by "ssh" encrypted tunnels, can allow multiple authors to safely collaborate over the internet. CVS is a source file version control system optimised for wide area networks, concurrent editing, and reuse of 3rd party source libraries. Pserver is a protocol used for communication between CVS clients and servers. SSH is a tool to transparently encrypt TCP/IP network connections.

MSWindows users can use a posix shell

The tools discussed here are most functional in a unix environment but windows users can obtain similar functions by using a posix shell available from http://www.cygwin.com/. which will include an openssh package in 'latest' and a cvs package in 'contrib'. Other native windows ports of the tools are available but may lack needed features such as SSH2 DSA support.

Creating the CVS Repository on the repository server machine

If you are a contributing author, you don't need to know how the repository was created. You may skip this section.
If you are the unix repository administrator, you would create a directory and run
cvs init
then adjust the control files in CVSROOT to suit the permitted users (writers, passwd). It is convenient to have all files owned by "cvsuser"

Starting the repository service

If you are a contributing author, you don't need to know how the repository server is started. You may skip this section. If you are the unix repository administrator, you allow the server to be started by xinetd with security constraints that specify that only clients local to the server machine may connect. To do this, create a configuration file named "/etc/xinetd.d/cvspserver" with contents similar to:

service cvspserver
{
        flags           = REUSE
        socket_type     = stream
        wait            = no
        user            = cvsuser
        server          = /usr/bin/cvs
        server_args     = -f --allow-root=/cycomcvs pserver
        passenv         =
        log_on_failure  += USERID
        only_from               = 127.0.0.1
        bind                    = 127.0.0.1
}   


then restart the xinetd super-service.

Generating a public/private DSA keypair on the author's client machine

Contributing authors must perform this step only once. The "ssh" tools have various ways of authenticating users. The method chosen here is to use the DSA Digital Signature Algorithm. This is a public/private keypair algorithm which means that the secret private key need never be communicated to anyone and can stay safe on the clients hard disk (protected by a passphrase). The public key can be advertised to anyone with no loss of security. If you do not already have the ssh tools then you should obtain them from http://www.openssh.com/. They must support SSH2 as we use the DSA algorithm not RSA.

A unix client will generate the keypair using:-
ssh-keygen -t dsa
This will result in the creation of a file "~/.ssh/id_dsa.pub". You must send this public file to the unix repository administrator. Do not send any other file nor reveal any passphrase.

Authorizing a client to tunnel to the repository server machine

If you are a contributing author, you don't need to know how authorization is allowed. You may skip this section.
If you are the unix repository administrator, on receipt of a clients "id_dsa.pub" file, append the single line therein to the "~cvsnobody/.ssh/authorized_keys2" file on the server. Note that some linux systems use "authorized_keys2" as the file and some other systems such as Solaris will use "authorized_keys". If one does not work try the other. Note that the users home directory must not be writeable by other users and that the private key files must not be readable by other users.

Creating the secure tunnel between author's client machine and the repository server machine.

CVS keeps a single copy of the master sources called a source repository. Remote authors access the repository using CVS client programs which talk to the repository service using a "pserver" protocol and connect using a registered TCP/IP port (port 2401).

The pserver protocol is insecure because passwords are transmitted unencrypted and there are often some hacked hosts on a network that are sniffing for passwords. The connection to be used for the pserver protocol therefore needs to be encrypted where it passes over any network. The "ssh" suite of programs provides such encrypted connections.

SSH will create a secure tunnel which makes the repository service appear to be local to your client machine. Similarly, your client machine will appear to be local to the repository service. Both client and server are fooled into thinking that they are on the same machine and that no traffic travels over any network.

The single line unix command to achieve this is:-

/usr/bin/ssh -v -a -L 2401:localhost:2401  cvsnobody@ns2.cyterm.com

Here is the output of an example session.

[cwturner@erika bin]$ /usr/bin/ssh -v -a -L 2401:localhost:2401  cvsnobody@ns2.cyterm.com
OpenSSH_3.5p1, SSH protocols 1.5/2.0, OpenSSL 0x0090701f
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: Applying options for *
debug1: Rhosts Authentication disabled, originating port will not be trusted.
debug1: ssh_connect: needpriv 0
debug1: Connecting to ns2.cyterm.com [194.105.69.33] port 22.
debug1: Connection established.
debug1: identity file /home/cwturner/.ssh/identity type -1
debug1: identity file /home/cwturner/.ssh/id_rsa type -1
debug1: identity file /home/cwturner/.ssh/id_dsa type 2
debug1: Remote protocol version 1.99, remote software version OpenSSH_2.5.2p2
debug1: match: OpenSSH_2.5.2p2 pat OpenSSH_2.5.0*,OpenSSH_2.5.1*,OpenSSH_2.5.2*
debug1: Enabling compatibility mode for protocol 2.0
debug1: Local version string SSH-2.0-OpenSSH_3.5p1
debug1: SSH2_MSG_KEXINIT sent
debug1: SSH2_MSG_KEXINIT received
debug1: kex: server->client aes128-cbc hmac-md5 none
debug1: kex: client->server aes128-cbc hmac-md5 none
debug1: SSH2_MSG_KEX_DH_GEX_REQUEST_OLD sent
debug1: expecting SSH2_MSG_KEX_DH_GEX_GROUP
debug1: dh_gen_key: priv key bits set: 126/256
debug1: bits set: 999/2049
debug1: SSH2_MSG_KEX_DH_GEX_INIT sent
debug1: expecting SSH2_MSG_KEX_DH_GEX_REPLY
debug1: Host 'ns2.cyterm.com' is known and matches the RSA host key.
debug1: Found key in /home/cwturner/.ssh/known_hosts:2
debug1: bits set: 1034/2049
debug1: ssh_rsa_verify: signature correct
debug1: kex_derive_keys
debug1: newkeys: mode 1
debug1: SSH2_MSG_NEWKEYS sent
debug1: waiting for SSH2_MSG_NEWKEYS
debug1: newkeys: mode 0
debug1: SSH2_MSG_NEWKEYS received
debug1: done: ssh_kex2.
debug1: send SSH2_MSG_SERVICE_REQUEST
debug1: service_accept: ssh-userauth
debug1: got SSH2_MSG_SERVICE_ACCEPT
debug1: authentications that can continue: publickey,password
debug1: next auth method to try is publickey
debug1: try privkey: /home/cwturner/.ssh/identity
debug1: try privkey: /home/cwturner/.ssh/id_rsa
debug1: try pubkey: /home/cwturner/.ssh/id_dsa
debug1: input_userauth_pk_ok: pkalg ssh-dss blen 434 lastkey 0x8090310 hint 2
debug1: read PEM private key done: type DSA
debug1: ssh-userauth2 successful: method publickey
debug1: Connections to local port 2401 forwarded to remote address localhost:2401
socket: Address family not supported by protocol
debug1: Local forwarding listening on 127.0.0.1 port 2401.
debug1: fd 4 setting O_NONBLOCK
debug1: channel 0: new [port listener]
debug1: channel 1: new [client-session]
debug1: send channel open 1
debug1: Entering interactive session.
debug1: ssh_session2_setup: id 1
debug1: channel request 1: pty-req
debug1: Requesting X11 forwarding with authentication spoofing.
debug1: channel request 1: x11-req
debug1: channel request 1: shell
debug1: fd 3 setting TCP_NODELAY
debug1: channel 1: open confirm rwindow 0 rmax 16384
Last login: Wed Jul 16 21:07:08 2003 from chello212186208023.32.11.vie.surfer.at[cvsnobody@ns2 cvsnobody]$

Note that at this point you have an ssh shell session and prompt on the remote server machine. When you later use a different window on the client to use cvs, you will see further debug messages in the remote session window like the following:-

debug1: fd 8 setting TCP_NODELAY
debug1: fd 8 setting O_NONBLOCK
debug1: channel 2: new [direct-tcpip]
debug1: channel 2: open confirm rwindow 32768 rmax 16384
debug1: channel 2: read<=0 rfd 8 len 0
debug1: channel 2: read failed
debug1: channel 2: close_read
debug1: channel 2: input open -> drain
debug1: channel 2: ibuf empty
debug1: channel 2: send eof
debug1: channel 2: input drain -> closed
debug1: channel 2: rcvd eof
debug1: channel 2: output open -> drain
debug1: channel 2: obuf empty
debug1: channel 2: close_write
debug1: channel 2: output drain -> closed
debug1: channel 2: send close
debug1: channel 2: rcvd close
debug1: channel 2: is dead
debug1: channel 2: garbage collecting
debug1: channel_free: channel 2: direct-tcpip: listening port 2401 for localhost port 2401, connect from 127.0.0.1 port 34045, nchannels 3
debug1: Connection to port 2401 forwarding to localhost port 2401 requested.
debug1: fd 8 setting TCP_NODELAY
debug1: fd 8 setting O_NONBLOCK
debug1: channel 2: new [direct-tcpip]
debug1: channel 2: open confirm rwindow 32768 rmax 16384
debug1: channel 2: read<=0 rfd 8 len 0
debug1: channel 2: read failed
debug1: channel 2: close_read
debug1: channel 2: input open -> drain
debug1: channel 2: ibuf empty
debug1: channel 2: send eof
debug1: channel 2: input drain -> closed
debug1: channel 2: rcvd eof
debug1: channel 2: output open -> drain
debug1: channel 2: obuf empty
debug1: channel 2: close_write
debug1: channel 2: output drain -> closed
debug1: channel 2: rcvd close
debug1: channel 2: send close
debug1: channel 2: is dead
debug1: channel 2: garbage collecting
debug1: channel_free: channel 2: direct-tcpip: listening port 2401 for localhost port 2401, connect from 127.0.0.1 port 34051, nchannels 3
    


The fixed cvsnobody user is just for ssh tunneling purposes; it is not relevant to CVS. The ns2.cyterm.com or j2ee.cycom.co.uk is the repository server machine name. If you used some batchmode flags to ssh then this command might block. Otherwise you will see a shell prompt that shows you are logged in to the remote server machine and you are able to type remote commands into this window. Use the "exit" command when you wish to close the tunnel. Use another window to operate the CVS clients.


In the terminal window that is logged on the remote machine, you can check that cvspserver is listening on its local interface using netstat. E.g.
[cvsnobody@ns2 cvsnobody]$ netstat -tl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 localhost.lo:cvspserver *:*                     LISTEN
...

Also on the local client machine you can check that the cvspserver port is being listened to even though no cvsserver is running on the client. E.g.
 [cwturner@erika cwturner]$ netstat -tl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 *:32768                 *:*                     LISTEN
tcp        0      0 erika.locald:cvspserver *:*                     LISTEN

Simulating on a single machine for both client and server

If you are trying to test this using the same machine for client and server then you will need to use different ports for the real cvs server and the forwarded connection. In the above examples the 2401 default pserver port was used for both client and server. You will need to change the clients port to something else (say 2402) when you simulate on a single machine. E.g.

/usr/bin/ssh -v -a -L 2402:localhost:2401  cvsnobody@localhost

Also ensure you then have the new port set in the appropriate CVSROOT environment content and  CVS/Root contents which match the new client ports. E.g.

:method:[[user][:password]@]hostname[:[port]]/path/to/repository

or set and export the CVS_CLIENT_PORT environment variable before all client commands. E.g.

[cwturner@erika cycom]$ export CVS_CLIENT_PORT=2402
[cwturner@erika cycom]$ cvs login
Logging in to :pserver:cwturner@localhost:2402/cycomsites
CVS password:
[cwturner@erika cycom]$

Operating CVS clients on the author's client machine

Having established a tunnel, the remote CVS repository service now appears to be local to your client machine (i.e. at localhost). The CVS client programs obtain configuration data from a CVSROOT environment variable which should be set in unix (e.g in your ".bash_profile") using:-

CVSROOT=':pserver:jsharp@localhost:/cycomcvs'
export CVSROOT


The username "jsharp" and the repository root "/cycomcvs" will have been sent to you by the repository administrator.

The first CVS client command should always be:-
cvs login
You will be prompted for a password (again sent to you by the repository administrator).

To create a new cvs working directory and populate it from Honest John Car Rental Demo sources, use :-

mkdir mywork
cd mywork
cvs co hjvh
cvs co hjvhear
cvs co hjvhmodel

To freshen an existing working directory with updates from other authors, use:-

cd mywork/hjvh
cvs update

To publish the files that you have changed in an existing working directory, use:-

cd mywork/hjvh
cvs commit

To publish a newly created file in an existing working directory, use:-

cd mywork/hjvh
cvs add mynewfile.txt
cvs commit

To import a new independent directory tree of sources into the repository, make sure all files in the tree are useful source and then use:-

cd projdir
cvs import projdir projV1_1 proj_V1_1
cd ..
mv projdir origprojsources
cvs co projdir

Further information

CVS (http://cvshome.org/)has an extensive FAQ, postscript manuals and other documentation which should be consulted. There are client ports to MSWindows and Java platforms . SSH (http://www.openssh.com/ ) tools have client ports to MSWindows and DOS.

A similar article discusses securing your mail via SSH

Author: Chris Turner