OASIS Mailing List ArchivesView the OASIS mailing list archive below
or browse/search using MarkMail.

 


Help: OASIS Mailing Lists Help | MarkMail Help

xacml message

[Date Prev] | [Thread Prev] | [Thread Next] | [Date Next] -- [Date Index] | [Thread Index] | [List Home]


Subject: Re: [xacml] Attributes of relations code sample



Hi Erik,

On 31/01/2013 3:06 AM, Erik Rissanen wrote:
Hi Steven,

On 2013-01-29 04:59, Steven Legg wrote:

Hi Erik,

On 29/01/2013 2:06 AM, Erik Rissanen wrote:
All,

I have written a small sample for the attributes of relations discussion.

You haven't shown what an XACML expression in the condition of an XACML rule
looks like, or how you get from an XACML attribute designator to a SQL query
that takes three parameters drawn from three different attributes in the
request context.


Oh, sorry about forgetting that. It's simply an attribute which is matched against:

<Match integer-less-than>
   <AttributeValue>0</AttributeValue>
   <AttributeDesignator
         Category="access-subject"
         AttributeId="number-of-valid-contracts"
         DataType="integer"/>
</Match>

The attribute category and id are of course arbitrary. Also note that it might be more practical to return
one of the contract identifiers instead of a count, so it could be used in an obligation for billing
purposes, etc, but I tried to keep the sample small.

You would need to define a new attribute to enable both possibilities, whereas
I could just edit my policy.


The context handler implementation would have a configuration of some kind to look up the attribute. The
configuration will contain the template for the database query with the blanks for the key values as well as
information about which attributes of the request to use to fill in the values. This is no different from
any other attribute, say looking up the role of a user in a directory/database based on the subject-id.

Mechanically, all attributes are similarly configured, but what attributes one
chooses to configure make a big difference. It determines what logic appears
in standardized XACML policies and what logic is buried in proprietary context
handler configuration, which in turn determines how interoperable a deployment
is. Mohammad identified one extreme of the spectrum: accessIsAllowed. I, and
Mohammad too I think, have been working close to the other extreme, where PIP
repositories are accessed using nothing more sophisticated than AttributeQuery.
The further one moves towards accessIsAllowed, the more obscure the policies
become, the more restrictions are placed on policy writers as to what they can
express in policy (unless you want to let them manage policy by fiddling with
the context handler configuration), and the less transportable those policies
are.

I think number-of-valid-contracts has already gone too far towards accessIsAllowed.
It is not discernable that the policy actually depends on the resource-id
and location provided by the PEP. The policy is only going to work in
another PDP if it also has a configured number-of-valid-contracts attribute
that does the same thing. It might if there's a profile describing it, but the
more one moves towards accessIsAllowed the harder it becomes to anticipate all
the attributes needed to express all the policy logic that deployers might need
or want. If the attribute is outside what a profile defines, then the policy
itself isn't enough for another PDP. The configuration of the context handler
is also needed, though it might not be much help if the original PDP's context
handler uses SQL and the second PDP uses a database or directory that doesn't
support SQL.

The goal of a group interested in standards for interoperability should be to
stay as close to the AttributeQuery end of the spectrum as possible. Note that
that doesn't mean sacrificing performance. An implementation that chooses to
disregard the standard PIP interface and have the context handler closely
coupled to a database is always free to have the PDP/context-handler factor out
any part of a policy, e.g., parts expressed using the iterator expressions, and
convert that into an optimized query on its database of choice. By having the
policy logic in a standardized form it is feasible to do such conversions
automatically. The original policies can be exported to various PDPs, each doing
conversions suited to their particular databases, or to PDPs that do no
optimization at all, and they will all operate equivalently.


Also, thank you for the thorough write up of this example using your proposed solution. That is most
helpful. I will have to devote a bit more time to understanding it fully, and I will see if I can modify the
example in order to break the solution. ;-) Like I said in my response to Mohammad, I suspect that it won't
scale to do this kind of processing on the PDP, and I will see if I can find an example which demonstrates
that.

If you do manage to find an example that is realistic, uncontrived and that I can't
morph into a form suitable for efficient processing using iterator expressions, then
I will just turn around and say that it is a case where a PDP with a closely-coupled
database has an opportunuity to perform a significant optimization, so you might want
to save us both the trouble.

Regards,
Steven


Best regards,
Erik


The example has contracts for customers who can access resources from locations specified in the contracts.
Each contract may have multiple locations, but only one resource. The contract also has a limit for the
number of accesses purchased and accesses are logged into an audit log.

An access policy would like to check that there exists a contract for the customer and resource in question,
such that the contract has not been spent up and the location from where the access is done is permitted by
the contract.

