Subversion and Trac setup with minimal privilege overlap
Over the years I developed (in discussion with a few colleagues and friends) a subversion and trac setup with a rather strong privilege separation. I've written it down for Debian wheezy, but it can be reproduced (with more or less effort) on other distributions as well. I once did it on SUSE, which was a nightmare, but this is more a judgment about the cleanness and flexibility of the distribution, not the setup described here. In addition some details will certainly need adjustments to new developments, but the basics are rather mature. Also, even when the focus of other setups vary, the whole concept is probably worth sharing. It also forced me to rewind my setup and write it down not in a bunch of snippets in a best practice folder on my machine (as I'm doing it all the time), but in a (still sparsely) documented blog post.
First of all, I will install trac from source as a regular user. As I frequently tweak details on the trac installation, I don't want to use the version that comes with the distribution. The important point here is, that trac will be installed neither as root nor as the user which runs the trac process in the end. On machines I'm managing myself (alone) I use my own user account for such installations, but you could use a specific software installation account. To make the installation process a little more universal, let us set an environment variable for this user (on a root shell we'll use in the course of this guide):
export SW=...
We install Apache, as it is still the solution of choice for serving subversion (webdav). We take subversion from the distribution as well as python and gunicorn (as a simple solution for serving trac):
apt-get install -y apache2 libapache2-svn python-virtualenv python-subversion python-dev gunicorn sudo
We don't want Apache to serve at Port 80, nor should it do anything else than subversion. As other use-cases of "specific" apache instances might come up, we disable the default configuration and setup an apache2-svn configuration. This apache instance will be run as a svn user to be created later on. The apache config is created by the following steps:
service apache2 stop update-rc.d -f apache2 remove echo -e '\necho "default apache disabled"\nexit' >> /etc/default/apache2 mkdir /etc/apache2-svn cp /etc/apache2/{apache2.conf,magic} /etc/apache2-svn sed -e 's/www-data/svn/' /etc/apache2/envvars > /etc/apache2-svn/envvars echo "Listen 127.0.0.1:49443" > /etc/apache2-svn/ports.conf ln -s /etc/apache2/{conf.d,mods-available} /etc/apache2-svn mkdir /etc/apache2-svn/{mods-enabled,sites-available,sites-enabled} APACHE_CONFDIR=/etc/apache2-svn a2enmod authz_host mime auth_basic authn_file authz_groupfile dav_fs authz_svn alias ssl touch /etc/default/apache2-svn sed 's/#.*apache2/&-svn/' /etc/init.d/apache2 > /etc/init.d/apache2-svn chmod +x /etc/init.d/apache2-svn mkdir /var/log/apache2-svn
As subversion uses more of http than simple get and post requests, it is rather hard to switch the scheme on a reverse proyx in front of the subversion server. Thus we create a private key, a signing request and self-sign it with our own private key:
openssl genrsa -out /etc/apache2-svn/server.key 2048 openssl req -new -batch -subj "/C=US/CN=localhost" -key /etc/apache2-svn/server.key -out /etc/apache2-svn/server.csr openssl x509 -req -days 3650 -in /etc/apache2-svn/server.csr -signkey /etc/apache2-svn/server.key -out /etc/apache2-svn/server.crt
Now comes the apache configuration:
echo -e "ServerName svn\n"\ "ServerAdmin webmaster@$(hostname)\n"\ "DocumentRoot /var/www/\n"\ "CustomLog /var/log/apache2-svn/access.log combined\n"\ "SSLEngine On\n"\ "SSLCertificateFile /etc/apache2-svn/server.crt\n"\ "SSLCertificateKeyFile /etc/apache2-svn/server.key\n"\ "SSLSessionCache shmcb:/var/run/apache2-svn/ssl_scache(512000)\n"\ "SSLMutex file:/var/run/apache2-svn/ssl_mutex\n"\ "\n"\ "<Location /repos/>\n"\ " DAV svn\n"\ " SVNParentPath /var/lib/svn/repos\n"\ " SVNListParentPath On\n"\ " AuthzSVNAccessFile /etc/apache2-svn/authz\n"\ " AuthType Basic\n"\ ' AuthName "svn repos"\n'\ " AuthUserFile /etc/auth/htpasswd\n"\ " Require valid-user\n"\ "</Location>\n"\ "\n"\ 'RedirectMatch /repos$ /repos/' > /etc/apache2-svn/sites-available/default APACHE_CONFDIR=/etc/apache2-svn a2ensite default
In this setup a subversion and trac user will be authenticated via a passwd file and the subversion access is configured in an authz file:
htpasswd -c /etc/$newuser/htpasswd user echo -e "[groups]\n"\ "all=user\n"\ "\n"\ "[/]\n"\ "@all=rw" > /etc/apache2-svn/authz
Now we're all done regarding subversion except for the svn user itself:
adduser --system --home /var/lib/svn --group svn sudo -u svn mkdir /var/lib/svn/{repos,backups,skel} echo -e '#!/bin/bash\n'\ '/usr/bin/sudo -u trac /home/'$SW'/trac/bin/trac-admin /var/lib/trac/projects/NAME changeset added "$1" "$2" &&\n'\ '/usr/bin/svnadmin dump "$1" --revision "$2" --incremental|/bin/gzip > "/var/lib/svn/backups/NAME/dump.$(/usr/bin/printf %05d $2).gz"' > /var/lib/svn/skel/post-commit echo -e '#!/bin/bash\n'\ '/usr/bin/sudo -u trac /home/'$SW'/trac/bin/trac-admin /var/lib/trac/projects/NAME changeset modified "$1" "$2" &&\n'\ '/usr/bin/svnadmin dump "$1" --revision "$2" --incremental|/bin/gzip > "/var/lib/svn/backups/NAME/dump.$(/usr/bin/printf %05d $2).gz"' > /var/lib/svn/skel/post-revprop-change chown svn.svn /var/lib/svn/skel/* chmod u+x /var/lib/svn/skel/*
Here we've included skeleton files for some hooks, which will create svn dump backups for each revision at checkin and inform trac about the changes in the repository. As trac will run as a separate trac user user, we need to add a sudoers rule:
echo "svn ALL=(trac)NOPASSWD:/home/wobsta/trac/bin/trac-admin /var/lib/trac/projects/* changeset added *,/home/wobsta/trac/bin/trac-admin /var/lib/trac/projects/* changeset modified *" > /etc/sudoers.d/trac chmod 440 /etc/sudoers.d/trac
Ok, now let's install trac:
cd /tmp sudo -u $SW virtualenv /home/$SW/trac sudo -u $SW /home/$SW/trac/bin/pip install trac sudo -u $SW mkdir /home/$SW/trac/wsgi echo -e "#!/home/$SW/trac/bin/python\n"\ "\n"\ "import site, sys\n"\ "site.addsitedir('/home/$SW/trac/lib/python2.7/site-packages')\n"\ "from pkg_resources import working_set\n"\ "for path in sys.path:\n"\ " working_set.add_entry(path)\n"\ "\n"\ "import os\n"\ "\n"\ "os.environ['PYTHON_EGG_CACHE'] = '/var/lib/trac/eggs'\n"\ "\n"\ "import trac.web.main\n"\ "def application(environ, start_response):\n"\ " environ['SCRIPT_NAME'] = '/projects'\n"\ " environ['REMOTE_USER'] = environ.get('HTTP_REMOTE_USER')\n"\ " environ['trac.env_parent_dir'] = '/var/lib/trac/projects'\n"\ " return trac.web.main.dispatch_request(environ, start_response)" | sudo -u $SW tee /home/$SW/trac/wsgi/projects.py > /dev/null
In the last step we've created a wsgi adapter. Due to the component architecture of trac adding the site-package path of the virtual env is not enough in this script, but package discovery requires a working_set modification as shown. In addition, we fix the SCRIPT_NAME environment variable and copy the HTTP_REMOTE_USER to the REMOTE_USER environment variable as required by trac. This setup enables us to use nginx as the front-end webserver in the end.
To complete the trac installation, we have to add the trac user to the system:
adduser --system --home /var/lib/trac --group trac sudo -u trac mkdir /var/lib/trac/{projects,eggs}
Now we're left with starting apache2-svn and adding trac to the gunicorn configuration:
service apache2-svn start update-rc.d apache2-svn defaults service gunicorn stop echo -e "CONFIG = {\n"\ " 'working_dir': '/home/$SW/trac/wsgi',\n"\ " 'environment': {\n"\ " },\n"\ " 'user': 'trac',\n"\ " 'group': 'trac',\n"\ " 'args': (\n"\ " '--bind=127.0.0.1:49080',\n"\ " '--workers=3',\n"\ " '--timeout=30',\n"\ " 'projects:application',\n"\ " ),\n"\ "}" > /etc/gunicorn.d/trac service gunicorn start
To create a new subversion repository and corresponding trac instance with a given name, execute:
export NAME=test sudo -u svn svnadmin create /var/lib/svn/repos/$NAME sudo -u svn mkdir /var/lib/svn/backups/$NAME rm -r /var/lib/svn/repos/$NAME/hooks cp -ra /var/lib/svn/skel /var/lib/svn/repos/$NAME/hooks sed -i s/NAME/$NAME/ /var/lib/svn/repos/$NAME/hooks/* sudo -u trac /home/$SW/trac/bin/trac-admin /var/lib/trac/projects/$NAME initenv
The reverse proxy configuration is straight forward and shown for as an nginx config snipped here
location /repos { proxy_pass https://127.0.0.1:49443; } location /projects { rewrite ^/projects/(.*)$ /$1 break; proxy_pass http://127.0.0.1:49080; } location ~ ^/projects/[a-z]+/login { auth_basic "trac projects"; auth_basic_user_file /etc/auth/htpasswd; proxy_set_header REMOTE_USER $remote_user; rewrite ^/projects/(.*)$ /$1 break; proxy_pass http://127.0.0.1:49080; }
Thank's all, folks.
Comments
No comments.