CVS via SSH, aka CVS Securely

by Steven J. Owens (unless otherwise attributed)

In general, you should not use CVS's pserver mode to access cvs via network. CVS pserver uses an unencrypted connection and any one can sniff your username and password floating by. Instead, use SSH to protect your connection.

There are two ways to use SSH. The generally better way is to use the built-in support for SSH in your CVS client:

There's another standard way to use ssh with cvs - if you can ssh directly to the box running CVS. Basically, you set up the following environment variables:

export CVSROOT=username@cvs.example.com:/path/to/repository
export CVSRSH=/usr/bin/ssh

Then use cvs normally, and cvs will use ssh to log into the cvs box and then use local CVS commands to do everything.

However, maybe you have CVS server that's inside a secured network, using pserver, and you have to get it at from the road. To do this, you need ssh access to a box inside the firewall, and you need to use ssh portforwarding.

To set up ssh portforwarding you use ssh with the -L option:

ssh -Llocalport:remotehost:remoteport

You have to combine this with a norman ssh session. Specifically, for CVS from outside the firewall:

ssh -L2401:192.168.1.60:2401 username@insidefirewall.example.com

Note that the address is the internal IP address; essentially, what you're doing is telling ssh to "connect me to the insidefirewall server, and while you're at it, forward any packets from localhost:2401 to the address 192.168.1.60:2401", where that address is from the perspective of the insidefirewall server."

One neat bit is that 192.168.1.60 can be any machine that insidefirewall.example.com can reach. So you can essentially "bounce" your network connection off insidefirewall.example.com to reach the other box.

There are two gotchas with this approach.

The first is that you have to set up your current archive using a tunnel, so that it shows as having checked out from localhost. Or you have to write a script to crawl through your current archive and change your checkout to do so. Fortunately I've already done that, it's below.

The second is that you'll also have to use tunneling when inside the firewall. Just do it the same way, only instead of tunneling to the firewall, tunnel to the cvs server. Or you have to write a script to crawl through your current archive and change your checkout to do so. Fortunately I've already done that, it's below.

Okay, three gotchas, which is that unless the firewall box is also the cvs box, your packets will not be encrypted when going across your internal network, from the firewall box to the cvs box. You can fix this by doing a two-step tunnel - from the login session at the firewall, tunnel from localhost (the firewall) to the cvs server. You can even look up the man page for ssh and figure out how to actually have ssh, instead of logging you in, log in and immediately execute your second cvs command.

Okay, four gotchas; your tunnel will last only as long as your ssh session does. So if you just log in via ssh and don't do anything on that connection, sooner or later your server will probably notice and disconnect you. I've never had a problem with this myself, I tend to log in, do the CVS stuff I need to do, and log back out. If you would rather have your tunnel stay up for longer periods of time, you can invoke ssh with a shell command, and apparently it's fairly common practice to have it invole a shell script that just loops.

Here's a perl script you can use to munge the contents of the CVS/Root files in your checkout. Change the appropriate $newroot and $oldroot lines to whatever values you need.

#! /usr/bin/perl -w
eval 'exec /usr/bin/perl -S $0 ${1+"$@"}'
    if 0; #$running_under_some_shell
use File::Find ();
# for the convenience of &wanted calls, including -eval statements:
use vars qw/*name *dir *prune/;
*name   = *File::Find::name;
*dir    = *File::Find::dir;
*prune  = *File::Find::prune;
if (!defined($ARGV[0])) {
    print "switchcvsroot.pl\n\n" ;
    print "        Replace contents of files named Root with\n" ;
    print "        new cvs repository.\n\n" ;
    print "Usage:  switchcvsroot.pl startdirectory\n" ;
    print "Usage:  switchcvsroot.pl startdirectory overwrite\n\n" ;
    print "        The first form displays contents of all Root files below \n        startdirectory.\n\n" ;
    print "        The second form displays and overwrites contents of all\ n        Root files.\n\n" ;
    print "        (Note that \"overwrite\" is a literal string.\n" ;
    exit ;
}
my $newroot = ":ext:foo\@bar.example.com:/var/lib/cvs" ;
my $oldroot = ":ext:foo\@baz.example.com:/var/lib/cvs" ;
my $startdir = $ARGV[0] ;
my $overwrite = 0 ;
if (defined($ARGV[1]) && ($ARGV[1] =~ /overwrite/)) {
    $overwrite = 1 ;
}
print "Starting in directory \"$startdir\" and overwriting is \"$overwrite\"\n"  ;
# Traverse desired filesystems
File::Find::find({wanted => \&wanted}, $startdir);
exit;
sub wanted {
    my $filename = $_ ;
    if ($filename =~ /^Root$/) {
        if ($filename =~ /^Root$/) {
            $/ = "" ;
            open (IN, $filename)  || die "can't open \"$filename\" for read ing\n" ;
            my $contents =  ;
            chomp($contents) ;
            close ($filename) ;
            my $answer = "not matched" ;
            if ($contents eq $oldroot) {
                $answer = "    matched" ;
            } else {
                $answer = "not matched" ;
            }
            if ($overwrite) {
                open(OUT, ">$filename") || die "Can't open \"$filename\" for writing.\n" ;
                print OUT $newroot ;
                close(OUT) ;
            }
	    print "$filename ($answer):   $contents\n" ;
	    if ($overwrite) {
		print "    overwriting to:   $newroot\n" ;
	    }
        }
}

See original (unformatted) article

Feedback

Verification Image:
Subject:
Your Email Address:
Confirm Address:
Please Post:
Copyright: By checking the "Please Post" checkbox you agree to having your feedback posted on notablog if the administrator decides it is appropriate content, and grant compilation copyright rights to the administrator.
Message Content: