Monday, June 14, 2010

Oracle Service Bus, SOA Suite Human Workflow Web Services and SAML

A customer recently was looking for some guidance on how to apply SAML and OSB to SOA Suite Human Workflow Web Services. I'd never looked at those particular web-services, but applying SAML to OSB and SOA Suite in 11g is pretty straightforward - OWSM...right? Well...this really depends on the requirements and which particular services are being used. In this post, I'll explain a little bit about the identity propagation options for the Human Workflow Web Services, and layout two different approaches to deploying OSB and SAML to protect them.

Identity Propagation Options for Human Workflow Web Services



The customer that I'm working with was particularly interested in the TaskService and TaskQueryServices. After looking at the documentation, I started by pulling down the WSDL. Most of the WSDL and dependent schemas are XML representations of the Workflow Services, but there is one part that caught my attention:


<service name="TaskQueryService">
<port name="TaskQueryServicePortSAML" binding="tns:TaskQueryServiceSOAPBinding">
<soap:address location="http://192.168.56.102:18001/integration/services/TaskQueryService/TaskQueryService2/*"/>
</port>
<port name="TaskQueryServicePort" binding="tns:TaskQueryServiceSOAPBinding">
<soap:address location="http://192.168.56.102:18001/integration/services/TaskQueryService/TaskQueryService"/>
</port>
</service>

The service has two ports - TaskQueryServicePort and TaskQueryServicePortSAML. They both have the same binding, so they support the same messages, just two different ports. Digging a little deeper, I took a look at the WSDL on the SAML Port by going to http://192.168.56.102:18001/integration/services/TaskQueryService/TaskQueryService2?ORAWSDL and look what I found....OWSM policy!

<wsp:Policy xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:oralgp="http://schemas.oracle.com/ws/2006/01/loggingpolicy" xmlns="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="wss10_saml_token_service_policy" xmlns:orawsp="http://schemas.oracle.com/ws/2006/01/policy" orawsp:displayName="Wss10 SAML Token Service Policy" xmlns:orasp="http://schemas.oracle.com/ws/2006/01/securitypolicy" orawsp:description="This policy authenticates users using credentials provided in SAML tokens in the WS-Security SOAP header. The credentials in the SAML token are authenticated against a SAML login module. This policy can be applied to any SOAP-based endpoint. " orawsp:attachTo="binding.server" Name="oracle/wss10_saml_token_service_policy" orawsp:category="security" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" orawsp:local-optimization="check-identity">
<orasp:wss10-saml-token orawsp:Silent="false" orawsp:name="WSSecurity SAML Token" orawsp:Enforced="true" orawsp:category="security/authentication">
<orasp:saml-token orasp:is-encrypted="false" orasp:is-signed="false" orasp:confirmation-type="sender-vouches" orasp:version="1.1"/>
</orasp:wss10-saml-token>
</wsp:Policy>


People know, that I'm not really a WS-Policy purist of really even much of a fan, so I really like the ?ORAWSDL "trick" to help figure our which OWSM policy is being applied. From looking at the ORAWSDL, we've learned that the SAML end-point is protected by the oracle/wss10_saml_token_service_policy. This is a simple WS-Security SAML 1.0 Sender Vouches policy. Putting OSB 11gR1 in front of this service should be a pretty simple application of the OWSM/OSB integration. This is the one of the two approaches I'll explain in a little bit.


If there are two ports, and the 2nd port supports SAML for identity propagation, what does the 1st port use? The answer is a proprietary token. Before everyone goes BOOOOOOO, if you look at the services that are exposed through this end-point, they are synchronous, single-party, RPC style services. If all the requests are made over SSL, then there is no real advantage or requirement for message level security. I suspect, that this proprietary token was at some point an HTTP Cookie, and was probably just passed around in the HTTP transport header...also a well established (read: WebSSO) practice. I was at a different customer a few weeks back and we came to a very similar recommendation to use a transport token instead of message level security and SAML. Every environment is different, but this other way of doing identity propagation is reasonable for this situation. The token itself has been SOAP-a-sized (made SOAP friendly?) by being passed in a com:workflowContext element in all of the messages. Here's a sample:



