Friday, June 14, 2013

Part 4: Kerberos Authentication, RBAC and SAML identity propagation in OAG

This post is the fourth and last post of a series by Andre Correa and Paulo Pereira on OAG (Oracle API Gateway).

In the first post we introduced the use case and talked about the Kerberos authentication part.

In the second post we talked about Role Based Access Control.

In the third post, we described how to propagate the authenticated user through a SAML token and also covered OWSM configuration in OSB and SOA.

In this post we talk about the client, how we actually submit a Kerberos token along with our request to OAG.

In the first post, we said (verbatim):

“Perhaps the most challenging part was the test client. Supporting Kerberos was a must. Our first and natural attempt was OAG’s API Explorer (formerly OEG Service Explorer). After numerous attempts, configuration changes everywhere, we came to the the conclusion that it simply does not support the SPNEGO protocol properly. In summary, it seems that it doesn’t honor the second leg of the protocol, i.e., after receiving a “WWW-Authenticate: Negotiate” HTTP header in the response, it does not resubmit the request along with the Kerberos token. And we also did not find a way to bypass SPNEGO and submit the Kerberos token directly. The free version of SOAP UI also failed.

Fortunately, via Prasad’s blog post, we came to know a .Net-based tool called WCFStorm-lite, dead simple, that just works. However, another requirement for this exercise was to show test automation best practices. So the client tool should ideally have scripts capabilities, allowing for the client integration into an ANT build script, for example. WCFStorm-lite has some scripts capabilities, but we did not go any further in looking at them, because almost at the same time we got to know Apache CXF’s support for Kerberos.

That basically put an end to our research. What could be better than simply writing a piece of java code? At the heart of Apache CXF’s framework are what they call interceptors, which are basically web services handlers. An inbound interceptor is attached to a web service and processes the message before service invocation. An outbound interceptor is attached to a web service proxy and process the message as it leaves the client. An out-of-the-box Kerberos interceptor interfaces with the KDC, gets a Kerberos token and attaches it to the request before sending it out to the web service (in our use case, a web service exposed by OAG).”

Very well, let’s talk about how to configure a Kerberos Outbound Interceptor in Apache CXF.

1. Create a web service proxy using Apache CXF’s wsdl2java utility

> wsdl2java -ant -client –d <path-to-java-classes-to-be-generated> <wsdl>

-ant option will generate a handy build.xml file.

-client option will generate a handy java class with a main method to call the web service.

In our environment, we executed:

> wsdl2java -ant -client –d /home/oracle/workspace/KerberosClient/src

2. Locate the generated client java class and add the Kerberos Interceptor

As the plan was to make our client integrated with JUnit and execute it via ant, we added the Interceptor part to the setup method.

   1: import;
   2: import org.apache.cxf.endpoint.Client;
   3: import org.apache.cxf.frontend.ClientProxy;
   4: import org.apache.cxf.headers.Header;
   5: import;
   6: import org.apache.cxf.transport.http.auth.HttpAuthHeader;
   7: import org.junit.Test;
   8: ...
  10: public final class PlaceOrderPtt_PlaceOrderPt_Client {
  12: private static final QName SERVICE_NAME = new QName("", "PlaceOrderService");
  13: private PlaceOrderService ss;
  14: private KerberosAuthOutInterceptor kbInterceptor;
  16: public PlaceOrderPtt_PlaceOrderPt_Client() {}
  18: private void setup(URL url, String keyTab) {
  19: ss = new PlaceOrderService(url, SERVICE_NAME);
  20: kbInterceptor = new KerberosAuthOutInterceptor();
  21: AuthorizationPolicy policy = new AuthorizationPolicy();
  22: policy.setAuthorizationType(HttpAuthHeader.AUTH_TYPE_NEGOTIATE);
  23: policy.setAuthorization(keyTab);
  24: kbInterceptor.setPolicy(policy);
  25: }
  27: private String placeOrder (URL wsdlURL, String keyTab) throws JAXBException {
  28: if (ss == null) {
  29: this.setup(wsdlURL, keyTab);
  30: }
  31: // The following 3 lines have nothing to do with Kerberos. They show how you can add some custom HTTP headers to your message.
  32: PlaceOrderPtt port = ss.getPlaceOrderPt(); 
  33: ((BindingProvider)port).getRequestContext().put(Header.HEADER_LIST, this.getSoapHeaders());
  34: Client cxfClient = ClientProxy.getClient(port);
  36: //----> Here we add the kerberos interceptor <-----
  37: cxfClient.getOutInterceptors().add(kbInterceptor);
  39: // Then we build the input payload and call web service
  40: return port.placeOrder(payload);
  41: }
  43: @Test
  44: public void testPlaceOrder() throws Exception {
  45: System.out.println(System.getProperty("number.of.runs"));
  46: for (int i=0; i < Integer.parseInt(System.getProperty("number.of.runs")); i++) {
  47: assertEquals("Expected result", System.getProperty("expected.output"), this.placeOrder(PlaceOrderService.WSDL_LOCATION, System.getProperty("krb5.keytab")));
  48: }
  49: }

Tip: The crucial snippet is in the setup method. Specifically, in line 23 we tell the interceptor the keytab containing the identity we want to send. It is a simple string that must match an entry in the JAAS login config file. The JAAS login config file is passed as the value of system property to the JVM.

3. Configure JAAS Login Module entries

The Kerberos interceptor uses JAAS (Java Authentication and Authorization Services) Kerberos Login Module. As such, the Login Module configuration is given as part of a configuration file specified via the system property. Here’s our file called login.config.

   1: oegserver.keytab {
   2: required useKeyTab="true" keyTab="/home/oracle/keytabs/oegserver.keytab" principal="HTTP/DB.US.ORACLE.COM@ADDOMAIN1.TEST";
   3: };
   4: oracle.keytab {
   5: required useKeyTab="true" keyTab="/home/oracle/keytabs/oracle.keytab" principal="HTTP/ORACLE@ADDOMAIN1.TEST" ;
   6: };
   7: andre.keytab {
   8: required useKeyTab="true" keyTab="/home/oracle/keytabs/andre.keytab" principal="HTTP/ANDRE@ADDOMAIN1.TEST" ;
   9: };

Notice that each entry refers to the Krb5LoginModule and some specific parameter, including the actual keytab containing a Krb5 principal. Remember that in post 1 we said a keytab should be generated for each user to be tested.

And the entry name itself is the one to match the value passed to policy.setAuthorization(keyTab) in line 23 in the code snippet above.

4. Externalize your input parameters and add a new task to your generated ant build file

Here’s ours.

   1: <target name="testPlaceOrder" depends="compile" description="Test Place Order Service" >
   2: <junit printsummary="yes" fork="no" haltonfailure="yes">
   3: <classpath refid="cxf.classpath" />
   4: <formatter type="plain" />
   5: <sysproperty key="" value="/home/oracle/login.config"/>
   6: <sysproperty key="krb5.keytab" value="andre.keytab"/>
   7: <sysproperty key="" value="true"/>
   8: <sysproperty key="number.of.runs" value="1"/>
   9: <sysproperty key="input.product.ids" value="0001,0002"/>    
  10: <sysproperty key="expected.output" value="Ok"/>
  11: <test todir="/home/oracle" name="" />
  12: </junit>
  13: </target>

Notice sysproperties and krb5.keytab in lines 5 and 6. At this point, their meanings are well understood.

And line 7 is your best friend when trying to tame the wild dog. You’ll get plenty of useful information for debugging Kerberos in the client.

May this 4-post journey be helpful and don’t let you stranded in the underworld!

No comments:

Post a Comment

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