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:
- ByProject()
- ByUser()
- BySecurityRight()
- ByEventType()
- ByDateRange()
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.
RSS - Posts







