Thursday, January 20, 2011

Custom Identity Assertion for OAM-OES SSO integration

Introduction
OAM and OES both provide mechanisms to protect Web Applications. Some customers have expressed the need to achieve SSO between these two Access Management solutions. This article intends to describe how one would implement such integration.
Overview
The solution mostly involves custom components from the OES side so we will list the ones required from OAM which are mostly just configurable components provided out of the box by OAM. So from OAM’s stand point all is needed is an Access Gate which will be tied to the custom Identity Asserter that will be built for OES. To accomplish this the Access Server SDK must be installed on the server where OES is installed. Locate the Access Server SDK distribution coming with OAM’s installation packages and install it locally on the OES server. Then configure an Access Gate in OAM’s Access System Console and then run the ConfigureAccessGate command line tool from the Access Server SDK access/oblix/ConfigureAccessGate/ directory. Make note of the installation directory of this access gate because you will have to provide that somehow to your implementation of the SSPI Identity Assertion Provider that will consume the SSO token from OAM, details following. For details on how to configure OAM’s Access Gate and install and configure the Access Server SDK you can refer to the OAM’s product documentation.

So far what you have done is to achieve connectivity between the OES server and the Access Server from OAM. This is needed to validate the ObSSOCookie coming from OAM which will be passed in the request to the WS-SSM of OES as an Identity Assertion with a Token Type set as ObSSOCookie, we will cover this in more detail in sections to come within this article.

Now we are ready to discuss the OES side components. First and foremost, we need an Identity Assertion Provider which is a SSPI implementation of an Identity Assertion provider. The code is presented below (Thanks to Chris Johnson and Josh Bregman for providing the code for this solution):

package com.oracle.access;
import java.util.Arrays;
import java.util.Enumeration;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.AppConfigurationEntry;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import weblogic.management.security.ProviderMBean;
import weblogic.security.service.ContextHandler;
import weblogic.security.spi.AuthenticationProviderV2;
import weblogic.security.spi.IdentityAsserterV2;
import weblogic.security.spi.IdentityAssertionException;
import weblogic.security.spi.PrincipalValidator;
import weblogic.security.spi.SecurityServices;
import com.oblix.access.*;

public class OAMIdentityAsserterProviderImpl implements AuthenticationProviderV2 {
/**
* The attribute of the DN used to identify the
* user in WebLogic. example: cn
*/
private String userAttribute;

public AppConfigurationEntry getAssertionModuleConfiguration() {
// TODO Auto-generated method stub
return null;
}

public IdentityAsserterV2 getIdentityAsserter() {
// TODO Auto-generated method stub
return new Foo(userAttribute);
}

public AppConfigurationEntry getLoginModuleConfiguration() {
// TODO Auto-generated method stub
return null;
}

public PrincipalValidator getPrincipalValidator() {
// TODO Auto-generated method stub
return null;
}

public String getDescription() {
// TODO Auto-generated method stub
return null;
}

public void initialize(ProviderMBean mbean, SecurityServices arg1) {
OAMIdentityAsserterMBean config = (OAMIdentityAsserterMBean)mbean;
String installDir = config.getAccessGateSDKInstallDir();
System.out.println(System.getProperty("java.library.path"));
System.out.println("Initalizing connection to OAM using "+installDir+" updated 3.");
try {
ObConfig.initialize(installDir);
System.out.println("Initialized");
} catch (Throwable t) {
t.printStackTrace();
throw new RuntimeException(t);
}
this.userAttribute = config.getNameAttribute();
}

public CallbackHandler assertIdentity(String arg0, Object arg1, ContextHandler ctxHandler) throws IdentityAssertionException {
// TODO Auto-generated method stub
System.out.println("Asserting ID for "+arg0+" value="+arg1);
String [] names = ctxHandler.getNames();
System.out.println("Attributes: "+Arrays.asList(names));
HttpServletRequest req = (HttpServletRequest)ctxHandler.getValue("HttpServletRequest");
if (req!=null) {
Enumeration e = req.getHeaderNames();
while (e.hasMoreElements()) {
String header = (String)e.nextElement();
String value = req.getHeader(header);
System.out.println(header+"=>"+value);
}
}
try {
String token = null;
if (arg1 instanceof byte []) {
token = new String((byte[])arg1);
} else if (arg1 instanceof String) {
token = (String)arg1;
} else {
System.out.println("Unknown token "+arg1.getClass().getName());
}
System.out.println("Token="+token);
if (token.equals("loggedoutcontinue")) {
HttpServletResponse resp = (HttpServletResponse)ctxHandler.getValue("HttpServletResponse");
System.out.println("Sending redirect to "+req.getRequestURI()); resp.sendRedirect(req.getRequestURI());
resp.getWriter().close();
throw new IdentityAssertionException("No Identity. Redirecting");
}
ObUserSession session = new ObUserSession(token);
String userName = session.getUserIdentity();
System.out.println(userName);
return new ObSSOTokenCallbackHandler(session,userAttribute);
} catch (Exception e) {
e.printStackTrace();
throw new IdentityAssertionException(e.getMessage());
}
}

public void shutdown() {
// TODO Auto-generated method stub
}
}

