I've been spending a lot of time lately working on OSB and OWSM. As people probably know, 11gR1 OSB is released, and you can now use OWSM to secure both proxy and business services.. You have been able for a while to use OWSM for WLS JAX-WS services and of course you can use it to secure SOA Suite composites and BPEL processes. I really like, and customers really like, the idea of being able to centrally manage WS-Security policy from one place. I also like the fact that there is now a single web-services stack across all of these products that OOTB interoperates with eachother. If you want to know why this is a real improvement, I refer you to the OSB to WLS + SAML post. Also, OWSM has native WS-Security Kerberos Token profile...and I have gotten it to work very nicely with WCF (a post for another time).
Given all this, I've been sharpening my pencil on how to write custom assertions (policies) in OWSM 11g. As people will recall, I did an OES+OWSM custom assertion at OOW 2009, and to be honest, that was really the last time I looked at it. My recollection from that time, and my recent experience was that it was a little challenging. Writing the custom assertions is very similar to writing the custom SSPI plugins. Its the type of thing that you don't do that often, and when you do, once you can get the build script going and some decent samples, its not too bad. So, my contribution to this effort can be found at https://owsm-11g-custom-assertions.samplecode.oracle.com/wiki/. In addition to the OES+OWSM assertion from last year, I added a new project called OWSMAC - OWSM Annotations Compiler. What I tried to do was look at what was challenging in working with the custom assertions and try to make it really, really simple.
These are a few of things that OWSMAC does:
- Automatically generate policy and assertion XML
- Simplified XPath processing
- Dynamic Reloading - no need to reboot the server after each little change
- Consistent and predictable lifecycle
- Programmatic selection of policy and overrides
I'm putting the cart before the horse here, as I've not really fully documented or "javadoc-ed" the project, but I have been able to solve a pretty interesting use case that a couple of customers have been interested in, so I wanted to share it now. I suggest that people join the project for updates on OWSMAC.
Dynamic Policy Selection
The scenario is that from an intermediary (likely OSB or SOA Suite composite) the request to the business service/reference requires message level security, but the specifics of the actual policy depend on some state - information in the message, or the location (network) of the destination or different targets have different security requirements - some partners want SAML and others want Username and Password. WS-SecurityPolicy once again is not sufficient or particularly helpful. You need to be able to determine the policy dynamically. I've done this through one of the samples in OWSMAC - DynamicClientPolicy
package owsmac.test;
import java.util.Map;
import javax.xml.soap.SOAPMessage;
import oracle.wsm.common.sdk.IContext;
import owsmac.annotations.Assertion;
import owsmac.annotations.AttachTo;
import owsmac.annotations.Category;
import owsmac.annotations.CustomMethod;
import owsmac.annotations.Executor;
import owsmac.annotations.MessageContextPropertyValue;
import owsmac.annotations.PolicyNameValue;
import oracle.wsm.security.util.SecurityConstants.ClientConstants;
import javax.xml.ws.BindingProvider;
import oracle.wsm.common.sdk.ISOAPBindingMessageContext;
import oracle.wsm.policyengine.IExecutionContext;
import owsmac.annotations.DestroyMethod;
import owsmac.annotations.ExecutionContext;
import owsmac.annotations.FaultMethod;
import owsmac.annotations.InitMethod;
import owsmac.annotations.PolicyPropertyValue;
import owsmac.annotations.Property;
@Assertion(displayName = "A dynamic client policy", customType = Assertion.CustomType.policy, category = Category.security, attachTo = AttachTo.binding_client)
@Executor(category = Category.security_authentication)
public class DynamicClientPolicy {
@PolicyNameValue()
public String selectedPolicy;
@PolicyPropertyValue(name="csf-key",policyNameValue="selectedPolicy")
//@MessageContextPropertyValue(name = ClientConstants.WSS_CSF_KEY)
public String csfKey;
@MessageContextPropertyValue(name = "javax.xml.ws.service.endpoint.address")
public String address;
@MessageContextPropertyValue(name = BindingProvider.USERNAME_PROPERTY)
public String samlUsername;
@MessageContextPropertyValue(name = "oracle.wsm.subject.precedence")
public String useSubjectPrecedence;
@Property(value = "localhost:389")
public static String LDAP_SERVER;
public static @ExecutionContext
IExecutionContext eCtx;
@InitMethod
public static void init() {
System.out.println("In Init: "+eCtx.getAllProperties());
System.out.println("In Init: The LDAP Server is "+LDAP_SERVER);
}
@DestroyMethod()
public static void destroy() {
System.out.println("Destroyed Dynamic Client Policy");
}
@FaultMethod()
public boolean onFault(IContext context) throws Exception {
ISOAPBindingMessageContext soapContext = (ISOAPBindingMessageContext)context;
SOAPMessage message = soapContext.getFault();
return true;
}
@CustomMethod(extendsPolicyNameValue="selectedPolicy")
public boolean selectPolicy(IContext context) throws Exception {
this.getPolicyFromContext(context);
return true;
}
/**
* This is where the custom logic goes for selecting the policy
* @param content
*/
private void getPolicyFromContext(IContext context) {
System.out.println("In the getContext.....");
Map<String,Object> properties = context.getAllProperties();
for (String property: properties.keySet()) {
Object value = properties.get(property);
System.out.println(property+"=>"+value);
}
System.out.println("The address is "+this.address);
if (this.address!=null && address.indexOf("UNT")!=-1) {
this.selectedPolicy = "oracle/wss_username_token_client_policy";
this.csfKey = "josh.creds";
} else {
this.selectedPolicy = "oracle/wss_saml_token_bearer_over_ssl_client_policy";
this.samlUsername = "foobar";
this.useSubjectPrecedence = "false";
}
System.out.println("The selected policy is "+this.selectedPolicy+" and user="+this.samlUsername);
}
}
The whole idea of OWSMAC is to allow people to use POJOs to build the assertions and let everything else happen "magically". I'll draw your attention to the selectPolicy method. This method has the @CustomMethod annotation with extendsPolicyNameValue. This basically means call this method, and when your done go invoke the policy stored in the field referenced in extendsPolicyNameValue. So, in this method, you can set the name of the policy and then also set additional policy overrides or programmatic overrides (these being the same as the properties for JAX-WS clients).
In the sample, we're just looking at the address (endpointURI) and then either invoking UNT - specifying the csf-key of the user or calling SAML and specifying the name of the user to include in the SAML assertion. In the SAML case, there is also something interesting going on - we're using 11gR1 PS SAML Identity Switching. Notice in order to this we're basically setting two properties - BindingProvider.USERNAME_PROPERTY and oracle.wsm.subject.precedence. The former is the name of the user (which doesn't have to exist in the user directory) and the latter is a flag that tells OWSM not to use the identity in the subject for the SAML assertion. Now, in order to perform identity switching, you need to grant a permission. The documentation is not particularly clear. The permission you need to grant is oracle.wsm.security.WSIdentityPermission resource=<composite name> assert. In the text box, you enter resource=<appname> not <appname>.
This is the simple composite that illustrates the scenario.
The references are to WLS web-services protected by OWSM service policies.
The policy file that gets generated by OWSMAC is then uploaded into EM to create a custom policy.
And then attach the policy to the references to the SAML and UNT services. You can also attach the same policy in OSB
Summary
In an ideal world, there would be no need to this type of programmatic extension of the core policy model. The standards would be precise and comprehensive and all of the OOTB policies would never need to be changed. But with our experience with SSPI and the WLS core security model, there are always occasions where customer requirements fall into that 20%, so its good to know that there are ways to simply extend the core product functionality. I like the simplicity of OWSM and the binding of configuration and policy is broadly very useful. Invariably there will be scenarios like the one above, where more dynamic behavior is required. My plan going forward it to continue to use the annotations model with the OWSMAC samples to illustrate how to execute these types of scenarios. I'm looking for additional samples to prove out or ideally some help developing and shaping the project, for everyone's benefit. Who's with me?
References
Creating Custom Assertions
Hi Josh,
ReplyDeleteGreat post !!! Can't wait for the "native WS-Security Kerberos Token profile" and WCF post, It will be very helpful for my organization.
Cheers,
Gilad.
Do you know if the URL https://owsm-11g-custom-assertions.samplecode.oracle.com/wiki/ is online?
ReplyDeleteThe samplecode repository was (re)moved.
ReplyDeleteWe have the code still but don't currently have a public place to put it. If you contact me by email I can send it to you.