Making Gerrit and LDAP speak in tongues together via SSL
If you're an experienced Java developer or container/AS user, you'll probably want to just skip this, since it involves just some Java tools and standards techniques.
If you, however, like me, have only scratched the surface of the ceremony of the Java world (no troll intended), then you can probably find this useful, since I couldn't find proper documentation about this but had to assembly it together from various sources.
I started to configure a Gerrit instance on our company's server to start putting a bit of order in the mess that is our structure by now. Since the instance is private, I didn't want to enable OpenID authentication, nor I wanted to set it in development mode (aka everyone's invited). Having to choose between offloading authentication to an HTTP proxy and setting up an LDAP backend, I opted for the latter. Also, to complicate things further, I have not only enabled SSL support on the LDAP server, but I required strict client certificate verification (
olcTLSVerifyClient: hard). That means that if OpenLDAP receives a request from a client that can't provide a valid SSL certificate, the connection is refused. This to me is a nice and easy way (if you have a proper PKI, of course) to globally raise a bit the level of security of the infrastructure.
That said, here come the problem: if you have ever configured a Gerrit instance, you'll know that the only parameter regarding SSL allows to provide a keyStore to the HTTPS engine. Unfortunately, this doesn't work also for the LDAPS client, so if you try to configure your LDAPS backend you'll end up hopelessly with an error stating "LDAP Authentication unavailable".
In addition, since my PKI is a self-signed one, everything that comes out of it is not trusted by default; so we need a way to tell Gerrit that a certificate signed by "Derecom Auth CA" is to be considered as a good friend.
Those two problems are solved by two standard and similar objects in the Java world: keyStores and trustStores.
A bit of theory about these two actors in the game: both the keyStore and the trustStore are repositories of security certificates; each one acts like a bucket containing more than one object. The difference is that while
keyStores are used to store security certificates and a private keys,
trustStores are used to store trusted Certificate Authorities.
So, Gerrit needs to be fed with a
keyStore containing the host private key/certificate pair to present to the LDAP server, and a
trustStore containing the Root CA chain that signed the certificate provided by the other endpoint.
Enough talk by now: let's see how to actually do this. I assume here that you have in your hands a SSL certificate with its corresponding private key, both in PEM format (the standard output format of the openssl command line tool). Also, I suppose you have at your disposal the Certificate Authority that signed all certificates in your network (in my case it's actually a chain of two CA, but it doesn't really matter). Let's assume that these three objects have the following names:
- SSL Certificate:
- SSL Private key:
We start by creating the
keyStore. We can do this in two steps:
creating a PKCS12 object containing all three objects
importing the PKCS12 object in a new keyStore The PKCS12 object can be created using the
opensslCLI tool, and it'll contain the chain of the public certificate and the CA that signed it, and the private key. We first need to chain the host's SSL certificate and the CA bundle (otherwise, we'll get an error from OpenSSL); this can be done simply with:
cat /etc/pki/tls/syllogism.derecom.it.crt /etc/pki/tls/Derecom-Auth-CA-bundle.crt > cert-chain.txt
At this point we can actually create the PKCS12 bundle:
openssl pkcs12 -export -inkey /etc/pki/tls/private/syllogism.derecom.it.key -in cert-chain.txt -out syllogism.derecom.it.pkcs12
The command will ask for a password to protect the file; you must enter a strong password here, since we're using that same password later to protect our keyStore.
Now we can use
keytool to create the keyStore (the directory
~/data is the Gerrit home):
keytool -importkeystore -srckeystore syllogism.derecom.it.pkcs12 -srcstoretype PKCS12 -destkeystore ~/data/etc/keystore
The command will prompt for two passwords: the first one is the password of the resulting keyStore, the second one is the password we entered to cypher the PCKS12. They must be the same, otherwise Java won't be able to decipher the key, and will raise a
If the output of the command is successfull (it should be !), we can delete the intermediate objects:
rm cert-chain.txt syllogism.derecom.it.pkcs12
After that we point our attention to the
trustStore. Here there are two ways: you can add the CA to the default JRE trustStore (which contains all the usual stuff: VeriSign, etc.), or you can proceed to create a new one like we did above. The only difference in the command below is the path of the trustStore (the default is in
keytool -importcert -alias derecom-root-ca -file /etc/pki/tls/Derecom-Auth-CA-bundle.crt -keystore /home/gerrit/data/etc/truststore -storepass changeit
The password is the same used also for the default trustStore; it doesn't need to be strong since the file contains only public certificates.
Now that we have the objects we need, we just have to tell Gerrit (actually, its Jetty container) where to find them. This can be done by passing some standard Java properties in
$GERRIT_SITE/bin/gerrit.sh if you don't believe the magic!):
JAVA_OPTIONS="-Djavax.net.ssl.keyStore=/home/gerrit/data/etc/keystore -Djavax.net.ssl.keyStorePassword=Mh8edvvp9vBmhZYEH2ar -Djavax.net.ssl.trustStore=/home/gerrit/data/etc/truststore -Djavax.net.ssl.trustStorePassword=changeit"
Note that if you added the CA bundle to the default trustStore, you don't need to pass the last two properties in the snippet above.
If everything went fine, after starting the server you'll able to login to Gerrit simply by providing the uid and password of your avatar in the LDAP tree (note that by default Gerrit will match username against
cn, but there are options to tell Gerrit what pattern to follow for the match).