PBAC can be used to protect all SOAP invocations on your web services.
PBAC 2 : Service Developer's Guide
PBAC can be used to protect all SOAP invocations on your web services.
Each service will have a PBAC policy for any operations which don't take a
context (these are similar to static methods and constructors in
Java). This is described in the Services section
below.
SOAP requests may also take a resource identifier in the SOAP header to
provide context for the operations. Operations which take a context work
like normal non-static methods in Java. Each resource has a unique
identifier and a unique resource type; there is one PBAC policy for each
resource type. Creating and managing resources is described in the Resources section.
The Web administration section shows how to create a
web-based adminstrator interface easily using JSP. This lets you deploy and undeploy
policies, view the resources protected by each policy, and change the dynamic rules
for assigning process roles.
The client
No special features should be required on the client except for WS-Security
support. If the context ID is passed in the header instead of the body,
the client will need to be able to create these headers (typically by
supporting WS-Addressing).
The PEP
The PEP wraps the service and prevents unauthorised users from invoking
any operations on resources to which they have not been granted access.
The PEP is intended to be very light-weight, as most of the work is done
in the PDP. Each web service platform requires its own implementation of
the PEP.
The client's SOAP message is received by the service framework, which
processes it with a series of inbound handlers before invoking the service
itself (see the Deployment section in the
overview).
The PEP passes the user's X.509 certificate, the resource ID
and the SOAP operation name to the PDP (either a local PDP, or a PDP Invoker).
The PDP locks the resource and then checks that the user is authorised to
perform the operation. If not, it unlocks the resource and throws an exception,
stopping processing of the message. If the user is authorised, the check
returns success with the resource still locked.
On successful completion of the check, the PEP invokes the service
operation itself. The service operation may signal events to the PDP
during its invocation; these events may change the process state of the
resource, with the service policy deciding to which state the resource will
transition on each event.
After the service operation has been invoked (whether successful or not),
the PEP tells the PDP to unlock the resource.
Axis deployment
The PEP class is in the itinnov-grid-pbac-pep-VERSION.jar JAR file.
To use PBAC, the service's server-config.wsdd file must list the PEP as the provider.
For example:
<service name="DataService" provider="java:PBAC-PEP" use="literal" style="wrapped">
<requestFlow>
<handler type="SecurityInboundHandler"/>
</requestFlow>
<responseFlow>
<handler type="IntegrityEnforcementHandler"/>
</responseFlow>
<parameter name="className" value="java.package.MyServiceInterface/>
<parameter name="allowedMethods" value="*"/>
<parameter name="scope" value="Application"/>
</service>
Note: WSS4J does not provide a configuration setting to disable calling the vertifyTrust method,
which checks that the signing certificate is in the service's keystore. Since PBAC roots of trust
are per-resource rather than global, this check must be disabled. This can be done by setting
SecurityInboundHandler to a subclass of WSDoAllReceiver which overrides this method to always
return true.
Note 2: When using the PBAC-PEP provider, the service's className attribute must be set to a Java
interface rather than to the implementing class. This removes the need to set allowedMethods
to an actual list of operations (since only operations in the interface are exposed) and permits
any web-based configuration interface access to the same singleton implementation object. The
implementing class to use for the interface is specified in the
implementationfactory.properties file:
java.package.MyServiceInterface = com.example.MyServiceImpl
.NET filters
The .NET filters are written in C# and make use of the WSE 2 libraries.
Since the PDP requires a JVM it cannot be run in-process with .NET
services, so the .NET filters communicate with the PDP using SOAP calls.
WSE 2 automatically includes the required WS-Security handlers, so this does
not need to be listed. The service's Web.config file therefore only
needs to list the PEP filter for both chains, eg:
<microsoft.web.services2>
<filters>
<input>
<add type="PBAC.PEPInputFilter, PBAC" />
</input>
<output>
<add type="PBAC.PEPOutputFilter, PBAC" />
</output>
</filters>
...
</microsoft.web.services2>
Services
To write a service that uses PBAC, you should already be familiar with creating web
services using your chosen service container (eg, tomcat/axis or .NET).
Creating a service policy
The service policy controls access to operations that don't act on an existing resource.
We'll start with a service which has just an echo operation.
A suitable PBAC policy for our service might look like this:
<?xml version="1.0" encoding="UTF-8"?>
<state-model description="My service"
xmlns="http://www.itinnovation.soton.ac.uk/uk/ac/soton/itinnovation/grid/pbac2/staticpolicy/types">
<state name="UNINITIALISED-STATE">
<transition>
<event name="init"/>
<to-state name="active"/>
</transition>
</state>
<state name="active">
<operation name="echo">
<process-role name="world"/>
</operation>
</state>
<state name="DESTROYED-STATE"/>
</state-model>
All resources start in the UNINITIALISED-STATE state. Once initialisation is
complete, the service will send the init event to change it to the active
state. The service will stay in this state for the whole time it is deployed.
While in the active state, anyone with the world role may invoke the echo operation.
Registering the service with PBAC
When your service is configured, it should deploy the XML policy, and then
create a singleton resource named
service:java.package.MyServiceInterface (the same interface that was
specified in the wsdd file):
PBACUtils.ensureDeployed(MyServiceInterface.MY_SERVICE_RESOURCE_TYPE, "my-service-policy.xml");
PBACUtils.ensureServiceResource(this, new MatchRule[] {MatchRule.createAnyoneRule("world"));
The first argument to ensureServiceResource is your service object, which must have
a PEPServiceResource annotation on its class.
Alternatively, you may pass the PBAC type and Java interface as arguments in place of this.
The second argument to creates an initial
dynamic policy which gives anyone at all the world role. This is
useful for completely public methods. The service policy above permits users with
the world role to invoke the echo operation once the service
is in the active state. Alternatively, you might choose to leave the
initial policy empty and have the service administrator set it through the
web interface.
MY_SERVICE_RESOURCE_TYPE is simply a unique string identifying the new type. To ensure
that it is unique, it is usual to pick an unused URL in a domain that you control. Including the
year that the URL was coined means you only have to remember which names
you've already used this year. For example:
public static final String MY_SERVICE_RESOURCE_TYPE =
"http://example.com/2006/MyServicePolicy";
Writing the echo opertion
The implementation of the echo operation doesn't require any interaction with PBAC.
Add the prototype for echo to MyServiceInterface:
public String echo(String message);
Add the implementation to MyServiceImpl:
public String echo(String message) {
return "Echo: " + message;
}
Anyone should now be able to invoke the echo operation, since we granted everyone the
world role.
Creating a web-based administration interface
Services generally export operations that can be used to manage the dynamic policy (e.g., who
gets access to the world role). However, it is useful (especially during development) to
have a web interface for this. This can be done with some simple JSP code:
<... resources of that type.</p><%
PolicyAdmin admin = new PolicyAdmin(request);
String message = admin.processPOST();
if (message != null)
out.write("<p>" + message + "</p>");
admin.showAdmin(out);
The processPOST operation checks whether the user is submitting a form, and performs the
requested action if so. It may return a message, which you should display to the user. The showAdmin
operation displays the policy tables.
When you view this page you should see your new policy and, when you select it, you
should see the singleton service object in the active state. Use of the administration
interface is described in the PBAC administrator's guide.
Clicking on a resource takes you to the resource's page. You can configure which JSP page to link to
for each resource type using registerResourceTypeLink. By default, the page pbac_resource.jsp
is assumed, but customising this allows you to show extra resource-specific information:
admin.registerResourceTypeLink(MyServiceInterface.MY_SERVICE_RESOURCE_TYPE, "my_service.jsp");
To include the dynamic policy component in your web interface in the
resource's page (my_service.jsp), use this code:
ACLadmin acladmin = new ACLadmin(request);
String message = acladmin.processPOST();
if (message != null)
out.write("<p>" + message + "</p>");
acladmin.showRules(out);
This will produce the table shown below for our sample service (a groups section will also be
shown if you have groups defined). Use of the dynamic policy administration
interface is described in the PBAC administrator's guide.
Resources created by services
Some services only have operations with no context (like static methods on a Java class). Most services
however will create other resources dynamically: an account service will create accounts, and a data service will
create data stagers.
Each such resource needs to have its own access control dynamic policy (every account will
grant a different person the budget-holder process role, for example).
Creating a resource policy
For this tutorial, we'll extend our service to allow the creation of
data stagers. Users will be able to create, write to, read from and
destroy stagers. A simple initial policy might look like this:
<?xml version="1.0" encoding="UTF-8"?>
<state-model description="DataService description"
xmlns="http://www.itinnovation.soton.ac.uk/uk/ac/soton/itinnovation/grid/pbac2/staticpolicy/types">
<state name="UNINITIALISED-STATE">
<transition>
<event name="init"/>
<to-state name="empty"/>
</transition>
</state>
<state name="empty">
<operation name="save">
<process-role name="owner"/>
</operation>
<operation name="destroy">
<process-role name="owner"/>
</operation>
<transition>
<event name="write"/>
<to-state name="full"/>
</transition>
<transition>
<event name="destroy"/>
<to-state name="DESTROYED-STATE"/>
</transition>
</state>
<state name="full">
<operation name="read">
<process-role name="reader"/>
<process-role name="owner"/>
</operation>
<operation name="deleteContents">
<process-role name="owner"/>
</operation>
<transition>
<event name="delete"/>
<to-state name="empty"/>
</transition>
</state>
<state name="DESTROYED-STATE"/>
</state-model>
Apart from the special uninitialised and destroyed states, a stager can be either empty or full.
When empty, anyone with the owner role can write to it or destroy it. When full,
the owner can delete the contents and both the owner and any reader can read the
data.
This model introduces some constraints on how a stager is used. For example, no-one (not even the owner)
can destroy a stager which is in the full state. It must be moved to the empty state first.
Add a line to your initialisation code to deploy the resource policy:
PBACUtils.ensureDeployed(MyServiceInterface.MY_SERVICE_RESOURCE_TYPE, "my-service-policy.xml");
PBACUtils.ensureDeployed(MyServiceInterface.MY_RESOURCE_TYPE, "my-policy.xml");
PBACUtils.ensureServiceResource(this, new MatchRule[] {MatchRule.createAnyoneRule("world"));
The web interface should now list both resource types, although there won't be any objects listed
for the new type because no data stagers have been created yet.
Creating a new resource
New resources are registered with PBAC using newProcess. Typically,
there is a web service operation which does not take a resource ID as input (and hence
is protected by the PBAC service policy above, rather than by a resource policy),
but just creates new resources. Methods that create new resources generally take a MatchRule
argument, allowing for future identification of the creator.
Our newDataStager operation is typical (it creates a new resource and initialises it with a
dynamic policy that grants the caller of newDataStager the owner role):
public String newDataStager(MatchRule owner) {
PBACUtils.validateOwner(owner, getCurrentUser(), "owner");
// Create the new resource
DataStager resource = new DataStager();
// Register it with PBAC
pdp.newProcess(MY_RESOURCE_TYPE, resource.ID);
try {
// Give the caller the 'owner' role
pdp.addAccessControlRule(resourceID, owner);
// Signal successful initialisation
pdp.signal(resourceID, "init");
return resource.ID;
} finally {
pdp.unlock(resourceID);
}
}
The validateOwner method checks that the caller of newDataStager is
matched by this policy as a sanity check (to prevent people from creating resources
that they can't access). It also ensures that the rule grants the correct role (this is
especially important if there are roles with greater access than owner, such
as service-admin).
New resources start life in the locked state (preventing the user from doing anything with them
until initialisation is complete). Since the PEP didn't lock it, it won't unlock it automatically,
so the finally clause is required to do this. If the operation fails, the resource will be
unlocked without receiving the "init" signal; the PDP will forget about it automatically in this
case.
Before sending the "init" signal, we also need to give someone access to the resource.
In this case, we grant the caller of newDataStager the owner process role.
Note: If we sent the init signal first, and an error occured after that and before adding
the new rule, we would get a new resource that no-one could access (except through the web
admin interface). So, don't send the init signal until the point where the resource should continue
to exist even if an exception is thrown.
If you try to invoke the newDataStager operation, PBAC will reject the request. To allow
people to call it, edit the service's policy (not the data stagers's policy!) to add the new operation:
<state name="active">
<operation name="echo">
<process-role name="world"/>
</operation>]]>
<operation name="newDataStager">
<process-role name="world"/>
</operation>
</state>
Undeploy the old version using the web interface and deploy the new one. You should now be able
to create new data stager objects. Of course, you may not want everyone to be able to create new
data stagers. In that case, specify a different role instead of world. You will have to add
a new rule using the web interface to grant someone this role.
Operations using the resource
Whenever a user invokes an operation on a resource, the PEP will check
that the user is authorised and lock the resource for you. When your operation returns,
the PEP will unlock it. Therefore, no-one else can perform operations on the resource
during this time (the PDP will queue requests for around a minute by
default before giving up).
A typical operation looks like this:
public void deleteContents() {
DataStager resource = getDataStagerFromContext();
resource.deleteContents();
pdp.signal(resource.ID, "delete");
}
Sending a signal may cause the state of the resource to change, affecting
what operations can be called. In this case, the delete signal will
cause the PDP to transition the resource to the empty state, from which
the read operation is unavailable.
When a SOAP message arrives, the PBAC PEP extracts the resource ID from a WS-Addressing
header element. It ensures that there is a signature covering both this header and the SOAP
body, and passes the context and operation to the PDP to check that the
signer is authorised to invoke the method. If the operation is authorised, the PEP stores
the context in the Axis message context for use by the service.
The private getDataStagerFromContext method gets this resource context from
the SOAP message context. There should always be a context by this point, because
if the user didn't give one then PBAC would have used the service policy rather than
the data stager policy to check the user's authorisation and the service policy
should never allow deleteContents to be called. A typical implementation looks like this:
private DataStager getDataStagerFromContext() {
ProcessContext primaryContext = ProcessContextHelper.getProcessContext();
String resourceID = primaryContext.getConversation();
if (resourceID == null)
throw new RuntimeException("No resource ID in message context (policy enforcement error)");
return loadFromDatabase(resourceID);
}
Getting a resource's process state
Many services will maintain their own idea of a resource's state. However, sometimes it
is useful to know the PBAC process state of a resource, or even make it available to
clients. This operation does not send any signals, and thus does not change
the state.
public String getState() {
DataStager resource = getDataStagerFromContext();
return pdp.getCurrentState(resource.ID);
}
Destroying a resource
There are two special states: UNINITIALISED-STATE and
DESTROYED-STATE. If a resource is unlocked while in either of
these two states, it is destroyed (causing PBAC to forget all about it).
The destroyed state is used to remove a resource which is no longer needed,
while unlocking an uninitialised resource indicates a failure while the
service was setting up the resource.
To destroy a resource, send a signal such as destroy. The policy should
cause the resource to transition to DESTROYED-STATE. When the PEP unlocks the
resource after the operation completes, PBAC will forget about the resource.
Other PBAC operations
For a complete list of available PBAC operations, consult the JavaDoc reference.
Unlocking a resource during an operation
A service's operation is invoked by Axis only once PBAC has checked that
the client is authorised to perform the operation. The resource is locked
at this point.
Before returning, the service may send any number of events to the PDP,
which may choose to update the state of the resource in response.
The fetch operation unlocks the resource, fetches the data
from a remote URL, and then sets the state to FULL and returns. The
purpose of this example is to show how the context can be unlocked during
a long-running operation. Note that the resource itself must not be
modified while it is unlocked; instead, we download to a temporary file. Once
the data is ready, we lock the context to actually copy it in.
"completeFetch" is an internal (fake) operation, used to reacquire the lock
during the fetch operations.
Since the resource is unlocked, another operation could change its
state (such as making it read-only or finishing it) while we fetch the data. In
that case, the lockAndCheck operation would throw an exception (which is
safe and reasonable). An alternative approach is to transition into a 'fetching'
state at the start, from which only getState can be called. However, this
makes the error handling more complex, as you must take care that the resource
is not left in the 'fetching' state on error.
public void fetch(URL source) {
DataStager resource = getDataStagerFromContext();
pdp.unlock(resource.ID);
File tempFile = source.fetchToTemp(); // Slow; resource unlocked
try {
pdp.lockAndCheck(resource.ID, "completeFetch");
resource.store(tempFile);
pdp.update(resource.ID, "fetch-complete");
} finally (
tempFile.unlink();
}
}
Another alternative would be use lockForAdmin instead of lockAndCheck. This
locks the resource without checking that it is in a suitable state. In this case
you don't need to pass the user's identity, and the operation name is only used for
display purposes (e.g., a notice in the admin page saying why the resource is locked).
Use lockAndCheck if you want the operation to fail if the resource is now in
the wrong state, or the user may have lost their privileges.
Delegation
Delegation operations (granting additional users access to an existing context)
are invoked by the service. The client therefore calls a normal service
operation, subject to the normal PBAC access control rules, to perform
these operations.
For example, a data service might provide an addReaderRule operation which
controls access to the reader role. The PBAC policy will determine who can call
the addReaderRule operation on the service.
public void addReaderRule(MatchRule rule) {
DataStager resource = getDataStagerFromContext();
delegate.checkRole("reader");
pdp.addAccessControlRule(resource.ID, rule);
}
You should also provide service operations that call
PDP.removeAccessControlRule and PDP.getAccessControlRules to provide a full
access control interface.
Discovering resources
Most services provide an operation to let users ask "Which resources can I access?". This is easily
implemented using PBAC:
public String[] getResources() {
return pdp.getResources(MyServiceInterface.MY_RESOURCE_TYPE, getCurrentUser(), null);
}
You may wish to convert the resources to EndpointReferenceTypes, so that they include the service
address and any other meta-data (such as the user's label for the resource).