Home > Computing > SSL Termination, Load Balancers & Java

SSL Termination, Load Balancers & Java

So you got this killer Java web app you are developing, or you are deploying some super cool off the shelf application, and you decide to use some sort of SSL Accelerator to offload your HTTPS traffic, and it looks something like this:


Only things aren’t going so well. Maybe all the generated URLs for HTTPS connections are coming out as plain HTTP. Or your web app is attempting something clever and ntify when specific requests are plain HTTP and redirect them over to HTTPS, however even after redirection the app still tries to redirect again.

What Gives?

As you have probably guessed, the introduction of the SSL Accelerator has caused a problem because now all the incoming requests to your web app are coming in as plain HTTP, because HTTPS was terminated upstream somewhere and so your application server never knows about it.  In Java web applications, this generally manifests itself as HttpServletRequest.isSecure() always returning false, wreaking havoc in its wake. The Servlet machinery makes lots of decisions based on the value of isSecure(). All the way down to when you are defining security-constraint elements in web.xml and setting transport-guarantee to CONFIDENTIAL.

So What’s the Fix?

I’ve encountered this quite often, with various SSL Accelerators, Load Balancers and App & Web Servers. In general, the idea is to maintain separate paths for the requests all the way through to the app servers, like so:


In doing so, you can typically configure the back end web or app server to correctly identify the originating scheme of the request (HTTP or HTTPS).

Any Examples?

The Easy – Plain Ol’ Tomcat Backend.

By far the easiest container to work with is Apache Tomcat. The options available in a Connector clearly had this type of configuration in mind. Specifically these are

  • scheme – this tells Tomcat which protocol to use to create links, either http or https.
  • proxyPort – this tells Tomcat what to use as the real originating port, instead of the port the connector is listening on.
  • proxyHost – similarly, this tells Tomcat what to use as the real host name, instead of the host Tomcat is running on.
  • secure – this tells Tomcat whether the Connector is secure or not.
  • SSLEnabled – whether to configure the connector for SSL/TLS or simply Plain HTTP.

So, in the case where you are only using Apache Tomcat in your platform,

The correct configuration can be accomplished by creating two Connectors, one for port 8080 and one for port 8081. These would look like

<!--
     Define the connector on port 8080 to handle
     originating plain HTTP requests. Nothing
     special here. Make sure to set proxyPort
     and proxyHost to the appropriate values
     for your web site.
-->
<Connector port="8080" protocol="HTTP/1.1"
           maxThreads="150" clientAuth="false"
           SSLEnabled="false"
           scheme="http" secure="false"
           proxyPort="80" proxyHost="my.website.com"
/>

<!--
     Define the connector on port 8080 to handle
     originating HTTPS requests. Here we
     set scheme to https and secure to true. Tomcat
     will still serve this as plain HTTP because
     SSLEnabled is set to false.
-->
<Connector port="8081" protocol="HTTP/1.1"
           maxThreads="150" clientAuth="false"
           SSLEnabled="false"
           scheme="https" secure="true"
           proxyPort="443" proxyHost="my.website.com"
/>

And then, of course, configure your load balancer (and SSL Accelerator) to direct traffic to the appropriate ports.

A Little Harder – Tomcat with Apache Httpd

Introducing Apache Httpd into the mix really all that much harder than using simply Tomcat. Here’s the desired traffic pattern.

You may notice that HTTP protocol stops at the Httpd and then from Httpd to Tomcat the protocol switches to AJP. And then there’s the two streams coming into Httpd, but lets tackle Tomcat first. Its actually not very different at all from straight Tomcat.  The only difference is switching to AJP/1.3 for the protocol.

<Connector port="8009" protocol="AJP/1.3"
           maxThreads="150" clientAuth="false"
           SSLEnabled="false"
           scheme="http" secure="false"
           proxyPort="80" proxyHost="my.website.com"
/>

<Connector port="8010" protocol="AJP/1.3"
           maxThreads="150" clientAuth="false"
           SSLEnabled="false"
           scheme="https" secure="true"
           proxyPort="443" proxyHost="my.website.com"
/>

Now, for Httpd. This is solved by setting up a separate virtual servers for each port, and passing them off to the appropriate Tomcat Connector using mod_jk and separate worker pools for each Tomcat Connector.

JkWorkersFile /etc/httpd/conf/workers.properties