This is an example of the authenticate call that returns the token for a user authenticated by their username and password. The really interesting part is what's commented out in the com:credential element - onBehalfOfUser. That's right - the proprietary token has impersonation built it. This actually goes beyond what is available in the SAML based identity propagation. The second solution will show how to use SAML with the proprietary token including the onBehalfOf functionality.

Solution 1 - OSB proxy calling Human Workflow Web Service as Business Service using SAML


Once you figure out what's going on with the Human Workflow Web Services, fronting it with OSB 11g and OWSM is pretty straight forward. The basic idea is that you're going to create a business service from the SAML Port, and then configure OSB to use OWSM when calling the business service. One of the nice things about OWSM is that picking the corresponding client policy for a service policy is pretty straight forward.



The only thing you really need to consider is are you going to set-up OSB as an active intermediary or as a pass-through. In either case, you create a proxy-service based on the business service, and configure the OWSM policy oracle/wss10_saml_token_service_policy. This policy uses a SAML Sender-Vouches, but there is no message protection. This means that the message itself is not signed. Notice, looking back at the policy, the assertion itself isn't signed or encrypted either, so we'll need to do something at the transport/network level to ensure that the request is authentic. If there is a trusted network between OSB and SOA suite, then you may not need anything else. You can use WebLogic's Connection Filters as an additional software firewall. If this is not sufficient, then you can set-up 2-way SSL between OSB and SOA Suite to ensure that the request is valid. OSB still uses Service Key Providers to determine the Certificate to use for making 2-way SSL calls, even if OWSM message level policies are applied. Why not just change the policy protecting the services to require that the message or assertion is signed? These end-points are not proper JAX-WS Web Services running in WebLogic Server...there are just servlets, so there is no simply way to change the policies. I think the best approach here is to use 2-way SSL.

Solution 2 - OSB Proxy calling Human Workflow Web Service using Proprietary Token


But Josh, I thought this was doing to be about using SAML? It is. OSB can still expose an endpoint protected by a SAML policy, but the call to Human Workflow Web Service is going to be done using the existing token identity propagation mechanism. The trick is that the proxy pipeline is going to make a Java callout to get the token and add it into the message. The key to the whole solution is the getWorkflowContextForAuthenticatedUser method. This call will return the token for the user. But what if OSB and SOA Suite are running on two separate domains? So this is a remote call, but the OSB and SOA suite domains have to be trusted. The simplest is to just set-up Global Trust. This means that the two domains will trust eachother's signed Principals. There are a few more subtle points that can best be explained by showing the Java callout code and the pipeline.


First, let's look at the pipeline. Conceptually, we have to make the Java callout, get the token, and then add the token to the request in the com:token element of the message. It's pretty simple, and there are far betten XPath people than I, but this is how I did it.



  • Assign $body//com:onBehalfOfUser/text() to a variable - we'll talk more about this in a second.
  • Make the Java Callout. The most important point is that the call requires a Service Account of type passthrough. This makes sure that the caller's identity is passed down so we know who the user that we're requesting a token for. We're passing in the onBehalfOfUser from above as a parameter. This method returns the token as a String.
  • Assign $body/child::node() to the variable operationName. This is the child node of the body...which is the operation request. The com:workflowContext needs to be added to the first child of this element.
  • Insert the following as the first child of operationNode

    <com:workflowContext>
    <com:credential>
    <com:identityContext>jazn.com</com:identityContext>
    </com:credential>
    <com:token/>
    <com:locale>en_US</com:locale>
    <com:timeZone>US/Eastern</com:timeZone>
    </com:workflowContext>

  • Replace ./com:workflowContext/com:token in operationNode with $token - the value returned from the Java callout
  • Replace child::node() in $body with opertaionNode

This is the complete pipeline:



Now the Java callout. The code itself is actually pretty simple, but there was some tricky stuff with classloading. I started off trying to load the Client Jar Files into OSB directly, but was getting some very wierd ClassNotFoundExceptions with weblogic.jndi.WLInitialContextFactory. I decided after some fiddling that I was just going to load the bpm-services.jar
from the system classpath. This means modifying the setDomainEnv.sh to add the jar at start-up. This works really well, except that since the jar contains EJBs, they workflow services don't get deployed on the server. This is fine since for this customer OSB and SOA Suite are two separate domains. I'm sure there is a different way to fix the classloading but I was under a deadline, so this is the best for now.

