oasis-tcs / xacml-spec

OASIS XACML TC: List for tracking issues and features for the OASIS XACML TC. https://github.com/oasis-tcs/xacml-spec
Other
4 stars 0 forks source link

Add global variable definitions #12

Open steven-legg opened 6 months ago

steven-legg commented 6 months ago

In writing fine-grained access control policies for non-trivial systems I find that common expressions keep recurring across the collection of policies. These expressions are obvious candidates for variable definitions but variable definitions are currently limited in scope to a single policy. Variable definitions with global scope would be very useful.

Issue #4 would extend the scope to a single policy set and its embedded and referenced policies and policy sets, but this still has limitations. In order for a variable definition to effectively have global scope all the policies and policy sets in the collection would need to be in the hierarchy of a single overarching policy set. The existence of a single overarching policy set isn't a requirement of XACML and my implementation doesn't require it either, though some implementations probably do. Issue #4 also requires the variables used by referenced policies and policies sets to be passed through as explicitly defined parameters. It would be simpler if some variable definitions could exist independently of policies and policy sets, i.e., a global variable definition, and be directly referenced in expressions.

Variables are identified with an arbitrary string. To avoid conflicts when combining policy from different sources the identifier for a global variable should be a URI, and global variable definitions should probably be versioned, like policies and policy sets, leading to this definition:

    <xs:element name="GlobalVariableDefinition" type="xacml:GlobalVariableDefinitionType"/>
    <xs:complexType name="GlobalVariableDefinitionType">
        <xs:sequence>
            <xs:element ref="xacml:Description" minOccurs="0"/>
            <xs:element ref="xacml:Expression"/>
        </xs:sequence>
        <xs:attribute name="GlobalVariableId" type="xs:anyURI" use="required"/>
        <xs:attribute name="Version" type="xacml:VersionType" use="required"/>
    </xs:complexType>

Referencing a global variable in an expression could be done with a new expression type:

    <xs:element name="GlobalVariableReference" type="xacml:GlobalVariableReferenceType" substitutionGroup="xacml:Expression"/>
    <xs:complexType name="GlobalVariableReferenceType">
        <xs:complexContent>
            <xs:extension base="xacml:ExpressionType">
                <xs:element ref="xacml:GlobalVariableIdReference"/>
            </xs:extension>
        </xs:complexContent>
    </xs:complexType>

    <xs:element name="GlobalVariableIdReference" type="xacml:IdReferenceType"/>

