Simple htpasswd for Hudson CI using PHP

Posted by John Kleijn • Wednesday, March 24. 2010 • Category: CI

My development server hosts Trac instances, SVN repos, and private testing/staging websites/applications. All of these use one htpasswd file with users and passwords in it.

But the build server, using Hudson, doesn't natively support that. Here's how you can have Hudson check a standard htpasswd file, and have project based security so one client or subcontractor wont mess with the builds of a project they are not involved in.


1. Install the "Script Security Realm" plugin in Hudson

From the dashboard select "Manage Hudson" and "Manage Plugins", browse to find the plugin, install it, restart Hudson, voila. It doesn't get much easier than this.

2. Create a php script to check the username and password against the htpasswd file

Now this is a little more tricky, although not really once you know how to reproduce the password in the file. The htpasswd tool uses DES encryption with a salt (at least by default). To reproduce this with use the standard PHP function crypt(), which works like *nix command crypt().

In short, the DES encrypted password can be reproduced like this: crypt($password, substr($desEncrypted, 0, CRYPT_SALT_LENGTH)).

The Hudson plugin sets some environment variables before executing the scipt, which end up in $_SERVER using indexes "U" and "P". The plugin will assume a failed authentication attempt if the script exists with anything other than 0. Knowing this, any adolescent spotted attic hyena can produce the end result, it's that simple:


<?php
foreach(file('/path/to/your/htpasswd-file') as $line)
{
        list($username, $des) = explode(':', rtrim($line));
       
        if(!$username || !$des)
        {
                syslog(LOG_CRIT, "Error reading config file");
                die(1);
        }
       
        if($_SERVER['U'] == $username)
        {
                if(crypt($_SERVER['P'], substr($des, 0, CRYPT_SALT_LENGTH)) == $des)
                {
                        syslog(LOG_AUTH, "Hudson auth passed for user '{$_SERVER['U']}'");
                        die(0);
                }              
        }
}

syslog(LOG_AUTH, "Hudson auth failed for user {$_SERVER['U']}");
die(1);
 

3. Configure global settings

Project based permissions is a relatively new feature for Hudson, not sure which version it was first included in (Google is your friend). For reference, I am using Hudson 1.339.

First, you go to the Hudson config panel, enable security, which will allow you to select "Authenticate via custom script". As command fill out "php path/to/script/relative/to/hudson/home/or/absolute/if/you/prefer.php". Immediately below that, choose "Project-based Matrix Authorization Strategy". Add all users, and give them at least "Overall > Read" permissions. Give yourself admin permissions. Save, and login. If you screwed something up and can't login, you'll have to edit config.xml in the Hudson home directory to set useSecurity to false. You can also just edit config.xml directly if you dislike the Hudson CPL. Add or replace the following before restarting Hudson:


<useSecurity>true</useSecurity>
<authorizationStrategy class="hudson.security.ProjectMatrixAuthorizationStrategy">
        <permission>hudson.model.Hudson.Administer:jkleijn</permission>
        <permission>hudson.model.Hudson.Read:huey</permission>
        <permission>hudson.model.Hudson.Read:dewey</permission>
        <permission>hudson.model.Hudson.Read:louie</permission>
</authorizationStrategy>
<securityRealm class="hudson.plugins.script_realm.ScriptSecurityRealm">
        <commandLine>php path/to/script/relative/to/hudson/home/or/absolute/if/you/prefer.php</commandLine>
</securityRealm>
 

4. Set permissions for your projects

Select a project you want to configure permissions for, select "Configure" and tick "Enable project-based security". Add the users you want to be able to see this project and set their permissions. Save and you're done.

In all it's pretty straightforward, but I probably saved you some trouble. Normally you could repay me by buying me a beer, but I'm trying to lose weight because I'm too cheap to buy new clothes. So maybe a cracker instead.

Happy CI'ing. :-)

0 Trackbacks

  1. No Trackbacks

0 Comments

Display comments as (Linear | Threaded)
  1. No comments

Add Comment

You can use [geshi lang=lang_name [,ln={y|n}]][/geshi] tags to embed source code snippets.
Standard emoticons like :-) and ;-) are converted to images.

To prevent automated Bots from commentspamming, please enter the string you see in the image below in the appropriate input box. Your comment will only be submitted if the strings match. Please ensure that your browser supports and accepts cookies, or your comment cannot be verified correctly.
CAPTCHA


Antiquities and such