Enterprise-class authentication is a key component of every application used in a corporate environment. Apache Subversion administrators have to comply with the very same constraint as everyone else. In a Microsoft Active Directory environment (or any other KDC implementation), Kerberos authentication is ubiquitous and Apache Subversion shall benefit from it too. This guide will show you how to leverage Kerberos authentication for your Apache Subversion setup and never ever bother with user management and plain text passwords again. Period.

Tip
What is Kerberos?

If you work in a corporate environment, but have never heard of Kerberos, here is a brief introduction: Kerberos has been used since the late 80s in Unix already and is the standard authentication protocol in Microsoft Windows since Windows 2000. See Wikipedia and RFC 4120 for more details.

Introduction

I have been developing software in a corporate environment with integrated Kerberos authentication for a couple of years now (mostly Java) along with Apache Subversion as the SCM system of choice. After gaining profound experience with both and being infected by the ubiquity and magic of Kerberos authentication, I wanted to the bring best of both worlds together.
Some time later, several months past and numerous improvements to libserf (Subversion’s HTTP transport) and mod_spnego, it was finally working. True enterprise-class single sign-on, cryptographically secure, tamper-proof, mutual, free and open standards authentication. In other words, your corporate users log into their workstations once and never again (to an application). This documentation addresses a most wanted feature "Enterprise Authentication Mechanisms" reported as SVN-3629.

Why bother?

You probably ask yourself: Why bother with Kerberos, I have LDAP simple/SASL bind authentication? Simply put, an LDAP bind is nothing more than another plain text authentication (restrictions apply) limited to one domain or forest only. No security benefits and again, there is no single sign-on, you have to retype your password over and over again or worse, save it in plain text on disk. If someone is selling you LDAP authentication as enterprise-class authentication, he’s either lying or does not know better.

Scope of this Documentation

