Introduction
This article attempts to discuss an approach to application-managed authorization that is intended to enhance the productivity of software development teams by separating and abstracting the logic for authorization in software from that for business features using dependency injection and interception.
Background
Providing runtime access to functions in software depends on successful identification of the subject attempting to gain access to an object. Subsequently, a decision is made to grant or deny access based on a myriad of variables such as group membership, time of day, history, etc. A traditional, widespread approach to this is to build features, and then to go on to decorate and intersperse that code with that for security and access privilege checks. Well-established practices and mature development frameworks may make this more straightforward but essentially the development of features must precede the writing of code to “secure” those features. Both sets of activities are typically done over several iterations. Additionally, the approach for securing assets across the different tiers of the application – database, mid-tier and presentation (user interface) – varies due to the different paradigms that define the different environments. For example, runtime access to database data and operations may be secured using the database security features that rely among other things on user accounts with permissions to tables and stored procedures and functions. Nevertheless, it is often the case when a significant amount of functionality is developed in stored procedures that custom security checks can also be included in the database code. However, the focus of this discussion is the middle tier in software applications which is typically developed using high level computer languages such as C# and Java.
Some Analysis
It is neither a new idea or practice to abstract business and security rules into a separate repository apart from application data and perform authorization checks against that but business logic code is still interspersed with that for security checks and must be written one after the other as is the case in traditional Role Based Access Control (RBAC) software. This happens whether the security checks are being done in stored procedures in the database or in code in the middle tier. Maintenance of those “checkpoints” thus requires someone with a good idea of the general code layout that would go from point to point making the necessary amendments.
The point being made is that the tedium of having to write business functionality and then security checks can benefit from a separation that allows both development and system build (security and other configuration) activities to run simultaneously against a common object and system model.
Some information security professionals often say that software practitioners need to be rescued from these bad habits of “insecure” software development practices because these introduce weaknesses in systems that may only become apparent after the instance of an exploit. By implication, software developers tend to be careless with security. The reason for this could be that they tend to focus on building the cool widgets, screen layouts and other eye-candy that foster job security leaving software security as a boring afterthought. In my opinion, this arguable notion in spite of having some basis in real life experience falls short of actually being productive if it mandates no clearly actionable remedy.
One can also opine that the basis for this opinion is the typical separation of Information Security from the software development experience. Information Security’s role often becomes prominent only after software has been developed and deployed for testing when issues get thrown up. Nevertheless, one must agree intensely that the responsibility for effective security in software lies with designers and developers.
This leads one to ask, how can security receive better attention from the onset of a software project? How can confidentiality, integrity and availability be addressed sufficiently from the beginning? Can security rules and business functionality be developed side by side in traditional languages such as Java and .NET and be somehow continuously merged together to achieve the desired result? My opinion is that someone needs to wear the information security hat right from the beginning of the software project and be involved through the entire process. This is one way better security can be achieved quickly and efficiently.
The following is an approach to the stated issue using the .NET platform, which relies on the concepts of Dependency Injection and Interception in general and specifically, object creation and abstraction of security rules.
To elaborate further, consider the scenario where a developer simply codes functionality – subject to automated unit tests and functional requirements and checks in code to a central repository without worrying about interspersing explicit role based checks inline with regular business logic. At the end of the day, you will have software that is good for a proof of concept demonstration but is hardly useable in the real world as there is no way to separate duties and enforce controls. In the same scenario, consider that another individual with information security skills codes security rules in some domain specific language against the same code and at the end of the day, the code written by the first developer runs based on the rules defined by the other. The following outlines a strategy for achieving this.
Effectively, security rules in this context are by implication, business rules and in general terms, application authorization facilitates the following scenarios:
- Securing access to an operation. In this scenario, the subject is either able to access the operation or not. For example, saving an expense report or retrieving the list of active projects. The operation is a method of an object. Whether it’s a CRUD (Create Retrieve Update Delete) call or one to calculate interest accrued on a fixed income security, it’s all in a method call. The instance of restricting access to a particular set of URLs can also fall into this scenario.
- Setting object attributes. In this scenario, even though we would like to allow access to an operation, we may like to restrict certain parameters to a range of values based on the subject. For example, in the case of retrieving all expense reports, we may want to restrict access to just expense reports belonging to the subject (logged in user). We can achieve this by always setting the value of say, a project ID parameter to a value in the user’s profile.
In these scenarios, the gate to access a feature is opened based on the evaluation of an expression of varying complexity that has a Boolean result. For example:
- Is the user allowed to login at this time of day?
- Does the user belong to a group that is allowed to access this operation?
- Is the user allowed to see all entities whether they “belong” to him/her or not?
- Has the user exceeded a threshold for performing the operation within a set amount of time?
Note that or the same system will often have a different set of rules per installation, i.e., the rules are different for each customer. Of course, two or more of these rules may be combined and be based on properties of the subject as well as those of the computing environment such as time of day, remaining disk space, etc.
Suggested Approach - A Bird's Eye View
DI (dependency injection) is at the center of the strategy and it’s a way to specify dependencies in code dynamically. There’s a Wikipedia article on the topic and there are a bunch of implementations out there. I will be using the Unity framework courtesy of Microsoft’s Patterns and Practices group. Additionally, we will need to understand Interception, which is a way to inject code dynamically at runtime and is very useful for handling cross cutting concerns such as logging, validation and authorization. (See this link.)
In a nutshell, we intend to dynamically inject code before method calls at runtime that performs authorization checks. To do this, the objects will not be created using the traditional instantiation method (e.g. Object<span data-blogger-escaped-style="mso-spacerun: yes;"> o = new Object()</span>
in C#) but via the DI framework. Because the code is injected, it has no way of knowing before hand what context (subject, object, etc.) applies so the authorization checks cannot be hard coded. Therefore, the code injected at runtime will perform authorization based on a coded set of rules that support the two (2) scenarios listed above. The format of choice can be any domain specific language of choice, e.g. XML based.
The strategy is summarized thus:
- Design object model for the system, i.e., entities, services and interfaces should be defined. This is like defining the wsdl file for webservices before the actual implementation.
- Configure dependency injection for every object that requires secure access.
- Develop code for authorization checks to be injected and rules format as per two scenarios.
- Configure interception for the objects to run authorization code whenever the methods are called in code.
- Define security rules that are to be evaluated at runtime when method call is intercepted.
- At runtime, instantiate the object using dependency injection and not in-built language instantiation.
- While attempting to make the call, interception invokes the code for authorization checks against the defined rules.
This seems like a fairly straightforward approach but as usual the devil is in the details. My own attempt at an implementation of this approach revealed the following areas requiring some planning and care.
- The format for defining security rules in terms of methods on objects and conditional expressions.
- The method for automatic DI configuration of objects.
- Developing the actual code which is to perform authorization checks at runtime. This needs to use a lot of reflection. (https://en.wikipedia.org/wiki/Reflection_(computer_programming))
- Deciding what frameworks to use for Dependency injection and Authentication.
This was an attempt to highlight the approach in broad strokes. Subsequently, I will dig deeper and provide details of an actual implementation.
The benefits of the approach include:
- It can assist with easier maintenance of security and business rules for different sites/installations of the same software.
- It fosters a separation of concerns, which allows different teams to focus on the different aspects of the system.
- It supports good programming practices such as DRY (Do not Repeat Yourself),
- Allows testing of system features in “totally unguarded” state on demand.
- The adopted security model can be easier to track and review as it is abstracted into its own repository and can be maintained in a more straightforward manner.
- Easier team management as developers can more easily handover activities to each other in event of an absence. Those working on features can see more clearly as they don’t have to worry about adding security.
- Supports different security models – e.g. transitive permissions: assign object permissions to a task/project and once people are assigned to that project they have the requisite permissions to the object
Nothing is without concerns and the following are some about this approach.
- How can the security rules be securely stored and retrieved at runtime? It would defeat the whole purpose if the rules could be easily hacked and modified after deployment. The rules can be encrypted in a file or hard coded into an assembly or DLL which can itself be obfuscated.
- Secured objects have to be created using dependency injection framework. Without creating the objects using DI, there is no interception possible. This is because the DI framework creates a sort of proxy object that allows it to manage the lifecycle of the object and method calls.
- Can this approach work for database procedures? Personally, I dislike putting business features in the database. I would rather use database queries just for data retrieval especially if some complex aggregation is required. That’s what databases are designed and optimized for, not necessarily for doing some complex XML manipulation. So database security using accounts the way it is to provide secure access to objects is fine by me.
- Security in the GUI. One might want to disable/hide buttons based on available permissions. This is still very possible leveraging on the strategy described. Security rules can be named and the access to widgets made based on the underlying Boolean condition of the security rule.
- Manage Permissions Priority: If multiple permission sets are assigned that are applicable to a subject, how do we handle priorities such that the effective permissions are either the most or least restrictive?
- The User’s profile should contain the necessary values needed to restrict parameter values: The subject’s profile must contain all the necessary information to make decisions and to set parameter values. For example, the user’s id, which will be used to query at runtime.
- What is the performance impact? I plan to run performance benchmarks for this approach in coming posts. However, there is a very useful and well-maintained set of benchmarks by Daniel Palme that shows the speeds of different frameworks for the underlying DI and interception scenarios at this link.
- Process Security. What does this approach do for the security of the process? Does it create any vulnerabilities that hackers can use to hijack the process and perform malicious actions? I don’t know of any vulnerabilities that would be as a result of this approach. Having said that, it doesn’t deter any efforts at further strengthening security from outside the process viz-a-viz mechanisms like Code Access Security for .NET, using special operating system accounts, obfuscation, etc.
As mentioned, I will make another post to demonstrate this in practical terms.
Thank you so much for reading. Also, thanks in advance for your comments.
History
- August 2016 - Initial version