Monday, May 24, 2010

Identity Propagation using JMS Transport with Oracle Server Bus 11gR1 PS2

In returning from Santa Clara, and the deep dive technical training on the new release, it made me release something about security in OSB. In general, when we discuss OSB security, we spend a lot of time talking about the message level (WS-Security), but not that much time in discussing what can be done at the transport. A lot of what is new in 11gR1 OSB is additional transports - SOA direct, transport JEJB, JCA - that optimizes connectivity among some of the existing FMW components. I'm still coming up to speed on the capabilities of those new transports, but I thought it would be worth discussing the security aspects one of the existing transports - JMS.

The scenario for this discussion is pretty simple: JMS Proxy to JMS Proxy to HTTP Business Service. So the question is what and how can I secure this type of set-up and how do I/can I propagate the user's identity? For purposes of this discussion, we'll use the SOAP/JMS JAX-RPC client to create the message to be placed on the JMS Proxy.

SOAP over JMS client to JMS Proxy


The SOAP/JMS client encapsulates the following steps:

  • Creating an InitalContext to the server
  • Looking up the ConnectionFactory
  • Looing up the Queue/Topic
  • Waiting for a response

This is a sample client that I used:

public static void main(String[] args) throws Exception {


System.setProperty("weblogic.security.CustomTrustKeyStoreFileName",
"C:\\Documents and Settings\\jbregman\\workspace\\client-project\\sample.jks");
System.setProperty("weblogic.security.CustomTrustKeyStorePassPhrase","welcome1");
System.setProperty("weblogic.security.CustomTrustKeyStoreType","JKS");
System.setProperty("weblogic.security.TrustKeyStore","CustomTrust");


test.HttpBackEndServicePortBindingQSService service =
new test.HttpBackEndServicePortBindingQSService_Impl();

test.HttpBackEndService hwService = service.getHttpBackEndServicePortBindingQSPort();

((Stub)hwService)._setProperty(WLStub.JMS_TRANSPORT_JNDI_URL,"t3s://jbregman-pc:7002");
((Stub)hwService)._setProperty(Stub.USERNAME_PROPERTY,"sender");
((Stub)hwService)._setProperty(Stub.PASSWORD_PROPERTY,"welcome1");


String uri = "jms://jbregman-pc.us.oracle.com:7002/"+URLEncoder.encode("OSB Project 1")+"/JMSProxyToBusinessService?URI=HelloWorldReq";

JmsTransportInfo ti =
new JmsTransportInfo(uri);


((Stub)hwService)._setProperty("weblogic.wsee.connection.transportinfo",
ti);


System.out.println("Sending....."+uri);
String hello = hwService.hello("jms");
System.out.println(hello);

}

From a security perspective, let's start with securing the transport between the client and the server. I'm using 1-way SSL, so this means that I need to be able to trust the certificate presented by the server. The JAX-RPC client uses the WLS SSL stack so I've specified the trust store set-up using the weblogic.security.CustomTrustKeyStorePassPhrase,weblogic.security.CustomTrustKeyStoreType, and weblogic.security.TrustKeyStore system properties. Note: The current release of the SOAP over JMS protocol does not support 2-way SSL from a stand alone client. The URL that is used to create the initial context is specified on the WLStub.JMS_TRANSPORT_JNDI_URL property of the stub. Notice I'm using t3s and port 7002 - my SSL port. To authenticated the user and create the initial context, I'm using username and password. These values are specified on the Stub.USERNAME_PROPERTY and Stub.PASSWORD_PROPERTY properties respectively.
Once the user is authenticated, they need to be authorized to place a message on the JMS Queue. By default all users are authorized to do this, but from the WebLogic console, you can restrict which users can perform which actions on a queue. These policies are specified on the Security tab, which can be found on every JMS resource inside of WebLogic Server. In this example, I configured the user sender to be able to send to the Queue and the user receiver1 to be able to receive messages from the queue.

Once the sender has sent the message, the receiver must be authorized to pull the message off of the queue. This begs the question "In an OSB proxy, who is the user retrieving the messge?" This is the service account associated with the JMS Transport. You can also specify in the JMS transport that the message is to be retrieved using SSL. Again, you cannot use 2-way SSL.

So, once the receiver has pulled the message off of the queue, we can take a look at the JMS Transport headers and see a few interesting things

