Coldbox and VueJS untangled

Cbsecurity (3): Security rules

In my previous post I explained cbauth based authentication combined with annotation based security. Annotations are easy to understand, so good as a starting point, but if you need something more flexible you need security rules. So when would you need security rules?

  • security rules can be stored inline in the rules key of cbsecurity in your configuration file, but also in a database, JSON or XML file or by generating your own array of rules in a model. If you don’t want to define your rules inline you have to specify rules = "db", rules="model", rules="json" or rules = "xml" instead of an inline array. For all external options you need to configure additional keys which help you to retrieve your rules from the correct location. These options give you all the flexibility to define your rules on the fly based on certain conditions. Think about denying people access to sensitive parts of your app if they don’t have 2 factor authenticiation enabled, or at certain times of day. Possibilities are endless, especially with the model approach.
  • if you store your rules in a database or file you can allow editing your rules by your (admin) users from within your application.
  • rules give more flexibility in what to do when a certain event is not allowed. For each rule you can change your action (redirect or override) and change the redirection or override target. In annotation based security you can only specify default targets, in rules the targets and action are checked and only when not specified it falls back to the defaults.
  • in rules you can specify if you want to protect an URI or an event. Events are the default. In annotation based security you can only protect your handlers and actions.

Properties of a rule

A rule can have many properties, as shown here:

{
    "whitelist"     : "", 
    "securelist"    : "", 
    "match"            : "event", 
    "roles"            : "", 
    "permissions"    : "", 
    "redirect"         : "", 
    "overrideEvent"    : "", 
    "useSSL"        : false, 
    "action"        : "", 
    "module"        : ""
};

but there is only one property required: secureList.

secureList and whitelist

In the secureList parameter you can specify which event(s) (or URI) you want to protect. You can add multiple events in a comma delimited list, but if you add to many entries to your list it may obscure your overview. Each part of your list can be a a simple event or a regular expression (the default). You can change this behaviour in your configuration with cbsecurity.useRegex= (true|false).

The whiteList parameter might be confusing at first sight. Assume you have three handlers Main, Secured and Users and you want to only protect all Secured and Users actions, except the Users.Login action. So if I create this rules, what would you expect?

{
    "whitelist"     : "Main.*,^Users.Login", 
    "securelist"    : "Users.*,Secured.*",
    "permissions"   : "somePermission,anotherPermission"
};

There are several issues with this example. First of all, my secureList is protecting Users.*, but also UnsecuredUsers, UsersInSecure etcetera. This is because this is regex matching, very handy, very powerful, so use these powers correctly. If you only want to protect ALL Users actions and nothing else, you should use ^Users\..* which means: Starting with (^), a literal dot (\.) and any action behind this literal dot (.*). Same is true for the whitelist. If you want to protect Users.login and nothing else, it is ^Users\.Login$. The last issue is with the Main.* in the whitelist. It is completely useless. If you would assume everything Main.* in your app is whitelisted now I have to disappoint you. The whitelist is only for the current rule, and when matched our security validation just continues processing the next rule. Since Main.* is not matching our secureList anyway, the same thing would happen without this whitelist entry: no match -> next rule! Another way to write these rule would be:

[
{
    "whitelist"     : "^Users\.Login$", 
    "securelist"    : "^Users\..*",
    "permissions"   : "somePermission,anotherPermission"
},
{
    "securelist"    : "^Secured\.*",
    "permissions"   : "somePermission,anotherPermission"
}
]

This is a bit longer but easier to understand. In this example I split the rule in two rules, cleaned up the whitelist and specified ^Users.Login$ exactly.

match

The match property tells you if you want to match the event or the URI. If you don’t specify it in your rule the default setting from cbsecurity will be used. If you don’t specify it in your cbsecurity settings the event will be matched. So what’s better? I always use event matching. If you have two different URI’s leading to the same event, the event is still protected. But it is up to you what’s most appropriate for your situation.

roles and permissions

In your rules you can specify roles and permissions. The default CFAuth validator only operates on permissions, so if that’s your setup you shouldn’t use roles. The CF validator is based on role-based Coldfusion security, so in this case you shouldn’t use permissions. If you create your own Custom Security validator you can use permissions or roles, you could even think of scenario’s where you could use both.

redirect, overrideEvent, action and useSSL

When redirect is specified, your invalid event or URI will be redirected to the specified event. When overrideEvent is defined, the current event will be overridden by the specified overrideEvent. overrideEvent is very useful in API scenarios where you don’t want to redirect to another event, but just want to override your response with a nice error response.
If both are not specified action can specify it there will be an override or redirect.
If useSSL is true, the action will be forced to use SSL.