<VirtualHost *:80>
    DocumentRoot /var/www/html
    ServerName my.website.com

    JkMount /webapp/* tomcat-8009

</VirtualHost>

<VirtualHost *:81>
    DocumentRoot /var/www/html
    ServerName my.website.com

    JkMount /webapp/* tomcat-8010

</VirtualHost>

And now the workers.properties file.

worker.list= tomcat-8009, tomcat-8010

worker.tomcat-8009.type=ajp13
tomcat.tomcat-8009.host=server1
tomcat.tomcat-8009.port=8009

worker.tomcat-8010.type=ajp13
tomcat.tomcat-8010.host=server1
tomcat.tomcat-8010.port=8010

And that’s it. The one caveat here, is that although originating from HTTPS and plain-HTTP follow different paths even through Httpd, Httpd doesn’t really know that. So if you are doing anything interesting other than serving static content from Httpd, you may have an issue, for example if you are running PHP or something else that may need to care, you may be out of luck.

The Difficult – Less Configurable Application Servers

The Tomcat team as really taken a lot of time to provide a highly configurable platform, but what if this is not your target deployment platform, and you are faced with attempting to do SSL Termination on a platform that isn’t so helpful. One example that I’ve got a bunch of experience with is what was called Sun Java System Web Server and now Oracle iPlanet Web Server. Although I did enjoy working with it, the platform did not provide such an easy method of integrating an external SSL Accelerator.

But all is not lost. Fortunately Java Servlets version 2.3 and newer provide some facilities that can help. Servlets 2.3 provide two interesting features, Filters and Wrappers.

Filters provide a method for you to do some pre-processing of requests before your Servlets, JSP and anything else is invoked (they also can do post-processing but we don’t need that here.)

Wrappers provide a method for you enclose a Request or Response in your own object.

The two, together can help you fake the Java machinery into setting HttpServletRequest.isSecure() and HttpServletRequest.getScheme() into returning the correct values depending on the original protocol.

Lets look at creating a Wrapper first. Its as simple as

public class FakeSecurityRequestWrapper extends HttpServletRequestWrapper {
    public FakeSecurityRequestWrapper(HttpServletRequest request) {
        super(request);
    }

    public boolean isSecure() {
        return true;
    }

    public String getScheme() {
        return "https";
    }
}

Pretty trivial. Now to create a Filter to inject the wrapped Request into the machinery.

public class FakeSecurityFilter implements Filter {

    public void doFilter(ServletRequest req, ServletResponse res,
        FilterChain chain) throws IOException, ServletException {

        FakeSecurityRequestWrapper fakereq =
            new FakeSecurityRequestWrapper(req);
        chain.doFilter(fakereq, res);
    }
}

Now just plumb this up in your Web App’s web.xml as the first filter in the chain, and you are there. Well, most of the way there. there’s the opposite problem now. Every request, no matter what the originating protocol, appear to be HTTPS. In a mixed protocol environment, that is still a problem.

No worries, we can do it! Yes, with a little more tweaking, it should be possible to support a mixed protocol environment.

And its with a little trick from the load balancer or SSL Accelerator. Most of them that I have run into allow you to modify the request as its passing through the appliance. To the idea is to insert a Header into the HTTP request for request that have gone through the SSL Accelerator. A typical flow would look something like this.


I won’t go into the configuration on the SSL Accelerator/Load Balancer end, but lets look at enhancing the Filter to handle mixed protocols. In this example, we’ll assume the Accelerator/LB sets the header X-Original-Protocol to https for requests coming through it. The doFilter() method becomes

    public void doFilter(ServletRequest req, ServletResponse res,
        FilterChain chain) throws IOException, ServletException {

        String origProto = req.getHeader("X-Original-Protocol");
        if (origProto != null && origProto.equals("https")) {
            FakeSecurityRequestWrapper fakereq =
                new FakeSecurityRequestWrapper(req);
            chain.doFilter(fakereq, res);
        } else {
            chain.doFilter(req, res);
        }
    }

Voila. Now the Java Servlet machinery should be handling a mixed protocol environment just fine. This technique should work with just about any web container, be it WebLogic, JBoss, SJSWS/IWS or GlassFish.

Its arguable whether this method is more straight forward than the other previous methods, but it does have it down sides, which for me, make it less desirable. It does require a little programming, and needs actively integrated into each web app you are deploying and I don’t like having to muck with 3rd party web.xml files if I don’t need to, and for that I leave this as a last resort option.

I’m sure there are other techniques for working with SSL Accelerators, these are just the methods that worked for me so far. Hope others find them useful.

About these ads
Categories: Computing
  1. May 16, 2012 at 1:56 am

    Really good post. This helped me a lot problem solving proxy requests towards Tomcat when sending 302 redirects, thanks!

  2. Mårten
    May 31, 2012 at 2:34 am

    In the mixed protocols header. I would keep the solution with two listeners an look at req.getLocalPort() instead. In this way you can not fool the system from the outside

  3. Ryan Cogswell
    July 20, 2012 at 2:41 pm

    There is another big downside to the request wrapper approach — I’m pretty sure it will NOT correct the behavior of HttpServletResponse.sendRedirect. In order for the sendRedirect to determine the appropriate scheme for the absolute url when a relative url is passed in, the HttpServletResponse implementations generally have a direct reference to the original HttpServletRequest and it will have no knowledge of your request wrapper or the correction it makes to the scheme.

  4. Mooman
    January 21, 2013 at 3:48 am

    Great article – It think the correct param is proxyName, not proxyHost

  5. Peter
    November 28, 2013 at 3:52 am

    Wow, great article. I was thinking on a solution like this really is the way I wanted to go. Nice that you paved it for me :)

    I will be using the Zen Loadbalancer for off-loading and I don’t think it will support the adding of a header. But I also use Apache HTTPD in the middle so configuration for this can be done at that tier.

    Again nice post, clear images!

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: