XML and Security: Real world examples of XACML security policies

Introduction

This document goes beyond the Hello, World! stage of specifying security policies using eXtended Access Control Markup Language (XACML). We will develop some realistic XACML rules and policies that will give you a good idea of what working with this language feels like in the real world.

 

Intended Audience

Anybody interested in writing or understanding security policies.XML and Security: Introduction to XACML - Access Control Policies in XML is assumed.

 

Case Study: Employee Records

Every Human Resources application has policies that control who can access employee records (with compensation and other performance assessment details). Let's assume that any user of such an application can view their own records as well as records of their direct and indirect reports. Also, human resource officials can view all records.

 

To make this a concrete example, consider the following situation:

  • Sam is an employee
  • Michelle is his boss
  • Peter is her boss
  • Diane is in HR
  • There is a report on Sam called self-appraisal-2009


Modelling Access Control Lists

The first solution is to use Access Control Lists (ACLs). ACLs grant permissions on objects. The simplest implementation in XACML is to model each grant in the ACL as a Rule. Here's the one for Sam:

 

<Rule RuleId="rul_self-appraisal-2009_sam" Effect="Permit">
  <Description>Sam can do anything with his 2009 self appraisal</Description>
  <Target>
    <Subjects>
      <Subject>
        <SubjectMatch MatchId=
            "urn:oasis:names:tc:xacml:2.0:function:string-equal">
          <AttributeValue DataType=
              "http://www.w3.org/2001/XMLSchema#string">
            Sam
          </AttributeValue>
          <SubjectAttributeDesignator DataType=
              "http://www.w3.org/2001/XMLSchema#string">
            urn:oasis:names:tc:xacml:1.0:subject:subject-id
          </SubjectAttributeDesignator>
        </SubjectMatch>
      </Subject>
    </Subjects>
  </Target>
</Rule>

A couple of points are worth mentioning here. First, each Rule must have a unique RuleId. We could use a simple numbering scheme, or -as we did here- use a descriptive name.

 

The XACML specification defines a number of standard names for matching functions, Subject attributes, Action attributes, etc. Here we use urn:oasis:names:tc:xacml:1.0:subject:subject-id (see section B.4 of the spec) to refer to the Subject's unique identifier. Since this is a small organization, people are identified by their first name. For larger organizations that wouldn't work, of course, but the structure of this Rule does not depend on the chosen naming scheme.

 

The MatchId of the SubjectMatch uses one of the standard XACML functions (see section A.3 of the spec) to compare the name of the person making the request with the string Sam. In short, the Rule targets the Subject Sam. Since this Rule doesn't mention any Action, Sam is allowed to do anything with the report (the Rule will match any Action, and the Rule's Effect is Permit.) Since the Rule also doesn't specify any Environment, Sam can do whatever he wants with his self appraisal whenever and wherever he wants to do it.

 

Next, we set up a similar Rule for Sam's direct and indirect bosses and the HR lady:

 

<Rule RuleId="rul_self-appraisal-2009_michelle+peter+diane" Effect="Permit">
  <Description>Sam's bosses and HR can only view the report</Description>
  <Target>
    <Actions>
      <Action>
        <ActionMatch MatchId=
            "urn:oasis:names:tc:xacml:1.0:function:string-equal">
          <AttributeValue DataType=
              "http://www.w3.org/2001/XMLSchema#string">
            urn:oasis:names:tc:xacml:1.0:action:action-id
          </AttributeValue>
          <ActionAttributeDesignator>
            view
          </ActionAttributeDesignator>
        </ActionMatch>
      </Action>
    </Actions>
  </Target>
  <Condition>
    <Apply FunctionId=
        "urn:oasis:names:tc:xacml:1.0:function:string-is-in">
      <SubjectAttributeDesignator DataType=
          "http://www.w3.org/2001/XMLSchema#string">
        urn:oasis:names:tc:xacml:1.0:subject:subject-id
      </SubjectAttributeDesignator>
      <Apply FunctionId=
          "urn:oasis:names:tc:xacml:1.0:function:string-bag">
        <AttributeValue DataType=
            "http://www.w3.org/2001/XMLSchema#string">
          Michelle
        </AttributeValue>
        <AttributeValue DataType=
            "http://www.w3.org/2001/XMLSchema#string">
          Peter
        </AttributeValue>
        <AttributeValue DataType=
            "http://www.w3.org/2001/XMLSchema#string">
          Diane
        </AttributeValue>
      </Apply>
    </Apply>
  </Condition>