The scope of this tutorial is to show you how you can expose your Subversion repositories via HTTP (http://) securely authenticated through Kerberos
[Actually SPNEGO is performed via HTTP, by browsers and other clients, and not Kerberos directly. See here as well as the RFC 4559]
. While the depicted repository creation supports multiple access methods, it is out of scope how to configure Kerberos authentication via svn:// and/or svn+ssh://. For this, you need to take a look at your SASL and/or SSH daemon configuration. In fact, Subversion’s SASL code is partially inadequate for.

License and Availability

This documentation is licensed under the Creative Commons BY-SA 4.0 license and has been created with the fabulous AsciiDoc tool (version 8.6.9). It is canonically available at https://s.apache.org/svn-enterprise-auth.

Contributing

If you would like to help improve this documentation, please check out the source code and provide patches. Any improvement is welcome, especially proofreading from native English speakers is highly appreciated since I am not a native English speaker.

Acknowledgement

Many thanks go to the Apache Subversion developers making this great tool happen. Thanks to Love Hörnquist Åstrand, the creator of mod_spnego which I stumbled upon on GitHub and a special thank you to Lieven Govaerts who helped me find the last couple of bugs in libserf related to Kerberos authentication.

Tested Environment(s)

Everything documented here has been tested and is already fully deployed under the following conditions: Authentication is completely handled by/against a battery of Microsoft Windows Server 2008 (or newer) domain controllers which are our default KDC implementation. Subversion repositories are served by FreeBSD 9.3-STABLE on Apache Web Server 2.2.x, PHP 5.6.x, Apache Subversion 1.8.x/1.9.x, libserf 1.3.x, and MIT Kerberos 1.14.x (all from FreeBSD Ports) as well as mod_spnego (from GitHub).

On the client side following setups are used:

  • Microsoft Windows 7 with Apache Subversion 1.8.x/1.9.x (from CollabNet), TortoiseSVN 1.8.x/1.9.x and Subclipse 1.10.x with JavaHL binding

  • RHEL 6.x with Apache Subversion 1.8.x with MIT Kerberos 1.10.x (from RHEL and WANdisco repositories)

  • FreeBSD 9.3-STABLE with Apache Subversion 1.8.x/1.9.x, libserf 1.3.8, MIT Kerberos 1.14.x (from FreeBSD Ports)

This means that you can host your Subversion repositories on any Unix-like operating system and the clients on any operating system.

Requirements

First of all, you should be able to perform administrative tasks through the shell (SSH) with root privileges on your target server.

Warning If you are uncertain about administration or any of the software listed below sounds unfamiliar to you, consult your local Unix admin before you perform any changes with root privileges.
Important Do not try with Subversion 1.7 it will not work as it uses libneon as HTTP transport which is broken for Kerberos authentication with SPNEGO.

What you will require to complete the setup:

  • Authentication environment:

    • Either Microsoft Active Directory or any Unix-based KDC implementation like MIT Kerberos or Heimdal up and running

  • Server environment:

    • A fairly modern Unix-like operating system, FreeBSD or Linux, preferrably but any will do

    • A recent GSS-API implementation like MIT Kerberos or Heimdal including development resources (header files, etc.).

    • Samba 4 or msktutil(1), if you are in a Microsoft Active Directory environment

    • The Apache Web Server 2.2.x including development resources (header files, apxs, etc.). Version 2.4.x has not been tested but should work too.

    • Apache Subversion 1.8.x including the Apache module (mod_dav_svn)

    • A C compiler like GCC

    • mod_spnego from GitHub, either the ZIP file or cloned Git repository

    • PHP 5.3.x or newner and the Apache module (mod_php5)

  • Client environment:

    • Any operating system with Kerberos support, e.g., Microsoft Windows or Unix-like with an installed GSS-API implementation.

    • libserf compiled against that GSS-API implementation

    • Apache Subversion 1.8.x/1.9.x

    • A Kerberos principal logged into a domain/realm on Windows or Unix

Go on to the next chapter to see how to all components play together.

Installation

Let’s install the necessary components now. I will use generic/symbolic package names which might not exactly match those used by your package management system, you need to figure out real package names.

Note
Compiling from Source

While you can compile everything mentioned above from source, you should rather use the package manager of your operating system unless you know how to properly compile and link those dependencies. If you still want to go that path, everything should work. I have already compiled everything mentioned above on several operating systems except the Apache Web Server.

Packages

Install the following packages: kerberos, kerberos-dev, samba or msktutil (if in an Active Directory environment), apache22, apache22-dev, subversion, apache22-mod-subversion, php5, apache22-mod-php5, compiler. This might take a while, so be patient. Make sure that libserf is also compiled with GSS-API support.

Warning
Heimdal from FreeBSD Base

If you are on FreeBSD, do not use Heimdal from base. It is horribly old and broken. Your client will crash with a segmentation fault. Make sure that you always either use MIT Kerberos or Heimdal from ports.

mod_spnego

Note
Alternative to mod_spnego

If you don’t want to use mod_spnego, check out the high quality alternative mod_auth_gssapi. Though consider that it has a more complex configuration set. All other modules available on the Internet are either unmaintained or don’t do things right.

Clone my fork of mod_spnego and check out the branch svn-enterprise-auth, alternatively download the ZIP file. The fork is necessary due to some open issues (pull requests are pending).

Change to the mod_spnego directory, open the Makefile and modify the KRB5_CONFIG value to the path of the krb5-config script on your system. Finally, run the following:

# make
# make install

The Apache module has been installed now. Make sure that this module is also registered in your httpd.conf and loaded successfully.

Configuration

The configuration is comprised of several steps. First, we need to configure Kerberos on your server, then create a Subversion system account, followed by a demo Subversion repository. After that, we need to expose it through mod_dav_svn on Apache Web Server via HTTP and enable mod_spnego. Finally, we will configure WebSVN, a web interface for your web browser.

The following will be assumed (adapt to your environment accordingly):

  • Your Kerberos realm is EXAMPLE.COM

  • Your server hostname is dev.example.com

  • All Subversion data reside in /var/svn which will be referred to SVN_ROOT

  • Apache Web Server

    • config files reside in /etc/apache22 which will be referred to as APACHE_CONFDIR

    • web files reside in /var/www which will be referred to as APACHE_WEBDIR

Kerberos

Let’s prepare the server for Kerberos first. Basically, you will need a krb5.conf and a keytab file. The keytab file will contain the keys for the machine itself and the keys for the HTTP service (with the appropriate SPN HTTP/dev.example.com) running on that machine.

Important Well-managed DNS records are vital, make sure that all KDCs are properly registered with DNS and your server has correct (forward) DNS records.

If you are in an MIT Kerberos/Heimdal environment, request your admin to create the above for you. In a Windows environment, configure Samba’s smb.conf in conformance with your Windows admin. Create a skeletal krb5.conf in /etc:

[libdefaults]
    default_realm = EXAMPLE.COM
    forwardable = true
    dns_lookup_kdc = true
    rdns = false

[domain_realm]
    .example.com = EXAMPLE.COM
     example.com = EXAMPLE.COM

Now you can let Samba do the rest with:

# net ads -U <domain admin account> join
# net ads -P keytab create
# net ads -P keytab add HTTP

In both cases, you should have a keytab in /etc/krb5.keytab. Execute klist -e -k /etc/krb5.keytab, the output should resemble this:

Keytab name: FILE:/etc/krb5.keytab
KVNO Principal
---- --------------------------------------------------------------------------
  63 host/dev.example.com@EXAMPLE.COM (aes128-cts-hmac-sha1-96)
  63 host/dev.example.com@EXAMPLE.COM (aes256-cts-hmac-sha1-96)
  63 host/dev.example.com@EXAMPLE.COM (arcfour-hmac)
  63 host/dev@EXAMPLE.COM (aes128-cts-hmac-sha1-96)
  63 host/dev@EXAMPLE.COM (aes256-cts-hmac-sha1-96)
  63 host/dev@EXAMPLE.COM (arcfour-hmac)
  63 dev$@EXAMPLE.COM (aes128-cts-hmac-sha1-96)
  63 dev$@EXAMPLE.COM (aes256-cts-hmac-sha1-96)
  63 dev$@EXAMPLE.COM (arcfour-hmac)
  63 HTTP/dev.example.com@EXAMPLE.COM (aes128-cts-hmac-sha1-96)
  63 HTTP/dev.example.com@EXAMPLE.COM (aes256-cts-hmac-sha1-96)
  63 HTTP/dev.example.com@EXAMPLE.COM (arcfour-hmac)
  63 HTTP/dev@EXAMPLE.COM (aes128-cts-hmac-sha1-96)
  63 HTTP/dev@EXAMPLE.COM (aes256-cts-hmac-sha1-96)
  63 HTTP/dev@EXAMPLE.COM (arcfour-hmac)

Make sure that the system account of the Apache Web Server has read access to the keytab.

Subversion

Now we need to create a system account for Subversion and create a demo repository to get started.

System Account

Note

You can skip this section if you don’t want to have a separate user for Subversion, but want the system account of the web server to own the repositories.

Following the least privilege principle, all of your Subversion repositories should be owned by a user who has minimal access to the system. Create, by the means of your OS, a system account svn (ID < 1000) and a system group svn (ID < 1000) without a home and default shell. It should look similar to this in your /etc/passwd:

svn:*:81:81:Subversion system account:/nonexistent:/usr/sbin/nologin

and in your /etc/groups:

svn:*:81:

Demo Repository

The final step is to create a demo repository enterprise in $SVN_ROOT/repos as root:

# cd $SVN_ROOT/repos
# umask 0027                                                     1
# svnadmin create "enterprise"                                   2
# chown -R svn:svn "enterprise"
# cd "enterprise"
# rm README.txt conf/{passwd,authz,svnserve.conf}                3
# rm -rf locks/
# chmod ug+x conf/hooks-env.tmpl                                 4
# find db -type d -perm 0750 -exec chmod g+s '{}' \;
# find . -type d -perm 2750 -exec chmod g+w '{}' \;
# chmod g+w db/{current,write-lock,txn-current,txn-current-lock}
# chmod g+ws .
1 Set a sane umask during creation
2 Create the repository and change the owner
3 Remove unnecessary files. Retain svnserve.conf if you intend to access the repository via svn:// too. The authz is not necessary here because we will use a global one for all repositories. You may move it somewhere else. locks/ is created for legacy reasons and can be safely removed.
4 Permissions are a common pitfall, set appropriate ones to avoid errors during access. The commands may vary on your operating system, see below.

Verify your repository with tree -pug after creation, ownership and permissions must look like this:

.
|-- [drwxr-x--- svn      svn     ]  conf
|   `-- [-rwxr-x--- svn      svn     ]  hooks-env.tmpl
|-- [drwxrws--- svn      svn     ]  db
|   |-- [-rw-rw---- svn      svn     ]  current
|   |-- [-r--r----- svn      svn     ]  format
|   |-- [-rw-r----- svn      svn     ]  fs-type
|   |-- [-rw-r----- svn      svn     ]  fsfs.conf
|   |-- [-rw-r----- svn      svn     ]  min-unpacked-rev
|   |-- [drwxrws--- svn      svn     ]  revprops
|   |   `-- [drwxrws--- svn      svn     ]  0
|   |       `-- [-r--r----- svn      svn     ]  0
|   |-- [drwxrws--- svn      svn     ]  revs
|   |   `-- [drwxrws--- svn      svn     ]  0
|   |       `-- [-r--r----- svn      svn     ]  0
|   |-- [drwxrws--- svn      svn     ]  transactions
|   |-- [-rw-rw---- svn      svn     ]  txn-current
|   |-- [-rw-rw---- svn      svn     ]  txn-current-lock
|   |-- [drwxrws--- svn      svn     ]  txn-protorevs
|   |-- [-rw-r----- svn      svn     ]  uuid
|   `-- [-rw-rw---- svn      svn     ]  write-lock
|-- [-r--r----- svn      svn     ]  format
`-- [drwxr-x--- svn      svn     ]  hooks
    |-- [-rwxr-x--- svn      svn     ]  post-commit.tmpl
    |-- [-rwxr-x--- svn      svn     ]  post-lock.tmpl
    |-- [-rwxr-x--- svn      svn     ]  post-revprop-change.tmpl
    |-- [-rwxr-x--- svn      svn     ]  post-unlock.tmpl
    |-- [-rwxr-x--- svn      svn     ]  pre-commit.tmpl
    |-- [-rwxr-x--- svn      svn     ]  pre-lock.tmpl
    |-- [-rwxr-x--- svn      svn     ]  pre-revprop-change.tmpl
    |-- [-rwxr-x--- svn      svn     ]  pre-unlock.tmpl
    `-- [-rwxr-x--- svn      svn     ]  start-commit.tmpl

9 directories, 22 files

If all fits, your repository is now ready to use.

Apache Web Server

A few remaining steps are now necessary to expose your repositories via HTTP. Expose them and have them properly secured.

Important

Since the svn:svn is the owner of the repository, you must add the web server to the group svn otherwise all access will fail.

It is completely up to you, where you put the following config whether in httpd.conf, an included svn.conf or in httpd-tls.conf via TLS only. What matters is that the config is sane and accepted by the server. I will refer to it as $APACHE_CONFDIR/svn.conf.

Repositories (mod_dav_svn)

Open up $APACHE_CONFDIR/svn.conf and activate the Apache module:

<Location "/repos/svn">
    DAV svn
    SVNParentPath /var/svn/repos
    SVNListParentPath On
</Location>

Instruct the web server to reload the configuration. This will happily serve all repositories from $SVN_ROOT/repos automatically.

Check now whether you can obtain a working copy of the newly created repository:

$ svn co http://dev.example.com/repos/svn/enterprise

Subversion will tell you, Checked out revision 0, as usual.

Authentication (mod_spnego)

The repository is still unprotected, let’s have all users authenticate to the web server. Open up your svn.conf again and add the following before the previous snippet:

<LocationMatch "/repos/(web)?svn">
    AuthType SPNEGO
    SPNEGOAuth On
    SPNEGOAuthKrb5AcceptorIdentity /etc/krb5.keytab
    SPNEGOUseDisplayName On
    SPNEGODebug On
    Require valid-user
</LocationMatch>

Note that debug is on, this will help you in the very first moments to see whether authentication is actually working or not. Update your working copy with svn up and check your web server access log file. If something went wrong Subversion will say:

Updating '.':
svn: E120190: Unable to connect to a repository at URL 'http://dev.example.com/repos/svn/enterprise'
svn: E120190: Error running context: An error occurred during authentication

and the logs:

[Fri Mar 18 21:18:02 2016] [error] [client <IP address>] mod_spnego: authenticating main request on connection 7
[Fri Mar 18 21:18:02 2016] [error] [client <IP address>] mod_spnego: no Authorization header

as well as

<IP address> - - [18/Mar/2016:21:17:30 +0100] "OPTIONS /repos/svn/enterprise HTTP/1.1" 401 553
<IP address> - - [18/Mar/2016:21:18:02 +0100] "OPTIONS /repos/svn/enterprise HTTP/1.1" 401 553

Verify that you are logged in with a domain account or have a ticket cache filled by kinit. Rerun the update and the logs should say:

<IP address> - - [18/Mar/2016:20:58:37 +0100] "OPTIONS /repos/svn/enterprise HTTP/1.1" 401 553
<IP address> - michael-o@EXAMPLE.COM [18/Mar/2016:20:58:37 +0100] "OPTIONS /repos/svn/enterprise HTTP/1.1" 200 200
<IP address> - michael-o@EXAMPLE.COM [18/Mar/2016:20:58:37 +0100] "OPTIONS /repos/svn/enterprise HTTP/1.1" 200 97
<IP address> - michael-o@EXAMPLE.COM [18/Mar/2016:20:58:37 +0100] "OPTIONS /repos/svn/enterprise HTTP/1.1" 200 200
<IP address> - - [18/Mar/2016:20:58:42 +0100] "REPORT /repos/svn/enterprise/!svn/me HTTP/1.1" 401 553
<IP address> - michael-o@EXAMPLE.COM [18/Mar/2016:20:58:42 +0100] "REPORT /repos/svn/enterprise/!svn/me HTTP/1.1" 200 641

and

[Fri Mar 18 20:58:37 2016] [error] [client <IP address>] mod_spnego: authenticating main request on connection 6
[Fri Mar 18 20:58:37 2016] [error] [client <IP address>] mod_spnego: no Authorization header
[Fri Mar 18 20:58:37 2016] [error] [client <IP address>] mod_spnego: authenticating main request on connection 6
[Fri Mar 18 20:58:37 2016] [error] [client <IP address>] mod_spnego: calling accept_sec_context
[Fri Mar 18 20:58:37 2016] [error] [client <IP address>] mod_spnego: successfully authenticated 'michael-o@EXAMPLE.COM'
[Fri Mar 18 20:58:37 2016] [error] [client <IP address>] mod_spnego: Negotiate: done: 0/0
[Fri Mar 18 20:58:37 2016] [error] [client <IP address>] mod_spnego: authenticating main request on connection 6
[Fri Mar 18 20:58:37 2016] [error] [client <IP address>] mod_spnego: 'michael-o@EXAMPLE.COM' already auhenticated
[Fri Mar 18 20:58:37 2016] [error] [client <IP address>] mod_spnego: authenticating main request on connection 6
[Fri Mar 18 20:58:37 2016] [error] [client <IP address>] mod_spnego: 'michael-o@EXAMPLE.COM' already auhenticated
[Fri Mar 18 20:58:42 2016] [error] [client <IP address>] mod_spnego: authenticating main request on connection 7
[Fri Mar 18 20:58:42 2016] [error] [client <IP address>] mod_spnego: no Authorization header
[Fri Mar 18 20:58:42 2016] [error] [client <IP address>] mod_spnego: authenticating main request on connection 7
[Fri Mar 18 20:58:42 2016] [error] [client <IP address>] mod_spnego: calling accept_sec_context
[Fri Mar 18 20:58:42 2016] [error] [client <IP address>] mod_spnego: successfully authenticated 'michael-o@EXAMPLE.COM'
[Fri Mar 18 20:58:42 2016] [error] [client <IP address>] mod_spnego: Negotiate: done: 0/0
[Fri Mar 18 20:58:42 2016] [error] [client <IP address>] mod_spnego: authenticating subrequest on connection 7
[Fri Mar 18 20:58:42 2016] [error] [client <IP address>] mod_spnego: 'michael-o@EXAMPLE.COM' already auhenticated

All is well, your request was authenticated. As a last test, perform a commit and have a look at the log:

r1 | michael-o@EXAMPLE.COM | 2016-03-18 21:14:04 +0100 (Fri, 18 Mar 2016) | 1 line

Your message goes here...

Authentication is complete, as you can see there are Kerberos principals stored in your repository’s metadata. You can safely set SPNEGODebug to Off.

Authorization (authz)

You are probably not going to be satisfied with authentication only, are you? Limiting read and write access is crucial. Fortunately, Subversion has a simple but yet effective approach with the authz file. Copy the aforementioned authz from the demo repository to $SVN_ROOT/auth/authz. This file includes some documentation about the format employed and some examples. Feel free to remove those examples because we will add our own authorization configuration for all repositories in one single file. After you have copied it, open up your svn.conf again and add the following line to your DAV location directive:

AuthzSVNAccessFile /var/svn/auth/authz

Don’t forget to reload Apache Web Server’s config again.

We are ready to take on authz now. Open it and add/modify the following:

[groups]
enterprise-rw = michael-o@EXAMPLE.COM
enterprise-ro = john.doe@EXAMPLE.COM

[enterprise:/]
@enterprise-rw = rw
@enterprise-ro = r

Update your working copy, there should be no change. Now ask your colleague John Doe and someone else to check out and commit some change. While John Doe will be able to check out, someone else will fail, but John’s commit will fail too:

svn: E175013: Unable to connect to a repository at URL 'http://dev.example.com/repos/svn/enterprise'
svn: E175013: Access to '/repos/svn/enterprise' forbidden
Tip

If you are uncertain whether your authorization configuration is correct, use svnauthz(1) to validate it.

WebSVN

WebSVN offers your a nice browser-friendly view of your repositories and much more. It is added to the web server and configured for your repositories. I am personally not very fond of ViewVC’s GUI.

Download a snapshot of my public fork of WebSVN and extract it to $APACHE_WEBDIR/websvn. Make sure that the directory and everything beneath can be read by the web server or is the owner of it.

Important

You can, of course, install WebSVN 2.3.3 from your package management system, but it won’t include all changes from trunk as well as some patches I have applied to. In this case, you have to create a diff between 2.3.3 and my trunk and apply it to your distribution.

We need now to create a minimal config.php in $APACHE_WEBDIR/websvn/include and add these configuration options:

<?php
$config->setSVNCommandPath('/usr/bin'); 1
$config->parentPath('/var/svn/repos', null, false, true, 'http://dev.example.com/repos/svn');
$config->addTemplatePath($locwebsvnreal.'/templates/calm/');
$config->setShowAgeInsteadOfDate(false);
$config->useMultiViews();
$config->useAccessFile('/var/svn/auth/authz');
$config->setBlockRobots();
$config->addInlineMimeType('text/plain');
$config->setMinDownloadLevel(2);
$config->useGeshi();
$config->expandTabsBy(4);
1 Assumes that all Subversion binaries reside in /usr/bin. Change to your OS configration accordingly.
Tip

You may also inspect $APACHE_WEBDIR/websvn/include/distconfig.php for all configuration options.

Then, open up the very same $APACHE_CONFDIR/svn.conf and add the following:

Alias /repos/websvn "/var/www/websvn"
<Directory "/var/www/websvn">
    Order Allow,Deny
    Allow from all
    Options MultiViews
    DirectoryIndex browse.php
</Directory>

Instruct the web server to reload the configuration. This will happily serve all repositories from $SVN_ROOT/repos automatically for your web browser too. Check this by navigating to http://dev.example.com/repos/websvn. You should see the repositories you have access to.

All configuration is now completed and set up correctly.

Further Outlook

So what’s next? Kerberos does authentication only, but I like centralized authorization too. Microsoft has extended Kerberos by embedding authorization information into the service ticket in the PAC data structure. In other words, one can expose Windows groups (more specifically their SIDs) to require-group by parsing this structure. Unfortunately, Subversion cannot make use of them, due to SVN-2338 and SVN-4603.