Monday, December 3, 2012

CARGO With TLS/SSL and Spring Security

In this post, I discuss how to configure Cargo to start Tomcat using TLS/SSL. To quote the CARGO project, "Cargo is a thin wrapper that allows you to manipulate Java EE containers in a standard way." Cargo can be used as part of an Integration Test strategy, where Cargo will manage starting the J2EE container, deploying an application, and stopping the server after the integration tests have completed.

In this environment we are using Tomcat 7.0.26, JDK 1.7.0_03, ANT 1.8.4 and Cargo 1.3.1.


Background


As I discussed in my previous post, we had been using Grizzly - Jersey's test container - to test our web services. As you may recall, Grizzly will start the container just before a test method, execute the test method, and then shut down the container after the test method has finished. If you have a large number of tests, this can become problematic for a few reasons. We found that starting and stopping the container between each test not only drastically increased the time it took to run our tests, but also masked a huge problem which caused our application to become non-responsive.  Additionally, Grizzly has a substantial footprint and to use it, you must increase the heap as well as the permGen space. In my opinion, Grizzly should NOT be used to run a large number of tests, rather it should be used to spot check web services.

It was obvious that we needed to adopt another approach to testing our web services - creating real world integration tests in addition to testing the individual web service - that would keep our container running throughout the duration of the tests. We decided to use CARGO to manage the test container.

Configuration


Our application can run in basic authorization using TLS/SSL leveraged by Spring Security. The problem was to configure CARGO such that it would leverage Spring Security to run in basic authorization using TLS/SSL. 

The following assumes that you are already familiar with two-way SSL, have already created your certificates/key pairs and have placed them in their respective truststore/keystores . If not, please refer to the following link: Tomcat SSL How-to. If you are not already familiar with the CARGO ANT tasks, you can refer to the following link CAROG-ANT Tasks for more information.  

Here is an example of how to configure CARGO/ANT to run web service tests in basic authorization using TLS/SSL:


    <target name="cargostart" depends="init">
        <delete dir="${tomcatconfig.dir}"/>
        <mkdir dir="${tomcatlog.dir}"/>
        <mkdir dir="${tomcatconfig.dir}"/>
        <echo message="Starting Cargo..."/>
        <!-- Property needs to be passed in as a system property -->
        <echo message="Using tomcat.home = ${tomcat.home} "/>
        <echo message="Using war = ${capf.war}"/>
        <cargo containerId="tomcat7x" home="${tomcat.home}" output="${tomcatlog.dir}/output.log"
               log="${tomcatlog.dir}/cargo.log" action="start">
            <configuration  home="${tomcatconfig.dir}" type="standalone">
                <property name="cargo.logging" value="medium"/>
                <!-- For installed container o.c.c.c.tomcat.Tomcat7xInstalledLocalContainer -->
                <property name="cargo.hostname" value="${test.hostname}"/>
                <property name="cargo.servlet.port" value="${test.hostname.port}"/>
                <property name="cargo.protocol" value="https"/>

                <property name="cargo.tomcat.connector.clientAuth" value="basic"/>

                <property name="cargo.tomcat.connector.keyAlias" value="${ssl.key.alias}"/>
                <property name="cargo.tomcat.connector.keystoreFile" value="${javax.net.ssl.keyStore}"/>
                <property name="cargo.tomcat.connector.keystorePass" value="${javax.net.ssl.keyStorePassword}"/>
                <property name="cargo.tomcat.connector.keystoreType" value="${javax.net.ssl.keyStoreType}"/>
                <property name="cargo.tomcat.connector.sslProtocol" value="https"/>
                <property name="cargo.tomcat.connector.truststoreFile" value="${javax.net.ssl.trustStore}"/>
                <property name="cargo.tomcat.connector.truststorePass" value="${javax.net.ssl.trustStorePassword}"/>
                <property name="cargo.tomcat.connector.truststoreType" value="${javax.net.ssl.trustStoreType}"/>
                <property name="cargo.tomcat.httpSecure" value="true"/>
                <property name="cargo.springSecurityFilterChain" value="org.springframework.web.filter.DelegatingFilterProxy"/>
                <deployable type="war" file="${myWar.war}" />
            </configuration>
        </cargo>


    </target>


    <target name="cargo-stop">

        <cargo containerId="tomcat7x" home="${tomcat.home}" output="${tomcatlog.dir}/output.log"
               log="${tomcatlog.dir}/cargo.log" action="stop"/>
            <configuration  home="${tomcatconfig.dir}" type="standalone">
                <property name="cargo.logging" value="medium"/>
...
    </target>


Of course, you'll need to supply all of the properties - i.e. "${javax.net.ssl.trustStore}" - with valid values.

Cargo will take a copy of the Tomcat container as defined by the "${tomcat.home}" property and place it into the "tomcatconfig.dir." One caveat is that a secure port must be configured in your "server.xml." The "server.xml" that comes with the Tomcat distribution will not suffice, it will need to be modified to configure a "connector" as shown below:


<connector address="${fully.qualified.hostname}" clientauth="want" connectiontimeout="20000" keyalias="${ssl.key.alias}" keystorefile="${javax.net.ssl.keyStore}" keystorepass="${javax.net.ssl.keyStorePassword}" keystoretype="${javax.net.ssl.keyStoreType}" maxsparethreads="75" maxthreads="150" minsparethreads="25" port="1443" protocol="HTTP/1.1" scheme="https" secure="true" sslenabled="true" sslprotocol="TLS" truststorefile="${javax.net.ssl.trustStore}" truststorepass="${javax.net.ssl.trustStorePassword}" truststoretype="${javax.net.ssl.trustStoreType}">
</connector>


Although I have been able to get this to work in an development environment, I have yet to configure Jenkins to allow integration tests to be run using TLS/SSL. At this point, the Cargo ping application cannot connect to the Tomcat container to signal that it is up and ready for tests. I suspect that it is a problem with the client's truststore and that I need to pass in the necessary "javax.net.ssl" properties when ANT/Jenkins starts up. I have posted this question on CARGO's user email list and should have some insight on how to do this soon. This will be the subject of upcoming posts.

In the interim, the integration tests now run in an development environment without interruption and the integration tests will provide some insight in to how the application will perform with some "typical" operations.