</Rule>

 

This Rule targets the view Action by using the standard Action attribute urn:oasis:names:tc:xacml:1.0:action:action-id (see section B.7 of the spec). There are no standard values defined for this attribute, so this is something that must be defined on a per project basis. It is really important that this is done properly, or else the security policies may not work. For instance, when the policy writer assumes a value of edit, while the Request contains update, the Rules in the Policy will not apply. This isn't of much concern with Rules that have an Effect of Permit, but it would be dangerous for Rules whose Effect is Deny: since those Rules wouldn't match, they wouldn't restrict access. Depending on the Rule Combining Algorithm of the Policy, this might mean that access is granted when it shouldn't be.

 

Note that the Rule's Target does not specify any Subjects. It could have contained multiple SubjectMatches, one each for Michelle, Peter, and Diane. Instead, it relies on the Condition to limit the Rule's applicability to Subjects. This makes it possible to use the urn:oasis:names:tc:xacml:1.0:function:string-is-in function (see section A.3.10 of the spec) that checks whether a string is in a list of given strings.

 

You may have noticed that neither of the above Rules mention the resource in question. That's because they share that information, and we are going to specify it only once in the Policy that contains the above Rules:

 

<Policy PolicyId="pol_self-appraisal-2009" RuleCombiningAlgId=
    "urn:oasis:names:tc:xacml:1.0:rule-combining-algorithm:permit-overrides">
  <Description>ACLs for Sam's 2009 self appraisal</Description>
  <Target>
    <Resources>
      <Resource>
        <ResourceMatch MatchId=
            "urn:oasis:names:tc:xacml:1.0:function:string-equal">
          <AttributeValue DataType=
              "http://www.w3.org/2001/XMLSchema#string">
            self-appraisal-2009
          </AttributeValue>
          <ResourceAttributeDesignator>
            urn:oasis:names:tc:xacml:1.0:resource:resource-id
          </ResourceAttributeDesignator>
        </ResourceMatch>
      </Resource>
    </Resources>
  </Target>
  <Rule RuleId="rul_self-appraisal-2009_sam" Effect="Permit">
    <!-- See above -->
  </Rule>
  <Rule RuleId="rul_self-appraisal-2009_michelle+peter+diane" Effect="Permit">
    <!-- See above -->
  </Rule>
</Policy>

 

The Policy targets the self-appraisal-2009 resource using the urn:oasis:names:tc:xacml:1.0:resource:resource-id standard Resource attribute (see section B.6 of the spec). Again, it is important to decide on how values for this attribute are represented as strings. For instance, the above example just lists the name, but what about self appraisals for other people in 2009? You need to define a naming scheme that is shared by both the policies and the Context Handler (that fills the Request).

 

The Policy's RuleCombiningAlgId is urn:oasis:names:tc:xacml:1.0:rule-combining-algorithm:permit-overrides (see section B.10 of the spec), which means that as soon as any matching Rule's Effect is Permit, the Policy's result will be Permit as well.

 

As stated before, the attributes used in the Rules and the Policy must also be present in the Request. For instance, the request to let Michelle view Sam's report looks like this:

