SSL Only web app with Clojure, Noir and Ring

I recently needed to make sure that my Clojure web application was listening for SSL connections only. This is the best way to make sure that client credentials are not accidentally sent over an un-encrypted connection.

First, I needed to convert my OpenSSL generated self-signed certificate to a Java Key Store. I found Dr. Herong Yang's Tutorial on the subject to be the best documentation on that step.

I have shorted it down to the following shell script, which takes two arguments; the key and the cert, both in PEM file format.

#!/bin/sh
set -e 

KEYFILE=$1
CERTFILE=$2

# first we need to merge the keys into a pkcs12 keystore
openssl pkcs12 -export -inkey $KEYFILE \
               -in $CERTFILE -out key_crt.p12 \
               -name key_crt \
               -password pass:thisisahardcodedpassword

keytool -importkeystore -srckeystore key_crt.p12  \
         -srcstoretype pkcs12 -srcstorepass supersecret -srcalias key_crt  \
         -destkeystore key_crt.jks -deststoretype jks  \
         -deststorepass thisisahardcodedpassword

I can then call that script on the key and cert my sysdamin utility generated for me (we're running this on an appliance). It creates a Java Key Store in the current directory, called key_crt.jks.

I'm using Noir which itself uses Ring which sits on top of Jetty. The Noir layer requires a port argument, which it passes the the ring constructor. If you then pass a :jetty-options map with :ssl? true and an :ssl-port value, it will open up both ports.

So what I did was write a function which will remove all Non-SSL connectors from the org.mortbary.jetty.Server object, and pass that in as my :configurator argument in the :jetty-options map:

(defn remove-non-ssl-connectors [server]
  (doseq [c (.getConnectors server)]
    (when-not (or (nil? c) (instance? org.mortbay.jetty.security.SslSocketConnector c))
      (.removeConnector server c)
  ))
  server)

Finally, my call to noir.server/start looks like this:

(noir.server/start 8443 {:mode :dev
                         :jetty-options {:configurator remove-non-ssl-connectors
                                         :ssl? true
                                         :ssl-port 8443
                                         :keystore "/path/to/my/key_cert.jks"
                                         :key-password "thisisahardcodedpassword"}
                         :ns 'my.app})

The result is that my Noir app listens on port 8443 for SSL connections, and only that port, and only does SSL. Woot.