32

I am trying to create a flexible ACL framework in Java for my application.

Many ACL frameworks are built on a whitelist of rules, where a rule is in the form of owner:action:resource. For example,

  • "JOHN can VIEW resource FOOBAR-1"
  • "MARY can VIEW resource FOOBAR-1"
  • "MARY can EDIT resource FOOBAR-1"

This is attractive because the rules can easily be serialized/persisted to a database. But my application has complex business logic. For example,

  • "All users in department 1 with over 5 years of seniority can VIEW resource FOOBAR-1, else not authorized"
  • "All users in department 2, if the date is after 03/15/2016, can VIEW resource FOOBAR-2, else not authorized"

Upon first thought, it would be a nightmare to devise a database schema that could handle infinitely complex rules such as these. Therefore, it seems as though I would need to "bake" them into the compiled application, evaluate them for each user, and then produce owner:action:resource rules as a result of the evaluation. I want to avoid baking the logic into the compiled application.

So, I was thinking of representing a rule in the form of predicate:action:resource, where the predicate is a boolean expression that determines whether a user is allowed. The predicate would be a string of a JavaScript expression that could be evaluated by Java's Rhino engine. For example,

  • return user.getDept() == 1 && user.seniority > 5;

In doing so, the predicates could easily be persisted to the database.

Is this clever? Is this sloppy? Is this gimmicky? Is this over-engineered? Is this safe (apparently, Java can sandbox the Rhino engine).

10
  • 8
    What is the benefit of trying to push these business rules into a database over putting the logic into the compiled application? Mar 26, 2015 at 14:37
  • 6
    @WinstonEWert Externalizing the rules eliminates the need to recompile and redistribute the application should a rule be changed, added, or removed.
    – Twittopher
    Mar 26, 2015 at 15:24
  • 2
    @WinstonEwert related: Are too many if-else statements for validation bad?
    – user40980
    Mar 26, 2015 at 15:58
  • 2
    Interesting question! I'd like to see an answer that doesn't focus on security so much but rather on the maintenance, reliability and ease-of-use aspects of such a solution.
    – oliver
    Mar 26, 2015 at 17:45
  • 6
    This sounds similar to Outlook email rules which is essentially a rules engine that is configurable by the user.
    – user22815
    Mar 26, 2015 at 17:55

6 Answers 6

37

Piping dynamic data into an interpreter of your implementation language is usually a bad idea, since it escalates the potential for data corruption into a potential for malicious application takeover. In other words, you are going out of your way to create a code injection vulnerability.

Your problem can be better solved by a rules engine or maybe a domain-specific language (DSL). Look those concepts up, there is no need to reinvent the wheel.

11
  • 16
    But wouldn't JavaScript be used as a DSL-like scriping language here? We set up necessary data (read-only), wrap the snippet in a function, and safely evaluate it. Since the code can't do anything except return a boolean, there would be no malicious opportunities here.
    – amon
    Mar 26, 2015 at 14:17
  • 6
    @Twittopher Dragging an entire JavaScript engine to evaluate some predicates still seems like 1) complete overkill, 2) risky and 3) error-prone to me. Case in point, you used == instead of === in your example. Do you really want to provide turing completeness when all the rules should arguably always terminate? Instead of jumping through hoops to make sure all the interactions between Java and JavaScript are kosher, why don't you just write a simple parser and interpreter like Kilian suggested? It'll be much easier to tailor to your needs and secure. Use ANTLR or something.
    – Doval
    Mar 26, 2015 at 14:41
  • 6
    @Doval Writing a small DSL isn't exactly rocket science, and I could whip up a simple language in 3hrs to 5 days. But this seems like 1) complete overkill, 2) risky, and 3) error-prone to me. Do you really want to write a whole mini-language? What if some business rules are more complicated than expected and need a fully-featured language? Are you suffering from Not Invented Here-syndrome? Instead of reinventing the wheel, why don't you use an existing language? It's much easier to use a language that's already been battle-tested.
    – amon
    Mar 26, 2015 at 14:56
  • 14
    @amon When that happens, you find a real rules engine (which JavaScript is not) like Killian said. To equate the risks of both approaches is misleading. It only takes one omission to destroy all your efforts of securing an interpreter for a turing-complete language; it's a subtractive process. It's much harder to accidentally make a small DSL dangerous; it's an additive process. The kind of mistake you're likely to make is interpreting the syntax tree incorrectly, and that can be unit tested. You're probably not going to accidentally give the interpreter the ability format a hard drive.
    – Doval
    Mar 26, 2015 at 15:13
  • 4
    @amon: Even if the js snippet might not have side effects, it could choose to not return a boolean value: while (true) ;
    – Bergi
    Mar 26, 2015 at 20:16
44

I did this, and I recommend that you don't.

What I did was write all the business logic in Lua, and stored that Lua script in a database. When my application started up it would load and execute the script. That way I could update the business logic of my application without distributing a new binary.

I invariably found that I always needed to update the binary when making changes. Some changes were in the Lua script, but I'd invariably have a list of changes that needed to be made, and so I almost always ended up having to make some changes in the binary and some changes in the Lua script. My imagination that I could avoid distributing binaries all the time simply didn't pan out.