<Request>
  <Subject>
    <Attribute AttributeId=
        "urn:oasis:names:tc:xacml:1.0:subject:subject-id">
      Michelle
    </Attribute>
  </Subject>
  <Resource>
    <Attribute AttributeId=
        "urn:oasis:names:tc:xacml:1.0:resource:resource-id">
      self-appraisal-2009
    </Attribute>
  </Resource>
  <Action>
    <Attribute AttributeId=
        "urn:oasis:names:tc:xacml:1.0:action:action-id">
      view
    </Attribute>
  </Action>
</Request>

 

Generalizing ACLs Models

In general, any Rule or Policy with a specific name in it doesn't scale very well. We have two types of names here: the names of the people involved and the name of the record. So every time a new record is added, we have to change the security policies. We also must do that every time the management hierarchy changes. And every time something in HR changes. This solution just isn't very robust.

 

We can fix that by not using names in the Rules and Policies, but by using more Attributes in the Request. Let's look at the name of the record, for instance. We now have a Policy that mentions self-appraisal-2009 explicitly. We either have to add a similar Policy at the end of the year (when Sam creates a new self appraisal), or we have to change the current Policy to include the new record. Neither option is very attractive.

 

So let's fix that by moving the name of the record out of the Policy. What we really need to make our security policy work, is not so much the name of the record, but the name of the owner of the record. That is enough information to decide whether to grant access to the record; we don't need to know the name of the record at all:

<Policy PolicyId="pol_own_records" RuleCombiningAlgId=
    "urn:oasis:names:tc:xacml:1.0:rule-combining-algorithm:permit-overrides">
  <Description>Anybody can do anything with their own records</Description>
  <Rule RuleId="rul_own_record" Effect="Permit">
    <Description>Anybody can do anything with his own records</Description>
    <Condition>
      <Apply FunctionId=
          "urn:oasis:names:tc:xacml:1.0:function:string-equal">
        <SubjectAttributeDesignator DataType=
            "http://www.w3.org/2001/XMLSchema#string">
          urn:oasis:names:tc:xacml:1.0:subject:subject-id
        </SubjectAttributeDesignator>
        <ResourceAttributeDesignator DataType=
            "http://www.w3.org/2001/XMLSchema#string">
          urn:emc:edn:samples:xacml:resource:resource-owner
        </SubjectAttributeDesignator>
      </Apply>
    </Condition>
  </Rule>
</Policy>

 

Here we've invented a new Resource Attribute called urn:emc:edn:samples:xacml:resource:resource-owner. The Policy has become independent of the name of the record, but now the burden is on the Context Handler to provide the value for this Attribute. So you can only use this trick when you have control over what Attributes the Context Handler can provide. Another drawback of this method is that we can no longer assume that this policy will work in a different system, since we're using a non-standard Attribute.

 

Modelling Role Based Access Control

So now we can efficiently grant access to a resource's owner. How about the HR people?

 

Here we're not dealing with just any Attribute, but with a role. The core XACML specification doesn't provide facilities for dealing with roles. However, there is an optional piece of the spec (called a profile) that specifically deals with roles: the Core and hierarchical role based access control (RBAC) profile of XACML v2.0. The RBAC profile specifies the urn:oasis:names:tc:xacml:2.0:subject:role Subject Attribute for specifying roles, but also allows you to specify your own Attributes to indicate roles.

 

Note that the RBAC profile assumes that information about what roles are enabled for a user is provided from outside, by a Role Enablement Authority. The Role Enablement Authority will provide the role information to the PDP.

 

When using the RBAC profile, we need to create a bunch of artifacts to model our HR access policy. First, we must create a Permission PolicySet for the role. This specifies what a user in that role is allowed to do:

