Integrating WS-Security and WS-SecureConversation Implementations with Apache Sandesha2

Archived Content
This article is provided for historical perspective only, and may not reflect current conditions. Please refer to relevant product page for more up-to-date product information and resources.
  • By Chamikara Jayalath
  • 29 Mar, 2007

Introduction

Security and reliability are two of the most important Quality of Service (QoS) aspects in distributed communication. When it comes to Web services, the same rule applies. People who want to apply the concepts of Web services into real business scenarios see the need for reliable and secure communication.

Apache Axis2 is one of the most promising Web services frameworks. Among many of its useful features, the architecture enables extending its functionalities using components known as Modules. This is the extension point to which new features and QoS aspects can be added. Apache Sandesha2 and Apache Rampart are two such extensions that provide the reliability and security aspects into the system.

The two modules worked well when each one was used individually to provide one of these aspects. Advanced users wanted to use both modules; they wanted to make their communication both reliable and secure. This introduced several challenges to the implementors.

This article gives you an insight into these challenges, and the steps taken to overcome them. We didn't want the solution to tightly bind Apache Sandesha2 and Apache Rampart in the code level. So the architecture we came up with is quite modular and can be used to bind any other WS-Security or WS-SecureConversation implementation (based on Axis2) into Apache Sandesha2.

 

Design and Implementation

Challenges

There were two scenarios we had to take into consideration.

  1. Reliable communication with all the messages secured using the WS-Security specification.
  2. Reliable communication that happens within a secured channel using WS-SecureConversation.

WS-ReliableMessaging, which is the specification followed by Apache Sandesha2, achieves reliability through an acknowledgement and retransmission based model. That is, when the client delivers a message to the Apache Sandesha2 RMS (RM component at the sending side) it will perform several retransmissions of it until an acknowledgement is received from the RMD layer (RM component at the server side). Within this communication, several RM control messages will be exchanged and some of these may get retransmitted as well.

Therefore, we had to secure all the retransmissions of the application messages and the WSRM control messages accurately.

 

WS-ReliableMessaging with WS-Security

Both Apache Sandesha2 and Apache Rampart work in several handlers that are introduced to the system while the modules are initialized and engaged to a particular service. Also, Sandesha2 introduces a special thread called Sender, which is responsible for the transmission and retransmission of messages. It is very important to consider the placement of these components.

The initial design was to place the Apache Sandesha2 Handlers first, Apache Rampart Handlers second, and the Apache Sandesha2 Sender at last. The model is explained in the Fugure 1.

Sandesha2+Rampart - Image1

Figure 1. First arrangement of Sandesha2 and Rampart components

In this scenario, the Sandesha2 Sender grabs the fully secured message. It will be transmitted and retransmitted until an acknowledgement is received.

However, this raised a problem. Think of a scenario where client Apache Rampart handlers add a timestamp to the secured messages. This timestamp will be valid only for a short time period and the server is expected to reject messages that come with invalid timestamps. However, WSRM sequences can be long running. The Apache Sandesha2 layer which is unaware of the timestamp will keep retransmitting the messages that will cause the server to send back security failures.

This caused us to re-think our design and figure out a way to achieve the secure-reliable behaviour overcoming the timestamp problem. The second solution we came up with is given in the following diagram.

Sandesha2+Rampart - Image2

Figure 2. Second arrangement of Sandesha2 and Rampart components

We moved the Sender to somewhere between the Apache Sandesha2 handlers and the Apache Rampart handlers. This was done by a small configuration change in Sandesha2. Apache Sandesha2 has a concept called Retransmittable Phases. These phases are called in every retransmission of a message. So we marked the Security phase (where the Apache Rampart Handlers reside) as a Retransmittable Phase.

This still did not solve the whole problem. There was a possibility of the Apache Sandesha2 retransmissions jeopardizing the messages that are sent to the wire. For example, if Apache Rampart is configured to sign the body of the messages, in the first transmission, the encryption of messages will take place with ease. The message may look as follows.

<env:Envelope>
<env:Header>
<wsse:Security>
<ds:Signature/></ds:Signature>
</wsse:Security>
</env:Header>
<env:Body></env:Body>
</env:Envelope>

 

