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.
services:
example:
[...]
objectsetters:
foo:
enable: true
bar:
enable: true
[...]
#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
{
"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.
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:
priority of foo < 100, no priority at bar
priority of bar > 100, no priority at foo
explicitly set priorities of foo < bar
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.
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.
{
"com.example.policysets.default": {
"Type": "PolicySet",
"Description": "Default Policy Set",
"Target": "True",
"Policies": ["com.example.policies.default"],
"PolicySets": [],
"Resolver": "ANY",
"Obligations" : ["PrintStderr"]
}
}
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.
services:
example:
[...]
obligations:
PrintStderr:
printTries: true
[...]
@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