<xml-fragment xmlns:tran="http://www.bea.com/wli/sb/transports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jms="http://www.bea.com/wli/sb/transports/jms">
<tran:headers xsi:type="jms:JmsRequestHeaders">
<tran:user-header name="_wls_mimehdrContent_Type" value="text/xml; charset=utf-8"/>
<tran:user-header name="_wls_mimehdrAuthorization" value="Basic c2VuZGVyOndlbGNvbWUx"/>
<tran:user-header name="_wls_mimehdrSOAPAction" value="&quot;&quot;"/>
<tran:user-header name="WSEE_JMS_SUBJECT" value="rO0ABXNyADN3ZWJsb2dpYy5zZWN1cml0eS5hY2wuaW50ZXJuYWwuQXV0aGVudGljYXRlZFN1YmplY3SyzoT2VRkIHAIAAUwACnByaW5jaXBhbHN0AEFMd2VibG9naWMvc2VjdXJpdHkvYWNsL2ludGVybmFsL0F1dGhlbnRpY2F0ZWRTdWJqZWN0JFNlYWxhYmxlU2V0O3hyADB3ZWJsb2dpYy5zZWN1cml0eS5hY2wuaW50ZXJuYWwuQXV0aGVudGljYXRlZFVzZXJc+OloT3PrewIAB0kACWxvY2FsUG9ydEIAA3Fvc0oACXRpbWVTdGFtcEwAC2luZXRBZGRyZXNzdAAWTGphdmEvbmV0L0luZXRBZGRyZXNzO0wADGxvY2FsQWRkcmVzc3EAfgADTAAEbmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sACXNpZ25hdHVyZXQAAltCeHD/////ZgAAAAAAAAAAcHBwcHNyAD93ZWJsb2dpYy5zZWN1cml0eS5hY2wuaW50ZXJuYWwuQXV0aGVudGljYXRlZFN1YmplY3QkU2VhbGFibGVTZXSsdHU43J7UBgIABUkACGhhc2hDb2RlWgANaGFzaENvZGVWYWxpZFoADmlzUHJpbmNpcGFsU2V0WgAGc2VhbGVkTAAIZWxlbWVudHN0ABZMamF2YS91dGlsL0xpbmtlZExpc3Q7eHDKABo1AQEAc3IAFGphdmEudXRpbC5MaW5rZWRMaXN0DClTXUpgiCIDAAB4cHcEAAAAAXNyACd3ZWJsb2dpYy5zZWN1cml0eS5wcmluY2lwYWwuV0xTVXNlckltcGy+Djjr39dsVQIAAHhyADB3ZWJsb2dpYy5zZWN1cml0eS5wcmluY2lwYWwuV0xTQWJzdHJhY3RQcmluY2lwYWyv/kb+QWV+/AIACFoAFWVxdWFsc0Nhc2VJbnNlbnNpdGl2ZVoAFmVxdWFsc0NvbXBhcmVEbkFuZEd1aWRaABdwcmluY2lwYWxGYWN0b3J5Q3JlYXRlZEwAAmRucQB+AARMAARndWlkcQB+AARMAARuYW1lcQB+AARbAARzYWx0cQB+AAVbAAlzaWduYXR1cmVxAH4ABXhwAAABdAA0dWlkPXNlbmRlcixvdT1wZW9wbGUsb3U9bXlyZWFsbSxkYz1zb2Ffc3VpdGVfZG9tYWluMXQAIEJCMjBCNTEwNjdBMjExREY5RjJGMURBMDBBRkVCNjRDdAAGc2VuZGVydXIAAltCrPMX+AYIVOACAAB4cAAAAA0xMjc0NzUzOTM3NTc4dXEAfgASAAAAEI1pUTSiH6ByzuuXkyMs4Ql4"/>
<tran:user-header name="URI" value="/OSB+Project+1/JMSProxyToBusinessService"/>
<jms:JMSDeliveryMode>2</jms:JMSDeliveryMode>
<jms:JMSExpiration>0</jms:JMSExpiration>
<jms:JMSMessageID>ID:&lt;179700.1274755177281.0></jms:JMSMessageID>
<jms:JMSPriority>4</jms:JMSPriority>
<jms:JMSRedelivered>false</jms:JMSRedelivered>
<jms:JMSTimestamp>1274755177281</jms:JMSTimestamp>
<jms:JMSXDeliveryCount>1</jms:JMSXDeliveryCount>
<jms:JMSXUserID>sender</jms:JMSXUserID>
</tran:headers>
<tran:encoding>UTF-8</tran:encoding>
<jms:message-type>Text</jms:message-type>
</xml-fragment>