I did this as a code sample, using the "PIP approach" for attributes of relations. I have pasted the code at
the end of this email. I ran the code sample using PostgreSQL (an open source database engine), so you may
need to tweak it a bit if you port it to something else.

The tables look like this:

Table contract:
  contract_id |   customer   |  resource   | use_limit

Table contract_locations:
  contract_id | allowed_location

Table usage:
  contract_id | log_entry

The program will create some random records. The parameters, as they are now create about 700k something
usage records.

At the end of the program you will find an example query for how a context handler can query a PIP using the
customer, resource and location as keys from the XACML request.

This works quite well and the query returns the number of contracts which exist. It could be modified also
to return one of the contract ids instead, which might be more useful.

This is a small and simple example, but I think this is already complex enough to break the iterator
approach proposed by Steven. The iterators will construct the cross product using higher order functions,
leading to millions of entries to traverse in the PDP.

Steven, could you have a look at the example and say if you think I am mistaken. Also, what will the
iterators look like in this case?

XACML allows multi-valued attributes, so for a start I would describe a
contract object that had these attributes:

    contract_id (single-valued)
    customer (single-valued)
    resource-id (multi-valued)
    use_limit (single-valued)
    allowed_location (multi-valued)
    log_entry (multi-valued)

And for the access-subject category, a multi-valued attribute called contract
that lists the contract objects relevant to the access-subject, and a location
attribute.

The expression in an XACML rule would look something like this, trimming the
URI prefixes as usual:

    <ForAny VariableId="$contract">
      <AttributeDesignator
        Category="access-subject"
        AttributeId="contract"
        DataType="anyURI"
        MustBePresent="false"/>
      <Apply FunctionId="and">
        <Apply FunctionId="string-is-in">
          <Apply FunctionId="string-one-and-only">
            <AttributeDesignator
              Category="access-subject"
              AttributeId="location"
              DataType="string"
              MustBePresent="false"/>
          </Apply>
          <Apply FunctionId="attribute-designator">
            <VariableReference VariableId="$contract"/>
            <AttributeValue DataType="anyURI">allowed-location</AttributeValue>
            <AttributeValue DataType="anyURI">string</AttributeValue>
            <AttributeValue DataType="boolean">false</AttributeValue>
          </Apply>
        </Apply>
        <Apply FunctionId="anyURI-is-in">
          <Apply FunctionId="anyURI-one-and-only">
            <AttributeDesignator
              Category="resource"
              AttributeId="resource-id"
              DataType="anyURI"
              MustBePresent="false"/>
          </Apply>
          <Apply FunctionId="attribute-designator">
            <VariableReference VariableId="$contract"/>
            <AttributeValue DataType="anyURI">resource-id</AttributeValue>
            <AttributeValue DataType="anyURI">anyURI</AttributeValue>
            <AttributeValue DataType="boolean">false</AttributeValue>
          </Apply>
        </Apply>
        <Apply FunctionId="integer-less-than">
          <Apply FunctionId="string-bag-size">
            <Apply FunctionId="attribute-designator">
              <VariableReference VariableId="$contract"/>
              <AttributeValue DataType="anyURI">log_entry</AttributeValue>
              <AttributeValue DataType="anyURI">string</AttributeValue>
              <AttributeValue DataType="boolean">false</AttributeValue>
            </Apply>
          </Apply>
          <Apply FunctionId="integer-one-and-only">
            <Apply FunctionId="attribute-designator">
              <VariableReference VariableId="$contract"/>
              <AttributeValue DataType="anyURI">use_limit</AttributeValue>
              <AttributeValue DataType="anyURI">integer</AttributeValue>
              <AttributeValue DataType="boolean">false</AttributeValue>
            </Apply>
          </Apply>
        </Apply>
      </Apply>
    </ForAny>

> Did you implement a PoC already?

Not yet.

> What kind of performance do you see?

The PDP would only need to look at the specific contract objects that
are relevant to the access subject; about 10 of them if I've read your
code correctly.

Regards,
Steven


I believe that any solution to this problem has to be something which can be executed at the data source, so
that the data does not need to be pulled. Also, it would be good if the language is something which can be
easily translated into something like SQL for implementation. However, a special language would have the
benefit of visibility, though it would be a lot of design effort in duplicating something which already
works.

With apologies to Mohammad, I have not yet had the time to read the SQL profile proposal. I will do so asap.

Best regards,
Erik

package com.axiomatics.demo.relations;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;
import java.util.Random;

public class CreateData {

     private static final String[] locations =
         {"London", "Tokyo", "New York", "Paris", "Milan"};

