The Authz component of caGrid provides integration of the NCICB's Common Security Module (CSM) with caGrid services. This document demonstrates how to use Authz by providing a simple tutorial.
In this tutorial we will:
- Create a simple service using Introduce
- Secure our service using Transport Level Security (TLS)
- Enforce authorization policy using both of the approaches supported by caGrid
- See how to extend the declarative authorization framework.
You will need:
- Java 1.5
- Ant 1.6.5 (or higher)
- Eclipse 3.2+
- Jakarta Tomcat 5.0.28 (or higher)
- Globus Toolkit 4.0.3
- caGrid 1.0 source distribution
- Access to a CSM database and UPT with administrative privileges
- Trust fabric synchronization with some grid (preferably the NCICB grid)
- Grid credentials
- Access to a GridGrouper service and privileges to create stems and groups and assign users
Please note that this is a work in progress. Your comments/suggestions are welcomed.
Let's start by creating a simple service that concatenates strings.
In CAGRID_HOME/projects/introduce, type:
The Introduce GUI will appear. Enter the basic service information as shown:
The press the Create button. When Introduce is finished, you'll see a screen like this:
Our service should have an operation that takes two strings and concatenates them. Navigate to the Operations tab and select the Add button. This will open an editor in which you can specify inputs, outputs, faults, etc.
Create two input parameters, s1 and s2, each of type string (that's XML Schema string). Create an output parameters, also of type string. Then click Done.
Now we want to secure the service.
|NOTE: Some kind of authentication method is required to support authorization (which is the main subject of this tutorial). Globus supports three method, which are all based on the use of X.509 certificates: Transport Layer Security (TLS), Secure Conversation, and Secure Message.|
We will use Introduce to configure the service to require that clients use TLS with a privacy protection level.
Click the Security tab. Select the Custom radio button. Check the Transport Level Security check box. Select Privacy as the Communication Method.
And then click Save.
At this point, Introduce has created an Eclipse project under the directory that you specified. We want to go right ahead implement the concat operation, so we'll open this project in Eclipse.
To open the project in Eclipse, right-click in the Package Explorer view and select the Import... option from the context menu. Then select General > Existing Projects Into Workspace option, and click Next.
Then you'll have the option to navigate to the root directory of the project to be imported. Navigate to the directory that you specified in Introduce.
Click the Finish button.
After you've imported the project and expanded some of the nodes, your package explorer view will look like this:
We'll first add some code to ConcatServiceImpl.java, and then add some code to ConcatServiceClient.java so that we can test the service.
Open ConcatServiceImpl.java and implement the concat operation like this:
Then, in ConcatServiceClient.java, change the main method to look like this:
Now we are ready to deploy the service. Make sure to set your CATALINA_HOME environment variable to the Tomcat root directory. Then, in your new project root directory, type:
This will deploy your service to Tomcat. Once that's done, you can start up Tomcat.
You can check that everything started up correctly by pointing your browser to this URL:
You should see a list of the deployed service. Check that there is one named cagrid/ConcatService. If you don't see anything, check CATALINA_HOME/logs/catalina.out for something like this:
This means your container credentials are not in the right place. Make sure they are where you specified in CATALINA_HOME/conf/server.xml.
Before we can test out the service, we need to acquire some credentials so that the service can verify our identity. The GAARDS project provides a GUI to interact with the Dorian, the service that is responsible for issue grid credentials to users of caGrid. This GUI also allows you to specify the Identity Provider (IdP) that will provide an assertion to Dorian that you are who you claim to be.
In CAGRID_HOME/projects/security-ui, type:
The GAARDS GUI will come up. Press the Login button and select the appropriate Identity Provider. The following image shows how this interface looks when the user wants to authenticate with the cagrid-auth IdP identity provided, which allows NIH users to obtain grid credentials using the NIH credentials.
Enter your username and password. Then press the Authenticate button.
The following window will appear.
Click the Save Proxy button and save the proxy file to your service's project directory. Now we are ready to run the grid service. Let's run the client with Ant. In your project directory edit the run-tools.xml file. As follows:
You should see this in CATALINA_HOME/logs/catalina.out:
And you should see this in your console window:
If you get some error about "Unknown CA", then you do not have the certificate of the CA that signed the conainer's certificate in your $HOME/.globus/certificates directory.
At this point, we've verified that authentication is working. Again, without authentication, we do not have the identity of the caller, which we need to do authorization.
Now we are ready to add CSM authorization to this service. There are two ways to do this in caGrid. The first approach is API-based. One can use Introduce to specify that access the service or operations in the service should be authorized against CSM authorization policy. Introduce will generate the necessary code in your grid service. The second approach uses the declarative security configuration framework that is provided by the Globus Toolkit.
We'll use the use the API-based approach first, and then use the declarative approach afterward.
Let's start by restricting access to the service. To do express this in CSM terminology, we need to create a protection element to represent the service. And specify that the user must have a certain privilege on that protection element. We also need to tell CSM what policy or application context applies to this protection element.
We'll use the following:
- Application Context: demo1
- Protection Element: http://authz.tutorials.cagrid.org/ConcatService
- Privilege: ACCESS
|Note: The projection element can be any string. But I like to use the XML namespace of the service's WSDL.|
Now, caGrid authorization is based on membership in groups that have been defined in GridGrouper some grid grouper service. So, we need to create a group in CSM that has the ACCESS privilege on the protection element that represents our service.
We'll use this group name:
The structure of this group name is interpreted by the caGrid authorization component to mean that it should verify with the GridGrouper service (running at https://localhost:8543/wsrf/services/cagrid/GridGrouper) whether the user is a member of the st1:grp1 group. If so, it will grant the user whatever privileges that group has been provisioned with in CSM.
Now we'll walk through the process adding authorization to our grid service, constructing the authorization policy in CSM, mapping it to a group in GridGrouper, and adding the user to the correct GridGrouper group.
In Introduce, click on the Security tab, and then the Authorization sub tab. This is where you set up authorization at the service level. Select Common Security Module (CSM) from the Authorization Mechanism drop-down list.
Enter the follow items:
To see the difference between service-level and operation-level, let's create another operation named concat2, with the same signature and return type as the concat operation. Then, click the SecurityTransport Level Security tab for that operation. with a privacy protection level should already be selected. Select the Authorization sub tab. Select Common Security Module (CSM) from the Authorization Mechanism drop-down list. Then enter the following items:
This means that the user must have the EXECUTE privilege on this protection element:
...which is the QName of the operation in the service's WSDL. Again, this is strictly a convention. Notice too, that we are using the same application context.
Press the Done button, and then the Save button. Introduce will update your service, adding the appropriate code. Let's take a look at this code. The two classes that are modified are highlighted in the following image:
In ConcatServiceProviderImpl.java, calls to ConcatServiceAuthorization.java before invoking the service implementation.
And it ConcatServiceAuthorization.java, calls are made directly to CSMGridAuthorizationManager to verify authorization on the configured protection elements.
Notice that we did not specify authorization for the concat method. So, the service-level authorization is used to protect that operation. On the other hand, we did specify authorization on the concat2 operation, so the operation-level authorization is checked there.
Introduce generates all the necessary code, but you still have to do a couple of things by hand. First, you need to specify the CSM ApplicationSecurityConfig.xml file to be used. In your project directory, edit the service.properties file, and set the value of the csmConfiguration property to the absolute path to you CSM configuration file. (see the appendix for an example of this file and the associated Hibernate configuration).
Second, you need to make sure the CLM ObjectStateLoggerConfig.xml file ends up on your service's classpath in Tomcat (i.e. in WEB-INF/classes). The easiest way to do that is to put that file into the source directory of your project. It will be deployed correctly.
Now we need to set up the authorization policy in our CSM database. A logical representation of the set of associations that we want to create is shown in the following image.
In CSM, privileges are grouped into a role and protection elements are grouped into a protection group. To grant the members of a group a set of privileges on a set of protection elements, one must assign a projection-group-and-role pair to the group.
In the above diagram, we are saying that group [https://localhost:8543/wsrf/services/cagrid/GridGrouper}\ st1:grp1|https://localhost:8543/wsrf/services/cagrid/GridGrouper%7Dst1:grp1] has ACCESS and EXECUTE privileges on the http://authz.tutorials.cagrid.or/ConcatService and http://authz.tutorials.cagrid.org/ConcatService}concat2 protection elements.
Now we will look at how to create this policy using the CSM User Provisioning Tool (UPT).
First we will create the protection elements. Note that a protection element has a name and an object ID. It is the object ID that matters. Use some meaningful abbreviation for the name.
Log into your UPT, by entering the admin username, password, and the name of the application context. For this tutorial, the application context is demo1.
From the initial UPT page, click the PROTECTION ELEMENT tab. Click the Create A New Protection Element link. Enter a protection element name and object ID. Then click the Add button. The two images below show the creation of these two protection elements.
Now let's create the protection group. Click on the PROTECTION GROUP tab and click the Create A New Protection Group link. Enter the protection group name as shown below.
Press the Add button. Then press the Associated PEs button. Select our two protection elements from the top list and press the Assign button. Then press the Update Association button.
Now let's create the role. Click the ROLE tab and click the Create A New Role link. Enter the name of the role as show below.
Click the Add button. Then click the Associated Privileges button. Select the ACCESS and EXECUTE privileges from the top list and click the Assign button. Then click the Update Association button.
Finally, let's create the group and add the protection group and role to it.
Click the GROUP tab. Select the Create A New Group link. Enter the group name as shown below.
Click the Add button. Then click the Assign PG & Roles button. Select ConcatService from the Available Protection Groups list and press the Assign button. Then select the ConcatService.INVOKERS role from the Available Roles list and press the Assign button.
Press the Update Association button.
That's it. Now click the Associated PE & Privileges button. This will display the protection elements and privilege pairings that have been granted to this group.
|NOTE: that we have granted both ACCESS and EXECUTE on both protection elements. That will work for this example, but it more access than absolutely required. To achieve finer-grained policy control, one can create roles that contain a single privilege and protection groups that contain a single protection element. But if we had done that, then I would've had to paste even more images into this already lengthy tutorial.|
So, at this point, we have used Introduce to generate code that will enforce an authorization policy that is defined in our CSM. And we have defined that policy in CSM. Now all that is left to do is ensure that the caller of our grid service is a member of the appropriate group in GridGrouper.
Recall that the group name is _
st1:grp1|https://localhost:8543/wsrf/services/cagrid/GridGrouper%7Dst1:grp1]. This means that in the GridGrouper service that is running at https://localhost:8543/wsrf/services/cagrid/GridGrouper, there must be a stem named _st1 and within that stem, there must be a group named grp1. And, our caller must be a member of that group.
Now we'll look at how to create this group and add the grid identity of the caller to it. You'll need the appropriate privileges in your GridGrouper to do the following (or ask your GridGrouper administrator to do it for you).
In CAGRID_HOME/projects/security-ui, type:
The GAARDS GUI will load. Make sure to login as a user that has the appropriate privileges in GridGrouper. Once you've logged in, your credentials (grid proxy) will be available to use other tools in the GAARDS GUI.
Click on the Group Management button. Then click the Add Grid Grouper button and select you credentials in the pop-up window.
Click the Add button. You will see the GridGrouper tree load in the left-hand panel. Select the root stem of the tree and click the View button. The properties of that stem will load in the right-hand panel. Select the Child Stems tab, and enter values st1 for Local Name and st1 for Local Display Name as in the following image.
Click the Add Child Stem button. The new step will appear in the list of child stems. Select that stem and click the View Stem button. Then click the Groups tab for that stem. Enter grp1 in the Local Name field and grp1 in the Local Display Name field. Press the Add Group button. You'll see the new group appear in the list of child groups.
Now we need to add the caller's identity to this group. Recall that we had to create a proxy to use to invoke our grid service. It is the grid identity of the user that Dorian created the proxy for that we need to add to our new GridGrouper group. When you login to Dorian through GAARDS, your proxy is displayed to you. Your grid identity is one of the properties that are displayed.
So, let's log into Dorian again to get our grid identity. Press the Login button. Enter your username and password (and make sure to select the right IdP). Press the Authenicate button. Then copy the value of the Identity field.
In this case, my identity is
Now go back to the GridGrouper interface. Select grp1 from the list of child groups, and press View Group button. Then select the Members tab for that group. Click the Add Member button end enter the grid identity that we just obtained.
Press the Add Member button. Click the List Members button to see the member you just added.
That's it! We are ready to test this all out.
|NOTE: If you've taken a little break (more than 12 hours) and come back to this. You'll have to re-save your proxy to file because the one you created before is now invalid.|
Redeploy your grid service and restart Tomcat. Then run the client. If all goes well, you should see something in catalina.out like this:
This means that when the GridGrouper client tried to contact the GridGrouper service, Globus attempted to use the default proxy. But, we haven't set up a default proxy. You can disregard this message. But it confirms that our service did call out to GridGrouper.
If all does not go well, and you see some sort of exception, keep in mind that the three most common problems are:
- Your CSM configuration file can't be found. Check that it is where you said it was.
- You didn't put ObjectStateLoggerConfig.xml on the classpath. Make sure it's either in your deployed jar (in our case ConcatService-common.jar) or in WEB-INF/classes.
- The group name that you created contains an incorrect GridGrouper service URL.
At this point, we've successfully created a grid service that uses transport level security and enforces a CSM authorization policy.
Now we will look at an alternative approach to enforcing authorization policy. Previously, we used Introduce to generate code into our service. That code made the necessary calls to the CSMGridAuthorizationManager to protect our service and operations. We refer to this as the API-based approach.
But caGrid also supports a declarative configuration approach that builds upon the pluggable authorization framework provided by the Globus Toolkit. This framework allows one to specify a chain of Policy Decision Points (PDPs) that will be consulted before granted access to any of the service's operations. Globus provides several built-in PDPs, and custom PDPs can be plugged in. We have provided a custom PDP that will map elements of the SOAP body to protection elements and privileges in CSM so that a policy decision can be made.
The value of this approach is that it enables authorization policy to be enforced without modifying code, in cases where that is undesirable or impossible. For example, authorization can be enforced on any operation that has been deployed with the service, including operations that are implemented by operation providers (i.e. not implemented in your service itself). It also allows fine-grained authorization policy to be enforced. Since any element of the SOAP body can be mapped to a protection element and privilege, one can enforce object instance-level authorization.
Let's start by using Introduce to remove the authorization code that we had created. Bring up Introduce and press the Modify Service button. Then navigate to and select the ConcatService directory. Press the Open button.
Select the Security tab and then the Authorization sub tab. Select No Authorization from the Authorization Mechanism drop-down list. This will reset authorization on all the operations. So, we won't have to explicitly remove authorization on the concat2 operation. Click the Save button.
Now if you look at ConcatServiceAuthorization.java, you'll see that the authorizeConcat and authorizeConcat2 methods are empty.
Let's add a few more operations to our service so that we can examine the various features of PDP-based authorization approach.
Create a third operation, named concat3 with the same signature and return type as the concat operation. Finally, let's create an operation that takes a complex data type.
Introduce allows you to load XML schemas from the file system. Any XML type that has been defined in such a schema is available as an input or output parameter for the operations that you define.
Create the following XML schema.
Then, in Introduce, select the Types tab and the File System sub tab. Click the Browse button to navigate to and select this XML schema file. Click the Add button. You'll see the namespace loaded into the Data Types tree. Select the new namespace, and change the value of the Package field to org.cagrid.tutorials.authz.beans as shown below.
Now select the Operations tab and click the Add button to add a new operation. Set the name of the operation to concat4, and create two input parameters, s1 and s2, of type http://beans.authz.tutorials.cagrid.org/ConcatService}statement. Create an output parameter of type http://beans.authz.tutorials.cagrid.org/ConcatService}compoundStatement.
Click the Done button. Click the Save button.
Make sure you've implemented concat2 and concat3. Then add the following implementation to concat4.
This implementation just inserts the given statements into a new compoundStatement, and adds a new statement with an id of 3.
Now let's use the PDP approach to enforce the authorization policy that we've already defined in CSM. Four steps are required to do this:
- Modify the service's security descriptor to specify that the caGrid PDP implementation should be used for authorization.
- Modify the services Axis deployment descriptor to point the PDP to its configuration file.
- Create a PDP configuration file.
- Configure CSM to use the CSMGridAuthorizationManager.
- Set the gov.nih.nci.security.configFile system property to point to our CSM configuration file.
Our service's security descriptor is located in our project directory at etc/ConcatService-security-desc.xml. Modify the authz element in that file to look like this:
Our service's Axis deployment descriptor is located in the root of our project directory. Add a parameter to the service configuration as follows:
Now we need to create the PDP configuration file. In the etc directory, create a file named pdp-config.xml and insert the following XML.
Let's examine the contents of this file piece-by-piece. First of all, this is a Spring beans file. The first bean definition sets up our PDP implementation, CSMPDP. This class implements the basic mapping algorithm.
The CSMPDP requires that its authorizationManager property be populated with and implementation of the GridAuthorizationManager interface. It also requires that its selectorSelector property be populated with an implementation of the PENodeSelectorSelector interface.
The GridAuthorizationManager interface looks like this.
Given an identity, objectId, and privilege, it will return a boolean indicating the authorization decision. For the CSMGridAuthorizationManager implementation of this interface, the objectId parameter represents the object ID of a protection element defined in CSM. The CSMPDP will map elements of the SOAP message to protection element object IDs and privileges and ask the GridAuthorizationManager instance if the caller isAuthorized. If the caller is not authorized for any object ID, authorization fails, and Globus will return a fault indicated that the caller is not authorized to invoke the operation.
But CSMPDP doesn't actually map SOAP message elements to object IDs itself. It delegates that behavior to other the objects that we set up in the rest of this PDP configuration file.
Let's look at the objects that CSMPDP delegates to.
The other property of CSMPDP is selectorSelector, which must be of type PENodeSelectorSelector. A PENodeSelector implementation is responsible for returning an implementation of PENodeSelector, given a org.apache.axis.MessageContext. The PENodeSelectorSelector interface looks like this.
A PENodeSelector is responsible for returning an array of PENode objects and the name of the privilege that applies to these objects, given a org.w3c.dom.Document object that is the contents of the SOAP body. The PENodeSelector interface looks like this.
A PENode object represents a mapping of a org.w3c.dom.Node object, which is an element of the SOAP body, to the object ID of protection element. The PENode interface looks like this.
The PDP configuration file builds and populates the instances of PENodeSelectorSelector and PENodeSelector that CSMPDP will use to do its job.
Let's return to the PDP configuration. The second bean definition, with an id of selectorSelector, sets an implementation of PENodeSelectorSelector that will choose PENodeSelectors based on the QName of the operation being invoked.
In this case, our configuration file indicates that if the operation QName ends in concat2, then the PENodeSelector object with the bean ID of concat2 should be returned. Otherwise, a PENodeSelector object with the bean ID of theService should be returned.
The value of the pattern property on these RegExPENodeSelectorMapping objects must be a Perl5 regular expression. The patterns are evaluated against the operation QName in the order they are declared in this file. So, the most specific pattern should be placed first, while the most general pattern should be placed last. In this way, we can easily enforce service- and operation-level authorization. If the operation does not match one of the more specific patterns, then if falls through to the most general pattern, which enforces service-level authorization.
Also note that if no PENodeSelector is returned, authorization fails. So, the default behavior is to deny access to the service.
Let's look at the two PENodeSelector objects now.
Both of these PENodeSelector objects are of type ConstantPENodeSelector, which simply returns a single PENode object (actually and array of size 1) that has the privilege and objectId that are specified in this file.
These two beans enforce the same authorization policy that we had previously enforced using Introduce (i.e. by generated code into our service implementation).
Now we will modify the ApplicationSecurityConfig.xml file to indicate that the CSMGridAuthorizationManager class should be used instead of the default CSM implementation. Recall the when we did this with Introduce, the generated code instantiated CSMGridAuthorizationManager directly by calling the constructor. Here we want to use the Factory method provided by CSM's SecurityServiceProvider.
Modify the demo1 application context of your ApplicationSecurityConfig.xml file to look like this:
The last thing we need to do is set the gov.nih.nci.security.configFile system property to point to our ApplicationSecurityConfig.xml file. We can do this by add this property to CATALIAN_HOME/conf/catalina.properties. The property should look like this:
Let's re-deploy the service and run the client. You should see the same results.
If you see some exception message like "... Error setting property values; ... PropertyAccessExceptionsException...", make sure you've configured CSM to use CSMGridAuthorizationManager, and not the default implementation.
At this point, we are enforcing the same authorization that we did using Introduce.
Now let's look at how we can enforce instance-level authorization. Suppose our client invokes the concat4 operation, passing in the following XML.
And suppose that we want to restrict operations on particular statement objects. We can create object instance-level authorization policy in CSM and update our PDP configuration to enforce it.
This is the logical structure that we want to create in CSM.
Construct this policy using the UPT (I won't include the screen shots here).
Then add the following to pdp-config.xml.
This new PENodeSelect object will select statement Nodes from the SOAP body contents. Each Node will be passed to and ObjectIdGenerator. In this case, the ObjectIdGenerator will apply an XSL transformation to the Node to generate an object ID, using the literal org.cagrid.tutorials.authz.beans.Statement. and the value of the statement element's id attribute.
Now let's change the client to invoke the concat4 operation. Change the implementation as follows.
Be sure to place compoundStatement.xml in the test/resources directory. This code will deserialize that file, pull out the two statement elements, and pass then to the concat4 operation. Then it will serialize the result to a string and print it out.
Re-deploy the service. Restart Tomcat. Run the client again. You should see the following output.
Notice that we have been granted access to concat statements 1 and 2. This is what we expected. But also notice that the service returned a third statement with an ID of 3. Our authorization policy has not granted the caller authorization to this object, but since our PDP is not checking the returned results, the object was returned to the caller.
In fact, neither Introduce authorization tooling nor the Globus authorization framework will supports filtering of results. Usually, this is something that the underlying application should handle. But, in cases where the application does not enforce authorization on returned results, we can enforce it in the grid service by either modifying the code or creating an Axis Handler that will inspect the SOAP response message.
In the caGrid Authz component, we provide an Axis Handler that can be used to filter SOAP messages using the same components that are used in the PDP implementation.
Let's look at how to use this Handler to prevent the caller from getting objects for which he is not authorized.
Edit the Axis deployment descriptor (service-config.xml) to include the following.
Then, create a file named response-filter-config.xml in the etc directory, and add the following content.
Most of this file can be copied direction from our PDP configuration. The important difference to notice is in the first bean definition....
First, the responseFilter bean is implemented by CSMNodeFilter. Second, the nodeHandler property expects an implementation of the PENodeHandler interface. That interface looks like this.
CSMNodeFilter will pass each PENode that the caller is not authorized for to the PENodeHandler. The PENodeHandler implementation decides what should be done with the Node contained in the PENode. The NodeRemovalPENodeHandler will simply remove the given Node.
Let's test it out. Re-deploy the service. Re-start Tomcat. Run the client again. You should see the following output.
At this point, we have enforced authorization policy ant the service, operation, and object instance levels.
Now, recall that the default behavior of the CSMPDP is to deny access to the service, unless at least one PENode is selected and the caller is authorized for all selected PENodes. Suppose that we would like to change this behavior and open up the service's other operations while still requiring the user to authenticate (say, for auditing purposes).
There is no parameter on the CSMPDP that will change its default behavior, but because the Authz PDP framework uses Spring to inject dependencies into CSMPDP, we can achieve this by simply providing a different implementation of GridAuthorizationManager.
Create the following class in your project.
This class requires two properties authorizationManager and publicPEs. The publicPEs property is a regular expression which will be evaluated against the objectId that is passed into the isAuthorized method. If it matches, then authorization will be granted. Otherwise, this class will delegate to an instance of GridAuthorizationManager (the authorizationManager property).
We can integrate this new authorization manager implementation into our PDP configuration (pdp-config.xml) as follows.
First, make the CSMPDP use our new implementation.
Second, make the theService PENodeSelector return anyOperation as the objectId.
|NOTE: We could have left this PENode selector as it is, but the regular expression to match the service URI would have been ugly.|
To test this out, we will change the client to call the concat2 and concat3 operations in succession and look at what our service prints to the console.
Now we need to... You guessed it: Re-deploy the service. Re-start Tomcat. Run the client.
In catalina.out you should see (somewhere) this...
...and then this...
In your console window, you should see this.
Now you should know how to use both Introduce and the CSMPDP to enforce authorization policy at multiple levels, and how to extend the CSMPDP framework to suit your needs.