When this message is retransmitted, it will go through the Apache Rampart handlers again. This will produce the following message.

<env:Envelope>
<env:Header>
<wsse:Security>
<ds:Signature/></ds:Signature>
<ds:Signature/></ds:Signature>
</wsse:Security>
</env:Header>
<env:Body></env:Body>
</env:Envelope>

 

As you can see, the Apache Rampart handlers that are unaware of this retransmission process will sign the message for the second time, which will introduce a duplicate signature header. Further retransmissions will add more and more of these headers. Most probably these messages will be identified as invalid by the server side security layer.

Also, this behaviour depends on the Apache Sandesha2 StorageManager implementation that is being used. For example, when a Persistent storage manager is used, the Sender picks the messages from the database. So it always has a fresh unsecured copy of the messages that were sent through the security handlers. In the InMemory StorageManager, it is the same message that gets sent repeatedly.

We changed the implementation of the InMemory StorageManager to clone MessageContexts in every retransmission. The final working solution is given below.

Sandesha2+Rampart - Image3

Figure 3. Third arrangement of Sandesha2 and Rampart components

Apache Sandesha2 SecurityManager – The Bridge for WS-SecureConversation

Then we faced another challenge. We had to provide a flexible and modular solution for binding Apache Sandesha2 and Apache Rampart, to support WSRM message exchanges within a channel that is secured using WS-SecureConversation.

The WSRM specification provides an extension point to bind these two specifications. It defines a SecurityTokenReference element, which could be within the CreateSequence message, which is the initial message that is sent to start a WSRM sequence. The rest of the messages of the Sequence that gets created here are expected to be secured using the Token referenced in that message.

Again, we did not want to tightly couple Apache Sandesha2 with Apache Rampart. In other words, we want to leave space to couple a different WS-SecureConversation implementation with Sandesha2, if required.

To overcome this challenge, we introduced an abstraction called the SecurityManager into Apache Sandesha2 to hide the details of the WS-SecureConversation implementations. The abstraction is given below.

public abstract class SecurityManager {
public abstract void initSecurity(AxisModule moduleDesc);
public abstract SecurityToken getSecurityToken(MessageContext message);
public abstract SecurityToken getSecurityToken(
OMElement theSTR, MessageContext message);
public abstract OMElement createSecurityTokenReference(
SecurityToken token, MessageContext message);
public abstract void checkProofOfPossession(
SecurityToken token, OMElement messagePart, MessageContext message);
public abstract String getTokenRecoveryData(SecurityToken token);
public abstract SecurityToken recoverSecurityToken(String tokenData);
public abstract void applySecurityToken(
SecurityToken token, MessageContext outboundMessage);
}

 

The abstraction is designed to provide enough flexibility to accurately bind Apache Sandesha2 with a WS-SecureConversation implementation, without any tight couplings. An implementation of this abstraction is also available for Apache Rampart, which is now shipped with Apache Sandesha2.

Let's look at the functionality of each method in the abstraction:

initSecurity

This initializes the particular SecurityManager implementation.

getSecurityToken(MessageContext message)

This method is called on the RMS side when creating the CreateSequence message. The parameter is the security configured application message. The method will be returning a SecurityToken (as defined by Apache Sandesha2), which will be referred in the SecurityTokenReference element mentioned above. If this returns null, the CreateSequence message will not contain a SecurityTokenReference, and there will be no secure conversation contexts involved in the message interaction.

In WS-SecureConversation, the SecurityToken is obtained by sending a special control message called RST (Request Security Token) to the server side. The server will respond with a RSTR (Request Security Token Response) message that contains the required token. A SecurityManager implementation can do all this work within the getSecurityToken method.

createSecurityTokenReference

After obtaining the SecurityToken, Apache Sandesha2 calls this method by passing it and the outbound CreateSequence message as parameters. The SecurityManager implementation can include a reference for this newly obtained SecurityToken in a suitable manner.

getSecurityToken(OMElement theSTR, MessageContext message)

Once the server gets hold of the CreateSequence message, it will check for a SecurityTokenReference element. If it is present, this method will be called passing the OMElement of the SecurityTokenReference and the CreateSequence message as parameters. If a valid SecurityToken is not found, this method will throw an exception.

