Skip to main content

Morio Access Control Guide

Morio ships with coarse-grained role-bases access control () in place, but it also supports fine-grained attribute-based access control ().

This allows you to write access control policies (ABAC policies) that not only allow you to define precisely who can access what resources, but also allow you to make that decision not merely on the user’s role but also on other attributes, such as group membership, or which identity provider was used.

This guide explains how things work. Refer to Morio Settings: access for the reference documentation.

How access control is evaluated

  • ABAC policies are evaluated in order: Policies are parsed top-to-bottom as they appear in the configuration
  • First match wins: The first policy whose conditions match determines whether access is allowed or denied
  • Fallback to RBAC: If no policy matches, Morio falls back to the default policy

This evaluation model is similar to firewall rules, where order matters.

ABAC policy structure

An access policy is made up of one or more rules. You add these rules under the access key in the Morio settings.

Each rule has a name and consists of two parts:

  • A when block that determines when the rule matches
  • A then action that determines what to do when the rule matches
Morio Settings
access:
"An (incomplete) example rule":
when: # conditions go here
then: allow # or deny

The then action

The then action determines what happens when the policy matches. It holds a string that is one of:

  • allow : Grant access
  • deny : Deny access
Morio only checks for allow

Under the hood, Morio will deny access if then holds anything else than allow.

So you could use banana instead of deny and it would work just the same. We advocate for boring here, and recommend you use allow and deny.

The when Block

The when block defines the conditions that must be met for the rule to match.

Within the when block, all the listed conditions are evaluated using AND logic. In other words, every condition must match for the rule to match.

Morio supports the following conditions:

  • url - The requested URL path
  • method - The HTTP method (GET, POST, PUT, DELETE, etc.)
  • role - The user’s role
  • user - The username
  • provider - The authentication provider
  • label - User labels (such as group memberships)

You only need to specify the conditions you want to match. Conditions that are not specified will not prevent a match. For example, if you do not specify method than any method will match as long as the other conditions match.

Summary

So far we’re learned that a policy is made up of rules. Those rules have a then action and a when block that holds conditions.

The structure is outlined below:

Morio Settings
access: # ABAC policies go under the `access` key in settings
rule1: # This is our first rule
when:
condition1:
then: deny # This rule denies access when matched
rule2: # This is our second rule
when:
condition2:
condition3:
then: allow # This rule allows access when matched
rule3: # This is our third rule
when:
condition4:
condition5:
condition6:
then: allow # This rule allows access when matched

The only thing that we need to clarify if those conditions. We know that Morio supports url, method, role, user, provider, and label as conditions. Let’s use some examples to clarify how we actually match against these.

Basic Examples

note

Below, we are not showing the entire rule, instead only the condition as that’s what our focus is on.

Simple String Matching

The simplest condition is an exact string match:

Exact string match condition
method: GET
role: user
url: /example/url/path

These three examples all need to exactly match for the rule to match.

tip

Note that method should always be written in uppercase

Pattern Matching

For more flexible matching, the condition can be an object where the key is one of: is, startsWith, glob, or regex.

Hopefully their names are somewhat self-explanatory, but let’s look at each in detail:

Available Pattern Types

Exact match with is

Use is for an exaxt string match:

Exact match with: is
method:
is: GET
tip

Note that this is equivalent to:

method: GET

And so you may wonder what’s the point.

Using is allow you to use logical operators, whereas the simple string match does not.

Partial match with startsWith

Matching the start of a URL is a common pattern, for which you can use startsWith:

Partial match with: startsWith
url:
startsWith: "/some/prefix/"

Glob patterns with glob:

If you need a more complex match, you can use a glob pattern:

Glob patterm match with: glob
url:
glob: "/clients/*/details/"
tip

Note that the glob syntax mimics wildcards in shells, meaning:

  • ? matches a single character
  • * matches any character but not /
  • ** matches any character

That being said, tacking ** at the end won’t match more than one / deep in a glob pattern. So so startsWith for that, or reach for the power of regex.

Regular expressions with regex:

If you need even more complex matching, you can write a regular expression with regex:

Regular expression match with: regex
url:
regex: "^/api/(logs|metrics)/production"

Logical Operators

note

Note that withing a rule, all conditions are always AND-matched. These logical operators apply to multiple entries within the same condition.

OR Logic - Match Any

Use or when you want to match if any of the conditions’ entries:

OR logic
method:
or:
- is: GET
- is: POST

AND Logic - Match All

Use and when all conditions’ entries must be true:

AND logic
user:
and:
- startsWith: /products/
- is_not: /products/secret # See below for negation
note

Top-level conditions already use AND logic, so and is most useful within a single condition.

Negation - Does NOT Match

Add the _not suffix to negate any pattern or operator:

  • To negate is, use is_not
  • To negate startsWith, use startsWith_not
  • To negate glob, use glob_not
  • To negate regex, use regex_not

Pattern negation

Negating is: is_not
role:
is_not: user # Role is NOT user
Negating glob: glob_note
url:
glob_not: "/api/admin/*/details" # URL does NOT match this pattern

Operator negation

Using or_not
role:
or_not: # Does NOT have any of these roles
- is: engineer
- is: root
Using and_not
label:
and_not: # Does NOT have all of these labels
- is: "oidc/sso/group/restricted"
- is: "oidc/sso/status/suspended"

Working with Labels

Labels are particularly powerful for matching group memberships or other attributes from authentication providers:

label:
or: # Has ANY of these labels
- is: "oidc/sso/group/teams.it.support"
- is: "oidc/sso/group/teams.it.operators"

Since labels are arrays, the matching works as follows:

  • The user must have at least one label that matches the condition
  • Use or to match if the user has any of several labels
  • Use and to require the user has all specified labels

Complete Examples

Allow all users access to healtcheck data

A policy like this will allow all Morio users to check the healthcheck dashboards:

access:
boards_checks_get:
when:
method: GET
# Roles operator and up have access to this data via the default RBAC
# so we only need this policy to cover the user and manager roles
role: &notOperator # We're using a YAML anchor here
or:
- user
- manager
url:
or:
# Access to the glob search that lists healthchecks cache keys
# Note that URL matches need to be URL-encoded (this ends with: check|*)
- is: "/-/api/cache/glob/check%7C%2A"
# Access to hostname lookup based on an inventory UUID
- startsWith: "/-/api/inventory/hostnames/*"
# Access to the cached dataset for the healthcheck itself
- startsWith: "/-/api/cache/keys/check%7C"
then: allow

boards_checks_post:
when:
method: POST
# Access to the bulk prefixed healthchecks cache keys
url: "/-/api/cache/prefixed-keys/check%7C"
role: *notOperator # Yaml anchor
then: allow

Read only vs write permissions

A policy like this will give some users read-only access to the EdA service, while others can make changes.

access:
eda_ro:
# Allow all IT staff read-only access to the EdA service
when:
url:
startsWith: /eda
method: GET # This makes it read-only
label: oidc/sso/group/team.it
then: allow
eda_rw:
# Allow IT operators full access to the EdA service
when:
url:
startsWith: /eda
label: oidc/sso/group/team.it.operators
then: allow