[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: chrooted cvs



On Fri, 30 Mar 2001, Tillman Hodgson wrote:

> Howdy,
> Whats the preferred way to run a secure (chrooted preferably) cvs server
> acting as a source repository only (i.e, compilation will take place on other
> systems) for trusted authenticated users & anonymous read-only access?

Here's how I did it for my Linux box.

# CVS chroot installation instructions.
# Copyright(C) 2000 Heikki Korpela for Fountain Park Ltd. All Rights Reserved.

# This file may not be distributed in any format,
# seperately or within another package, without
# a prior written permission from the author.

# ----------------------------------------------------------------------

# This is NOT an RPM spec file. (Although
# the instructions should apply for an RPM,
# too.)

# TODO: There are some unfixed incompatibilities
# here; in specific:
# o the OpenBSD's check for -lcrypt
#   fails for some reason (and yes, I tried with --enable-shared
#   and without -static -s)
# o there are some -v options in rm, ln and cp (which only work
#   in GNU versions of these programs)
# o I've left cvsroot-fp in places instead of a <cvsroot>
# o I've left FP internal IP addresses in places

# I have used the following documents as
# helpers for these instructions:

# http://guv.ethz.ch/~flip/cvsd/
# http://www.badgertronics.com/writings/cvs/chroot.html
# http://www.cvshome.org/cyclic/cyclic-pages/security.html
# http://www.linuxdoc.org/HOWTO/CVS-RCS-HOWTO.html
# http://www.kegel.com/cvsadmin.html
# http://www.securityfocus.com/archive/1/72584

# ----------------------------------------------------------------------

# OpenBSD version of CVS sources:
{
   mkdir --verbose -p obsd-cvs
   cd obsd-cvs
   wget --recursive --no-parent ftp://ftp.sunet.se/pub/OpenBSD/src/gnu/usr.bin/cvs
   # (the -v option only applies to GNU rm)
   find . -name CVS -a -type d | xargs rm -rvf
   # XXX TODO: Temporary hack to disable OpenBSD's changes to
   # Search for the Sacred Crypt(3); don't uncomment even on OpenBSD,
   # please rerun autoheader and autoconf instead
   rm -f configure
   cd ..
}
# The official CVS version:
{
   ncftpget ftp://ftp.cvshome.org/pub/cvs-1,11/cvs-1.11.tar.gz
   tar zxvvfp cvs-1.11.tar.gz
   # XXX TODO: Temporary hack to disable OpenBSD's changes to
   # Search for the Sacred Crypt(3) - UNCOMMENT IF YOU RUN OPENBSD
   cp cvs-1.11/configure.in obsd-cvs/
}
diff -uNr cvs-1.11 obsd-cvs > obsd-cvs.patch

# ----------------------------------------------------------------------

# The following SRPMS: (unpack them with e.g. rpm2cpio | cpio -d -v -m -i,
# and copy the patches somewhere)

# Conectiva: cvs-1.10.8-4cl
# Mandrake:  cvs-1.11-5mdk
# RedHat:    cvs-1.11-3
# Trustix:   cvs-1.10.8-8

# ----------------------------------------------------------------------

cd cvs-1.11

# The following patches are to be applied to the official version:

# OpenBSD (various, including info patches, getdate patch, patch to
# make ssh default instead of rsh, a fix to handle replica+real
# repositories better, patch to add read-only support)
patch -p1 < ../obsd-cvs.patch

# Mandrake
# MMAP support ; I recommend sys-zlib patch if you don't
# want mmap.
# (FAILS out of box, affects configure.in)
# commit_readonly reports problems with munmap,
# as do a bunch of imports)
# patch -p0 < ../cvs-srpms/mdk/cvs-1.10.7-mmap.patch
# Fixed to:
# patch -p1 < ../cvs-srpms/mdk/cvs-1.11-mmap.patch
# Patch to use system zlib - mutually exclusive with mmap
# (FAILS out of box, affects configure.in)
patch -p1 < ../cvs-srpms/mdk/cvs-1.10.8-zlib.patch
autoheader
autoconf

# Trustix
# Patch to clear errno in while loops
patch -p1 < ../cvs-srpms/trustix/cvs-1.10.8-errno.patch

# Conectiva (fix to use mktemp - so this assumes the system
# has mktemp, like any reasonably secure system should)
patch -p1 < ../cvs-srpms/cl/cvs-1.10-tmprace.patch

# RH
# Fixes some unlink calls to unlink_file calls and adds
# some existence_error checks
# (FAILS out of box)
# patch -p1 < ../cvs-srpms/rh/cvs-1.11-existence.patch
# Fixed to:
patch -p1 < ../cvs-srpms/rh/cvs-1.11-fixed-existence.patch
# Adds some security to file opening (checks for mode
# attempts that differ from a/w and checks for
# open() success); - seems to cause errors
# with chrooting, so commented out
# patch -p1 < ../cvs-srpms/rh/cvs-1.11-security.patch

# My own.
# Patch to use mkstemp instead of the stinking tempnam:
# ('coz I'm lazy, I'm skipping the checks for the mkstemp in
#  config.in, so make sure you have it. If you don't, get it.
#  You should have it anyways.)
patch -p1 < ../cvs-1.11-mkstemp.patch

# Fix prog-exploits:
#   see http://www.securityfocus.com/archive/1/72584
patch -p1 < ../cvs-1.11-progexploit.patch

# ----------------------------------------------------------------------

# Create the chroot directories
# (Change <project> to project name.)

sudo mkdir -p --verbose /chroot/cvs
sudo mkdir -p --verbose /chroot/cvs/cvsroot-<project>
sudo mkdir -p --verbose /chroot/cvs/{etc,bin,lib,tmp,dev}

# ----------------------------------------------------------------------

# Configure and compile CVS
# (CFLAGS acquired jointly from Trustix rpm SRPM
#  (the patch to rpmrc) and Athlon GCC.)
# Please note that I would recommend stack boundary of 2
# for compiling kernels. Also, -mpreferred-stack-boundary doesn't
# seem to work for some versions of gcc.
# export CFLAGS=" -Wall -O3 -march=i586 -fomit-frame-pointer -fno-exceptions -fno-rtti -pipe -s -ffast-math -fexpensive-optimizations -malign-loops=4 -malign-jumps=4 -malign-functions=4 -mpreferred-stack-boundary=4 -funroll-loops -static "
export CFLAGS=" -Wall -O3 -march=i586 -fomit-frame-pointer -fno-exceptions -fno-rtti -pipe -s -ffast-math -fexpensive-optimizations -malign-loops=4 -malign-jumps=4 -malign-functions=4 -funroll-loops -static "
./configure --prefix=/chroot/cvs --enable-static \
  --disable-shared --enable-server --disable-client --enable-encryption
make
sudo make install
PATH=$PATH:/sbin:/usr/sbin

# ----------------------------------------------------------------------

# Create a minimal password file in '/chroot/cvs/etc/passwd'.
# Add cvsowner and cvs. They don't need a home or
# a shell.

sudo /usr/sbin/groupadd -g 6000 cvs
sudo /usr/sbin/useradd -c "CVS pseudo account" -g cvs -d / -s /bin/false -M cvs -u 6000
sudo passwd cvs

# Enter password..

sudo useradd -c "CVS pseudo account" -g cvs -d / -s /bin/false -M cvsowner -u 6000
sudo passwd cvsowner
# Enter password..

# Disable the accounts

sudo usermod -e 1970-01-01 -f 0 -L cvs
sudo usermod -e 1970-01-01 -f 0 -L cvsowner
# (You might also want to fix /etc/ssh/sshd_config)

# ----------------------------------------------------------------------

# Fix permissions

sudo chown -R 6000.6000 /chroot/cvs
sudo chmod -R 700 /chroot/cvs
sudo chmod 755 /chroot/cvs
sudo chmod -R 755 /chroot/cvs/dev
sudo chmod -R 777 /chroot/cvs/tmp

# ----------------------------------------------------------------------

# Move the password and group entries to /chroot:
# (passwd, group, shadow)

# *** NB *** This may disrupt important system files *** NB ***

sudo bash
mkdir /tmp/etc
for f in /etc/passwd /etc/group /etc/shadow; do
	cp $f $f.bak
	grep 'cvs' $f > /chroot/cvs$f
	grep -v cvs $f > /tmp/$f
	mv -f /tmp/$f $f
done
exit

# These should be unneeded
sudo userdel cvs
sudo userdel cvsowner
sudo groupdel cvs

# ----------------------------------------------------------------------

# Make device entries:
# (The options might differ for your system; consult null(4))

sudo mknod -m 666 /chroot/cvs/dev/zero c 1 3
sudo mknod -m 666 /chroot/cvs/dev/null c 1 5
sudo chown root:mem /chroot/cvs/dev/null /chroot/cvs/dev/zero

# Copy and link libraries:

sudo bash
{
 	cd /chroot/cvs/lib
 	cp -apvf /lib/libc-2.*.so .
 	ln -svf libc-2.*.so libc.so.6
 	cp -apvf /lib/ld-2.*.so .
 	ln -svf ld-2.*.so ld-linux.so.2
 	cp -apvf /lib/libpam* .
 	cp -apvf /lib/libdl* .
 	cp -apvf /lib/libnsl-* .
 	ln -svf libnsl-* libnsl.so
 	ln -svf libnsl-* libnsl.so.1
 	cp -apvf /lib/libcrypt-* .
 	ln -svf libcrypt-* libcrypt.so.1
 	cp -aprvf /lib/security .
 	cp -apvf /lib/libnss_files* .
 	cp -apvf /usr/lib/libz*
}

# ----------------------------------------------------------------------

# Copy pam.d support files

{
 	cd /chroot/cvs/etc
 	mkdir --verbose -p pam.d
 	cd pam.d
 	cp -rvf /etc/pam.d/* .
}
exit

# ----------------------------------------------------------------------

# Copy binaries and run ldconfig

sudo cp /bin/mktemp /chroot/cvs/bin
sudo cp /sbin/ldconfig /chroot/cvs/bin
sudo chroot /chroot/cvs /bin/ldconfig -v

# ----------------------------------------------------------------------

# Copy time

sudo cp /etc/localtime /chroot/cvs/etc

# Compile the following:
# (http://www.unixtools.org/cvs/run-cvs.c)
# (http://www.kegel.com/cvs-chroot.c)

# ----------------------------------------------------------------------

# Init a CVS repository
sudo chroot /chroot/cvs cvs -d /cvsroot-fp init

# ----------------------------------------------------------------------

cat > /tmp/run-cvs.c <<EOF

#include <stdlib.h>
#include <unistd.h>

int chroot(const char *path);
int execl( const char *path, const char *arg, ...);
int chdir(const char *path);
int setresuid(uid_t ruid, uid_t euid, uid_t suid);
int setresgid(gid_t rgid, gid_t egid, gid_t sgid);

#define BASE "/chroot/cvs"
#define REPOSITORY "/cvsroot-fp"
#define OWNER_UID 6000
#define OWNER_GID 6000

int main(int argc, char *argv[])
{
   int res;

	/* Why do this?  See doc/cvs.info-8, Node: Connection. */
	/* (Suggested by Scott Bronson) */
	unsetenv("HOME");

   res = chdir(BASE);
   if ( res ) exit(1);

   res = chroot(BASE);
   if ( res ) exit(2);

   res = setresgid(OWNER_GID, OWNER_GID, OWNER_GID);
   if ( res ) exit(3);

   res = setresuid(OWNER_UID, OWNER_UID, OWNER_UID);
   if ( res ) exit(4);

   execl("/bin/cvs", "cvs",
      "-f",
      "--allow-root=" REPOSITORY,
      "pserver",
      NULL);
   exit(3);
}
EOF
gcc -Wall $CFLAGS /tmp/run-cvs.c -o /tmp/run-cvs
sudo mv /tmp/run-cvs /usr/sbin/run-cvs
rm -f /tmp/run-cvs.c
sudo chmod 755 /usr/sbin/run-cvs

# ----------------------------------------------------------------------

# Add the following to /etc/services:

cvspserver             2401/tcp

# ----------------------------------------------------------------------

# If you use inetd, please switch to xinetd.
# If you don't have xinetd yet, try:

# http://saitti.net/~heko/xinetd/

# Use the following for xinetd.conf (assuming you don't want
# to run anything else: )

sudo bash
cat > /etc/xinetd.conf <<EOF
defaults
{
        instances               = 25
        # Reducing syslog level is recommended after the server is
        # confirmed to work.
        log_type                = FILE /var/log/xinetd.log
        log_on_success          = HOST PID DURATION USERID EXIT
        log_on_failure          = HOST RECORD ATTEMPT USERID
        only_from               = 192.168.31.0 127.0.0.1
        disabled                = tftp
}

service cvs
{
        log_type                = FILE /var/log/cvs.log
        log_on_success          = HOST PID DURATION USERID EXIT
        log_on_failure          = HOST RECORD ATTEMPT USERID
        flags                   = REUSE NORETRY NODELAY
        socket_type             = stream
        protocol                = tcp
        wait                    = no
        user                    = root
        nice                    = -5
        server                  = /usr/sbin/run-cvs
        port                    = 2401
        cps                     = 1 10
        max_load                = 2
}

EOF
exit

# And probably you will want to change the
#   daemon xinetd
# in /etc/rc.d/init.d/xinetd to:
#   daemon xinetd -filelog /var/log/xinetd-meta.log -reuse

sudo /etc/rc.d/init.d/xinetd restart

# ----------------------------------------------------------------------

# Compile cvspasswd:
# (http://www.kegel.com/cvspasswd.c)

export CFLAGS=`echo $CFLAGS | sed 's|-static||'`
cat > /tmp/cvspasswd.c <<EOF
/* Trivial password generator for cvs.  Compile with 'cc -o cvspasswd cvspasswd.c -lcrypt' */
#define _XOPEN_SOURCE
#include <unistd.h>
#include <stdio.h>
#include <sys/times.h>
#include <time.h>

time_t time(time_t *t);
char *crypt(const char *key, const char *salt);

/* Generate a single character of salt given a random integer.  See 'man crypt'. */
int base64(int x)
{
	const char b64[64] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz./";
	return b64[x % 64];
}

int main(int argc, char **argv)
{
	char ibuf[256];
	char passwd[256];
	char saltstr[3];
	struct tms t;

	if (argc != 2) {
		fprintf(stderr, "Usage: cvspasswd username\n");
		exit(1);
	}
	fprintf(stderr, "Password for %s: ", argv[1]);
	ibuf[0] = 0;
	fgets(ibuf, sizeof(ibuf), stdin);
	sscanf(ibuf, "%s", passwd);
	saltstr[0] = base64(times(&t));
	saltstr[1] = base64(time(0));
	saltstr[2] = 0;

	printf("%s:%s:cvsowner\n", argv[1], crypt(passwd, saltstr));
	exit(0);
}
EOF

gcc -Wall $CFLAGS -lcrypt /tmp/cvspasswd.c -o /tmp/cvspasswd
sudo mv /tmp/cvspasswd /usr/sbin/cvspasswd
rm -f /tmp/cvspasswd.c

# ----------------------------------------------------------------------

# Add CVS users:

sudo bash
cd /chroot/cvs/cvsroot-fp/CVSROOT
cvspasswd <user> >> passwd
<Password>
exit

# ----------------------------------------------------------------------

# Try it out:

# (For any of the following, use:
# strace -f -F -o strace.out -tt <command> <args>
#   or
# strace -f -F -o strace.out -tt -p `/sbin/pidof <command>`

# ----------------------------------------------------------------------

# [1] Check that cvs server supports 'pserver':

sudo bash
echo "foo" | chroot /chroot/cvs /bin/cvs -f --allow-root=/cvsroot-fp pserver
exit

# (This should produce an error:
# cvs [pserver aborted]: bad auth protocol start: foo

# [2] cd to the original CVS compile directory and run
# *** THIS WILL TAKE A WHILE *** (several minutes). Be patient -
# it should fail by itself if anything goes wrong; I haven't
# met a timeout yet. If you are in doubt, you can always do a
# ps axuww or top
# (ps axufww on Linux procps version 2.0.6 or compatible)
# (ps axuHww on BSD procps)
# (top S s c d 10, on top revision 1.2)
# This is mostly because the sanity.sh sleeps for a minute at points,
# because it assumes a non-GNU ls that does not support showing
# seconds. Make check will fail some 70% of imports, plus commit_readonly,
# if you applied the mmap patch instead of system zlib.
make check

# [3] Check that you can login in to the cvs server

# (Use pidof xinetd if this fails)

export CVSROOT=:pserver:<user>@127.0.0.1:/cvsroot-fp

cvs login

# [4] Check that you can create a new module

cvs import -m "<Comment>" <module> <vendor> start

# [5] Check that you can create a new file

cvs co <module>
cd <module>

echo "Test" > test.txt
cvs add test.txt
cvs com -m "Test" test.txt
cvs status -v

# ----------------------------------------------------------------------

> -Tillman

<!-- ---------------------- 72 characters -------------------------- -->
                   Heikki Korpela -- heko@saitti.net