getTokenRecoveryData

Both RMD and RMS will store a reference for the SecurityTokens that will be used to secure their out bound and in bound sequences respectively. They will be stored in the RMDBeanManager and the RMSBeanManager. At the time of storing, Apache Sandesha2 will call this method to get a storage friendly representation of a particular SecurityToken. For example, a SecurityManager implementation may store the Tokens in an internal HashMap and may return its ID when this method is invoked.

recoverSecurityToken

This method should return the SecurityToken when the ID obtained from the previous method is passed as a parameter.

applySecurityToken

This method is used to secure a particular outbound message with the SecurityToken of its Sequence. Apache Sandesha2 will call this for every outbound message that has to be secured.

checkProofOfPossession

When the RMS receives a message, if the Sequence it belongs to is expected to be secured using a SecurityToken, this method will be called to check whether the message possesses that particular Token. This method may not be doing the actual security verification and most probably will be checking only the presence of that Token. If the Token is not present, an exception will be thrown. The actual security verification will most probably be done in a Handler of the WS-SecureConversation implementation.

Client API

The client API that is produced will depend on the WS-Security or WS-SecureConversation implementation used underneath. In this section, we will give you a small introduction to the code of a Rampart+Sandesha2 client.

WSRM+WS-Security Client Using Apache Sandesha2 and Apache Rampart

First you have to make a small configuration change to the Apache Sandesha2 module.xml. On the client side, Apache Rampart retrieves its policy from a property in the MessageContext. The application client is expected to set this property in the application messages (i.e., by setting it to the Options object). If you want to apply the same security policy to the WSRM control messages as well, you'll have to add its key to the 'propertiesToCopyFromReferenceMessage' parameter of the Apache Sandesha2 module.xml.

<parameter name="propertiesToCopyFromReferenceMessage" >
rampartPolicy
</parameter>

If you need to set a different security policy for WSRM control messages, you may do as follows.

<parameter name="propertiesToCopyFromReferenceMessage" >
RMControlSecPolicy:rampartPolicy,RMControlSecPolicy:RMControlSecPolicy
</parameter>

See the OxygenTank KB item on this for more details.

 

A code sample is given below:

ConfigurationContext configContext = ConfigurationContextFactory.
createConfigurationContextFromFileSystem(CLIENT_REPO,AXIS2_XML);
Options clientOptions = new Options ();
clientOptions.setTo(new EndpointReference (toEPR));
ServiceClient serviceClient = new ServiceClient (configContext,null);
clientOptions.setAction("urn:wsrm:Ping");
serviceClient.setOptions(clientOptions);

serviceClient.engageModule(new QName ("sandesha2"));
serviceClient.engageModule(new QName("rampart"));

StAXOMBuilder builder = new StAXOMBuilder(POLICY_XML_FILE);
Policy policy = PolicyEngine.getPolicy(builder.getDocumentElement());
clientOptions.setProperty(RampartMessageData.KEY_RAMPART_POLICY, policy);

serviceClient.fireAndForget(getPingOMBlock("ping1"));
serviceClient.fireAndForget(getPingOMBlock("ping2"));
clientOptions.setProperty(SandeshaClientConstants.LAST_MESSAGE, "true");
serviceClient.fireAndForget(getPingOMBlock("ping3"));

SequenceReport sequenceReport = null;
boolean complete = false;
while (!complete) {
sequenceReport = SandeshaClient.getOutgoingSequenceReport(serviceClient);
if (sequenceReport!=null && sequenceReport.getCompletedMessages().size()==3)
complete = true;
else {
Thread.sleep(1000);
}
}

serviceClient.cleanup();

As you can see, initially, you have to engage both the Apache Sandesha2 and Apache Rampart modules. After that, according to the Apache Rampart API, you have to load the security policy from a file and set it as a property in the Options object. Then you can do the invocation as a normal Axis2/Sandesha2 client.

WSRM+WS-SecureConversation Client Using Apache Sandesha2 and Apache Rampart

Here too, you have to set the 'propertiesToCopyFromReferenceMessage' property just like in the previous section.