Alternatively, a Policy or PolicySet (or RuleSet, #11) could signal its intention to use a global variable by importing it and assigning it a string name with local scope to use in a VariableReference:

    <xs:element name="ImportVariable" type="xacml:ImportVariableType"/>
    <xs:complexType name="ImportVariableType">
        <xs:element ref="xacml:GlobalVariableIdReference"/>
        <xs:attribute name="VariableId" type="xs:string" use="required"/>
    </xs:complexType>

An ImportVariable element can appear wherever a regular VariableDefinition element can appear.

Presumably we extend the SAML profile to additionally allow GlobalVariableDefinition elements wherever Policy and PolicySet elements are allowed in assertions and protocol messages and GlobalVariableId wherever PolicySetId and PolicyId are allowed.

humantypo commented 6 months ago

I like the idea of using Type to indicate intent for the variable to be global and the move toward a simpler Policy(Set) solution. I like the "embedded metadata" approach you're suggesting. It does a nice job of keeping context close at hand (vs. a more "hierarchical" approach).

Now if we could get it all to look like this in v4 it would be glorious 😬

{
  "ImportVariable": {
    "@type": "xacml:ImportVariableType",
    "GlobalVariableId": "uri",
    "VariableId": "id"
  }
}
steven-legg commented 6 months ago

Adding versioning complicates the JSON representation.

{
  "ImportVariable": {
    "@type": "xacml:ImportVariableType",
    "GlobalVariableIdReference": {
      "GlobalVariableId": "uri",
      "Version": "1.0"
    },
    "VariableId": "id"
  }
}

Though we could do something like this:

    <xs:complexType name="ImportVariableType">
        <xs:simpleContent>
            <xs:extension base="xacml:IdReferenceType">
               <xs:attribute name="VariableId" type="xs:string" use="required"/>
            </xs:extension>
        </xs:simpleContent>
    </xs:complexType>

Then the JSON representation would be:

{
  "ImportVariable": {
    "@type": "xacml:ImportVariableType",
    "GlobalVariableId": "uri",
    "Version": "1.0"
    "VariableId": "id"
  }
}

The "GlobalVariableId" member name is plucked from thin air since the value corresponds to unnamed simple content. Issue #11 would see us replacing PolicySetIdReference and PolicyIdReference with RuleSetIdReference so we could swap from simple content to an XML attribute and then we'd have a name.

I'm thinking something like this:

    <xs:complexType name="IdReferenceType">
        <xs:attribute name="Version" type="xacml:VersionMatchType" use="optional"/>
        <xs:attribute name="EarliestVersion" type="xacml:VersionMatchType" use="optional"/>
        <xs:attribute name="LatestVersion" type="xacml:VersionMatchType" use="optional"/>
    </xs:complexType>

    <xs:complexType name="RuleSetIdReferenceType">
        <xs:complexContent>
            <xs:extension base="xacml:IdReferenceType">
                <xs:attribute name="RuleSetId" type="xs:anyURI" use="required"/>
            </xs:extension>
        </xs:complexContent>
    </xs:complexType>

    <xs:complexType name="GlobalVariableIdReferenceType">
        <xs:complexContent>
            <xs:extension base="xacml:IdReferenceType">
                <xs:attribute name="GlobalVariableId" type="xs:anyURI" use="required"/>
            </xs:extension>
        </xs:complexContent>
    </xs:complexType>

    <xs:complexType name="ImportVariableType">
        <xs:complexContent>
            <xs:extension base="xacml:GlobalVariableIdReferenceType">
               <xs:attribute name="VariableId" type="xs:string" use="required"/>
            </xs:extension>
        </xs:complexContent>
    </xs:complexType>
humantypo commented 6 months ago

OK, I was thinking the URI would “manage” explicit versioning since it makes sense in that scope. I had not considered something like an “earliest” and/or “latest” mechanism, which rightly belongs in the policy scope. In theory they could coexist, but that suggests the URI would need to be named via a MUST mechanism to determine version. I need to think on that a bit I guess…

steven-legg commented 6 months ago

Taking account of issue #16 gives us:

    <xs:element name="GlobalVariableDefinition" type="xacml:GlobalVariableDefinitionType"/>
    <xs:complexType name="GlobalVariableDefinitionType">
        <xs:sequence>
            <xs:element ref="xacml:Description" minOccurs="0"/>
            <xs:element ref="xacml:Expression"/>
        </xs:sequence>
        <xs:attribute name="GlobalVariableId" type="xs:anyURI" use="required"/>
    </xs:complexType>

    <xs:element name="GlobalVariableReference" type="xs:anyURI"/>

    <xs:element name="ImportVariable" type="xacml:ImportVariableType"/>
    <xs:complexType name="ImportVariableType">
        <xs:sequence>
            <xs:element ref="xacml:GlobalVariableReference"/>
        </xs:sequence>
        <xs:attribute name="VariableId" type="xs:string" use="required"/>
    </xs:complexType>

This is less verbose for ImportVariable but not consistent with the way we would be referencing policies:

    <xs:complexType name="ImportVariableType">
        <xs:attribute name="GlobalVariableId" type="xs:anyURI" use="required"/>
        <xs:attribute name="VariableId" type="xs:string" use="required"/>
    </xs:complexType>
cdanger commented 6 months ago

Why not make GlobalVariableReference a type of Expression, like VariableReference? Then we don't need ImportVariable because we can use it in VariableDefinition, and we can use it as expression in Condition, Apply, etc.. as well.

<xs:element name="GlobalVariableReference" type="xacml:GlobalVariableReferenceType" substitutionGroup="xacml:Expression"/>

<xs:complexType name="GlobalVariableReferenceType">
   <xs:complexContent>
         <xs:extension base="xacml:ExpressionType">
                <!-- Only difference with VariableReference here is the VariableId type anyURI. -->
                <xs:attribute name="VariableId" type="xs:anyURI" use="required"/>
          </xs:extension>
   </xs:complexContent>
</xs:complexType>

Then something like ImportVariable can be done with a VariableDefinition as follows:

<VariableDefinition VariableId="local_x"><GlobalVariableReference VariableId="GLOBAL_X"/></VariableDefinition>
steven-legg commented 6 months ago

Why not make GlobalVariableReference a type of Expression, like VariableReference?

I do suggest this option in the first comment. Bill seems to like the idea of declaring the imports up front, but I don't have a strong preference for one way over the other. Using a VariableDefinition to map a global variable to a local variable name, as you suggest, achieves much the same effect as an import.

Being consistent with the way policies would be referenced means defining GlobalVariableReferenceType like so:

    <xs:complexType name="GlobalVariableReferenceType">
        <xs:complexContent>
            <xs:extension base="xacml:ExpressionType">
                <xs:element ref="xacml:GlobalVariableIdReference"/>
            </xs:extension>
        </xs:complexContent>
    </xs:complexType>

Of course we could change the way we reference policies instead.

I think there is value in being overt that we are referencing a global variable rather than a local one. Using the same attribute name with a different type doesn't make that clear, even though the element name is different. So:

<xs:complexType name="GlobalVariableReferenceType">
   <xs:complexContent>
         <xs:extension base="xacml:ExpressionType">
                <xs:attribute name="GlobalVariableId" type="xs:anyURI" use="required"/>
          </xs:extension>
   </xs:complexContent>
</xs:complexType>

Note that #16 suggests shortening GlobalVariableIdReference to GlobalVariableReference which would create a name conflict.

humantypo commented 6 months ago

I'm flexible on this so long as we are consistent and, where possible, trying to keep things easily digestible. One of my sincere hopes is that with v4.0 we can make the spec more approachable. If you both agree on a solution that addresses these objectives I will happily defer to your judgement. 🙂

cdanger commented 6 months ago

@steven-legg Your last schema proposal with GlobalVariableId is the one I like better :

<xs:complexContent>

... with the element definition:

<xs:element name="GlobalVariableReference" type="xacml:GlobalVariableReferenceType" substitutionGroup="xacml:Expression"/>
steven-legg commented 5 months ago

A global variable is a referenceable thing like a policy, PolicyIdReference is currently used in three places (PolicySetIdReference is used in the same places):

(1) The children of PolicySet (to be merged with Policy). We don't need to reference a global variable at the same level as a policy.

(2) The children of PolicyIdentifierList. We could also list the global variables that were evaluated, but I don't see a compelling need.

(3) The children of XACMLPolicyQuery. There would be a need to fetch global variable definitions by identifier. Of course we can discuss whether XACMLPolicyQuery is the way to do it. This reference isn't a kind of expression.

Noting that (1) and (2) aren't quite the same thing for parameterized policy references (see #16), I suggest that we use PolicyReference, with optional arguments, as the child of Policy, GlobalVariableReference as an expression type, PolicyIdReference (no arguments) in PolicyIdentifierList and XACMLPolicyQuery, and GlobalVariableIdReference in XACMLPolicyQuery.

I would still welcome an alternative naming pattern to ... "IdReference".

humantypo commented 5 months ago

This got me to thinking about the term “global” in the v4 world. The effective scope of “global variables” will at the Policy level, correct? That is how I’m reading it in the proposals but wanted to confirm given the broad nature of the term, which could be interpreted grammatically as cross-Policy/PEP.

FYI im not suggesting we change it; I’m just making a placeholder for a point that we may want to call out in the spec verbiage.

steven-legg commented 5 months ago

Section 7.17 of the core spec tells us that "the PDP is defined by a policy-combining algorithm and a set of policies and/or policy sets". That is the scope I'm thinking of for global variables. So for v4 the PDP will be defined by a combining algorithm, a set of policies and a set of global variable definitions. The combining algorithm only applies to the policies. A global variable is evaluated if it is referenced from an expression anywhere within any policy.

A policy-combining algorithm and a set of policies and/or policy sets can be packaged up as a single policy set and I know of at least one implementation that defines the PDP by such a policy set. We don't have a structure for v4 that packages everything including the global variables but we might want to define one.

FWIW, in v3 I define the PDP by a policy combining algorithm, a set of primary policies and/or policy sets and a set of secondary policies and policy sets. The primary policies and policy sets are always evaluated and combined. The secondary policies and policy sets are only evaluated to satisfy a reference from something that is evaluated. I think of the global variable definitions as akin to the secondary policies and policy sets; only evaluated to satisfy a reference from an expression that is evaluated.

It is possible for me to wrap up all the primary and secondary policy sets in a single, overarching policy set if I wrap up the secondary policies and policy sets in a child policy set that has a target that can never be true. That way the secondary policies and policy sets can only be evaluated if they are referenced. It's rather ugly though. If we were to define a package for everything in v4 then I would suggest a structure that has a combining algorithm, a set of primary policies, a set of secondary policies for referencing and a set of global variable definitions. The combining algorithm and set of primary policies could also be represented as a single entry-point policy. We could alternatively have a set of policies, a set of global variable definitions and a policy reference that identifies the entry-point policy.

humantypo commented 5 months ago

Yes, Policy combination is what I was thinking about. In particular, how to handle conflicting global variables. Without the policy combining algorithm itself defining the global variables it seems possible to run into conflict, which further suggests that we will need to determine how to deal with resolution mechanisms in v4.

We don't have a structure for v4 that packages everything including the global variables but we might want to define one.

That’s where I was heading in my initial thinking. I personally like the idea of clearly identifying the key elements explicitly.

steven-legg commented 5 months ago

In particular, how to handle conflicting global variables.

The core spec says this in regard to PolicyId:

"It is the responsibility of the PAP to ensure that no two policies visible to the PDP have the same identifier. This MAY be achieved by following a predefined URN or URI scheme."

In due course we would have said something like:

"It is the responsibility of the PAP to ensure that no two global variable definitions visible to the PDP have the same identifier. This MAY be achieved by following a predefined URN or URI scheme."

Does that address the concern? Assuming that the stuff "the PDP is defined by" is what is "visible to the PDP".

humantypo commented 5 months ago

Yes, URIs address the base concern, it's just that I kinda find it unsatisfying on some levels in the context of my semantic interpretation "global". Part of me thinks we should consider some sort of naming structure (to make these things semi hierarchical) and/or collective, PDP level reference (to make things logically referenceable for policy writers), while the other part of me thinks it's futile and will be handled by the implementations (uniquely) anyway. If we were to call these "common" variable (not that I am suggesting it), then the implications would be much less weighty linguistically. Perhaps I'm just having PIP flashbacks... 😁

steven-legg commented 5 months ago

Any conceptual structure that is encoded into the identifier means updating all the references if one wants to rethink the structure. My PAPs support something that looks like global variables but they are called named expressions because they also have mnemonic names. The name is separate from the identifier (which is currently a UUID). The policy writers see and interact with the names but the PAPs use the identifiers under the covers. There is no restriction on the names so a policy writer can use a hierarchical naming scheme if they choose. They can also easily rename any named expression at any time because the identifier is what is actually written into the policies. So I've moved the conceptual organization of the common expressions to be a PAP concern rather than a policy concern. I wasn't going to suggest adding names to the global variable definitions because they don't do anything within the scope of the standard and I can continue associating names in the PAP implementation, but we could add them if it will keep Bill's "parts" in harmony.

I certainly recognize the baggage that can come with the word "global". Here's a few alternative adjectives to toss around: unbound, detached, free, loose, shared. Named expression is also unencumbered.

humantypo commented 5 months ago

Agree that naming carries all the baggage of "standardized" anything: It's great during definition but a beast during maintenance (99% of the functional lifespan of an implementation). Ultimately I think it falls back to examples/Best Practices/Should descriptions in the spec to offer guidance...that is taken/left by each implementer.

"shared" has a nice ring to it. I can feel my linguistic hobgoblins jumping ship already!

steven-legg commented 5 months ago

On the topic of a package for policies and global variable definitions:

We could alternatively have a set of policies, a set of global variable definitions and a policy reference that identifies the entry-point policy.

The entry-point policy could be a parameterized policy and the policy reference a reference with arguments.

cdanger commented 1 month ago

@steven-legg Where would the GlobalVariableDefinitions go in the end? I mean: what would be the exchange format? You mentioned the possibility of using the SAML profile as far as I understand. Can you give an example?

steven-legg commented 1 month ago

@cdanger The SAML profile defines the XACMLPolicyStatementType as a wrapper for a collection of policies and policy sets, which is the only exchange format defined in XACML 3.0, though it isn't sufficient to describe what "the PDP is defined by". Global variable definitions would naturally go in there, but I think we could do better defining a standard policy package for XACML 4.0 that is independent of SAML.

The SAML profile seems to be mostly ignored anyway and implementations do their own thing, including wrapping everything in a single policy set, but that doesn't work for global variable definitions, hence the earlier discussion about packaging.