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:
- enabling the module;
- routing login/logout requests to the RBAC Auth plugin;
- configuring the server to prevent unauthorized privilege escalation;
- defining resource patterns that separate files into groups that have homogeneous privilege needs;
- refining the patterns to further separate resources based on request method (whether they are being being retrieved or modified);
- 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.