This code works as follows: The first thing it does is to retrieve the configuration directory for the Access Gate configured as described in previous sections of this post when the Access Server SDK was installed and an Access Gate was configured. When you create a security provider in WebLogic, there is a utility that creates the provider as a Managed Bean (MBean creation utility). This creates the provider with a set of properties that can be configured via the WebLogic System Console to supply configuration values need by providers. This is how it retrieves the installation directory of the Access Server SDK to retrieve the configuration to connect to OAM’s Access Server. The rest is just processing the ObSSOCookie. In order to consume the cookie as part of this implementation there is also a CallbackHandler that retrieves the ObSSOCookie from the HttpServletRequest. The code for this handler is shown below:

package com.oracle.access;
import java.io.IOException;
import java.util.StringTokenizer;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import com.oblix.access.ObUserSession;

public class ObSSOTokenCallbackHandler implements CallbackHandler {

private ObUserSession session;
private String userNameAttribute;

public ObSSOTokenCallbackHandler(ObUserSession session, String userNameAttribute) {
super();
this.session = session;
this.userNameAttribute = userNameAttribute;
}

public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
// TODO Auto-generated method stub
System.out.println("There are "+callbacks.length);
for (int i=0; i Callback c = callbacks[i]; i++) {
if (c instanceof NameCallback) {
try {
System.out.println("Handling Name Callback for "+session.getUserIdentity()+" "+userNameAttribute);
NameCallback nc = (NameCallback)c;
nc.setName(getName());
} catch (Exception e) {
e.printStackTrace();
}
} else {
System.out.println("Can't handle callback "+c.getClass().getName());
}
}
}

private String getName() throws Exception {
String dn = session.getUserIdentity();
StringTokenizer parser = new StringTokenizer(dn,"=,");
while (parser.hasMoreTokens()) {
String token = parser.nextToken();
if (token.equals(userNameAttribute)) {
return parser.nextToken();
}
}
throw new Exception("Couldn't find "+userNameAttribute+" in "+dn);
}
}


The Class above has the purpose of extracting the ObSSOCookie from the HttpRequest and creating an ObSession object with it. Then it retrieves the identity within the cookie and parses the resulting DN to extract the user name which will be used as the principal for the authenticated subject. Now, once all these components are in place we need to tell OES when to call this provider, which is anytime it sees a Token Type of ObSSOCookie. OES represents Security Tokens via implementations of the CredentialHolder interface. We need to create an implementation of the CredentialHolder interface as follows:

package com.bea.security.ssmws.credentials;
public class OAMSSOCookieIndentityHolderImpl implements CredentialHolder
{
private String m_cookie;
public static final String m_Type = "ObSSOCookie"; // This should match the Token Type expected by the OAMIdentityAsserter configured
public void setCookie(String cookie)
{
this.m_cookie = cookie;
}

public String getCookie()
{
return this.m_cookie;
}

public Object getObject()
{
return getCookie();
}
public void setObject(Object cred)
{
setCookie((String)cred);
}

public String getType()
{
return this.m_Type;
}

public String getAsString()
{
return this.m_cookie;
}
}


