In this post I’d like to explain how it’s possible to integrate SVN (Subversion) source control using WebDAV and HTTPS using Apache and Active Directory to provide authentication and access control.
It’s generally accepted that SVN over WebDAV/HTTPS Â provides finer granulation security controls than SVN+SSH. The problem is that SVN+SSH is really easy to set up, requiring knowledge of svnadmin and the filesystem and very little else but WebDAV+HTTPS requires knowledge of Apache and its modules relating to WebDAV, authentication and authorisation which is quite a lot more to ask. Add to that authenticating to AD and you have yourself a lovely string of delicate single point of failure components. Ho-hum, not a huge amount you can do about that but at least the Apache components are pretty robust.
For this article I’m using CentOS but everything should be transferrable to any distribution with a little tweakage.
Repository Creation
Firstly then, pick a disk or volume with plenty of space, we’re using make your repository – same as you would for svn+ssh:
svnadmin create /var/svn/repos
Apache Modules
Install the prerequisite Apache modules:
yum install mod_dav_svn
This should also install mod_authz_svn which we’ll also be making use of. Both should end up in Apache’s module directory, in this case /etc/httpd/modules/
Download and install mod_authnz_external from its Google Code page. This allows Apache basic authentication to hook into an external authentication mechanism. mod_authnz_external.so should end up in Apache’s module directory but in my case it ended up in its default location of /usr/lib/httpd/modules/
.
Download and install the companion pwauth utility from its Google Code page. In my case it installs to /usr/local/sbin/pwauth and needs suexec permissions (granted using chmod +s
).
Apache Configuration (HTTP)
ServerName svn.example.com
ServerAdmin me@example.com
Listen *:80
NameVirtualHost *:80
User nobody
Group nobody
LoadModule setenvif_module modules/mod_setenvif.so
LoadModule mime_module modules/mod_mime.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule dav_module modules/mod_dav.so
LoadModule dav_svn_module modules/mod_dav_svn.so
LoadModule auth_basic_module modules/mod_auth_basic.so
LoadModule authz_svn_module modules/mod_authz_svn.so
LoadModule authnz_external_module modules/mod_authnz_external.so
LogFormat "%v %A:%p %h %l %u %{%Y-%m-%d %H:%M:%S}t "%r" %>s %b "%{Referer}i" "%{User-Agent}i"" clean
CustomLog /var/log/httpd/access_log clean
<virtualhost *:80>
ServerName svn.example.com
AddExternalAuth pwauth /usr/local/sbin/pwauth
SetExternalAuthMethod pwauth pipe
<location / >
DAV svn
SVNPath /var/svn/repos
AuthType Basic
AuthName "SVN Repository"
AuthzSVNAccessFile /etc/httpd/conf/authz_svn.acl
AuthBasicProvider external
AuthExternal pwauth
Satisfy Any
<limitexcept GET PROPFIND OPTIONS REPORT>
Require valid-user
</limitexcept>
</location>
</virtualhost>
Network Time (NTP)
In order to join a Windows domain, accurate and synchronised time is crucial, so you’ll need to be running NTPd.
yum install ntp
chkconfig ntpd on
ntpdate ntp.ubuntu.com
service ntpd start
Samba Configuration
Here’s where AD comes in and in my experience this is by far the most unreliable service. Install and configure samba:
yum install samba
chkconfig winbind on
Edit your /etc/samba/smb.conf
to pull information from AD.
[global]
workgroup = EXAMPLE
realm = EXAMPLE.COM
security = ADS
allow trusted domains = No
use kerberos keytab = Yes
log level = 3
log file = /var/log/samba/%m
max log size = 50
printcap name = cups
idmap backend = idmap_rid:EXAMPLE=600-20000
idmap uid = 600-20000
idmap gid = 600-20000
template shell = /bin/bash
winbind enum users = Yes
winbind enum groups = Yes
winbind use default domain = Yes
winbind offline logon = yes
Join the machine to the domain – you’ll need an account with domain admin credentials to do this:
net ads join -U administrator
Check the join is behaving ok:
[root@svn conf]# net ads info
LDAP server: 192.168.100.10
LDAP server name: ad00.example.com
Realm: EXAMPLE.COM
Bind Path: dc=EXAMPLE,dc=COM
LDAP port: 389
Server time: Tue, 15 May 2012 22:44:34 BST
KDC server: 192.168.100.10
Server time offset: 130
(Re)start winbind to pick up the new configuration:
service winbind restart
PAM & nsswitch.conf
PAM needs to know where to pull its information from, so we tell it about the new winbind service in /etc/pam.d/system-auth
.
#%PAM-1.0
# This file is auto-generated.
# User changes will be destroyed the next time authconfig is run.
auth required pam_env.so
auth sufficient pam_unix.so nullok try_first_pass
auth requisite pam_succeed_if.so uid >= 500 quiet
auth sufficient pam_winbind.so try_first_pass
auth required pam_deny.so
account required pam_unix.so broken_shadow
account sufficient pam_localuser.so
account sufficient pam_succeed_if.so uid < 500 quiet
account [default=bad success=ok user_unknown=ignore] pam_winbind.so
account required pam_permit.so
password requisite pam_cracklib.so try_first_pass retry=3
password sufficient pam_unix.so md5 shadow nullok try_first_pass use_authtok
password sufficient pam_winbind.so use_authtok
password required pam_deny.so
session optional pam_keyinit.so revoke
session required pam_limits.so
session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid
session required /lib/security/pam_mkhomedir.so
session required pam_unix.so
session optional pam_winbind.so
YMMV with PAM. It can take quite a lot of fiddling around to make it work perfectly. This obviously has an extremely close correlation to how flaky users find the authentication service. If you’re running on 64-bit you may find you need to install 64-bit versions of pam modules, e.g. mkhomedir which aren’t installed by default.
We also modify nsswitch.conf
to tell other, non-pam aspects of the system where to pull information from:
passwd: files winbind
shadow: files winbind
group: files winbind
To check the authentication information is coming back correctly you can use wbinfo
but I like seeing data by using getent group
or getent passwd
. The output of these two commands will contain domain accounts if things are working correctly and only local system accounts otherwise.
External Authentication
We’re actually going to use system accounts for authentication. To stop people continuing to use svn+ssh (and thus bypassing the authorisation controls) we edit /etc/ssh/sshd_config
and use AllowUsers
or AllowGroups
and specify all permitted users. Using AllowGroups will also provide AD group control of permitted logins but as the list is small it’s probably overkill. My sshd_config list looks a lot like this:
AllowUsers root rmp contractor itadmin
To test external authentication run /usr/local/sbin/pwauth
as below. “yay” should be displayed if things are working ok. Note the password here is displayed in clear-text:
[root@svn conf]# pwauth && echo 'yay' || echo 'nay'
rmp
mypassword
Access Controls
/etc/httpd/authz_svn.conf
is the only part which should require any modifications over time – the access controls specify who is allowed to read and/or write to each svn project, in fact as everything’s a URL now you can arbitrarily restrict subfolders of projects too but that’s a little OTT. It can be arbitrarily extended and can take local and active directory usernames. I’m sure mod_authz_svn has full documentation about what you can and can’t put in here.
#
# Allow anonymous read access to everything by default.
#
[/]
* = r
rmp = rw
[/myproject]
rmp = rw
bob = rw
...
SSL
So far that’s all the basic components. The last piece in the puzzle is enabling SSL for Apache. I use the following /etc/httpd/httpd.conf
:
ServerName svn.example.com
ServerAdmin me@example.com
Listen *:80
NameVirtualHost *:80
User nobody
Group nobody
LoadModule setenvif_module modules/mod_setenvif.so
LoadModule mime_module modules/mod_mime.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule dav_module modules/mod_dav.so
LoadModule dav_svn_module modules/mod_dav_svn.so
LoadModule auth_basic_module modules/mod_auth_basic.so
LoadModule authz_svn_module modules/mod_authz_svn.so
LoadModule ssl_module modules/mod_ssl.so
LoadModule authnz_external_module modules/mod_authnz_external.so
Include conf.d/ssl.conf
LogFormat "%v %A:%p %h %l %u %{%Y-%m-%d %H:%M:%S}t "%r" %>s %b "%{Referer}i" "%{User-Agent}i"" clean
CustomLog /var/log/httpd/access_log clean
<virtualhost *:80>
ServerName svn.example.com
Rewrite / https://svn.example.com/ [R=permanent,L]
</virtualhost>
<virtualhost *:443>
ServerName svn.example.com
AddExternalAuth pwauth /usr/local/sbin/pwauth
SetExternalAuthMethod pwauth pipe
SSLEngine on
SSLProtocol all -SSLv2
SSLCipherSuite ALL:!ADH:!EXPORT:!SSLv2:RC4+RSA:+HIGH:+MEDIUM:+LOW
SSLCertificateFile /etc/httpd/conf/svn.crt
SSLCertificateKeyFile /etc/httpd/conf/svn.key
<location />
DAV svn
SVNPath /var/svn/repos
AuthType Basic
AuthName "SVN Repository"
AuthzSVNAccessFile /etc/httpd/conf/authz_svn.acl
AuthBasicProvider external
AuthExternal pwauth
Satisfy Any
<limitexcept GET PROPFIND OPTIONS REPORT>
Require valid-user
</limitexcept>
</virtualhost>
/etc/httpd/conf.d/ssl.conf
is pretty much the unmodified distribution ssl.conf and looks like this:
LoadModule ssl_module modules/mod_ssl.so
Listen 443
AddType application/x-x509-ca-cert .crt
AddType application/x-pkcs7-crl .crl
SSLPassPhraseDialog builtin
SSLSessionCache shmcb:/var/cache/mod_ssl/scache(512000)
SSLSessionCacheTimeout 300
SSLMutex default
SSLRandomSeed startup file:/dev/urandom 256
SSLRandomSeed connect builtin
SSLCryptoDevice builtin
SetEnvIf User-Agent ".*MSIE.*" \
nokeepalive ssl-unclean-shutdown \
downgrade-1.0 force-response-1.0
You’ll need to build yourself a certificate, self-signed if necessary, but that’s a whole other post. I recommend searching the web for “openssl self signed certificate” and you should find what you need. The above httpd.conf references the key and certificate under /etc/httpd/conf/svn.key
and /etc/httpd/conf/svn.crt
respectively.
The mod_authnz_external+pwauth combination can be avoided if you can persuade mod_authz_ldap to play nicely. There are a few different ldap modules around on the intertubes and after a lot of trial and even more error I couldn’t make any of them work reliably if at all.
And if all this leaves you feeling pretty nauseous it’s quite natural. To remedy this, go use git
instead.