Now that we have the set-up out of the way, this is the code for the callout


public static String getToken(final String runAsUser) throws Exception {
System.out.println("RunAsUser="+runAsUser);
Subject theUser = weblogic.security.Security.getCurrentSubject();

System.out.println("The User IS="+theUser);

if (theUser==null || theUser.toString().trim().equals("Subject:")) {
System.out.println("No user....");
return "";
} else {
System.out.println("'"+theUser.toString().trim()+"'");
}


System.out.println("Calling to get token for "+theUser+" from t3://localhost:8001");
Object token = weblogic.security.Security.runAs(theUser, new PrivilegedAction() {
@Override
public String run() {

try {
Class clientFactoryClass = ClassLoader.getSystemClassLoader().loadClass("oracle.bpel.services.workflow.client.WorkflowServiceClientFactory");

Method factoryMethod = clientFactoryClass.getMethod("getWorkflowServiceClient", new Class [] {String.class, oracle.bpel.services.workflow.client.config.WorkflowServicesClientConfigurationType.class, Logger.class});


WorkflowServicesClientConfigurationType wscct =
new WorkflowServicesClientConfigurationType();

List servers = wscct.getServer();
ServerType server = new ServerType();
server.setDefault(true);
server.setName("default");
servers.add(server);


RemoteClientType rct = new RemoteClientType();
rct.setServerURL("t3://localhost:18001");
rct.setInitialContextFactory("weblogic.jndi.WLInitialContextFactory");
rct.setParticipateInClientTransaction(false);

server.setRemoteClient(rct);


IWorkflowServiceClient client =
(IWorkflowServiceClient)factoryMethod.invoke(clientFactoryClass, new Object[] { WorkflowServiceClientFactory.REMOTE_CLIENT, wscct, null});

ITaskQueryService tqs = client.getTaskQueryService();

IWorkflowContext wfContext = tqs.getWorkflowContextForAuthenticatedUser();

if (runAsUser==null) {

return wfContext.getToken();

} else {

IWorkflowContext onBehalfOfCtx = tqs.authenticateOnBehalfOf(wfContext, runAsUser);
return onBehalfOfCtx.getToken();
}

} catch (Exception e) {
e.printStackTrace();
}

return "";
}

});

System.out.println("The token is "+token);
return (String)token;
}


The code itself is really pretty simple. You do need to pull the Subject using the always handy weblogic.security.Security.getCurrentSubject() call, and then invoke the PriviledgedAction. This is required so that when the client running inside of OSB makes the call to SOA Suite, the user's identity is passed, and a token for that user is returned. Again, you have to make sure that you've configured the Java callout with the passthrough service account. Finally, we check if a runAsUser (onBehaldOfUser) is passed, and if it is then instead of returning the token based upon the user's identity in SAML, you get a token onBehalf of the passed in user. In my testing, using the OOTB SAML capabilities, the information in the com:workflowContext was not processed, including the onBehalfOfUser information, so without a username and password, I think using the token based approach is the only way to leverage this functionality with SAML.

Summary



In laying out the two approaches, the simpler OOTB approach will work if you don't need the onBehalfOfUser functionality and you can secure the link between OSB and SOA Suite either at the network or transport level using 2-way SSL. The more complicated approach has the restriction of SOA Suite and OSB being in separate domains, but does have the advantage of supporting onBehalfOf. I would also make sure that for this approacj, there is transport or network security between the OSB and SOA suite since the token (though encrypted) could be replayed. There could be performace issues with the constant creation of the com:workflowContext objects, so its not hard to imagine that the Java callout is extended to use Coherence for caching of Subjects to tokens.


All and all a very good first "real test" for my new "huggable server" - multiple SOA and OSB domains + IDE + Browser without breaking a sweat. I can't but help feel that this solution can be improved, or somehow simplified. Is all of the complexity of the custom approach warranted? What do you think?

References


Edward Biedmond's Blog

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.