Processing your rules

For now we just leave the source of our rules for later. But if you have a set of rules, how are the processed? Your rule set is an array of rules, and each individual rule is processed in the order in which your rules are retrieved, just like in most firewalls. This diagram pictures this flow:

Rules processing

Starting with rule 1, the event will be matches against the whitelist property. if there is a match the rules process will continue with the next rule. If there is no whitelist match and no secureList match it will also continue with the next rule. If there is a secureList match the Validator will check if a user is authenticated. If the user is authenticated the next check will be executed to find if a user is authorized. If this last check is true access will be granted.

It is important to realize this flow. If there is a match on a whitelist, the following rule will be checked, so no access will be granted yet, unless this was the last check. If there is a match on secureList processing of the whole rule array will be stopped and only the current rule will be further evaluated. This will ALWAYS lead to an INVALID AUTHORIZATION, INVALID AUTHENTICATION or ACCESS granted.

The order of your rules

From the above diagram we can conclude the order of your rules is important. Let’s say you have a rule with secureList = “^Secured\..*” and permissions=”notForEveryOne”, followed by a securelist = “^Secured\.supersecretAction$” and permissions=”onlyForTheBoss”. If this is the rule order, the second order will NEVER be hit and the superSecretAction is not so secret anymore because it already matches on the first action. This can be solved in two different ways. The easiest way is to reverse the order of these two rules. The second way is to add whitelist = “^Secured\.supersecretAction$” to the first action. I am asking myself if this last way is very intuitive by adding a whitelist for something very secure to something less secure. I would prefer the correct processing order, because it is cleaner. You just have to makes sure your rules are loaded in the right order which is trivial for most use cases. Only if you store your rules in a database you might need some extra precautions to make sure they load in the right order.

Catch all rule

What if you want to make sure everything is secured, except for a few pages (such as the login page? If you forget to secure some pages with a rule, they will be accessible for anyone, unless you define a catch-all rule. This rule looks simular to this one:

{
  secureList  = ".*",
  permissions = "NONEXISTING_PERMISSION",
  whitelist   = "^User\.login$,^Public\.somethingPublic$"
}

This rule will be skipped for the whiteList, but will match for every other event or URI. Since the permissions don’t exist it will always deny access. Just makes sure nobody can throw away this rule by accident. Although we used user-editable rules we always processed our rules afterwards to make sure this last rule was appended.

Where to store my rules

Cbsecurity gives you plenty of options to store your rules. You can specifiy the inline by adding an array of rules to the rules key. If you don’t want to define your rules inline you have to specify rules = "db", rules="model", rules="json" or rules = "xml" instead of an inline array. For all external options you need to configure additional keys, but please don’t forget the rules key, or your rules will not be loaded from the correct location.

Declared inline in your config/Coldbox.cfc

If you just have a small ruleset, this is an easy place to store your rules. In v2 of cbsecurity you only have the specify the rule properties which are relevant, so you could have a simple set of rules in case of simple secure validation requirements

A JSON file or an XML file

A JSON file is not much different from a inline config. The only advantage is that it is easier to retrieve your rules from external sources without merging them in a coldbox config.

XML files are for eh…. XML lovers? Wonder if many people are still using this option, but since cbsecurity is an old module it probably has its use cases.

From a model object via a method call

This is the most flexible option. Just grab your rules from any external place, a database, a cache or whatever. Add hardcoded rules so nobody forgets your well needed catch all rule, or modify your rule order in any imaginable way. Personally I would never use the database option when I have the model object options available, because it is more flexible.

From a database

You can create a table with the same fields as your rules properties, or create some SQL statement to generate your rules. Everything you can do with a model object, but maybe you like this better. You have to realize rule order is important. So don’t forget to add some priority field to your database and sort by configuring the rulesOrderBy property.

Declared inline in ANY module’s ModuleConfig.cfc

This is new in cbsecurity 2.x. You can define global rules, but for each module you can define additional rules in the module itself.

Summary

So to summarize:

  • Rules are more flexible than annotation based security
  • Rules can be defined in many different ways. But order is important
  • Rules have many properties, but you don’t have to define them all for each rule.

I hope you can tame the rules engine of cbsecurity now. Once you get the basics it is not that hard. Please stay tuned for my next cbsecurity post wich will handle JSON Web Tokens! And I welcome all comments, suggestions and improvements for this post.

1 Comment

  1. Daniel Mejia

    heard about your blog post on ModernizeOrDie podcast today, and your post is good. I’ve bookmarked it so I can reference later when I redo a couple websites. Thank you.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

© 2024 ShiftInsert.nl

Theme by Anders NorenUp ↑