javalin/javalin-ssl

How to use the SSLPlugin#patch method?

Closed this issue · 6 comments

This is a very nice (and most welcome) plugin - thank you.

Currently, I create my own custom org.eclipse.jetty.server.Server and then register it using config.jetty.server(server);. That server:

  • creates a non-SSL connector
  • creates a SSL connector
  • re-routes all traffic to the secure connector (http to https)

I then register it using config.jetty.server(MyJetty::create);. This all works as expected.

The re-route code is basically this:

private static Server redirectHttpToHttps(Server server) {
    // create a constraint: transport guarantee = CONFIDENTIAL
    ConstraintSecurityHandler security = new ConstraintSecurityHandler();
    Constraint constraint = new Constraint();
    constraint.setDataConstraint(Constraint.DC_CONFIDENTIAL);
    //make the constraint apply to all uri paths
    ConstraintMapping mapping = new ConstraintMapping();
    mapping.setPathSpec("/*");
    mapping.setConstraint(constraint);
    security.addConstraintMapping(mapping);
    // add the constraint:
    server.setHandler(security);
    return server;
}

My Question:

Can I combine my customization (just the reroute configuration - no connectors) with this SSL plugin, so that the SSL plugin handles connector creation?


My attempts to do this have not worked. For example - in my Javalin configuration code:

private static void configureJavalin(JavalinConfig config) {

    Supplier<Server> server = MyJetty::create;

    SSLPlugin sslPlugin = new SSLPlugin(conf -> {
        // testing with a self-signed cert:
        conf.pemFromPath("localhost_cert.pem", "localhost_key.pem", "redacted");
        conf.insecure = true;
        conf.insecurePort = 8080;
        conf.secure = true;
        conf.securePort = 8443;
        conf.sniHostCheck = true;
        conf.http2 = true;
    });

    sslPlugin.patch(server.get()); // one of my failed attempts to use patch()

    config.plugins.register(sslPlugin);
}

My various attempts have ended up with either the redirectHttpToHttps() method not being used, or the http connection not being created.

You can achieve this but I'd advise you to use the plugin in a normal fashion, and use javalinConfig.plugins.enableSslRedirects() to do it. Way simpler :)

I never noticed this method before - thank you for mentioning that.

But now I have 2 questions:


Question 1: How do I use enableSslRedirects()?

I have the following SSL config (basically the same as my original question above, but without a manually created server):

SSLPlugin sslPlugin = new SSLPlugin(conf -> {
    conf.pemFromPath("localhost_cert.pem", "localhost_key.pem", "foobarbaz");
    conf.insecure = true;
    conf.insecurePort = 8080;
    conf.secure = true;
    conf.securePort = 8443;
    conf.sniHostCheck = true;
    conf.http2 = true;
});

I tried the following:

config.plugins.enableSslRedirects();
config.plugins.register(sslPlugin);

This allows http: traffic - no redirects. I also swapped the order of the two statements, but the result is the same.

I also tried setting conf.insecure = false; - but I did not expect that to work - and indeed it results in a failed connection.


Question 2: I would still be interested to know how to correctly use sslPlugin.patch(), for a more appropriate use case. Do you have a code example you can share? I was not able to find any test code for that - but clearly I am very capable of missing things.

Question 1

It should be working correctly, but, be aware that the implementation of the plugin is the following:

app.before { ctx ->
    if (ctx.isLocalhost()) {
        return@before // Won't redirect localhost (idk why) introduced in #23cda7d
    }
  
    val xForwardedProto = ctx.header(X_FORWARDED_PROTO) // reverse proxies and such
  
    if (xForwardedProto == "http" || (xForwardedProto == null && ctx.scheme() == "http")) {
        ctx.redirect(ctx.fullUrl().replace("http", "https"), MOVED_PERMANENTLY)
    }
}

If you'd like to implement this yourself, you can see it's pretty easy.

conf.insecure = false; just removes the insecure connector.


Question 2

It's just to apply the config to a server outside of Javalin, the problem with your code comes from the supplier:

Let's assume you have the following code:

Supplier<Server> serverSupplier = ()->{
    Server server = new Server(); // Fresh server
    server.setStopAtShutdown(false); // Change whatever
    return server;
};

SSLPlugin sslPlugin = new SSLPlugin(conf -> {
    // testing with a self-signed cert:
    conf.pemFromPath("localhost_cert.pem", "localhost_key.pem", "redacted");
    conf.insecure = true;
    conf.insecurePort = 8080;
    conf.secure = true;
    conf.securePort = 8443;
    conf.sniHostCheck = true;
    conf.http2 = true;
});

This is where you're wrong:

sslPlugin.patch(serverSupplier.get()); // The supplier is going to create a server *every* time you call get()

// So the following statements will output two different instances:
System.out.println("Server: "+ serverSupplier.get()); // org.eclipse.jetty.server.Server@2f92e0f4
System.out.println("Server: "+ serverSupplier.get()); // org.eclipse.jetty.server.Server@55f96302

A correct approach would be to use the plugin after getting the server, or inside the supplier:

Server server = serverSupplier.get();
sslPlugin.patch(server);

Javalin app = Javalin.create(conf ->{
    conf.jetty.server(()->server);
});

Questions answered - many thanks for your time and insights. (I now have a much better way to redirect http to https, also, thanks to this.)

One final thought about using patch(), for future visitors to this thread:

When you use sslPlugin.patch(server), you no longer need to register the plug-in.

// do NOT use this with patch():
config.plugins.register(sslPlugin);

You will get a java.net.BindException: Address already in use exception, if you do - for obvious reasons (in hindsight).

Exactly, they're mutually exclusive. In fact the plugin uses the patch method when activating the plugin.