$\newcommand{\true}{\mathsf{true}} \newcommand{\false}{\mathsf{false}} \newcommand{\grant}{\mathtt{grant}} \newcommand{\deny}{\mathtt{deny}} \newcommand{\ifrule}{\mathtt{if}} \newcommand{\elserule}{\mathtt{else}} \newcommand{\cond}{cond} \newcommand{\guard}{guard} \newcommand{\undefined}{\mathtt{undef}} \newcommand{\conflict}{\mathtt{conflict}} \newcommand{\decision}[1]{\mathtt{decision}\!\left({#1}\right)} \newcommand{\dec}{dec} \newcommand{\policy}{pol} \newcommand{\eval}{\mathtt{eval}} \newcommand{\casepolicy}{\mathtt{case}} \newcommand{\goc}[1]{\mathsf{GoC}(\vphantom{g_{-}}{#1})} \newcommand{\doc}[1]{\mathsf{DoC}(\vphantom{g_{-}}{#1})} \newcommand{\gobl}[1]{\mathsf{GObl}(\vphantom{g_{-}}{#1})} \newcommand{\dobl}[1]{\mathsf{DObl}(\vphantom{g_{-}}{#1})} \newcommand{\TrueGuard}[1]{\mathsf{T}(\vphantom{g_{-}}{#1})} \newcommand{\ReachGuard}[1]{\mathsf{R}(\vphantom{g_{-}}{#1})} \newcommand{\obligation}[3]{\mathsf{oblg}(\vphantom{g_{-}}{#1},\,{#2},\,{#3})} \newcommand{\obligationguard}[3]{\mathsf{oblg'}(\vphantom{g_{-}}{#1},\,{#2},\,{#3})} \newcommand{\dand}{\;\&\&\;} \newcommand{\dor}{\;||\;} \newcommand{\accesscontrolname}{\textsf{FROST}} \newcommand{\mergepol}{\mathtt{join}} \newcommand{\refast}[1]{(\ast_{#1})}$

Properties of Obligations

This document provides references to literature as well as some analysis on properties of obligations, namely temporality and accountability as well as their monitoring. The representation of obligations as obligation policies in the $\accesscontrolname{}$ language is explained and an obligation object is introduced to replace the key-value string-pair obligation. Then a generalized policy object is defined to unify the notation of policy objects and obligation object and an example is given to demonstrate its purpose.

Documents

Author(s) Year Title
M. Ali
et al
‘10 Obligation Language for Access Control
and Privacy Policies
D. Chadwick ‘09 Obligation Standardization
D.E. Kateb
et al
‘14 Towards a Full Support of Obligations In XACML
K. Irwin
et al
‘06 On the Modeling and Analysis of Obligations
P. Gama,
P. Ferreira
‘05 Obligation Policies: An Enforcement Platform
Q. Ni
et al
‘08 An Obligation Model Bridging Access Control
Policies and Privacy Policies
M. Hilty
et al
‘05 On Obligations
L. Chen
et al
‘12 Obligations in Risk-Aware Access Control
M. Pontual
et al
‘11 On the Management of User Obligations
M. Pontual
et al
‘10 Toward Practical Authorization-dependent
User Obligation Systems
Oasis ‘17 eXtensible Access Control Markup Language
Version 3.0
Xain ‘18 The $\accesscontrolname{}$ Language v0.8.1

Contents

Obligations can be classified into positive and negative obligations. The term positive shall denote duties corresponding to an access request. The term negative shall denote prohibitions (e.g. write locks while reading access is granted), which can be reformulated as part of the policy itself and therefore are enforceable by the PEP most of the time.

In contrast to obligations for the system/resource, the obligations for a user of an access request, where the actual fulfillment of this duty usually lies in the future (e.g. the payment of a monthly subscription), are unenforceable by the PEP and rely solely on the user in many cases. The violation of obligations targeted at users may be punished and their diligent fulfillment may be incentivised, which can be monitored, e.g., by some kind of credit rating.

Optional obligations, also called advices, may be monitored by the PEP, but their violation must not change the decision of the PDP. The differentiation between obligations and advices is dependent on the use case.

Temporal Categories

Positive obligations, from now on only called obligations, can be categorized into pre-, ongoing- and post-obligations. A violation of an obligation only makes sense in a temporal context, i.e. without any deadline/timeframe there is little meaning to the violation of an obligation related to an access request.

Pre-obligations refer to obligations to be fulfilled before the actual access is granted (e.g. complying with terms of service of a rental car). They are sometimes called provisions by other authors. Ongoing-obligations refer to obligations to be periodically checked while the actual access is performed (e.g. obeying to the speed limit or regional restrictions of the rental car stated in the terms of service). Post-obligations refer to obligations to be fulfilled after the actual access is completed (e.g. sending a report about the rental car usage after return). They are sometimes called obligations (without any prefix) by other authors.

If pre-obligations are not fulfilled, then the PEP should not grant access. If ongoing-obligations are not fulfilled, then the PEP might restrict or terminate access. If post-obligations are not fulfilled, then the PEP should not grant access for reoccurring requests in the future.

Post-obligations might additionally trigger future obligations once or periodically. The trigger itself can be a fixed date in the future. Alternatively, the trigger can be an event, which requires the monitoring of events by the PEP.

Accountability and Monitoring

The violation of obligations can be transferred into an accountability problem, such that it can be attributed who caused the violation of the obligation. Violation of obligations by the system/resource should usually not happen, because the PEP should deny access if any obligations are known to be unfulfillable by the system/resource. That means, violation of obligations might usually be caused by users participating in the access control setting.

While the obligation_grant and obligation_deny objects provided by the PDP represent the circuits used to calculate the set of obligations for a $\grant$ or $\deny$ decision, it is in the domain of the PEP to monitor all pending obligations and to check if they are fulfilled or violated at some point in time. The monitoring may be partitioned into several subtasks.

It should check if users are authorized to perform the necessary actions needed to fulfill their obligations. Due to possible violations of post obligations the monitoring should include a history log to base future access decisions on past violations. Also, it should monitor if newly appended obligations are contradicting or preventing the fulfillment of currently existing obligations. Moreover, in a policy composition setting it might be necessary for the PEP to check for self-contradicting, or cyclic-dependent, or cascading, or subsumed obligations.

  • cascading obligations: obligations which trigger further obligations and so on, i.e. finite or infinite obligation sequences
  • cyclic-dependent obligations: cascading obligations which repeat at some point, i.e. infinite obligation sequences
  • self-contradicting obligations: two (sets of) obligations which have opposite semantic meanings
  • subsumed obligations: two (sets of) obligations where one is part of the other, formally or semantically

In the case of policies composed by the PDP this is covered by the 4-valued logic, so one could argue about a mechanism for obligation composition and static analysis for obligation policies.

Obligation Policies

Obligations themselves might be written as policies. Common reoccurring elements are deadlines or timeframes to state when unfulfilled obligations become violated. Obligations usually specify actions to be taken by some subject or user or system. Also, it should be specified on which subjects or resources the actions are targeted. These key elements may be tied to conditions or events.

This means that obligation policies can be expressed as $\accesscontrolname{}$ policies containing conditions that check the time constraints as well as conditions checking some state or attribute, respectively, that is supposed to change when the specific action is performed. The same applies to events (which are not defined here), because this should be observable by the system to be taken into account, i.e. any kind of altered state. The access control of whether the subject is able to perform the action on the object in the first place can be handled by the $\accesscontrolname{}$ language, too. Since it usually matters if the obligation was fulfilled by the user who is obliged to it or some other entity, this can be checked by additional conditions which, e.g., read data from logs.

It is important to note, that the result from an evaluated obligation policy has to be interpreted differently than access request policy decisions. One way to do this is to assign the meaning obligation is fulfilled to the decision $\grant$, and obligation is violated to the decision $\deny$. A pending obligation which still has to be monitored can be assigned $\undefined$. Pending obligations may be further divided into available and unavailable pending obligations, where the latter refers to the unsatisfiability of a pending obligation by the user due to missing authorization by the system.

It is then up to the PEP to periodically task the PDP with the evaluation of the obligation policies corresponding to pending obligations and to take action based on the computed decision. The PEP should always enable the user to be able to fulfill her obligations, i.e. the general problem of accountability should be reduced to user accountability.

Obligation Objects

To facilitate obligation policies, the notation of type-value pairs like

{
    "type": "Obligation",
    "value": "log_event"
}

from the previous sections could be extended to an obligation object like

// obligation object
{
    "type": "Obligation",
    "attribute_list": [
        // name/id of obligation policy
        {
            "type": "ObligationName",
            "value": "log_event"
        },
        
        // temporal type of obligation policy
        {
            "type": "ObligationType",
            "value": "pre"
        },
        
        // target of the obligation policy
        {
            "type": "ObligationTarget",
            "value": "resource"
        },
        
        // additional context data for obligation policy
        {
            "type": "ObligationContext",
            "attribute_list": [
                {
                    ...
                },
                ...
            ]
        },
        
        // circuit GoC for obligation policy
        "obligation_goc": {
            ...
        },
        
        // circuit DoC for obligation policy
        "obligation_doc": {
            ...
        }
    ]
}

Next to an unique name or identifier ObligationName and the temporal type ObligationType for the obligation policy there are four more items contained in the obligation object. The circuits obligation_goc and obligation_doc are the compiled intermediate language representations of the obligation policy and their compilation is performed the same way as for policies themselves. The additional field ObligationTarget specifies the entity that must fulfill the obligation and the field for context information ObligationContext should only ever be used for things that can’t be expressed by the $\accesscontrolname{}$ language, which basically means for states that can’t be observed by the system.

Note, that the obligation_goc and obligation_doc circuits for obligations policies are not tied to either obligation_grant or obligation_deny circuits of the policy object. Instead both circuits obligation_grant and obligation_deny contain a list of obligation objects which in turn contain the above described circuits obligation_goc and obligation_doc. Therefore, the data structure of a policy object looks like the following:

  • policy_object (object)
    • policy_goc (circuit)
    • policy_doc (circuit)
    • obligation_grant (circuit)
    • obligation_object (list of objects)
      • ObligationName (field)
      • ObligationType (field)
      • ObligationTarget (field)
      • ObligationContext (list)
      • obligation_goc (circuit)
      • obligation_doc (circuit)
    • obligation_deny (circuit)
    • obligation_object (list of objects)
      • ObligationName (field)
      • ObligationType (field)
      • ObligationTarget (field)
      • ObligationContext (list)
      • obligation_goc (circuit)
      • obligation_doc (circuit)

The obligation object must be normalized in the same way as the policy objects as formalized in Attribute Store & Policy Store.

Generalized Policy Objects

Another important observation about obligations policies is, that they could in turn have obligations too, which would result in a cascade of obligations. This way, an obligation object can be seen as a policy object with additional information about name, type and context.

Hence, a uniform approach to policy and obligation objects leads to a generalized policy object with five components: the four circuits policy_goc, policy_doc, obligation_grant, obligation_deny and a list context. The obligation circuits themselves contain a list of obligation policies represented as generalized policy objects, which in turn can have obligations, etc. The context list of the outermost generalized policy object will be empty, whereas the context list of the inner generalized policy objects will contain information about the obligations.

The data structure of the generalized policy object is

  • generalized_policy_object (object)
    • context (empty list)
    • policy_goc (circuit)
    • policy_doc (circuit)
    • obligation_grant (circuit)
    • generalized_policy_object (list of objects)
      • context (list)
      • policy_goc (circuit)
      • policy_doc(circuit)
      • obligation_grant (circuit)
      • obligation_deny (circuit)
    • obligation_deny (circuit)
    • generalized_policy_object (list of objects)
      • context (list)
      • policy_goc (circuit)
      • policy_doc(circuit)
      • obligation_grant (circuit)
      • obligation_deny (circuit)

where the JSON representation will be

// generalized policy object of the policy
{
    // empty context list for the policy
    "context": [],
    
    // circuit GoC for the policy
    "policy_goc": {
        ...
    },
    
    // circuit DoC for the policy
    "policy_doc": {
        ...
    },
    
    // circuit GObl for the policy
    "obligation_grant": {
        // conditional branches containing lists of obligations
        ...
        "obligations": [
            // generalized policy object for the first grant-obligation policy
            {
                // context list for the obligation policy
                "context": [
                    // name, type, target, etc
                    {
                        ...
                    },
                    ...
                ],
                
                // circuit GoC for the obligation policy
                "policy_goc": {
                    ...
                },
                
                // circuit DoC for the obligation policy
                "policy_doc": {
                    ...
                },
                
                // circuit GObl for the obligation policy
                "obligation_grant": {
                    ...
                    // empty or
                    // further nested lists of obligations
                    ...
                },
                
                // circuit DObl for the obligation policy
                "obligation_deny": {
                    ...
                    // empty or
                    // further nested lists of obligations
                    ...
                }
            },
            
            // generalized policy object for the second grant-obligation policy
            {
                ...
            },
            ...
        ]
        ...
    },
    
    // circuit DObl for the policy
    "obligation_deny": {
        // conditional branches containing lists of obligations
        ...
        "obligations": [
            // generalized policy object for the first deny-obligation policy
            {
                // context list for the obligation policy
                "context": [
                    // name, type, target, etc
                    {
                        ...
                    },
                    ...
                ],
                
                // circuit GoC for the obligation policy
                "policy_goc": {
                    ...
                },
                
                // circuit DoC for the obligation policy
                "policy_doc": {
                    ...
                },
                
                // circuit GObl for the obligation policy
                "obligation_grant": {
                    ...
                    // empty or
                    // further nested lists of obligations
                    ...
                },
                
                // circuit DObl for the obligation policy
                "obligation_deny": {
                    ...
                    // empty or
                    // further nested lists of obligations
                    ...
                }
            },
            
            // generalized policy object for the second deny-obligation policy
            {
                ...
            },
            ...
        ]
        ...
    }
}

The generalized policy object must be normalized in the same way as the policy objects as formalized in Attribute Store & Policy Store. We will give an example in the following section to illustrate the usability of the generalized policy object.

Example

Picking up the example policy $q$ from the Compilation of Policies with Obligations document, where \begin{align*} \goc{q} &= \left( Subj == \text{"}owner\text{"} \right) \\ \doc{q} &= \lnot\! \left( Subj == \text{"}owner\text{"} \right) \\ \gobl{q} &= \begin{cases} \{\text{"}log\_event\text{"}\} & \text{if } \left( Subj == \text{"}owner\text{"} \right) \\ \{\,\} & \text{otherwise} \end{cases} \\ \dobl{q} &= \{\,\} \end{align*}

an obligation policy for logging the user access on granting constitutes an obligation for the ObligationTarget system/resource. Therefore, the PEP can check whether this obligation is fulfillable before issuing the final access decision.

The value of ObligationType may depend on the kind of resource and the intention of the policy writer. In case of just logging the access, e.g. reading a document, this can be written as a pre type obligation. But in the case of logging interactions with the resource during the access, e.g. writing in a document, this can be expressed by an ongoing obligation.

The obligation policy for $\{\text{"}log\_event\text{"}\}$ has to have a check whether a log entry for the respective identifiers is contained in the log file, e.g. by an unary membership operation. Time constraints (relative to the current time of the access request) might be written to prevent DoS attack vectors, otherwise the resource has arbitrary time to abide by the pre type obligation before granting access.

Here, we will consider a pre-obligation with a ten seconds time frame: \begin{align*} o &= \grant\ \ifrule\ \left( resource{.}log{.}isMember(entryID) \right. \\ & \qquad\qquad\qquad \left. \dand currentTime \leq (requestTime+10s) \right) \\ & \qquad \oplus\ \deny\ \ifrule\ \left( currentTime > (requestTime+10s) \right) \end{align*}

The obligation policy $o$ yields $\grant$ if the log entry exists before the specified time has expired, it yields $\deny$ after the specified time has expired and it yields $\undefined$ otherwise. Here, $\oplus$ denotes the join operator in the information ordering, also called $\mergepol$ in the $\accesscontrolname{}$ yellow paper.

An empty obligation policy $e$ for $\{\,\}$ can be modeled as a constant $\grant$ policy, which is always fulfilled when the PEP monitors the pending obligations: \begin{align*} e = \grant \end{align*}

Exploiting the logical equivalences for arbitrary policies $p_1$ and $p_2$ under an environment $\rho$ it holds \begin{align*} \goc{p_1 \oplus p_2} &= \goc{p_1} \dor \goc{p_2} & \doc{p_1 \oplus p_2} &= \doc{p_1} \dor \doc{p_2} \\ \goc{p_1 \otimes p_2} &= \goc{p_1} \dand \goc{p_2} & \doc{p_1 \otimes p_2} &= \doc{p_1} \dand \doc{p_2} \end{align*}

with the information join $\oplus$ and the information meet $\otimes$. The compiled circuits for the obligation policies $o$ and $e$ are \begin{align*} \begin{split} \goc{o} &= resource{.}log{.}isMember(entryID) \\ & \quad\dand currentTime \leq (requestTime+10s) \\ \doc{o} &= currentTime > (requestTime+10s) \\ \gobl{o} &= \{\,\} \\ \dobl{o} &= \{\,\} \end{split} \qquad\qquad \begin{split} \goc{e} &= \true \\ \doc{e} &= \false \\ \gobl{e} &= \{\,\} \\ \dobl{e} &= \{\,\} \end{split} \end{align*}

and the (simplified) JSON notation for the generalized policy object for policy $q$ looks like

// generalized policy object for policy q
{
    // empty context list for policy q
    "context": [],
    
    // circuit GoC(q) for policy q
    "policy_goc": {
        // subj==owner
        "operation": "eq",
        "attribute_list": [
            {
                "type": "Ed25519",
                "value": "0x1234..."
            },
            {
                "type": "request.subject.type",
                "value": "request.subject.value"
            }
        ]
    },
    
    // circuit DoC(q) for policy q
    "policy_doc": {
        // not(subj==owner)
        "operation": "not",
        "attribute_list": [
            {
                "operation": "eq",
                "attribute_list": [
                    {
                        "type": "Ed25519",
                        "value": "0x1234..."
                    },
                    {
                        "type": "request.subject.type",
                        "value": "request.subject.value"
                    }
                ]
            }
        ]
    },
    
    // circuit GObl(q) for policy q
    "obligation_grant": {
        "operation": "if",
        "attribute_list": [
            // if-condition: subj==owner
            {
                "operation": "eq",
                "attribute_list": [
                    {
                        "type": "Ed25519",
                        "value": "0x1234..."
                    },
                    {
                        "type": "request.subject.type",
                        "value": "request.subject.value"
                    }
                ]
            },
            
            // if-statement: obligation policy o
            {
                "obligations": [
                    // generalized policy object for obligation policy o
                    {
                        // context for obligation policy o
                        "context": [
                            {
                                "type": "ObligationName",
                                "value": "log_event"
                            },
                            {
                                "type": "ObligationType",
                                "value": "pre"
                            },
                            {
                                "type": "ObligationTarget",
                                "value": "resource"
                            }
                        ],
                        
                        // circuit GoC(o) for obligation policy o
                        "policy_goc": {
                            "operation": "and",
                            "attribute_list": [
                                // 1st and-term: isMember(entryID)
                                {
                                    "operation": "isMember",
                                    "attribute_list": [
                                        {
                                            "type": "resource.log.type",
                                            "value": "resource.log.value"
                                        },
                                        {
                                            "type": "String",
                                            "value": "entryID"
                                        }
                                    ]
                                },
                                
                                // 2nd and-term: currentTime <= requestTime+10s
                                {
                                    "operation": "lte",
                                    "attribute_list": [
                                        {
                                            "type": "resource.time.type",
                                            "value": "resource.time.value"    
                                        },
                                        {
                                            "operation": "add",
                                            "attribute_list": [
                                                {
                                                    "type": "request.context.time.type",
                                                    "value": "request.context.time.value"
                                                },
                                                {
                                                    "type": "Time",
                                                    "value": "000010"
                                                }
                                            ]
                                        }
                                    ]
                                }
                            // end of and
                            ]
                        },
                        
                        // circuit DoC(o) for obligation policy o
                        "policy_doc": {
                            // currentTime > requestTime+10s
                            "operation": "gt"
                            "attribute_list": [
                                {
                                    "type": "resource.time.type",
                                    "value": "resource.time.value"
                                },
                                {
                                    "operation": "add",
                                    "attribute_list": [
                                        {
                                            "type": "request.context.time.type",
                                            "value": "request.context.time.value"
                                        },
                                        {
                                            "type": "Time",
                                            "value": "000010"
                                        }
                                    ]
                                }
                            ]
                        },
                        
                        // empty circuit GObl(o) for obligation policy o
                        "obligation_grant": {},
                        
                        // empty circuit DObl(o) for obligation policy o
                        "obligation_deny": {}
                    }
                ]
            },
            
            // else-statement: obligation policy e
            {
                "obligations": [
                    // generalized policy object for obligation policy e
                    {
                        // context for obligation policy e
                        "context": [
                            {
                                "type": "ObligationName",
                                "value": "empty"
                            },
                            {
                                "type": "ObligationType",
                                "value": "pre"
                            },
                            {
                                "type": "ObligationTarget",
                                "value": "resource"
                            }
                        ],
                        
                        // circuit GoC(e) for oblgation policy e
                        "policy_goc": {
                            "type": "Boolean",
                            "value": "true"
                        },
                        
                        // circuit DoC(e) for obligation policy e
                        "policy_doc": {
                            "type": "Boolean",
                            "value": "false"
                        },
                        
                        // empty circuit GObl(e) for obligation policy e
                        "obligation_grant": {},
                        
                        // empty circuit DObl(e) for obligation policy e
                        "obligation_deny": {}
                    }
                ]
            }
        // end of if
        ]
    },
    
    // circuit DObl(q) for policy q
    "obligation_deny": {
        "obligations": [
            // generalized policy object for obligation policy e
            {
                // context for obligation policy e
                "context": [
                    {
                        "type": "ObligationName",
                        "value": "empty"
                    },
                    {
                        "type": "ObligationType",
                        "value": "pre"
                    },
                    {
                        "type": "ObligationTarget",
                        "value": "resource"
                    }
                ],
                
                // circuit GoC(e) for oblgation policy e
                "policy_goc": {
                    "type": "Boolean",
                    "value": "true"
                },
                
                // circuit DoC(e) for obligation policy e
                "policy_doc": {
                    "type": "Boolean",
                    "value": "false"
                },
                
                // empty circuit GObl(e) for obligation policy e
                "obligation_grant": {},
                
                // empty circuit DObl(e) for obligation policy e
                "obligation_deny": {}
            }
        ]
    }
}

Note, that we need to allow for empty circuits obligation_grant and obligation_deny if there are no obligations attached to an obligation policy. Otherwise we would run into a infinite cascade of circuits. This is not a restriction, but it merely reflects the intention of empty obligations in the $\accesscontrolname{}$ language. The policy object for the “empty” obligation policy $e$ on the other hand cannot be omitted, since it is part of the if-clause of the non-empty obligations for policy $q$. (However, this could be short-circuited by defining if-clauses without an else-branch as having the generalized policy object for the empty obligation policy $e$ in the omitted else-branch. Also, an obligation circuit containing only the generalized policy object for the empty obligation policy $e$ could be left void.) That means, generalized policy objects and policy circuits must never be empty, but (inner) obligation circuits and (outer) contexts may be empty.

An access request by the owner of the resource then results in a $\grant$ decision by the PDP, which is returned to the PEP together with its respective obligation to log the event. Then the obligation monitor of the PEP periodically checks with the PDP, e.g. every fraction of a second, if the obligation policy yields $\grant$ and then in turn grants access for the user (based on the decision of the PDP as well as the fulfillment of the obligation). While the obligation policy yields $\undefined$ the PEP keeps on monitoring the obligation and keeps the final decision on the access request on hold. When the obligations policy yields $\deny$ the PEP in turn denies access for the user (based on the violation of the obligation, even though it is overwriting the decision of the PDP).