As you can see in the code above this class has the only purpose of representing the ObSSOCookie as an acceptable token type. The next step is to configure the castor.xml files to allow OES to successfully de-serialize the incoming token from the xml request submitted to the WS-SSM. This is accomplished by modifying the following files:
Open the file C:\bea\1032\ales32-ssm\webservice-ssm\lib\com\bea\security\ssmws\soap\.castor.xml and modify it as follows:
<class name="com.bea.security.ssmws.credentials.OAMSSOCookieIndentityHolderImpl ">
<map-to cst:xml="ObSSOCookie" />
<field name="cookie" type="java.lang.String" >
<bind-xml node="text"/>
</field>
</class>


Open the file C:\bea\1032\ales32-ssm\webservice-ssm\lib\com\bea\security\ssmws\credentials\.castor.xml and modify it as follows:
<class name="com.bea.security.ssmws.credentials. OAMSSOCookieIndentityHolderImpl ">
<map-to cst:xml="ObSSOCookie" cst:ns-uri="http://security.bea.com/ssmws/ssm-soap-types-1.0.xsd" />
<field name="cookie" type="java.lang.String" >
<bind-xml node="text"/>
</field>
</class>

The next step is to test this. So when making a Web Service method call to the WS-SSM the SOAP message should contain an identity assertion named as ObSSOCookie, see XML fragment below:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<isAccessAllowed xmlns="http://security.bea.com/ssmws/ssm-soap-types-1.0.xsd">
<IdentityAssertion>
<ObSSOCookie>34EFB3201BCA31F21A3E320A21B31D19</ ObSSOCookie >
</IdentityAssertion>
<RuntimeResource>
<ResourceString>app/claim </ResourceString>
<AuthorityName>ARME_RESOURCE_AUTHORITY </AuthorityName>
</RuntimeResource>
…
</soap:Body>

</soap:Envelope>


The tag name needs to reflect exactly the name of the token configured in the Identity Assertion Provider of the Web Logic Security configuration and also the configuration changes made in castor.xml files.


Monday, January 17, 2011

The (Windows) Natives Are Restless

From Brian:  I'm adding this excellent post by Matt to our OAM 11g Academy series. To view the first post in the series which will be updated throughout to contain links to the entire series, click here: http://fusionsecurity.blogspot.com/2011/02/oracle-access-manager-11g-academy.html 
 OAM 11g has the ability to do Windows Native Authentication to give a Windows client desktop SSO to the OAM-protected application. This was possible in OAM 10g as well, but it required an IIS server to do the heavy lifting of getting the Kerberos ticket and authenticating the user. In 11g, Oracle does not require IIS to accomplish desktop SSO. WebLogic also had this capability by using its SPNEGO Identity Asserter, but this approach gives one SSO to any other OAM-protected application as well. The documentation is in chapter 7 of the Integration Guide.
For the krb5.conf file my example is (showing all the edited parts; there were other pieces that came with my Amazon images that I left alone):
[realms]
IAM.COM = {
kdc = ip-10-116-199-182.ec2.internal
admin_server = ip-10-116-199-182.ec2.internal
default_domain = IAM.COM
}
[domain_realm]
.iam.com = IAM.COM
iam.com = IAM.COM
On the KDC (Active Directory), I created a user named “idam11g”. This is the WebGate host, what you’re going to use in the browser. I ran the following to create the keytab:
C:\>ktpass -princ HTTP/idam11g@IAM.COM -pass P@ssw0rd -mapuser idam11g -out c:\logs\keytab.service
Make sure that your User Login name in the Active Directory looks like “HTTP/idam11g”
Copy the keytab.service file to your OAM server.
Test on your OAM Server box that you can generate the Kerberos token by using the kinit command:
$ kinit HTTP/idam11g@IAM.COM -k -t /oracle/stage/wna/keytab.service
Now make the changes to OAM to tell it how to contact the KDC. Here is the oam.config.xml entry:
<Setting Name="KerberosModules" Type="htf:map">
<Setting Name="6DBSE52C" Type="htf:map">
<Setting Name="principal" Type="xsd:string">HTTP/idam11g@IAM.COM</Setting>
<Setting Name="name" Type="xsd:string">Kerberos</Setting>
<Setting Name="keytabfile" Type="xsd:string">/oracle/stage/wna/keytab.service</Setting>
<Setting Name="krbconfigfile" Type="xsd:string">/etc/krb5.conf</Setting>
NOTE: The docs are off on the contents of oam-config.xml. Also, if you make changes to the oam-config.xml, these changes may get reset if you make other changes in OAM through the console. I’ve heard of similar problems when doing the OAM-OAAM integration. I think you are better off making this edit through the console. You can configure this through the console by going to System Configuration (tab)->Authentication Modules->Kerberos Authentication Modules->Kerberos:






