Using role based access controls to restrict resource usage

RBAC

Preliminaries

This note describes the Role Based Access Control (RBAC) mechanism of the server. Access to resources are allowed or denied based on resource patterns and request methods matched against user-assigned roles.

Websites often need to restrict access to portions of the public document area based on a user's assigned privileges. The server's Role Based Access Control (RBAC) module provides a way to do that.

Requirements

These are the working assumptions for the RBAC module: 1) there must be a way to assign access rules to resources; 2) there should be enough flexibility in the rules to accommodate groups of similar resources; 3) there should be enough specificity in the rules to allow separate access permissions based on whether the resource is being retrieved or modified; 4) there should be enough expressiveness in the system to define similar rules to users with similar needs; 5) there should be a way to allow anyone to access designated public areas of the website.

Briefly, these requirements are met in the following ways:

# Requirement Fulfillment
1 Assign access rules Assignment is done by the webmaster using the rbac configuration section.
2 Groups of similar resources Rules are defined with wildcard-based resource path-patterns.
3 Retrieval vs. Modification Rules are associated with HTTP methods to stratify permission levels.
4 Similar user needs Users are granted access to resources via roles.
5 Designated public areas The "anonymous" role may be granted access to public resources.

Definitions

In this specification a role is simply a name that is used to connect resources that have similar purposes with users who have similar needs. Role names are not prescribed by the server; it is up to the webmaster to determine which roles are useful and to provide an identifying name. The only exception to this is the "anonymous" role, which has special usage that is described in this note. Generally a few simple rules are all that are needed. For a publish/subscribe type of website, a good start might be to define the roles "subscriber", "author", "editor" and "devops".

A resource pattern is a text value that is used to identify similar files based on the leading portion of a filename. Patterns may contain special wildcards symbols * and ? to match arbitrary portions of a filename. For example, the pattern `/img/*.jpeg` identifies all JPEG files located in the public document /img directory.

A method is an HTTP method sent by the requestor to access a resource.

A resource/method pair is the combination of a resource pattern and an HTTP method. Privileges are always configured based on the combination of these two.

Access Privileges

Privileges are defined by role, and are always specified in the affirmative, that is, when a role is added to a resource/method's access list it is allowed to perform the requested action on the resource. Conversely when a role is omitted from a resource/method's access list it is not allowed to perform the requested action on the resource. Privileges are never specified in the negative.

When processing a request, the server scans the resource/method access table to find the first pair that matches the request's resource and method, then examines the list of configured roles for that pair, and determines if any of them match any role assigned to the user. If there is a match, the resource processing continues. When there isn't a match, resource processing is interrupted, and the response generates a status code 403 with a rw-rbac-no-matching-role information header.

The special "anonymous" role may be applied to a resource/method pair to indicate that anyone may perform the requested method on the resource. At a minimum, GET requests made to a website's landing page will almost always be granted anonymous access.

If the resource/method access table is scanned and no pair matches the request, the server generates a status code 403 with a rw-rbac-no-resource-rule information header. Because of this there is no default anonymous access; every resource must be explicitly configured to match a resource/method pair or it will be unreachable.

Prerequisites

There are a some prerequisites, beyond configuring and enabling the RBAC module, that are necessary in order to support role based access: 1) a way to create user accounts and assign roles to them; 2) a way to authenticate users; 3) a way to preserve a user's designated roles between separate, stateless, HTTP requests.

These are explained in separate notes:

Configuration

Configuring the server to use Role Based Access Control entails:

  1. enabling the module;
  2. routing login/logout requests to the RBAC Auth plugin;
  3. configuring the server to prevent unauthorized privilege escalation;
  4. defining resource patterns that separate files into groups that have homogeneous privilege needs;
  5. refining the patterns to further separate resources based on request method (whether they are being being retrieved or modified);
  6. assigning roles to resource/method pairs.

1. Enabling RBAC Module

The rbac module is used to make full use of the server's Role Based Access Control feature. It must be enabled in the configuration, like this:

host {
modules {
rbac on
}
}

2. Routing login/logout requests

Requests to authenticate are made to the built-in "rwserve-rbac-auth" plugin, which has no predefined address; rather, the webmaster should establish a virtual resource path to the module that fits idiomatically with the web site's layout. There are no restrictions or suggested conventions on its name. More details regarding the Router are available in a separate note. Here is what a typical configuration could look like:

host {
plugins {
router {
`/login-logout/*` *methods=POST *plugin='rwserve-rbac-auth'
}
}
}

