Automated Coder

Exploring the Code of CruiseControl.Net

Securing CruiseControl.Net – Web Dashboard (Part III: Authentication)

Posted by Craig Sutherland on 30 September, 2008

The Story to Date

Previously I’ve written on my modifications to add security to CruiseControl.Net. While this has been a multi-part set of changes, I’m nearly at the end. Initially my changes involved modifying the server components (Core, Console and Service) to have security enabled. This was followed by modified CCTray for .Net remoting. In my last post I started modifying the dashboard, but only to allow HTTP access from CCTray. In this post I’ll finally look at adding security to the web dashboard.

But first, a quick refresher on how security works in Core. In core there is now an ISecurityManager manager associated with the ICruiseServer instance. This is the interface that actually handles security – thus removing the need for ICruiseServer to know anything about security. Instead, all ICruiseServer has is a set of session management methods (login and logout), plus the secured methods have been extended to require a session token.

Thus to add authentication to the dashboard I need to receive a set of user credentials and pass them onto the server. This is where it gets tricky as there can be a number of different types of credentials – for example I implemented a simple user name, and a user name/password set for credentials. In CCTray the user selected which credentials to send and the server then validated them. The dashboard doesn’t have this option as it is setup remotely by an administrator. Plus it is stateless, and it can monitor mulitple different servers!

To keep things simple, my initial implementation of security will have the following limitations:

  • It must be configured by the administrator
  • Only one type of security can be configured
  • Only one server can be monitored
  • The session token will be stored in the query parameters

Later on I hope to work around these limitations, but this will have to do for a start.

A New Type of Plugin

Like the server, the dashboard uses plugins to allow new or modified functionality. The configuration is exposed via IPluginConfiguration, which is returned from an IDashboardConfiguration. The actual loading of the configuration is handled by DashboardConfigurationLoader, which uses Exortech.NetReflector to dynamically load the configuration.

IPluginConfiguration contains definitions for FarmPlugins, ServerPlugins and ProjectPlugins, which return arrays of IPlugin, plus a defintion for BuildPlugins which returns an array of IBuildPlugin. I’m going to modify this to add a new definition – SecurityPlugins. This will return an array of a new interface – ISecurityPlugin. This is an extension of IPlugin that contains an extra method stating whether it is valid for a server.

IPluginConfiguration is implemented by NetReflectorPluginConfiguration. This contains the Exortech.NetReflector decorated properties so the library can dynamically load the configuration. Therefore I modified this class to include a new property for the security plug-ins. This is an optional reflector property.

First Security Plugin

For testing purposes I’m going to add an implementation of the simple user name authentication. This plugin is called UserNameSecurityPlugin, and it implements ISecurityPlugin and ICruiseAction. ICruiseAction is required since the login will be action-based (otherwise it won’t fit in with the rest of the dashboard).

The action is very simple – it just generates a view with a field for the user to enter a user name. On postback this field will be sent to the server and the response displayed.

Adding the Login Link

The next step is to add this plugin to the UI somewhere. As I stated in my plan, I want to add this to the header next to the Documentation link. This link is defined in SiteTemplate.vm, which is an NVelocity template. So, I modified this template to insert the login link.

The values in SiteTemplate.vm are set from SiteTemplateActionDecorator, so this is where I will need to set the security link. Now, the way the main template works is it has a number of sub-templates, which are built via builders. To maintain this pattern, I’ve added a LoginViewBuilder which will take in the values and generate an HTML fragment containing the login link (or any other login functionality, e.g. logout).

The builder simply checks the configuration for any security plug-ins. If one is found, and the request is for a server, then it generates an HTML fragment containing a link to the first named action. Later I can expand this for handling multiple login actions.

Handling a Login Request

Now that I have the login button added, the next step is to handle a login request. If I try to run the code now I get an objection error saying the object can’t be found. This is because the request controller is trying to find an action that matches my login action. To resolve this I modified CruiseObjectSourceInitializer to load all the security plugins and store them in the object store. Additionally I added all the same decorations as for a server plugin (since I’m not sure which ones I will need).

Now when I run the code I get my nice little template displayed :) And since I’ve already implemented the action I can test my login (it works nicely).

Now I need to store the session token somewhere!

Storing the Session

My plan is to store the session token in the query portion of the URL. Now, while this is simple in concept, there is a huge number of URLs that can be generated for a page! What I need is a single place that I can modify so that all URLs are affected. And thankfully this is provided by IUrlBuilder, but it will take a bit of work to achieve this.

First, IUrlBuilder is part of core, not dashboard. So any changes need to be generic enough to not break the server. Because of this I can’t have one class for storing and loading the session. So I’ve split it into two – ISessionStorer and ISessionRetriever. ISessionStorer just has a property for storing the session token and a method for converting this into a query string parameter. ISessionRetriever is the same, but in reverse (it searches the query string for the session token).

Next I added an implementation of these classes and modified CruiseObjectSourceInitializer to store these as instances (so that objection will automatically load them). And that’s where my code came unstuck :(

The problem took me a while to work out, but it was very simple in the end. When I implemented my plugin and action I made them the same class. Now the problem is plugins are cached, whereas the rest of the entities (including my ISessionStorer) are not. Therefore when my new action was called, it was using the cached plugin, with the cached ISessionStorer. Everywhere else the new instance of ISessionStorer was being used!

So, time to do some more refactoring. First I added a new property to my ISecurityPlugin - an ISessionStorer property. Secondly I split UserNameSecurityPlugin into UserNameSecurityPlugin and UserNameSecurityAction. When the actions are being retrieved from the plugin it creates a new instance of an UserNameSecurityAction. Prior to this I set the ISessionStorer property on the plugin and now my new action is using the same session storer as everywhere else. The action now does all the actual execute functionality on its own.

Now when I login all the links now have the session id in them :)

Logging Out

It doesn’t make much sense to have a login button when the session has already been generated, so I went back and modified the LoginViewBuilder to return “Login” if there is no session and “Logout” otherwise.

All this required was modifying the builder to return a logout action when there is a session. This is a default action that just clears the session token from the query string and sends the user to a view telling them they are logged out.

Authentication Completed

That is now the end of the authentication in the dashboard. I’ve built a framework of plugins and actions to handle logging in to the server and storing the session token in the query string. As it’s taken me a while to figure this out, I’m going to finish here for the moment.

In my next post I’m going to look at making the session storer and retrievers dynamic, so I can build alternate implementations. Then I’ll take a look at authorisation in the dashboard. Once that’s done then I’ve finished the basics of adding security to CruiseControl.Net!

Yay :D

2 Responses to “Securing CruiseControl.Net – Web Dashboard (Part III: Authentication)”

  1. [...] ICruiseUrlBuilder – which is one of the family of classes that builds the URL (read more about it here). The basic purpose of these classes is to generate an output URL that can be used in the [...]

  2. [...] my last post (here), I had added authentication and sessions to the web dashboard. While authentication is an [...]

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <pre> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>