I think the two most interesting headers here are _wls_mimehdrAuthorization and JMSXUserID. The JMXUserID is a feature that WLS JMS has to pass the sender's identity. You can specify a default behavior (send if asked, send always) and then override in the destination. Here we see that it was the sender that sent the message, even though it was de-queued by receiver1. We'll talk more about _wls_mimehdrAuthorization in a second. For now, I'm going to hold on to the _wls_mimehdrAuthorization header by copying to the outbound request in the transport header.

Invoking 2nd JMS Proxy as JMS Business Service


The set-up for the next proxy is similar to the set-up of the first. The JMS transport can be configure to use 1-way SSL and a service account to retrieve messages. The protection on the second queue is a little different. The permission for who can receive messages is the service account - lets call this identity reciever2, but who can we restrict to sending messages to this queue? Unfortunately, neither the sender or receiver identity is propagated, automatically and the call to the second queue is done as anonymous. This is the debug details:

<xml-fragment xmlns:tran="http://www.bea.com/wli/sb/transports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jms="http://www.bea.com/wli/sb/transports/jms">
<tran:headers xsi:type="jms:JmsRequestHeaders">
<tran:user-header name="_wls_mimehdrContent_Type" value="text/xml; charset=utf-8"/>
<tran:user-header name="_wls_mimehdrAuthorization" value="Basic c2VuZGVyOndlbGNvbWUx"/>
<tran:user-header name="_wls_mimehdrSOAPAction" value="&quot;&quot;"/>
<tran:user-header name="WSEE_JMS_SUBJECT" value="rO0ABXNyADN3ZWJsb2dpYy5zZWN1cml0eS5hY2wuaW50ZXJuYWwuQXV0aGVudGljYXRlZFN1YmplY3SyzoT2VRkIHAIAAUwACnByaW5jaXBhbHN0AEFMd2VibG9naWMvc2VjdXJpdHkvYWNsL2ludGVybmFsL0F1dGhlbnRpY2F0ZWRTdWJqZWN0JFNlYWxhYmxlU2V0O3hyADB3ZWJsb2dpYy5zZWN1cml0eS5hY2wuaW50ZXJuYWwuQXV0aGVudGljYXRlZFVzZXJc+OloT3PrewIAB0kACWxvY2FsUG9ydEIAA3Fvc0oACXRpbWVTdGFtcEwAC2luZXRBZGRyZXNzdAAWTGphdmEvbmV0L0luZXRBZGRyZXNzO0wADGxvY2FsQWRkcmVzc3EAfgADTAAEbmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO1sACXNpZ25hdHVyZXQAAltCeHD/////ZgAAAAAAAAAAcHBwcHNyAD93ZWJsb2dpYy5zZWN1cml0eS5hY2wuaW50ZXJuYWwuQXV0aGVudGljYXRlZFN1YmplY3QkU2VhbGFibGVTZXSsdHU43J7UBgIABUkACGhhc2hDb2RlWgANaGFzaENvZGVWYWxpZFoADmlzUHJpbmNpcGFsU2V0WgAGc2VhbGVkTAAIZWxlbWVudHN0ABZMamF2YS91dGlsL0xpbmtlZExpc3Q7eHDKABo1AQEAc3IAFGphdmEudXRpbC5MaW5rZWRMaXN0DClTXUpgiCIDAAB4cHcEAAAAAXNyACd3ZWJsb2dpYy5zZWN1cml0eS5wcmluY2lwYWwuV0xTVXNlckltcGy+Djjr39dsVQIAAHhyADB3ZWJsb2dpYy5zZWN1cml0eS5wcmluY2lwYWwuV0xTQWJzdHJhY3RQcmluY2lwYWyv/kb+QWV+/AIACFoAFWVxdWFsc0Nhc2VJbnNlbnNpdGl2ZVoAFmVxdWFsc0NvbXBhcmVEbkFuZEd1aWRaABdwcmluY2lwYWxGYWN0b3J5Q3JlYXRlZEwAAmRucQB+AARMAARndWlkcQB+AARMAARuYW1lcQB+AARbAARzYWx0cQB+AAVbAAlzaWduYXR1cmVxAH4ABXhwAAABdAA0dWlkPXNlbmRlcixvdT1wZW9wbGUsb3U9bXlyZWFsbSxkYz1zb2Ffc3VpdGVfZG9tYWluMXQAIEJCMjBCNTEwNjdBMjExREY5RjJGMURBMDBBRkVCNjRDdAAGc2VuZGVydXIAAltCrPMX+AYIVOACAAB4cAAAAA0xMjc0NzUzOTM3NTc4dXEAfgASAAAAEI1pUTSiH6ByzuuXkyMs4Ql4"/>
<tran:user-header name="URI" value="/OSB+Project+1/JMSProxyToBusinessService"/>
<jms:JMSDeliveryMode>2</jms:JMSDeliveryMode>
<jms:JMSExpiration>0</jms:JMSExpiration>
<tran:headers xsi:type="jms:JmsRequestHeaders">
<tran:user-header name="_wls_mimehdrAuthorization" value="Basic c2VuZGVyOndlbGNvbWUx"/>
<tran:user-header name="SOAPAction" value="&quot;&quot;"/>
<jms:JMSCorrelationID>ID:424541534594cf52454b9f5300000128cc73e121fffff612</jms:JMSCorrelationID>
<jms:JMSDeliveryMode>2</jms:JMSDeliveryMode>
<jms:JMSExpiration>0</jms:JMSExpiration>
<jms:JMSMessageID>ID:&lt;179700.1274755177328.0></jms:JMSMessageID>
<jms:JMSPriority>4</jms:JMSPriority>
<jms:JMSRedelivered>false</jms:JMSRedelivered>
<jms:JMSTimestamp>1274755177328</jms:JMSTimestamp>
<jms:JMSXDeliveryCount>1</jms:JMSXDeliveryCount>
<jms:JMSXUserID>receiver2</jms:JMSXUserID>
</tran:headers>