What I found much more helpful was to ease the distribution of binaries. My application automatically checks for updates on startup, downloads, and installs any update. My users are thus always on the latest binaries that I've pushed. There is almost no difference between a change in the binary and a change in the scripts. If I did it again, I'd put even more effort to making the update seamless.

3

I would not have the database contain code. But you can do something similar by having the database contain function names and then using reflection to call them. When you add a new condition, you have to add it to your code and your database, but you can combine conditions and parameters that get passed to them to create quite complex evaluations.

In other words, if you have numbered departments, it would be easy to have a UserDepartmentIs check and a TodayIsAfter check and then combine them to have a Department=2 and Today>03/15/2016. If you then want to have a TodayIsBefore check so that you can end date the permission, you have to write the TodayIsBefore function.

I haven't done this for user permissions, but have done it for data validation, but it should work.

2

XACML is the solution you are really looking for. It is a type of rules engine that is focused on access control only. XACML, a standard defined by OASIS, defines three parts:

  • an architecture
  • a policy language (which is really what you want)
  • a request / response scheme (how you ask for an authorization decision).

The architecture is as follows:

  • the Policy Decision Point (PDP) is the core part of the architecture. It is the component that evaluates incoming authorization requests against a known set of policies
  • the Policy Enforcement Point (PEP) is the piece of code that protects your application / API / service. The PEP intercepts the business request, creates a XACML authorization request, sends it off to the PDP, receives a response back, and enforces the decision inside the response.
  • the Policy Information Point (PIP) is the component that can connect the PDP to external sources of data e.g. an LDAP, a database or a web service. The PIP comes in handy when the PEP sends a request e.g. "Can Alice view doc #12?" and the PDP has a policy that requires the user's age. The PDP will ask the PIP "give me Alice's age" and will then be able to process the policies.
  • the Policy Administration Point (PAP) is the place where you manage the entire XACML solution (defining attributes, writing policies, and configuring the PDP).

eXtensible Access Control Markup Language - XACML Architecture

Here's what your first use case looks like:

/*
 * All users in department 1 with over 5 years of seniority can VIEW resource FOOBAR-1, else not authorized
 * 
 */
 policy departmentOne{
    target clause department == 1
    apply firstApplicable
    /**
     * All users in department 1 with over 5 years of seniority can VIEW resource FOOBAR-1, else not authorized
     */
    rule allowFooBar1{
        target clause resourceId=="FOOBAR-1" and seniority>=5 and actionId=="VIEW"
        permit
    }
    rule denyOtherAccess{
        deny
    }

 }

Your second use case would be:

 /*
  * "All users in department 2, if the date is after 03/15/2016, can VIEW resource FOOBAR-2, else not authorized"
  *  
  */
  policy departmentTwo{
    target clause department == 1
    apply firstApplicable
    rule allowFooBar2{
        target clause resourceId=="FOOBAR-1" and seniority>=5 and currentDate>"2016/03/15":date and actionId=="VIEW"
        permit
    }
    rule denyOtherAccess{
        deny
    }
  }

You can combine both use cases into a single policy by using references:

  policyset global{
    apply firstApplicable
    departmentOne
    departmentTwo
  }

And you're done!

You can read more on XACML and ALFA from:

0

What you really want here is XACML. It pretty much gives you exactly what you want. You don't necessarily have to implement the full architecture with all the roles being completely separated... if you only have a single application, you can probably get away with integrating the PDP and PEP into your app with balana and the PIP is whatever your existing user database is.

Now, anywhere in your app you need to authorize something, you create a XACML request which has the user, the action, and the context, and the XACML engine will make the decision based on the XACML policy files you've written. These policy files could be kept in the database or on the filesystem, or wherever you want to keep the configuration. Axiomatics has a nice alternative to the XACML XML representation called ALFA that's a little easier to read than the raw XML, and an Eclipse plugin to generate XACML XML from ALFA policies.

2
  • 1
    how does this answer the question asked?
    – gnat
    Mar 28, 2015 at 5:33
  • He's specifically trying to implement an externally configured authorization system. XACML is a ready to go externally configured authorization system that covers his specific use case very well. I'll admit that it may not be a great answer to the more general question of dynamic code execution, but it is a good solution to his specific question.
    – gregsymons
    Mar 30, 2015 at 14:04
0

We did this at my current company, and we're very happy with the results.

Our expressions are written in js, and we even use them to restrict the results the users can get from querying ElasticSearch.

The trick is making sure that enough info is available to make the decision, so that you can really write whatever perms you want without code changes, but at the same time keeping it fast.

We are not really worried with code injection attacks, as the permissions are written by those that have no need to attack the system. And the same applies to DOS attacks like the while(true) example. The admins of the system have no need to do that, they could just remove everybody's permissions...

Update:

Something like XACML seems to be better as a central auth management point for an organization. Our use case is slightly different in that our clients tipically have not an IT department to run all that. We needed something self-contained but tried to preserve as much flexibility as possible.

Not the answer you're looking for? Browse other questions tagged or ask your own question.