Thursday, May 2, 2013

Wicket + SSL + Nginx

I just fought a battle with technology, getting Wicket + SSL + NGINX to play well together, so I thought I'd document the procedure for the next poor soul who needs to do this.

My goal is to use NGINX as a proxy in front of wicket, where NGINX is responsible for https decryption.  Thus NGINX is responsible for decrypting the data and passing it along to Wicket.

Configuring NGINX to serve up HTTPS is pretty easy.  I'll just describe that briefly here.
Get an SSL cert from godaddy, download the zip file for "apache", concatenate the key, crt, and gd_bundle.crt into a single file, let's say: cert.pem, then set up something like the following in the /etc/nginx/nginx.conf file:


# HTTPS server
    #
    server {
        listen       443;
        server_name  www.ggrocer.com;

        ssl                  on;
        ssl_certificate      cert.pem;
        ssl_certificate_key  cert.key;

        ssl_session_timeout  5m;

        ssl_protocols  SSLv2 SSLv3 TLSv1;
        ssl_ciphers  HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers   on;

        location / {
            proxy_pass http://www.ggrocer.com;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_redirect http:// http://;
        }
    }

    upstream www.ggrocer.com {
      server 127.0.0.1:9090;
    }


The X-Forwarded-For may not be needed for this case, but the X-Forwarded-Proto is.  This tells NGINX to add the HTTP header X-Forwarded-Proto to the http request that is forwarded to Wicket.

The proxy_redirect is also important.  I spent hours trying to figure out why, when viewing a non-secure page under HTTPS, the browser was going into a redirect loop.  What happened was this: if @RequireHTTP is not set on a page and the request is HTTPS, then wicket forces a redirect to HTTP.  NGINX then rewrites the LOCATION in the HTTP Response to the browser to be HTTPS.  Rinse and repeat.  Via the proxy_redirect setting, it's possible to prevent NGINX from rewriting the redirect URL.

With this configured, NGINX will listen to https traffic on port 443, decrypt it, and pass it on to my app, which is listening on 9090.

In the Wicket App, pages (and components) can be marked as requiring HTTPS via the annotation:
@RequireHTTP
on the page class.  This is well documented elsewhere.

The following bits were not well documented however.
To get Wicket to honor the X-Forwarded-Proto header (i.e. to recognize that the request was HTTPS, even though it was already decrypted by the time it hit Jetty, two things are required:

1) In your Wicket Application's init method, add:  

       getFilterFactoryManager().addXForwardedRequestWrapperFactory(null);

2) To tell wicket attribute to use the "X-Forwarded-Proto" parameter, add a protocolHeader init-param to your web.xml like the following:


<filter>
<filter-name>wicket.ggrocer</filter-name>
<filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>
<init-param>
<param-name>applicationClassName</param-name>
         <param-value>com.ggrocer.site.GGrocerApp</param-value>
     </init-param>
      <init-param>
             <param-name>protocolHeader</param-name>
             <param-value>X-Forwarded-Proto</param-value>
       </init-param>
</filter>