Notice that the JMXUserID is receiver2. This is the user that pulled the message off of the second queue. Presumably, this is because the service account of the business service is receiver2.

If you want to restrict access to places messages on this queue, you'll need to do it using message-level security, and then using message level access control in OSB.

JMS Proxy invoking HTTP Business Service


Finally, time to invoke the HTTP Business Service. This can be done over 1-way SSL or 2-way SSL with a Service Key, but what if we want to pass the original user's identity? If only we had their username and password....low and behold, simply copy the _wls_mimehdrContent_ transport header to the Authorization HTTP header and you will have HTTP Basic Authentication to the business service. I'm not great at XQuery but $inbound/ctx:transport/ctx:request/tp:headers/tp:user-header[1]/@value retrieved the value of the header. We can see from the JWS Web Service that I used to test, that we're really getting the user's identity passed.

@WebService
public class HttpBackEndService {

@WebMethod
public String hello(String in) {
return "Hello "+weblogic.security.Security.getCurrentSubject()+" "+in;
}
}

You get output like this:

Sending.....jms://jbregman-pc.us.oracle.com:7002/OSB+Project+1/JMSProxyToBusinessService?URI=HelloWorldReq
Hello Subject:
Principal: sender
Principal: SenderGroup
jms

Summary


WebLogic Server can define authorization policies on a JMS destination and OSB can work with those policies through the use of service accounts. You can pass the identity of the sender by using the JMXUserID. You can also capture the HTTP Basic Authentication of the user stored in the _wls_mimehdrContent_. Think very carefully about this. You are going to store the users passwords in a simple well known encoding. Its probably a better approach to use the worlds most dangerous identity asserter and a custom authentication to push the sender's identity on to the stack than holding on to the password. All told, the JMS protocol has rich security functionality and can be easier to implement than message level security. On the other hand, since JMS messages can be stored for a long time, in many cases it makes a great deal of sense to use message level protection - encryption of sensitive data and signing to avoid tampering. What do you think? Do you have to use message level security with JMS or is some of what I've shown is possible with the JMS transport and SSL effective in some situations?

No comments:

Post a Comment

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