A New Security Manager is Here
When I started on security I put in an interface called ISecurityManager. This interface handles all the security functions – CruiseServer merely calls this interface. Now I could have put all the security functionality directly in CruiseServer, but this had a couple of disadvantages. First it would have made it harder to turn security off (i.e. CruiseServer would have to know whether security was on or off). Second it would have meant that security was hard-coded in.
With ISecurityManager I can easily implement a new security manager and I don’t need to change CruiseServer at all! Actually, anybody can implement a new security manager – CC.Net wouldn’t care as long as it uses Exortech.NetReflector and implements ISecurityManager.
(One idea I’m playing with is getting the security manager to call directly into Active Directory for all it’s security, but that’s just an idea currently!)
In this post, I have implemented a new security manager – one that stores the users and permissions in external files (yes, I said files – it will handle multiple external definitions).
The new class is called ExternalFileSecurityManager – yes this is a mouthful, but it describes the manager nicely. The security settings are stored in external files (that is external to the main configuration.)
Here is an example of what the configuration looks like:
<externalFileSecurity><audit><xmlFileAudit location="C:\Logs\CCNet_Audit.xml"/></audit><auditReader type="xmlFileAuditReader" location="C:\Logs\CCNet_Audit.xml"/><files><file>users.xml</file></files></externalFileSecurity>
And the external file would look something like this:<security><simpleUser name="johndoe"/></security>
When the security manager is initialised, it will load each external file and load all the users and permissions (the external file can contain both). Otherwise this manager functions the same as the internal security manager.
As for allowed settings in the external files, the manager will handle any settings from either the users or permissions sections in the internal security. Actually, this manager is the same, but there is the additional step of loading from the file, instead of getting it from the configuration.
Behind the Scenes
For those who want to know how I implemented this manager – it was pretty simple. First off, I cheated – I copied all the code from InternalSecurityManager and did some renaming. (There’s a lot of code in these classes so writing one from scratch would take a lot of work.)
Next, I removed the Users and Permissions properties (plus their backing fields) and added a Files property.
Finally, I modified the Initialise() method to go through the list of files and load each one. The load method merely loads the external file into an XmlDocument, and then iterates through each element and attempts to load it with NetReflector.
If the element is a valid security setting (i.e. IPermission or IAuthentication) it loads it into the correct dictionary and then moves on. Otherwise an exception is thrown.
Otherwise, nothing else has been changed. Because the Initialise() method in InternalSecurityManager loaded the settings anyway, I was able to piggy-back on the process
As a final note, I didn’t inherit from InternalSecurityManager because I didn’t want the Users and Permissions properties. Since they both use NetReflector under the hood the user would still have been able to set these properties.
Where to Next?
Now, while this works, there are a couple of extra steps I want to add:
- File monitoring – when the external files change, they should automatically reload. But at the same time I don’t want to force everything to reload (i.e. the entire configuration file).
- Password changing – the entire drive behind this is I want to allow users to change their passwords. With internal security this is not possible (persisting the passwords causes the entire config to reload!) Now I can start to add in this functionality.
So that’s my next plans for security – in between working on testing and documenting the whole thing.
What’s in a Name?
When I first started working on security (around four months ago) I had a very clear plan on what I was going to do. However, like life, things changed as I progressed. I’d like to think the changes have made a better end-product, but along the way some things became less clear.
One example of this is the names within security. Originally the names were along the lines of what I was planning – but things changed and the names became less and less clear And instead of changing the names to reflect their new meaning, I left them as they were.
An example of this is the settings element on the security manager. Initially this was designed to hold all the server-level security settings – without any differentiation on the different types. When I implemented server-level permissions, I could no longer use the settings (got a conflict with the names) so I added the assertions section (i.e. the same section as for project security).
Now there is a section in the security element for permissions, and a second section for permissions and users! But which section should which permission go into?
Assertion != Permission
The first change is to move from assertions to permissions. An assertion is something that is checks to see that it is true (e.g. a if the current user is Joe then he can force build), but it is more generic than just permissions (e.g. if the user name is Joe and the password is whoareyou, then the user is Joe).
In my code I’ve been referring to permissions as assertions. While these are included in an assertion it’s a very narrow sub-set. Therefore to clarify this, and make it easier for new users, I’ve changed from <assertions> to <permissions>. I’ve also changed the current names of the assertions to be permission names (e.g. userPermission and rolePermission).
Under the hood this required no changes to the code beyond some renaming of classes (I’m trying to keep the class names reasonably close to the reflection names) and the changing of the reflection attributes.
Specifying Users, No More Settings
The second change is to remove the settings section under the security manager, and add in a users section instead. I say remove and add, rather than rename, because I’ve subtly changed the way this section works.
In the old code the settings section could contain users, permissions or any other class that implements ISecuritySetting. The new users section can only contain users now – which forces the permissions to be in their own section. If we add more types of security settings later on, they will need their own sections to be added to the configuration.
This required renaming (similar to the permissions change), plus removing and adding the properties. But still very straight-forward.
Why Session Security?
My final change is to rename SessionSecurityManager. Initially when I designed security I had the grand allow of allowing people to use different means of passing security information between the client and the server. As the project progressed this narrowed down to user credentials for logging in and session tokens for everything else.
Additionally I hard-coded these session tokens into all of the external methods – so calling the method now requires a session token for any secured method. While it is possible to pass non-session details in this token, it’s a bit hacky.
Therefore, since I’ve modified the security to be all session-based this means the name of SessionSecurityManager doesn’t have as much meaning. Instead a better name is InternalSecurityManager (and internalSecurity for the XML). As the name means, it’s a security manager that stores it’s configuration internal to the configuration file.
On my to-do list is to make an ExternalSecurityManager – this will store the settings in an external configuration file and load them as required (this will also allow changing or passwords).
Enough Changes for Now
That covers the three names that I wasn’t happy with – settings, assertions and sessionSecurityManager. With these changes it should make the configuration more meaningful and also easier to develop moving forward.
And now to you dear reader, are there any other names that you think are incorrect? If so, please let me know and I’ll see if I can make them clearer.
Since this post security has been modified slightly. Please see this post for further details.
Welcome Back to Security
Here is the second in a set of scenarios on security. In my previous scenario (here) I looked at a small team, this time I’ll look at a large team.
The company Acme Banking, a large multi-national bank. Lu is the manager of the lending application software development department and has a staff of 19 people working for him. He has charged his system admin, Peter, with securing CruiseControl.Net.
Some background, Lu is responsible for three applications: lending applications system (LAS), load approval and tracking system (LATS) and bad debt analysis and recovery system (BARS). Each system is an independent web application with a complex set of business rules and a backing database. To allow interactions between the system each application exposes web services. Finally each application has a number of support tools that are Windows-based.
However, to simplify things, Lu’s department is only responsible for development. The QA department is responsible for all testing (including deployments) and the Server department is responsible for the actual deployment to PROD. All of these deployments are done via hand-overs, with the receiving department responsible for getting the binaries from the build server.
As for department structure, each team has a team leader and a senior developer. There are also between two and six junior developers in each team. Peter is not included in any of the teams as his full-time role is supporting the developers. All staff are on the same network and have windows logins.
Lu wants to limit access for project to the team that is responsible for it. No team is allowed access to any project that belongs to another team. Additionally only the team leader and senior developer are allowed to start/stop the projects, although the junior developers can force builds. Everything done in the system must be audited.
The Build Setup
Each application has two projects – main and tools. Main includes the actual web application, plus the web services, while tools contains all the support tools. This makes a total of six projects.
Each project contains everything required to build the binaries and deploy them to the build server (for the other departments). These are triggered on an interval basis, plus every night at 3am.
Since the hand-over process is manual, there is no impact on other environments. However considering the team size and low-level of trust they want everything locked down.
There will be three security zones: low, medium and high. The following table shows these zones:
|Low||Permission to force/abort builds.||All||Junior developers|
|Medium||As above, plus permission to start/stop projects||All||Team leaders and senior developers|
|High||As above, plus access to security information||None||Lu & Peter|
Each application will be divided into low and medium zones. The high level security zone is generic to the system rather than application-specific.
These goals give a total of seven security groups – low and medium for each application plus a high security group. Everybody within the department will belong to one of these seven groups.
This actual model is a little more complex, but I’ve put together a diagram as follows:
Again I’m going to use the same basic configuration for each project:
<intervalTrigger buildCondition=”IfModificationExists” seconds=”300″/>
The project name (i.e. LAS-Main) will be changed for each project – a total of six projects.
Again the first step is turning on security – just added the sessionSecurity element, plus the settings and assertions children.
Adding The Roles and Users
Next, I added the users. Since everyone is on the network I added them with ldapUser elements. This is defined as follows:
<ldapUser name=”lu.jones” domain=”localhost”/> <!– Manager –>
<ldapUser name=”peter.smith” domain=”localhost”/> <!– SysAdmin –>
<ldapUser name=”mark.doulos” domain=”localhost”/> <!– LAS Team leader –>
<ldapUser name=”jill.white” domain=”localhost”/> <!– LAS Senior developer –>
<ldapUser name=”john.asher” domain=”localhost”/> <!– LAS Junior developer –>
<!– Remaining users omitted –>
Note there is a domain in here – I’ve just changed it to localhost for this example. Without the domain the active directory authentication will fail as the authenticator won’t know where to look.
I’ve also added a server-level assertion for the high-security permissions:
<roleAssertion name=”Admin” defaultRight=”Allow”>
This just says that Lu and Peter have full access to everything.
Configuring the Clients
This is exactly the same for scenario one for CCTray. Each individual user will need to go in and configure their security authentication. The only difference is they can use the WinLogin authentication method.
At the moment it is not possible to add the authentication to the dashboard. I’ll look into adding a authentication plug-in and then I’ll update this post.
Locking Down Projects
The final step is to lock down the projects. This is where there is slightly more work than scenario one. Since each project needs to be secured, every project will need to be modified.
There are two ways this can be done. One way would be to add the security to each project, the second is to define roles and then link each project to them. Since there are two projects for each application, with identical permissions I’m going to use the second approach.
In the settings section of sessionSecurity I define each application role. The following shows an example:
<roleAssertion name=”LAS-Developers” forceBuild=”Allow” defaultRight=”Deny”>
<!– Remaining users omitted –>
<roleAssertion name=”LAS-Admin” forceBuild=”Allow” startProject=”Allow” stopProject=”Allow” defaultRight=”Deny”>
This defines the developer and admin (team leader/senior developer) roles for an application. Each of the other applications has a similar definition except with a different list of users.
Then, in each project I add a security section like the following:
<security type=”defaultProjectSecurity” defaultRight=”Deny”>
<roleAssertion name=”LAS-Developers” ref=”LAS-Developers”/>
<roleAssertion name=”LAS-Admin” ref=”LAS-Admin”/>
<roleAssertion name=”Admin” ref=”Admin”/>
The first two assertions will change to match the application each project is for, while the last one is the same for all projects (gives Peter & Lu access to the projects).
Finally Lu wants everything audited. This is very simple to do, just add an audit logger and an audit reader. This is done in the sessionSecurity element by adding the following elements:
By default these will use an audit log called SecurityAudit.xml in the same directory as the executables. This can be changed by added a location attribute as follows:
<auditReader type=”xmlFileAuditReader” location=”C:\Logs\CCNet_Audit.xml”/>
Note that the location must be on both elements and they point to the same location. This is because the two items work independently of each other and it is possible to have multiple audit loggers.
And that’s the security settings for this scenario.
As always, working through this scenario I found a couple of issues.
First, while the LDAP security is generic (i.e. the authentication is handled externally) I still needed to define every user. This is because of the internal validation that happens. As different users would belong to different roles I couldn’t just use a wildcard for the authentication. While this does make things a little more security, it also forces duplication of security Instead it would be nice to define something that keys off an LDAP group, hence reducing the need for duplication.
Secondly there is no dashboard plug-in to detect a Windows Login. I haven’t implemented this as I’m not sure of the best way yet (if anybody has any suggestions let me know!) This wouldn’t be so bad, except the only way to view the audit logs currently is via the dashboard!
Once again I have posted the complete example on my storage site:
There is no dashboard configuration for the reason above.
Feel free to send me any suggestions for improvements or ideas on how to clarify these scenarios or security in general.
Since this post security has been modified slightly. Please see this post for further details.
Welcome to Security
As I promised previously, here is the first in the set of tutorials on security. In this scenario I’ll look at setting up security for a small, fictitious team.
The company is Acme Insurance, the company that provides development insurance for software houses. Bob leads a team of six people – three developers, a business analyst (BA) and a couple of testers. The systems they develop are in-house systems for the company – and consists of a couple of Windows applications plus an internal intranet web application.
Bob wants to secure their installation of CruiseControl.Net so he knows what is happening and to limit access to certain projects.
The Build Setup
Each application (two windows apps, plus one web app) has three projects in CruiseControl.Net. Additionally there are a few smaller projects for common libraries – which are used by the main applications – bringing the total number of projects to twelve.
Each application, plus the libraries, have a simple compile-and-test build project. This projects ensures that the code builds, the local unit tests run and the successful outputs are copied to a central “store” for the executables. These are triggered on an interval basis (when code is checked in) or via a force build.
The applications also have two deploy projects each – deploy to QA and deploy to PROD. These generate “production” builds, copy all the executables and related files into deployment packages and copy them to the staging area. Additionally the web application is automatically deployed to either the QA or pre-PROD servers. The QA versions run overnight, the PROD versions must be manually triggered.
So despite this being a small team, there are still a fair number of projects, plus some can potentially screw up processes (either QA or PROD).
The twelve projects can be divided into three security zones: low, medium and high. The following table helps to define these three zones:
|Low||Projects that everybody can access||Compile-and-test projects (six)||Everybody|
|Medium||Projects that need to limit access||Deploy to QA projects (three)||Team lead, BA and testers|
|High||Projects that can affect production||Deploy to PROD projects (three)||Team lead and BA|
For low security, they don’t really care what happens to the projects – it is the entire responsibility of the team. While this can still lead to an abuse of trust, the consequences would be reasonably minor.
As the security needs increase, the consequences of someone screwing things up (either deliberately or by accident) increases. Damaging the QA environment could lead to a loss of one or more days work by the testers and the BA – not serious consequences but potentially a large waste of time and resources. Damaging PROD would lead to a serious waste of time, plus potentially damage the company’s client-relationships.
With these goals in mind, this gives two actual levels of security. The first level is unsecured – anyone can do anything to the projects and there is no real need to audit anything. This is very similar to how CC.Net currently works.
The security level limits who can access and modify projects in CC.Net. Within this level there are two groups – testers and releasers. Membership to the testers group allows full access to the deploy to QA projects, while the releasers group allows access to all deployment projects.
This definition gives a very simple design – two roles with two users in each. Six projects need security, while the other six do not. Since the users need to be secured they will use passwords (to ensure the other people don’t “use” their user name). Finally, all deployments will be audited to assess the effectiveness of security in the long run.
The following diagram shows how I’m going to implement the security:
Since I’m looking at the security, not the actual projects, I’m going to use the following general configuration for each project:
<intervalTrigger buildCondition=”IfModificationExists” seconds=”300″/>
WinApp1 will be changed to the different project names. There are twelve projects in total – their names roughly match the names above (the arrows are changed to dashes). With this background, I can now begin to configure the security.
Turning On Security
First, I’m going to turn on security for the server. This involves adding a security block to the config file. Since there is only one security type defined so far, I’ve added a sessionSecurity element (this can be added anywhere within the config, as long as it is directly under the cruisecontrol element – i.e. it cannot be in a project or queue element). For the moment, I’ll leave it with the default settings.
Running this config through the validator, I see that the security element needs an assertions element and a setting element, so I’ve added some blank instances of these. Now the config file passes validation and security has been turned on for the server. But, all this has done is completely lock down the server, so no one can do anything!
Adding Some Roles and Users
So the next thing to do is to add the users and roles. First, I’ve added the two roles – “Testers” and “Releasers”. These are defined as follows:
<roleAssertion name=”Testers” forceBuild=”Allow” defaultRight=”Deny”/>
<roleAssertion name=”Releasers” forceBuild=”Allow” defaultRight=”Deny”/>
Each of the roles has force build rights only, all other rights are still denied. Before I can add some people to the roles, I first need to add the users to the security. So I’m going to add four users, each with their own password (which are hard-coded for the moment).
So, now the security settings looks like this:
<roleAssertion name=”Testers” forceBuild=”Allow” defaultRight=”Deny”/>
<roleAssertion name=”Releasers” forceBuild=”Allow” defaultRight=”Deny”/>
<passwordUser name=”bob” display=”Bob (Team Lead)” password=”bob1″/>
<passwordUser name=”jane” display=”Jane (BA)” password=”jane2″/>
<passwordUser name=”john” display=”John (QA)” password=”john3″/>
<passwordUser name=”joe” display=”Joe (QA)” password=”joe4″/>
Next step is to add the users to the roles, so now the roles look like this:
<roleAssertion name=”Testers” forceBuild=”Allow” defaultRight=”Deny”>
<roleAssertion name=”Releasers” forceBuild=”Allow” defaultRight=”Deny”>
Finally, I added a generic role for all the other uses:
This now gives me the roles and users. But, until CCTray and the dashboard are configured, they still can’t do anything!
Configuring the Clients
Configuring CCTray is done on the client-side. The user needs to go to settings, click on the Build Projects tab, and then click on “Add…”. This will open up the list of servers – from here the user clicks on the server to modify and then clicks on “Configure”. This will finally open up the security configuration:
Here the user will need to check the “Server is secure” checkbox and then choose the authentication mode. Once they have done this they need to click on the “Configure” button and enter their credentials. Finally click on “Ok” all the way back to the main screen and security is now configured for CCTray.
Configuring the dashboard needs to be done at the server level instead. This involves opening dashboard.config and adding the following section underneath <plugins>:
This adds a login section to the site, which allows the person to login with their username only. People with password secured accounts will fail to login. For this scenario this mode is fine for most people – the people in the other roles will need to use CCTray.
Now people can login and perform force builds, etc. However, the problem is everyone can force build any project! The next step is to configure the projects.
Locking Down Projects
For the projects that are in the low security zone, there is no need to change the configuration. These will continue to work as-is.
For the remaining projects, I need to lock them down. This is done by adding a <security> element to the project element. At the moment there is only one type of security – defaultProjectSecurity. So to start with I added the following XML to the project element:
<security type=”defaultProjectSecurity” defaultRight=”Deny”/>
The defaultRight attribute is optional, but this changes the default permission so no-one can do anything on it. Of course, this isn’t much use, so it’s time to add in the assertions:
<security type=”defaultProjectSecurity” defaultRight=”Deny”>
<roleAssertion name=”Testers” ref=”Testers”/>
<roleAssertion name=”Releasers” ref=”Releasers”/>
This is the definition for a medium security zone project, the high security zone is similar, but without the Testers role in it.
And that’s it! Security has now been configured for this scenario.
While this security setup does work, I found a few issues along the way.
First, because the dashboard plug-in only supports one authentication method I couldn’t configure the security plug-ins in the dashboard. Therefore I’ve turned them off in my dashboard configuration for this scenario (otherwise people would be confused).
Second, security is very open by default. While this was intentional, it does mean the onus is placed on the administrator to lock everything down.
Third, passwords are stored openly in the configuration, and people have no way of changing them. This is similar to the basic approach that Subversion uses, but it would be nice to make it a little more secure.
For those who want to see my complete configuration, I have added them to my storage site:
Feel free to send me any suggestions for improvements or ideas on how to clarify these scenarios.
A while back (here) I started posting on displaying the audit log in the web dashboard. Now, at the time I made the mistake of not posting all the files to SVN, and so while I was on holiday the source was broken (I was rushing, but I realise that is no excuse). Now that I’m back home I’ve added all the relevant files to SVN and now hopefully all is building happily.
Now, while I was away I was planning on finishing this topic, which covers the web side of the audit viewer (the first part was the server-side changes required). Now that I have access to all the source code, this post will complete the topic and add the web dashboard plug-in.
A Quick Recap
When I built the security framework originally I had added the ability to write audit events to one or more logs. In my previous post I then extended this framework to add audit readers – an extensible section for reading in audit records and passing them onto a client application.
This involved configuring an audit reader in ccnet.config for the server. This reader will then read the records from its configured source and pass them on. This uses a new class called AuditRecord, which contains the main details on the event. Additionally I added a set of fluid-syntax filters to the extension to allow filtering the records.
Finally I added an XML file audit reader to read in records that were written by the XML file audit logger (currently the only logger that is available).
With this in place I can now add the dashboard plug-in to expose the audit log to a remote user.
The new plug-in will look very similar to the user list plug-in I recently implemented. This will have the list of projects across the top of the page and then all the audit events listed underneath. This list will contain the date, the project name, the user name, the type of event and the outcome.
As none of this functionality is new this shouldn’t take too long to implement
Implementing the Design
The first part is the plug-in and template. I just copied the classes and template from the user list plug-in (ServerUserListServerPlugin, ServerUserListProjectPlugin and UserList.vm) and renamed them (to ServerAuditHistoryServerPlugin, ServerAuditHistoryProjectPlugin and AuditHistory.vm).
Then I modified the ServerAuditHistoryServerPlugin to retrieve the audit log. This also required a change to ICruiseManagerWrapper and ServerAggregatingCruiseManagerWrapper to retrieve the records from the server. Once I had modified these, I just stored the results in the velocity context and passed it onto the template.
The final change was to return to ServerAuditHistoryServerPlugin and remove the user diagnostics checks. And that’s all that was required. The following picture shows the end result:
This shows all the details from the audit log. Any additional messages appear underneath the main details in italics (if there is no details then this is omitted).
Clicking on a project will show a similar view, except the project column is omitted (since it just contains duplicate data).
That sums up the audit history plug-in. Hopefully this will make auditing easier in the long-term.
As a final note, I don’t consider this the end product yet. It would be nice to offer the user some additional filtering options (e.g. by outcome, user, event, etc.) However it’s a start, I will add some more functionality as I have time.
The Value of Auditing
One of the components I added to the security work was an audit logger component. This allows the auditing of security events, but the other half of the picture is missing – how does someone view the audit log?
Therefore at the moment the audit logging only of use if the person has access to the log location (e.g. the server, etc.) While this does have some value, it would be more useful if the log could be viewed remotely – hence the topic of this post.
A Cunning Plan
As always, I have a plan of attack on how I want to do this. Since the data will need to come from the server, there will need to be changes to three projects: Remote, Core and WebDashboard.
For Core I will add a new security interface to allow reading the audit log. There will only be one reader per security manager (there can be multiple loggers) to reduce any confusion as to where the logs are coming from (and also to reduce the need for duplicate filtering). The reader will take in the starting location and the number of records to read. It also optionally allow a filter, so the user can reduce the number of records.
When this component is finished I will then add a new plug-in to the dashboard to display the retrieved audit records. This will display the records in a table – plus when the user clicks on a record it will display a pop-up with additional details.
Nice and simple? Hopefully But because there is a fair amount of work involved I’m going to split this task over two posts. In this post I’ll cover the server-side changes, and in the next post the dashboard plug-in.
A Record of Events
The first part of the process is to add a class to pass the audit information around. This class is called AuditRecord and contains the following properties:
- Time: The time the event was logged
- Project: The name of the project the event is for
- User: The name user the event is for
- SecurityRight: The outcome of the security action (e.g. Allow or Deny)
- EventType: The type of security event (maps to SecurityEvent)
- Message: Any additional audit data
This class is part of the Remote project and has been marked as serialisable so it can be passed over remoting.
Some Fluid Filters
For the filters I wanted to use some fluid syntax – this will make it easier for building the actual filters in future. The syntax I have in mind is:
filter = AuditFilters.ByProject("Project #1").DyDateRange(DateTime.Today.AddMonths(-1), DateTime.Today);
This will generate a filter that contains all projects with the name “Project #1” that occurred in the past month.
To accomplish this I added a new interface called IAuditFilter and a static class called AuditFilters.
The main method on the interface is called CheckRecord(). This checks a record against the filter and if it matches returns true, otherwise false. Each filter must implement this method.
Additionally there are a number of methods to enable the fluid syntax:
Each of these methods returns a filter that performs the specified filter. To reduce the amount of duplicate code each of these methods is implemented by a class called AuditFilerBase. As long as the filter inherits from this class the fluid methods methods are automatically included.
AuditFilters also has the same fluid methods, plus another method called Combine(). This joins two or more filters together and returns the combination. This filter is an OR operation – it will return all records that match any of the child filters.
Finally I added an implementation of each filter. These are straight-forward – they are called the type of filter suffixed with “Filter”. They just perform the required check and that’s it. Each filter has two constructors – one that takes in the filter criteria and a second that has the criteria and an inner filter. The inner filter is used for chaining filters (performing an AND operation).
Finishing the Puzzle
The final piece of the puzzle is the actual reader itself. This is a new interface called IAuditReader. As far as the end-user is aware, everything is still done through CruiseServer (and CruiseManager, etc.) To achieve this CruiseServer and ISecurityManager both have a new method called ReadAuditRecords() that take in the required parameters and call the required security component to get the records. The CruiseServer implementation also requires a session token as this method is secured (it requires the ViewSecurity permission).
But in the end the actual task of reading the records get passed to the IAuditReader that has been configured. At the moment, since there is only one logger, I have added only one reader – FileXmlReader.
This turned out to be a little bit tricky. While the data is written in an XML format, it violates the rules of XML because there are multiple root elements. In the end I modified FileXmlLogger so each record is on a single line (removed new lines and added one at the end of each record). Then the reader loads the entire file in one go and splits it into the lines.
To actually read the lines the method starts at the last line and works its way through the lines until it comes to the starting line (i.e. the record number specified by the user). I had to do it this way because there can be blank lines (due to the way I did the splitting), but it works!
Once the method has found the starting line it then starts parsing each record and adding them to the result set. If the there is a filter then this gets checked before the record is added. Once the result set has the specified number of records in it the process stops.
The actual parsing of the record is done on a line-by-line basis. Each line is loaded into an XmlDocument and the relevant pieces of information extracted.
Part I Completed
This completes the first part of displaying the audit log. I can now retrieve all the previous audit records and return them to a client, with paging and auditing (I haven’t worried about sorting).
My next post will carry on this work and display the results in a dashboard plug-in.
Where To Begin?
The changes I have made recently to add security to CruiseControl.Net allows a lot of flexiblity and control. However, with this flexibility also comes increased complexity – making it harder for someone to understand what is required and why, let alone actually configuring the security.
To try and make things easier, I’m thinking of writing a series of posts on different ways to configure the security. These will all be based on the current implementation of security, and so might change over time. But I will test them and and add to add them into the test suite.
So, if you have any scenarios then let me know and I’ll look at doing some tutorials on them. So far the ideas that I have are:
- Small development team, high level of trust, most projects are open to all
- Large development team, high need for security, requirement for auditing and accountability
- Open source project, multiple developers, few administrators
- Contracting firm, large number of projects that need to be secured
Each scenario will cover from planning to implementation, including both the server and client-components (e.g. CCTray and Web Dashboard).
I’m planning on covering them as step-by-step processes – this is what I would do and why. It will also be a good place to discuss some of the current limitations.
I’ll also fully test them, and hopefully I’ll be able to put together an automated process for this. In the long term these will also serve as test-scenarios going forward (e.g. for future work around security).
So let me know if there is anything you would like me to cover
Last week I was asked to add a plug-in to the dashboard that would show the security configuration. This gave me a couple of ideas of additional plug-ins to add, which I have now partially implemented. However, this now raises another issue – anybody can now see the security configuration. So, to get around this I want to add another security permission to only allow some people access to the security plug-ins.
Locking Down the Server
Before I can do this, there is another issue to be covered first. The permissions model I have currently added only works for projects – it does not handle server-level permissions.
To handle this I have added an assertions element to the security manager configuration. This is where people can define assertions that are for the server-level (e.g. user and role definitions). I also added a new method called CheckServerPermission() which checks against these assertions. Now there is a mechanism in place for checking server-level permissions.
The next step is to modify CheckSecurity() in CruiseServer. If a empty project name (string.Empty or null) is received, then it assumes that it is a server-level check and thus calls the new CheckServerPermission() method. Otherwise the standard project-level checking is performed. (I also modified the log messages to be more relevant.)
Now that I can check permissions at the server-level, the next step is to add a new permission.
A Permission to View
The new permission is called ViewSecurity. I have added it to the SecurityPermission enum, plus modified AssertionBase base to include it. This involved added a new property, plus backing field, and then modifying CheckPermissionActual() to return the new permission.
The next step is to lock down the relevant methods in CruiseServer. The three methods that require this permission are:
This involved modifying the method signatures to take in a session token (plus all the interfaces and pass-through classes), and then making a call to CheckSecurity() – passing in string.Empty for the project name.
The final step was to modify the plug-ins so they first retrieve the session token, and then pass it on.
With all this completed the security plug-ins are now locked down. In order to view any of these plug-ins the user must either have the ViewSecurity permission, or belong to a role that does.
A Small Problem…
Yesterday I was playing around with the security branch of CruiseControl.Net, adding a bit more functionality, when I ran into a small problem. As this is the second time this problem has affected me (in slightly different variation), I thought it’s time to write a post on the issue.
First, in order to understand how this problem works let’s take a look at a page in the web dashboard:
At a very high level the page can be broken into page content and the site template. The content is what is generated by the plug-in, whereas the site template is added to the page via one of the many decorators (yes, it is also broken into components, but that’s another post )
My problem was all the URLs in the site template contained the correct session token but the URLs in the page content contained an older (and expired) session token! This means the session tokens were coming from two different locations.
Building a URL
When I added the security I didn’t want each and every plug-in builder to have to know about security. Instead it should be totally transparent to any component builders (unless they need to use it).
To achieve this I used a class called 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 pages.
Hence I extended these classes to also add in the session token. This way the individual plug-ins just call the URL builder and the session token gets magically added.
Now this works because all the class instances are generated on the fly and added to objection so they can be used by any other classes. So every page request I generate the new session token adding and retrieving classes, hook them up to the URL builders and then forget about them.
Without going into the headache I had finding the problem, here is the cause:
The site template decorator instance is always generated and added to objection for each page request. Hence the session tokens are always the latest (i.e. associated with the page request).
The page content comes from a plug-in, which can use NetReflector to retrieve the configuration (this is not the only approach, but it is the most common). The configuration is loaded once, when the web application starts, and is then cached. Every subsequent web request retrieves the cached configuration.
Now because some plug-ins need a URL builder they have it as a constructor argument, so objection automatically adds it. So, the first time the plug-in is served, it gets a URL builder with a link to the correct session token. The next time, the plug-in is not reloaded – it is retrieved from the cache, which contains the URL builder from when the page was created. The problem is this URL builder links to all the page variables (e.g. the session token) from the original page request!!!
At this point I haven’t decided whether this is an issue with the design of the application, my mis-understanding of the application, or somewhere in between, but still yuck
Note: Actually, when URLs are static the design does make perfect sense, it’s just security needs dynamic URLs which change for each request. So I’ve started doing things which weren’t in the original design.
To get around this problem I changed where the URL builder comes from. I’ve added a new property to ICruiseRequest which contains a URL builder.Then instead of retrieving the URL builder in the constructor, I use the one from the request. This way, each and every request will contain its own URL builder, which links to the correct session token.
At the moment I’ve only done this for the security plug-ins, but someone will need to look at the other plug-ins to see if this problem also exists!
So, to reiterate the main point I learnt:
Plug-ins are cached, as are all the parameters set using their constructor! Never use a constructor parameter for request-specific values (e.g. session tokens).
Hopefully this will save someone some heachaches in the future when developing plug-ins.
Why Can’t I…?
The larger and more complex a security setup becomes the greater the likelihood is that something won’t work! Hopefully this is an issue with the configuration, rather than the code, but either way it would be nice to narrow down where the problem is occuring.
To try and help with diagnosing permissions issues I have added a new plug-in to the dashboard that will allow an administration to diagnose what is happening.
The new plug-in will be similar to the security configuration plug-in. It will work into two modes – server or project. However, instead of displaying the configuration information will display a list of all the users that have been defined.
I did initially think about adding some diagnostics as to how the permission is generated, but this turned out to be a bit more complex (on the server side). Therefore I won’t be including it in the initial version.
Like my previous plug-in, this one also requires changes to the server-side as well. CruiseServer (and CruiseManager, ICruiseServer, etc.) will need to return two types of data:
- A list of all the users
- A set of permissions for a user
To simply things the list of users will actually come from ISecurityManager and CruiseServer will just act as a pass-through layer (yes, yet another one!) So, time to start implementing.
First I added a new method to ISecurityManager and its implementation, SessionSecurityManager, called ListAllUsers(). This method iterates through all the security settings and generates a set of UserNameCredentials whenever it finds an IAuthentication. These credentials contains the user name, the display name and a friendly authentication-type name. These then get added to a list for return.
To enable the names I also made a couple of changes to IAuthentication. I added UserName, DisplayName and AuthenticationName properties to the interface. Most of the implementations already had UserName and DisplayName, but AuthenticationName is new. This just returns a friendly-name describing the type of authentication (and so will generally be hard-coded).
The set of permissions needs to come from CruiseServer, as ISecurityManager doesn’t know anything about projects.This is a new method called DiagnoseSecurityPermissions() – which will return the permissions for a user and one or more projects (I’m trying to build in some flexibility for some future ideas).
I added a new class to Remote called SecurityCheckDiagnostics which will contain the results of the check. This has the name of the permission, user and project, plus whether the permission is allowed or not (not worrying about inherit here). This gets returned from DiagnoseSecurityPermissions() in a List<> instances and can be serialised (and thus passed via remoting).
The actual generation of this list is very simple, just iterate through all the projects and check each permission with a modified version of CheckSecurity(). To retrieve the list of possible permissions I used Enum.GetValues() on SecurityPermission. Each result is added to an instance of SecurityCheckDiagnostics, which is then added to the list.
I needed to use a modified version of CheckSecurity(), called DiagnosePermission(), because the normal version performs logging and raises errors on failures (which would slow performance). The modified version just checks that everything is there and then performs the security check (via the CheckPermission() method on IProjectAuthorisation).
That was a lot of work for a couple of simple tasks, but now it’s all done, including unit tests.
Displaying the Users
Now it’s time for the fun part – actually generating the output. Since this will look similar to the configuration plug-in, I’ll just take the same approach and copy the existing code and then modify it. This gave me a new class called ServerUserListServerPlugin, with all the occurances of security configuration replaced with user list, and a second class called ServerUserListProjectPlugin for the project-level plug-in.
This of course looks the same as the configuration plug-in, just with a different name and entry point. So, the next step is to copy the template and start to modify it, then generate the data to be displayed. The new template contains a table with user name, display name and authentication name. To populate the table I first needed to call the new ListAllUsers() method and store it in the velocity context. Then I just used the values in generating the table – nice and simple.
That now gives me the following output:
Which looks the same for all the projects as well. Now it’s time to do the fun part – pop-ups!
A Little jQuery, a Dash of Ajax and Viola – Dynamic Displays
So, first I downloaded jQuery core (from http://docs.jquery.com/Downloading_jQuery), then I built a custom version of jQuery.UI that contained the dialog widget and some of the helper widgets (from http://ui.jquery.com/download_builder/). Finally I went to ThemeRoller (http://ui.jquery.com/themeroller) and generated a theme for the dialog. And with that I’m already to add some dynamic functionality.
The actual AJAX call was simple – just another jQuery call and this automatically sent a HTTP post off to the server and loaded the response into the <div>.
But then I ran into a problem!
The current actions all get decorated with a SiteTemplateActionDecorator - thus wrapping the results of each action in the site template. For what I’m doing, I just wanted an HTML fragment – and nowhere can I add this
Sending Naked Content
After thinking about a few different ways I settled on adding a new interface called INoSiteTemplateAction. When the object initialiser comes across an action that also uses this interface it generates a different set of decorations – this time without the site template. This might not be the nicest way of doing it, but at least it ensures that all the other decorations are correctly added. From here I generated an implementation of this interface that just inherits from ImmutableNamedAction.
With this framework in place, I’m now ready to generate an AJAX response. I added a new ImmutableNamedActionWithoutSiteTemplate to NamedActions on my plug-in. This just points back to the plug-in. When Execute() is called on the plug-in it checks to see whether a user name has been set in the form values, if so it generates the user diagnostics, otherwise the user list.
The following shows the end-result of these changes:
So, this new plug-in will list all the users on a server. When the user clicks on a user it will bring up details on that user. If the user is viewing it within a project context, then the permissions for that project are displayed. All done without refreshing the page
I’ll look at checking in the code tomorrow, and then start work on the audit log viewer plug-in.