While in the oamconsole, you want to configure AD to be the Primary Identity Store. This is located at System Configuration (tab)->Data Sources->User Identity Stores.

A word about Role Mapping: The “OAM Administrator’s Role” field is looking for an existing group in AD. Users in that group will be able to login to oamconsole once AD becomes the primary identity store. Don’t forget to press the “Set as Primary” button. Once you do this, you should be able to authenticate to your default web page with AD users’ credentials.
I recommend creating a separate Authentication Policy for WNA with a OnAuthFailure redirect so you can see when you are getting an OAM Auth failure vs. other reasons. I used the existing “KerbScheme” for the Authentication Scheme as is, without editing.

I configured an 11g WebGate on 11g OHS via standard means.
For IE7, here is the process for setting Integrated Windows Authentication for the client:
  • Select Tools, Internet Options.
  • Select the Security tab.
  • Ensure that your WebGate-protected OHS site is in the list of trusted “Sites”
  • Select Local intranet and click Custom Level....
  • In the Security Settings dialog box, scroll to the User Authentication section.
  • Select “Automatic logon only in Intranet zone”.
  • Click OK.
  • Select the Advanced tab.
  • Scroll to the Security section.
  • Make sure that Enable Integrated Windows Authentication option is checked and click OK.
  • If this option was not checked, restart the client.

Tools like isHTTPHeaders can help you determine whether the Negotiate token is being issued. It should look something like:
GET /oam/CredCollectServlet/WNA?request_id=-3931587206375492112&error_code=OAM-1001&redirect_url=http%3A%2F%2Fip-10-124-122-41%3A7777%2Fwna%2Findex.html HTTP/1.1
…
OAMRequestContext_idam11g:7777_444a69=DJi+QPvh2J7VEs6bzv0EAQ==
Authorization: Negotiate
<Long encrypted string of about 1600 chars>
For OAM troubleshooting, you want to be able to turn on some tracing via WLST:
cd <MW_HOME>/Oracle_IDM1/common bin
./wlst
connect(‘weblogic’,’<password’)
listLoggers(pattern=”oracle.oam.*”,target=”oam_server1”)
setLogLevel(logger=”oracle.oam”,level=”TRACE:32”, persist=”0”, target=”oam_server1”)
You can then check your diagnostics log at <IDM_DOMAIN>/servers/oam_server1/logs/oam_server1-diagnostic.log.
A successful transaction should look something like:
[2011-01-13T12:34:40.730-05:00] [oam_server1] [TRACE:16] [] [oracle.oam.controller] [tid: [ACTIVE].ExecuteThread: '0' for queue: 'weblogic.kernel.Default (self-tuning)'] [userId: ] [ecid: 0000Iq0SORD1rYspkgg8yZ1DBnSC00000R,0] [SRC_CLASS: oracle.security.am.engines.enginecontroller.AuthnEngineController] [APP: oam_server] [dcid: d03843071ed98d9b:6369d65b:12d806f7606:-8000-0000000000000015] [SRC_METHOD: createSubject] RETURN oracle.security.am.engines.sso.SSOSubject@16748a2c, Subject: [{ Subject: Subject:[[
Principal: username@IAM.COM
Principal: CN=User Name,cn=users,dc=iam,dc=com
Principal: \4f\81\08\d9\90\14\29\43\81\fd\2b\a2\59\c3\21\ab
, GroupsLoaded: false, UpdateSession: false, isAnonymous: false }], Subject Attrs: [String Map: {}], User Id: will.laase@IAM.COM, User DN: CN=Will Laase,cn=users,dc=iam,dc=com, GUID: \4f\81\08\d9\90\14\29\43\81\fd\2b\a2\59\c3\21\ab, Auth Level: 2, Auth Scheme: KerbScheme
]]
The documentation talks about doing the same with Firefox, by browsing to about:config and setting:
network.negotiate-auth.trusted-uris =http://idam11g:7777
I was not able to get firefox to work. If anyone can comment on a success path here, that would be appreciated.

My first custom OVD adapter