     public static void main(String[] args) throws Exception {
         String url = "jdbc:postgresql://localhost/relationsdb";
         Properties props = new Properties();
         props.setProperty("user","user");
         props.setProperty("password","password");
         Connection conn = DriverManager.getConnection(url, props);

         // Create tables

         String table_contract = "CREATE TABLE contract ("
                 + "contract_id INTEGER,"
                 + "customer VARCHAR(20),"
                 + "resource VARCHAR(20),"
                 + "use_limit INTEGER)";
         PreparedStatement ps0 = conn.prepareStatement(table_contract);
         try {
             ps0.executeUpdate();
         } finally {
             ps0.close();
         }

         String table_usage = "CREATE TABLE usage ("
                 + "contract_id INTEGER, log_entry VARCHAR(20))";
         ps0 = conn.prepareStatement(table_usage);
         try {
             ps0.executeUpdate();
         } finally {
             ps0.close();
         }

         String table_contract_locations = "CREATE TABLE contract_locations ("
                 + "contract_id INTEGER, allowed_location VARCHAR(10))";
         ps0 = conn.prepareStatement(table_contract_locations);
         try {
             ps0.executeUpdate();
         } finally {
             ps0.close();
         }

         // Create data

         int locationSelector = 0;

         // These determine the size of the data set
         int numContracts = 10000;
         int maxLimit = 200;

         // These determine the spread in the random data created
         int numCustomers = numContracts / 10;
         int numResources = 20;

         Random random = new Random(System.currentTimeMillis());

         for(int contract = 0; contract < numContracts; contract++) {
             int limit = random.nextInt(maxLimit);
             String customer = "customer_" + random.nextInt(numCustomers);
             String resource = "resource_" + random.nextInt(numResources);

             String createContract = "INSERT INTO contract (contract_id," +
                     " customer, resource, use_limit) VALUES (?,?,?,?)";
             PreparedStatement ps1 = conn.prepareStatement(createContract);

             try {
                 ps1.setInt(1, contract);
                 ps1.setString(2, customer);
                 ps1.setString(3, resource);
                 ps1.setInt(4, limit);

                 ps1.executeUpdate();
             } finally {
                 ps1.close();
             }


             int numUseForContract = random.nextInt(limit+1);
             // 50% probability that the contract has been used maximum
             // number of times
             if(random.nextInt(2) == 0) {
                 numUseForContract = limit;
             }
             for(int useCount = 0; useCount < numUseForContract; useCount++) {

                 String createUsegeRecord = "INSERT INTO usage (contract_id," +
                         " log_entry) VALUES (?,?)";
                 PreparedStatement ps2 = conn.prepareStatement(createUsegeRecord);

                 try {
                     ps2.setInt(1, contract);
                     ps2.setString(2, "Used: " + useCount);

                     ps2.executeUpdate();
                 } finally {
                     ps2.close();
                 }

             }

             // For simplicity add three locations for each contract
             for(int i = 0; i < 3; i++) {
                 String createUsegeRecord = "INSERT INTO contract_locations (contract_id," +
                         " allowed_location) VALUES (?,?)";
                 PreparedStatement ps2 = conn.prepareStatement(createUsegeRecord);

                 try {
                     ps2.setInt(1, contract);
                     ps2.setString(2, locations[locationSelector%locations.length]);

                     ps2.executeUpdate();
                 } finally {
                     ps2.close();
                 }
                 locationSelector++;
             }

             if(contract%(numContracts/1000) == 0) {
                 System.out.println(((double) contract)/numContracts*100.0 + "% done");
             }
         }

         // Here is a sample query to see how many a valid contracts there exist
         // for customer_0 in London for resource_0
         String customer = "customer_0";
         String resource = "resource_0";
         String location = "London";
         String query = "select count(distinct contract.contract_id)" +
                 "FROM contract, contract_locations where" +
                 " contract.contract_id=contract_locations.contract_id" +
                 " and contract_locations.allowed_location=?" +
                 " and contract.customer=? and contract.resource=?" +
                 " and (select count(*) from usage" +
                 " where usage.contract_id=contract.contract_id)<contract.use_limit";
         PreparedStatement ps2 = conn.prepareStatement(query);

         try {
             ps2.setString(1, location);
             ps2.setString(2, customer);
             ps2.setString(3, resource);

             ResultSet rs = ps2.executeQuery();

             rs.next();
             int result = rs.getInt(1);
             rs.close();
             System.out.println("The number of valid contracts is " + result);
         } finally {
             ps2.close();
         }
     }
}


---------------------------------------------------------------------
To unsubscribe from this mail list, you must leave the OASIS TC that generates this mail.  Follow this link
to all your TCs in OASIS at:
https://www.oasis-open.org/apps/org/workgroup/portal/my_workgroups.php





[Date Prev] | [Thread Prev] | [Thread Next] | [Date Next] -- [Date Index] | [Thread Index] | [List Home]