Next, you have to set the Apache Sandesha2 SecurityManager for Apache Rampart. To do this, set the SecurityManager policy in the Apache Sandesha2 module.xml as follows.

<sandesha2:SecurityManager>
org.apache.sandesha2.security.rampart.RampartBasedSecurityManager
</sandesha2:SecurityManager>

Now you can write your client code. An example is given below:

ConfigurationContext configContext = ConfigurationContextFactory.
createConfigurationContextFromFileSystem(CLIENT_REPO,AXIS2_XML);
Options clientOptions = new Options ();
clientOptions.setTo(new EndpointReference (toEPR));
ServiceClient serviceClient = new ServiceClient (configContext,null);
clientOptions.setAction("urn:wsrm:Ping");
serviceClient.setOptions(clientOptions);

serviceClient.engageModule(new QName ("sandesha2"));
serviceClient.engageModule(new QName("rampart"));

StAXOMBuilder builder = new StAXOMBuilder(POLICY_XML_FILE);
Policy policy = PolicyEngine.getPolicy(builder.getDocumentElement());
clientOptions.setProperty(RampartMessageData.KEY_RAMPART_POLICY, policy);

serviceClient.fireAndForget(getPingOMBlock("ping1"));
serviceClient.fireAndForget(getPingOMBlock("ping2"));
clientOptions.setProperty(SandeshaClientConstants.LAST_MESSAGE, "true");
serviceClient.fireAndForget(getPingOMBlock("ping3"));

SequenceReport sequenceReport = null;
boolean complete = false;
while (!complete) {
sequenceReport = SandeshaClient.getOutgoingSequenceReport(serviceClient);
if (sequenceReport!=null && sequenceReport.getCompletedMessages().size()==3)
complete = true;
else {
Thread.sleep(1000);
}
}

serviceClient.getOptions().setProperty(SandeshaClientConstants.UNRELIABLE_MESSAGE,
Constants.VALUE_TRUE);
serviceClient.getOptions().setProperty(RampartMessageData.CANCEL_REQUEST,
Constants.VALUE_TRUE);
serviceClient.fireAndForget(getPingOMBlock("ping4"));

The security policy file is different from the previous scenario (i.e., it will be a WS-SecureConversation policy). Also, after completing your message interaction, you have to do an extra invocation marked as UNRELIABLE. This will tell Apache Rampart to send the Cancel message that will terminate the SecureConversation context established with the server.

Server Side Configuration

If you want to make your Apache Axis2 based Web services secure and reliable, you have to follow the steps given below.

First you have to engage both the Apache Sandesha2 and Rampart modules. You can engage them globally or you can engage them only to the Web service you require.

Then you have to add the value 'RECV_RESULTS' to the 'propertiesToCopyFromReferenceMessage' property and the 'propertiesToCopyFromReferenceRequestMessage' property of the Apache Sandesha2 module.xml. You need this to exchange security policy results from application messages to WSRM control messages.

<parameter name="propertiesToCopyFromReferenceMessage" locked="false">
RECV_RESULTS
</parameter>
<parameter name="propertiesToCopyFromReferenceRequestMessage" >
RECV_RESULTS
</parameter>

Next, if you want your service to be WS-SecureConversation enabled, you have to set the Apache Sandesha2 SecurityManager to the RampartBasedSecurityManager as mentioned in the previous section.

Finally, you have to specify your security policy for the service. This can be global (in the axis2.xml), service level (in the services.xml), or at the operation level (in an operation element of the services.xml).

Now your services are secure and reliable.

 

Conclusion

Apache Sandesha2 and Apache Rampart are the most widely used WSRM and WS-Security/WS-SecureConversation implementations for Apache Axis2. Many users need to use them together to have a fully secure and reliable Web services message interaction. This article described the architectural challenges of carrying out this integration in a modular manner and the steps that were taken to overcome those challenges.

Resources

Apache Sandesha2 Architecture Guide

Apache Axis2 Architecture Guide

Apache Sandesha2 Storage Framework

 

Author

Chamikara Jayalath, Software Engineer, WSO2 Inc. chamikara at wso2 dot com

About Author

  • Chamikara Jayalath
  • Software Engineer
  • WSO2 Inc.