<PolicySet PolicySetId="PPS:HR:role" RuleCombiningAlgId=
  <Policy PolicyId="Permissions:for:HR:role" RuleCombiningAlgId=
      "urn:oasis:names:tc:xacml:1.0:rule-combining-algorithm:permit-overrides">
    <Description>Anybody in HR can view any record</Description>
    <Rule RuleId="Permission:to:view:HR:record" Effect="Permit">
      <Target>
        <Resources>
          <Resource>
            <ResourceMatch MatchId=
                "urn:oasis:names:tc:xacml:1.0:function:string-equal">
              <AttributeValue DataType=
                  "http://www.w3.org/2001/XMLSchema#string">
                hr-record
              </AttributeValue>
              <ResourceAttributeDesignator>
                urn:emc:edn:samples:xamcl:resource:resource-type
              </ResourceAttributeDesignator>
            </ResourceMatch>
          </Resource>
        </Resources>
        <Actions>
          <Action>
            <ActionMatch MatchId=
                "urn:oasis:names:tc:xacml:1.0:function:string-equal">
              <AttributeValue DataType=
                  "http://www.w3.org/2001/XMLSchema#string">
                urn:oasis:names:tc:xacml:1.0:action:action-id
              </AttributeValue>
              <ActionAttributeDesignator>
                view
              </ActionAttributeDesignator>
            </ActionMatch>
          </Action>
        </Actions>
      </Target>
    </Rule>
  </Policy>

  <!-- Include permissions for the employee role -->
  <PolicySetIdReference>PPS:employee:role</PolicySetIdReference>
</PolicySet>

 

The RBAC profile states:

Permission PolicySet instances must be stored in the policy repository in such a way that they can never be used as the initial policy for an XACML PDP; Permission PolicySet instances must be reachable only through the corresponding Role PolicySet. This is because, in order to support hierarchical roles, a Permission PolicySet must be applicable to every subject. The Permission PolicySet depends on its corresponding Role PolicySet to ensure that only subjects holding the corresponding role attribute will gain access to the permissions in the given Permission PolicySet.

This sounds like a weak spot in the RBAC profile: we now have to keep some policies apart from the other policies and treat them differently. And to make matters worse, there is nothing in the XACML schema to help recognize Permission PolicySets.

 

The PolicySetIdReference refers to the Permission PolicySet that defines the permissions of the employee role. This is the way to model roles that are senior to other roles (meaning the senior role has all the privileges associated with the junior role). The employee role is defined by a similar Permission PolicySet:

<PolicySet PolicySetId="PPS:employee:role" RuleCombiningAlgId=
  <Policy PolicyId="Permissions:for:employee:role" RuleCombiningAlgId=
      "urn:oasis:names:tc:xacml:1.0:rule-combining-algorithm:permit-overrides">
    <Description>Anybody can do anything with their own records</Description>
    <Rule RuleId="Permission:to:do:anything:to:own:record" Effect="Permit">
      <Condition>
        <Apply FunctionId=
            "urn:oasis:names:tc:xacml:1.0:function:string-equal">
          <SubjectAttributeDesignator DataType=
              "http://www.w3.org/2001/XMLSchema#string">
            urn:oasis:names:tc:xacml:1.0:subject:subject-id
          </SubjectAttributeDesignator>
          <ResourceAttributeDesignator DataType=
              "http://www.w3.org/2001/XMLSchema#string">
            urn:emc:edn:samples:xacml:resource:resource-owner
          </SubjectAttributeDesignator>
        </Apply>
      </Condition>
    </Rule>
  </Policy>
</PolicySet>

 

This is the same policy that we had before, but now wrapped in a PolicySet so that it can be referenced correctly from the HR role Permission PolicySet.

 

Now we need the Role PolicySets that define the HR and employee roles:

<PolicySet PolicySetId="RPS:HR:role" RuleCombiningAlgId=
    "urn:oasis:names:tc:xacml:1.0:rule-combining-algorithm:permit-overrides">
  <Target>
    <Subjects>
      <Subject>
        <SubjectMatch
            MatchId="urn:oasis:names:tc:xacml:1.0:function:anyURI-equal">
          <AttributeValue
              DataType="http://www.w3.org/2001/XMLSchema#anyURI">
            urn:emc:edn:samples:xacml:role-values:hr
          </AttributeValue>
          <SubjectAttributeDesignator
              AttributeId="urn:oasis:names:tc:xacml:2.0:subject:role"
              DataType="http://www.w3.org/2001/XMLSchema#anyURI"/>
        </SubjectMatch>
      </Subject>
    </Subjects>
  </Target>
  <!-- Use permissions associated with the HR role -->
  <PolicySetIdReference>PPS:HR:role</PolicySetIdReference>
