Friday 1 November 2013

Getting Jenkins CI running on Debian 6 under Tomcat

Today's job was to get a continuous integration server set up and integrated with my Redmine project management system. Since I run Debian 6 on my server, and I prefer where possible to install from the official Debian packages, the Redmine version I'm running is 1.1, which is somewhat behind the curve. I had a look around at which continuous integration server to use. I've tentatively picked Jenkins, the more purist-open-source variant of the Hudson/Jenkins project. Reasons include: it's available in the Debian 7 distribution (but sadly not in Debian 6), and it has a plugin for Leiningen, which is my favourite build tool.

So... on to install, and there the fun began.

Installing Jenkins

As I said, Jenkins is not available in the Debian 6 distribution. However, the Jenkins project had set up their own Debian repository, so after adding their key and link to my system I was able to apt-get it. You'd have thought that would be all, but sadly no.

The Jenkins package, as packaged by Jenkins, does not depend on either Tomcat or Jetty. Instead, it assumes you will be serving no other web-apps and tries to install its own servlet engine (I think Jetty, but to be honest I was too annoyed to check before taking it off again). Obviously, I do have other web-apps, so this didn't work for me. However, I copied the WAR file from the the Jenkins release into /var/lib/tomcat6/web-apps, and, of course, being a web-app, it just worked...

Except it didn't. Jenkins expects to have some space of its own to write to, outside the servlet engine sandbox. That is, in my opinion, bad behaviour. Specifically it expects to be able to create a directory /usr/share/tomcat6/.jenkins, which is bad in two ways: it writes to a directory to which, for security reasons, Tomcat damned well should NOT be able to write, and it creates a hidden file which a naive administrator might not notice and which consequently might not be backed up.

After some thought I decided to put Jenkins writable space in /var/local, so I executed:

root@goldsmith# mkdir -p /var/local/jenkins
root@goldsmith# chown tomcat6.tomcat6 /var/local/jenkins

(I also symlinked that back to /usr/share/tomcat6/.jenkins, but that seems safe enough to me). I then edited /etc/default/tomcat6 (a useful place to put pre-boot Tomcat stuff) and added

# Jenkins home directory: added by simon 20131101                               
JENKINS_HOME=/var/local/jenkins

I then restarted Tomcat:

root@goldsmith# /etc/init.d/tomcat6 restart
Stopping Tomcat servlet engine: tomcat6.
Starting Tomcat servlet engine: tomcat6.

... and all was well; by which I mean, Jenkins started.

Configuring Jenkins for even modest security, however, was a complete bitch.

Jenkins has five different authentication models:

  1. It can have authentication switched off entirely. Anyone can do anything... No. Not going to happen, on an Internet facing server.
  2. It can delegate authentication to the servlet engine. I'm not wonderfully happy about that, because administering Tomcat users is a bit of a pain. 
  3. It can use LDAP... if you have an LDAP server, which I don't.
  4. It can delegate authentication to the undelying UN*X system, but only if the servlet engine can read /etc/shadow! There's NO WAY I'm permitting that. 
  5. It can run its own internal authentication... you'd think that was the obvious one. But as soon as you've selected that option, you're locked out and cannot proceed further.

Fortunately, you can completely reinitialise Jenkins by deleting everything under its home directory and rebooting Tomcat.; it then proceeds to reinstall a default set of files, and you get a new, empty Jenkins.

But, you can't add people to Jenkins until you've configured 'enable security' and chosen one of the security models. So, first, configure 'Security Realm' to 'Jenkins's own user database', and remember to tick 'Allow users to sign up'.

Then, sign up. That bit's easy, it prompts you.

Then, you need an authorisation strategy. Of these, there are five:

  1. Anyone can do anything (aye, right!)
  2. 'Legacy mode' (only 'admin' can do anything)
  3. Logged-in users can do anything
  4. Matrix-based security
  5. Project-based Matrix Authorization Strategy

If you tick 'Matrix-based security' or 'Project-based Matrix Authorization Strategy' and click 'Save', you're locked out again and have to go back to deleting everything in the home directory, rebooting and starting again.

After ticking either 'Matrix-based security' or 'Project-based Matrix Authorization Strategy' (which are, frankly, the only authorisation strategies which make sense), you MUST tick the box which allows the group 'Anonymous' to 'Administer' BEFORE you do anything else. Otherwise, you're stuffed.

So then you try to add a security group, and, wait, you can't. You're stuffed. The 'internal' security model does not have groups, so you must add yourself - your own user ID - to the security matrix, give yourself permission to administer, and then save, and then revoke 'anonymous' permission to administer, and save. Otherwise any Johnny hacker out there in Netland can come along and pwn your server.

To be fair, there are plugins available to add a number of additional authentication methods, including OpenID. I haven't tried these.

Integrating with Redmine

Now, integrating Redmine with Jenkins. Recall that Jenkins is a fork of the Hudson project; they're still pretty similar, and although there isn't a Redmine plugin specifically for Jenkins, there is one for Hudson. I installed that, and on initial testing it appears to work. I wanted to do the integration from the Redmine end, because Redmine does work for me as a project management tool, and I don't yet know whether I shall stick to Jenkins. But the alternative would have been to install a Redmine plugin into Jenkins - that exists; and, indeed, I may install it, as well, since it seems to have some useful functionality.

However, all this still left one gaping hole. Both my Redmine installation and my Jenkins installation were running over plain old fashioned HTTP, which means I was passing passwords in plain text over HTTP, which is asking for trouble - a continuous integration server, simply in the nature of the beast, can do pretty extensive things and would be a wonderful tool for an attacker to control. So I set up HTTPS using a self-signed certificate - I know, but I don't need a better one - and configured Tomcat to communicate only locally over AJP; I then configured the Apache2 HTTP daemon to redirect appropriate requests received over HTTPS via AJP to Tomcat, using mod_jk.

So far so good.

Still to do

I need to integrate Jenkins with Git; I've downloaded the plugins (and downloading and installing plugins for Jenkins is extremely straightforward) but I've yet to configure them.

No comments:

Creative Commons Licence
The fool on the hill by Simon Brooke is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License