3. Preventing privilege escalation

As described in the note on stateful roles, the rw-rbac cookie employs the AES-192 symmetric encryption algorithm which requires a cipher key that is unforgeable, but forgettable, that is, the webmaster can set it once and not think about it again. The cipher-secret entry is used for this. It should never be exposed to the public and never added to a git repository.

The max-idle entry should be configured with some reasonable number of seconds.

Finally, the location of the roles file (created and used by the addrole CLI utility) should be specified using the roles entry. This entry should contain the fully qualified, absolute path to the roles file. That file should be granted read permission by the rwserve user (which is the standard Linux process owner for the server, when using the systemd service manager). Be sure to delimit this path by enclosing it in GRAVE-ACCENTs.

Here is a configuration showing possible values for these three entries:

host {
rbac {
roles `/etc/rwserve/roles` // the file created by the 'addrole' CLI utility
cipher-secret C#9fB$2gD@5zR*7e // secret used to encrypt the 'rw-roles' cookie
max-idle 1800 // number of seconds of inactivity before credentials expire
}
}

4. Defining resources

Designing a website's directory structure is entirely up to the webmaster; the RBAC module has enough flexibility to accommodate any approach. For the purpose of demonstration only, so that different access scenarios can be explored, consider the following directory structure:

/public
/free-pages
/subscriber-area
/author-area
/media
/img
/audio
/video
/css
/js

Also, consider a set of roles such that different privileges are granted to subscribers, authors, editors, and devops.

If the general work-flow is to have authors place works under revision in the author-area, and the editor to move finished works to the subscriber-area, and to allow any anonymous requestor to be able to read documents in the free-pages directory, then the definition of which roles to assign to which directories is initially fairly obvious. Assume that the directories containing images, audio, video, style sheets and scripts should be readable by anyone.

The rbac section contains a subsection labeled resources which is used to configure resource/method pairs and their assigned roles. It comprises a collection of entries, where each entry contains a path-pattern, a *methods attribute, and a *roles attribute.

Refer to the separate note regarding Path Pattern rules.

A first pass at configuring the resources section (without considering the methods or roles attributes), might look like this:

host {
rbac {
resources {
`/login-logout/*` *methods=TODO *roles=TODO
`/free-pages/*` *methods=TODO *roles=TODO
`/subscriber-area/*` *methods=TODO *roles=TODO
`/author-area/*` *methods=TODO *roles=TODO
`*` *methods=TODO *roles=TODO
}
}
}

Two things stand out in this sample. First the /login-logout directory is a virtual resource path; it is not really part of the public directory structure, rather, it is under the control of the Router module, and is used as a dynamic route to the RBAC Auth plugin, as discussed in that note.

The second thing to note is that the media, styles sheets and scripts are not listed explicitly; instead, they are grouped together using the asterisk wildcard '*`. This is a good technique for reducing the number of explicit configuration entries; just remember that this asterisk wildcard by itself may occur only at the end of the list.

5. Refining methods

The next configuration step is to refine the basic resource patterns by splitting them into read-only and write-only access groups, as needed. This is done using the *methods attribute, which accepts a comma-separated list of HTTP methods. The sample configuration might be refined into something like this:

host {
rbac {
resources {
`/login-logout/*` *methods=POST *roles=TODO
`/free-pages/*` *methods=GET,HEAD *roles=TODO
`/free-pages/*` *methods=PUT,DELETE *roles=TODO
`/subscriber-area/*` *methods=GET,HEAD *roles=TODO
`/subscriber-area/*` *methods=PUT,DELETE *roles=TODO
`/author-area/*` *methods=GET,HEAD *roles=TODO
`/author-area/*` *methods=PUT,DELETE *roles=TODO
`*` *methods=GET,HEAD *roles=TODO
`*` *methods=PUT,DELETE *roles=TODO
}
}
}

Most of the resource patterns have simply been duplicated into read-only (GET,HEAD) and write-only (PUT,DELETE) access, in anticipation of the final roles assignments. The only exception to this, in this scenario, is the /login-logout resource, which is restricted to the POST method only.

6. Assigning roles

The proposed scenario calls for everyone to have read access to the free-pages, so *roles=anonymous is appropriate for the GET,HEAD pair; and since the editor and the devops staff should both be allowed to move fresh pages into that directory, and remove old pages from it, the configuration *roles=editor,devops is appropriate for the PUT,DELETE pair.