</PolicySet>
<PolicySet PolicySetId="RPS:employee:role" RuleCombiningAlgId=
    "urn:oasis:names:tc:xacml:1.0:rule-combining-algorithm:permit-overrides">
  <Target>
    <Subjects>
      <Subject>
        <SubjectMatch
            MatchId="urn:oasis:names:tc:xacml:1.0:function:anyURI-equal">
          <AttributeValue
              DataType="http://www.w3.org/2001/XMLSchema#anyURI">
            urn:emc:edn:samples:xacml:role-values:employee
          </AttributeValue>
          <SubjectAttributeDesignator
              AttributeId="urn:oasis:names:tc:xacml:2.0:subject:role"
              DataType="http://www.w3.org/2001/XMLSchema#anyURI"/>
        </SubjectMatch>
      </Subject>
    </Subjects>
  </Target>
  <!-- Use permissions associated with the employee role -->
  <PolicySetIdReference>PPS:employee:role</PolicySetIdReference>
</PolicySet>

 

Modelling Hierachies

Although our policies are now much better than what we started out with, there is still an issue with the management hierarchy. Even the power of the RBAC profile isn't enough to capture that Michelle has access to self-appraisal-2009. Not because she is a manager, which we could specify with the RBAC profile and a manager role, but because she's Sam's manager.

 

Luckily, there is another optional part of XACML that can help us with this problem: the  Hierarchical resource profile of XACML v2.0. This profile specifies how to deal with resources that are part of a hierarchy. Hierarchy here simply means that it is possible to think of the resources as somehow having parent-child relationships. It does not have to deal with any organizational hierarchy or such, although it could.

 

Let's make that a bit more concrete using our HR example. We'd like to model the access control rules for managers. Since these rules state that managers have access to documents on direct and indirect reports, we have a hierarchy containing the following branch:

  /Peter
    /Michelle
      /Sam
        /self-appraisal-2009

 

With the hierarchical resource profile, we can model Peter's request to access Sam's self appraisal as follows:

  <Request>
    <Subject>
      <Attribute AttributeId=
          "urn:oasis:names:tc:xacml:1.0:subject:subject-id">
        Peter
      </Attribute>
    </Subject>
    <Resource>
      <ResourceContent>
        <Peter>
          <Michelle>
            <Sam>
              <self-appraisal-2009/>
            </Sam>
          </Michelle>
        </Peter>
      </ResourceContent>
      <Attribute AttributeId=
          "urn:oasis:names:tc:xacml:1.0:resource:resource-id"
          DataType="urn:oasis:names:tc:xacml:2.0:data-type:xpath-expression">
        /Peter/Michelle/Sam/self-appraisal-2009
      </Attribute>
      <Attribute AttributeId=
          "urn:oasis:names:tc:xacml:1.0:resource:resource-parent"
          DataType="urn:oasis:names:tc:xacml:2.0:data-type:xpath-expression">
        /Peter/Michelle/Sam
      </Attribute>
      <Attribute AttributeId=
          "urn:oasis:names:tc:xacml:1.0:resource:resource-ancestor"
          DataType="urn:oasis:names:tc:xacml:2.0:data-type:xpath-expression">
        /Peter/Michelle
      </Attribute>
      <Attribute AttributeId=
          "urn:oasis:names:tc:xacml:1.0:resource:resource-ancestor"
          DataType="urn:oasis:names:tc:xacml:2.0:data-type:xpath-expression">
        /Peter
      </Attribute>
      <Attribute AttributeId=
          "urn:oasis:names:tc:xacml:1.0:resource:resource-ancestor-or-self"
          DataType="urn:oasis:names:tc:xacml:2.0:data-type:xpath-expression">
        /Peter/Michelle/Sam/self-appraisal-2009
      </Attribute>
      <Attribute AttributeId=
          "urn:oasis:names:tc:xacml:1.0:resource:resource-ancestor-or-self"
          DataType="urn:oasis:names:tc:xacml:2.0:data-type:xpath-expression">
        /Peter/Michelle/Sam
      </Attribute>
      <Attribute AttributeId=
          "urn:oasis:names:tc:xacml:1.0:resource:resource-ancestor-or-self"
          DataType="urn:oasis:names:tc:xacml:2.0:data-type:xpath-expression">
        /Peter/Michelle
      </Attribute>
      <Attribute AttributeId=
          "urn:oasis:names:tc:xacml:1.0:resource:resource-ancestor-or-self"
          DataType="urn:oasis:names:tc:xacml:2.0:data-type:xpath-expression">
        /Peter
      </Attribute>
    </Resource>
    <Action>
      <Attribute AttributeId=
          "urn:oasis:names:tc:xacml:1.0:action:action-id">
        view
      </Attribute>
    </Action>
  </Request>

 

