How-to write plugins

General settings

The plugin directory must be set in the configuration. Suppose you want your plugins at /opt/arpoc-plugins. Then your configuration file must include:

1
2
3
misc:
  plugin_dirs:
    - /opt/arpoc-plugins

Environment Plugin

In the AC entities you want to use environment.foo. In our simple example this just returns bar, but it can be anything. Place the file inside the plugin directory. Note that the filename must end with .py. The class of your plugin can be choosen freely, but it must inherit from arpoc.plugins._lib.EnvironmentAttribute. The target variable is the attribute key (after environment.). On access, the run() is executed.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#filename: /opt/arpoc-plugins/env_dummy.py
from typing import Any
from arpoc.plugins import _lib

class EnvFoo(_lib.EnvironmentAttribute):
    """ Returns bar for environment.foo """

    target = "foo"

    @staticmethod
    def run() -> Any:
        return "bar"

Object Plugin

We want to show two dependent object setters here. Remember, object setters don’t set a single variable but can change the entire objects dictionary. We use the object setters foo and bar. First, we must enable them in the configuration of the service. So for the service example, you need this configuration.

Basic configuration file for the objectsetters
 services:
   example:
     [...]
     objectsetters:
       foo:
         enable: true
       bar:
         enable: true
     [...]
Python Code for the object setters
#filename: /opt/arpoc-plugins/obj_dummy.py
from typing import Any, Dict

from arpoc.plugins import _lib



class ObjFoo(_lib.ObjectSetter):
    """ Executes the object setter foo """

    name = "foo"

    def __init__(self, cfg: Dict) -> None:
        super().__init__(cfg)
        self.cfg = cfg

    def run(self, data) -> Any:
        if "foobar" in self.cfg and self.cfg['foobar']:
            data['foobar'] = True
        data['foo'] = True
        return data

class ObjBar(_lib.ObjectSetter):
    """ Executes the object setter bar """

    name = "bar"
    def __init__(self, cfg: Dict) -> None:
        super().__init__(cfg)
        self.cfg = cfg

    def run(self, data) -> Any:
        if 'foo' in data and data['foo']:
            data['bar'] = True
        return data
Access Control Entities for the object setters.
{
    "com.example.policysets.default": {
             "Type": "PolicySet",
             "Description": "Default Policy Set",
             "Target": "True",
             "Policies": ["com.example.policies.default"],
             "PolicySets": [],
             "Resolver": "ANY",
             "Obligations" : []
    },
    "com.example.policies.default": {
             "Type": "Policy",
             "Description": "Default Policy",
             "Target" : "True",
             "Rules" : [ "com.example.rules.foo","com.example.rules.foobar","com.example.rules.bar" ],
             "Resolver": "AND",
             "Obligations" : []
    },
    "com.example.rules.foo" : {
             "Type": "Rule",
             "Target": "True",
             "Description": "Default Rule",
             "Condition" : "object.foo",
             "Effect": "GRANT",
             "Obligations" : []
    },
    "com.example.rules.foobar" : {
             "Type": "Rule",
             "Target": "object.path startswith 'foobar'",
             "Description": "Default Rule",
             "Condition" : "object.foobar",
             "Effect": "GRANT",
             "Obligations" : []
    },
    "com.example.rules.bar" : {
             "Type": "Rule",
             "Target": "object.path startswith 'bar'",
             "Description": "Default Rule",
             "Condition" : "object.bar",
             "Effect": "GRANT",
             "Obligations" : []
    }
}

With this configuration you can access the paths foo, bar and foobar and the plugins are executed. With foo the access will be granted, but bar depends that the foo is executed beforehand. This is handled by priorities. foobar is only set if the configuration is set accordingly. We show this in the next two sections.

Priorities

bar must be executed after foo. To ensure that, you can set the priorities in the configuration file. Plugins with a lower priority will run first. The default priority is 100. We show now a negative example.

bar runs now before foo, access will be forbidden
 services:
   example:
     [...]
     objectsetters:
       foo:
         enable: true
       bar:
         enable: true
         priority: 99
     [...]

Of course all ways to set the priority of foo lower than bar are allowed:

  1. priority of foo < 100, no priority at bar

  2. priority of bar > 100, no priority at foo

  3. explicitly set priorities of foo < bar

bar runs now after foo, access will be granted
 services:
   example:
     [...]
     objectsetters:
       foo:
         enable: true
         priority: 98
       bar:
         enable: true
         priority: 99
     [...]

Plugin Configuration

The configuration can be used for any configuration of the object setters. Every key besides enable and priority can be used. Our plugin uses the key foobar.

Configure foo to also set foobar
 services:
   example:
     [...]
     objectsetters:
       foo:
         enable: true
         priority: 98
         foobar: true
       bar:
         enable: true
         priority: 99
     [...]

Obligation Plugin

The obligations are executed after the access control evaluation and before the delivery to the user. The obligations are executed when an access control entity lists the obligation and the entity is evaluated and the target specifier evaluated to true. It does not depend on the entity condition evaluation.

The obligations are executed without any sorting. The obligation plugin is executed with the access control decision, the evaluation context and its configuration in the configuration file.

Example

Suppose you want to print to stderr if the access was granted. We will build the PrintStderr obligation plugin.

Access Control Entities with obligations.
{
    "com.example.policysets.default": {
             "Type": "PolicySet",
             "Description": "Default Policy Set",
             "Target": "True",
             "Policies": ["com.example.policies.default"],
             "PolicySets": [],
             "Resolver": "ANY",
             "Obligations" : ["PrintStderr"]
    }

 }
Obligation Plugin.
from typing import Any, Dict
import sys

from arpoc.plugins import _lib
from arpoc.ac.common import Effects


class OblFoo(_lib.Obligation):
    """ Executes the object setter foo """

    name = "PrintStderr"

    @staticmethod
    def run(effect, context: Dict, cfg: Dict) -> bool:
        if effect == Effects.GRANT:
            print("Access was granted", file=sys.stderr)

        return True

Configuration & Context

We will alter the example, so that it uses the evaluation context and is configurable. In our example, users with admin as preferred username will not be logged. Denied accesses will be logged if configurred that way.

Configure PrintStderr to also print access tries.
 services:
   example:
     [...]
     obligations:
       PrintStderr:
         printTries: true
     [...]
Obligation Plugin.
 @staticmethod
 def run(effect, context: Dict, cfg: Dict) -> bool:
     try:
         if context['subject']['preferred_username'] == 'admin':
             return True
     except KeyError:
         pass

     if effect == Effects.GRANT:
         print("Access granted", file=sys.stderr)
     elif "printTries" in cfg and cfg['printTries']:
         print("Access was tried, but denied", file=sys.stderr)

     return True