The media, style sheets and scripts directories should follow the same rules.

The subscriber-area is configured with the same analysis, except read-only access should be granted to *roles=subscriber.

The author-area should be completely available to allow authors to read and write documents, plus editors and devops staff should be able to maintain everything as well, so the final configuration can be collapsed into a single entry, instead of two, and configured with *methods=GET,HEAD,PUT,DELETE and *roles=author,editor,devops.

Finally, since everyone starts with anonymous access until they are logged in, the /login-logout virtual resource path must allow POST access to *roles=anonymous.

The resources configuration with roles will look something like this:

host {
rbac {
resources {
`/login-logout/*` *methods=POST *roles=anonymous
`/free-pages/*` *methods=GET,HEAD *roles=anonymous
`/free-pages/*` *methods=PUT,DELETE *roles=editor,devops
`/subscriber-area/*` *methods=GET,HEAD *roles=subscriber
`/subscriber-area/*` *methods=PUT,DELETE *roles=editor,devops
`/author-area/*` *methods=GET,HEAD,PUT,DELETE *roles=author,editor,devops
`*` *methods=GET,HEAD *roles=anonymous
`*` *methods=PUT,DELETE *roles=editor,devops
}
}
}

EBNF

SP ::= U+20
CR ::= U+0D
ASTERISK ::= U+2A
COMMA ::= U+2C
EQUALS-SIGN ::= U+3D
QUESTION-MARK ::= U+3F
SOLIDUS ::= U+2F
GRAVE-ACCENT ::= U+60
LEFT-CURLY-BRACKET ::= U+7B
RIGHT-CURLY-BRACKET ::= U+7D
visible-ascii ::= U+21..U+7E
file-system-chars ::= (ALPHA | DIGIT | )*
roles-file-entry ::= 'roles' SP GRAVE-ACCENT file-system-chars GRAVE-ACCENT CR
cipher-secret-entry ::= 'cipher-secret' SP visible-ascii* CR
max-idle-entry ::= 'max-idle' SP (0..9)* CR
wildcards ::= ASTERISK | QUESTION-MARK
path-pattern ::= (SOLIDUS | file-system-chars | wildcards)*
delimited-path-pattern ::= GRAVE-ACCENT path-pattern GRAVE-ACCENT
method-name ::= 'HEAD' | 'GET' | 'PUT' | 'DELETE' | 'POST' | 'PATCH' | 'OPTIONS' | 'TRACE'
methods-attribute ::= ASTERISK 'methods' EQUALS-SIGN (method-name COMMA)*
roles-name ::= (ALPHA | DIGIT)*
roles-attribute ::= ASTERISK 'roles' EQUALS-SIGN (roles-name COMMA)*
resources-entry ::= delimited-path-pattern SP methods-attribute SP roles-attribute CR
resources-subsection ::= 'resources' SP LEFT-CURLY-BRACKET CR
resources-entry*
RIGHT-CURLY-BRACKET CR
rbac-section ::= 'rbac' SP LEFT-CURLY-BRACKET CR
roles-file-entry
cipher-secret-entry
max-idle-entry
resources-subsection
RIGHT-CURLY-BRACKET CR

† Legal file system characters vary by platform

Cookbook

Example: A complete RBAC setup
host {
modules {
rbac on
}
plugins {
router {
`/login-logout/*` *methods=POST *plugin='rwserve-rbac-auth'
}
}
rbac {
roles `/etc/rwserve/roles` // the file created by the 'addrole' CLI utility
cipher-secret C#9fB$2gD@5zR*7e // secret used to encrypt the 'rw-roles' cookie
max-idle 1800 // number of seconds of inactivity before credentials expire
resources {
`/login-logout/*` *methods=POST *roles=anonymous
`/free-pages/*` *methods=GET,HEAD *roles=anonymous
`/free-pages/*` *methods=PUT,DELETE *roles=editor,devops
`/subscriber-area/*` *methods=GET,HEAD *roles=subscriber
`/subscriber-area/*` *methods=PUT,DELETE *roles=editor,devops
`/author-area/*` *methods=GET,HEAD,PUT,DELETE *roles=author,editor,devops
`*` *methods=GET,HEAD *roles=anonymous
`*` *methods=PUT,DELETE *roles=editor,devops
}
}
}

Review

Key points to remember:

  • Resources are assigned permissions based on the combination of resource path-patterns and request methods.
  • Users are granted access privileges when their role matches a resource/method role.

Using role based access controls to restrict resource usage