The ResourceContent element contains the entire hierarchy that the requested resource is part of. This element is optional, but when you leave it out, the PDP needs some other way to access the hierarchy. You could then use the new AttributeId   urn:oasis:names:tc:xacml:2.0:resource:document-id that the hierarchical profile defines (see section 6.1).

 

The Attributes with AttributeIds urn:oasis:names:tc:xacml:1.0:resource:resource-parent (section 6.2),   urn:oasis:names:tc:xacml:1.0:resource:resource-ancestor (section 6.3), and   urn:oasis:names:tc:xacml:1.0:resource:resource-ancestor-or-self (section 6.4) are optional as well, but can be very useful when specifying policies. For instance:

<Policy PolicyId="pol_self-or-direct-or-in-direct-reports" RuleCombiningAlgId=
    "urn:oasis:names:tc:xacml:1.0:rule-combining-algorithm:permit-overrides">
  <Target>
    <Actions>
      <Action>
        <ActionMatch MatchId=
            "urn:oasis:names:tc:xacml:1.0:function:string-equal">
          <AttributeValue DataType=
              "http://www.w3.org/2001/XMLSchema#string">
            urn:oasis:names:tc:xacml:1.0:action:action-id
          </AttributeValue>
          <ActionAttributeDesignator>
            view
          </ActionAttributeDesignator>
        </ActionMatch>
      </Action>
    </Actions>
  </Target>
  <Condition>
    <Apply FunctionId=
        "urn:oasis:names:tc:xacml:1.0:function:anyURI-is-in">
      <SubjectAttributeDesignator DataType=
          "http://www.w3.org/2001/XMLSchema#anyURI">
        urn:oasis:names:tc:xacml:1.0:subject:subject-id
      </SubjectAttributeDesignator>
      <ResourceAttributeDesignator DataType=
          "http://www.w3.org/2001/XMLSchema#anyURI">
        urn:oasis:names:tc:xacml:1.0:resource:resource-ancestor
      </ResourceAttributeDesignator>
    </Apply>
  </Condition>
</Policy>

 

Here we grant access if the subject matches any ancestor of the requested resource.

 

More to Explore

Implementing an XACML PEP in Java

As you've seen in this HR example, there are a lot of possibilities and subtleties when it comes to writing access control policies in XACML. We covered the core and two optional profiles here, but there is more:

The upcoming 3.0 version of the XACML spec will include additional profiles.

 

For more help on writing your own policies, see Writing Policy Rules. To test your policies for their intended effects, check out the Access Control Policy Test (ACPT) tool.