For a PoC I have been working on I wrote a (demo quality) OVD adapter that I thought might be interesting for others.

In my PoC I had OIF, OAM, OVD and DSEE and OES. In the main use case a user can come along to the SP with an x.509 certificate issued by an issuer known to the SP. The SP is expected to do the normal certificate authentication "stuff" (crypto operations, CRL & OCSP checking) and then make authorization decisions for URLs based upon the user identity including information about the user that the SP doesn't get in advance. To get that information the SP will need to make SAML attribute requests to an IdP.

Out of the box OAM and OIF support this use case via the Oracle Access Manager AuthZ Plug-in. That plug-in makes calls to OIF which in turn goes and gets the necessary attributes from the right IdP. This is great and answers most of the requirement.

The missing bit is that this customer wanted to go further... much, much further. They want to make very fine-grained authorization decisions deep down in their applications (by calling OES). And they want OES to make its decisions based on the attributes coming from the IdP.

My first plan was to write an equivalent to the OAM x.509 Attribute Sharing plug-in for OES. It should have taken me all of a couple of hours start to finish including deploying it in the PoC environment. Naturally I thought to myself "That's not all that interesting. Why don't you do something more clever?". I blame too much caffeine and inadequate sleep for that thought.

Everything in the environment (OAM, OES, WebLogic and a few other things) already make LDAP calls to get user info and make authorization decisions. Why not hide all of the SAML stuff down in OVD. Then there's only one place that needs to talk to OIF.

Click through to see what I did.

Friday, January 14, 2011

WebLogic Domain Models for Installing the Oracle Identity Management Suite – Part 2

A couple days ago I wrote what I consider to be an important post about whether different Oracle middleware packages (or bundles) should be installed together in a single domain or installed in separate domains.

I’ve received a few questions asking a logical extension of that topic which is what about the individual products within one package? Should individual products within one package be installed together in a single domain (which is really the default behavior) or be spread across several domains? For example, if you are deploying the Identity Management package with OID,OVD, and OIF, should you install them all in one domain or maybe put OIF in one domain and OID in a separate domain?

There are a number of things to consider in answering this question.

Let’s begin by looking at the issues that led me to recommend that you not install multiple packages/bundles in a single domain.

The first issue was the risk of incompatibilities between the packages and the difficulty in dealing with such issues when they arise. I would have to say that this issue does not apply to multiple products within one package. After all, the package was explicitly developed and tested with the idea that all the products would be running in same domain.

The second issue I raised was the notion that deploying multiple packages to one domain could complicate patching and upgrading; even potentially leading to a situation where you will be kept from upgrading due to version incompatibilities. Again, since we are now talking about products within one package, there is less of a concern about patching and upgrades. However, since even a single product patch could include components that are common across the entire package, having all the products from a package in a single domain means that you should really test every product that you use in the domain before deploying the patch to production. I don’t see this as being a huge deal but it is something to consider.

The next consideration which I did not address in my last post is delegation of duty or purpose for a domain. Some customers segregate certain WLS domains for certain purposes. Often this is seen as a security practice such as the case where a customer deploys all intranet apps to one domain , extranet apps to a second domain, and utility services to a third domain. If you are a customer that does this you may see some products in a package as falling in a different category from another. One example of this is that many customers will see OID and OVD as being “internal” or “utility” applications where as they might see OIF as being an “external “– end user facing application. This might lead them to deploy these applications from the Identity Management package into separate domains.

The last consideration is to note that some of the integrations between products in a package only work if the products are installed in the same domain. Two examples of this are the OAM/OIM integration and the native integration between OAM and OAAM. If you want to use the integrated functionality offered by these packages, you have to deploy them in the same WLS domain.

Thursday, January 13, 2011

Behavior of the OVD "Entry" class

As part of a demo I'm doing this week I wrote a custom OVD adapter and (for various reasons) included my own logic to cache the results between LDAP calls. Along the way I discovered behavior that perplexed me and probably shortened my life by days thanks to how high my blood pressure went.

If you ever find yourself thinking things are going "just swell" on a PoC you should try using an API you've never seen before on a product you've barely used, but wait until the last day to start.

yes, my own fault. And yes it's humbling.

What I hope to save you from discovering the hard way is that OVD's class com.octetstring.vde.Entry can't be shared across calls into the directory.