Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hot Reload SSL Configuration #4452

Closed
Hakky54 opened this issue Aug 9, 2022 · 1 comment
Closed

Hot Reload SSL Configuration #4452

Hakky54 opened this issue Aug 9, 2022 · 1 comment

Comments

@Hakky54
Copy link
Contributor

Hakky54 commented Aug 9, 2022

I discovered that the community of Vert.x wants ssl reloading mechanism from the following 4 issues:

They all have different use-cases. Some need to reload the ssl configuration more frequently than others, but the main goal is to have some kind of reloading mechanism without the need of restarting the server or recreating the client. A kind developer, @tsaarni has even created this pull request which does the trick already: #4372 However it is limited to a KeyStore/PEM File. The Vert.x core library has two interfaces which needs to be supported: TrustOptions and KeyCertOptions which should return a KeyManager/TrustManager.

I was reading through all of the comments and noticed this one of @vietj and it seems like he prefers a solution outside of the core library:

ok, I would like to see if we can have it as something more decoupled from vertx core

I even added my input in this issue: #2870 (comment)
However there is no follow-up till this day although the library maintainer appreciated the solution:

thanks @Hakky54

I think we could provide in Vert.x an SPI to integrate Vertx to more easily integrate with SSLContext library

So based on the remarks of @vietj I thought it would be maybe a good to provide additional documentation to the project for this kind of use case. It seems like the maintainer does not want to have this in the core library, but want to provide it as a separate util library combining for other utilities which can be added in the future. I already have created a library couple of years ago which has this capability since last year, see here: GitHub - SSLCcontext Kickstart. I also created a reference implementation with a live demo to demonstrate how to configure it and demonstrate that it is working. So therefor I though to create a pull request to include code snippets as example ssl configuration to help the end-user of enabling this kind of feature. The vertx core library won't get additional code complexity and also does not need to maintain it. If developers would like to enable this feature they can just add an additional library. Next to that by giving the control to the end-user he/she can decide when to update the ssl configuration, either via a file listener, scheduled on a fixed interval of minutes, hours, or maybe days, or based on triggers?

Below is one example for a server and two examples for the webclient:

Scheduled reload for server

import io.vertx.core.AbstractVerticle;
import io.vertx.core.http.ClientAuth;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.core.json.JsonObject;
import io.vertx.core.net.KeyCertOptions;
import io.vertx.core.net.TrustOptions;
import io.vertx.ext.web.Router;
import nl.altindag.ssl.SSLFactory;
import nl.altindag.ssl.util.SSLFactoryUtils;

import java.nio.file.Paths;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class MainVerticle extends AbstractVerticle {

    @Override
    public void start() {
        Router router = Router.router(vertx);
        router.route().handler(context -> context.json(
                new JsonObject().put("message", "Hello World!")
        ));

        SSLFactory baseSslFactory = SSLFactory.builder()
                .withDummyIdentityMaterial()
                .withDummyTrustMaterial()
                .withSwappableIdentityMaterial()
                .withSwappableTrustMaterial()
                .build();

        Runnable sslUpdater = () -> {
            SSLFactory updatedSslFactory = SSLFactory.builder()
                    .withIdentityMaterial(Paths.get("/path/to/your/identity.jks"), "password".toCharArray())
                    .withTrustMaterial(Paths.get("/path/to/your/truststore.jks"), "password".toCharArray())
                    .build();

            SSLFactoryUtils.reload(baseSslFactory, updatedSslFactory);
        };

        // initial update of ssl material to replace the dummies
        sslUpdater.run();

        // update ssl material every hour
        Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(sslUpdater, 1, 1, TimeUnit.HOURS);

        HttpServerOptions serverOptions = new HttpServerOptions()
                .setSsl(true)
                .setClientAuth(ClientAuth.REQUIRED)
                .setKeyCertOptions(KeyCertOptions.wrap(baseSslFactory.getKeyManager().orElseThrow()))
                .setTrustOptions(TrustOptions.wrap(baseSslFactory.getTrustManager().orElseThrow()));

        vertx.createHttpServer(serverOptions)
                .requestHandler(router)
                .listen(8443)
                .onSuccess(server -> System.out.println("HTTP server started on port " + server.actualPort()));
    }

}

Reload on demand for webclient

SSLFactory baseSslFactory = SSLFactory.builder()
  .withIdentityMaterial(Paths.get("/path/to/your/identity.jks"), "password".toCharArray())
  .withTrustMaterial(Paths.get("/path/to/your/truststore.jks"), "password".toCharArray())
  .withSwappableIdentityMaterial()
  .withSwappableTrustMaterial()
  .build();

WebClientOptions clientOptions = new WebClientOptions();

sslFactory.getKeyManager()
  .map(KeyCertOptions::wrap)
  .ifPresent(clientOptions::setKeyCertOptions);

sslFactory.getTrustManager()
  .map(TrustOptions::wrap)
  .ifPresent(clientOptions::setTrustOptions);

WebClient webClient = WebClient.create(Vertx.vertx(), clientOptions);

// After some time whenever needed the code below
// can be executed to reload the ssl configuration

SSLFactory updatedSslFactory = SSLFactory.builder()
  .withIdentityMaterial(Paths.get("/path/to/your/identity.jks"), "password".toCharArray())
  .withTrustMaterial(Paths.get("/path/to/your/truststore.jks"), "password".toCharArray())
  .build();

SSLFactoryUtils.reload(baseSslFactory, updatedSslFactory);

Scheduled reload for http client

SSLFactory baseSslFactory = SSLFactory.builder()
  .withDummyIdentityMaterial()
  .withDummyTrustMaterial()
  .withSwappableIdentityMaterial()
  .withSwappableTrustMaterial()
  .build();

WebClientOptions clientOptions = new WebClientOptions();

sslFactory.getKeyManager()
  .map(KeyCertOptions::wrap)
  .ifPresent(clientOptions::setKeyCertOptions);

sslFactory.getTrustManager()
  .map(TrustOptions::wrap)
  .ifPresent(clientOptions::setTrustOptions);

WebClient webClient = WebClient.create(Vertx.vertx(), clientOptions);

Runnable sslUpdater = () -> {
  SSLFactory updatedSslFactory = SSLFactory.builder()
    .withIdentityMaterial(Paths.get("/path/to/your/identity.jks"), "password".toCharArray())
    .withTrustMaterial(Paths.get("/path/to/your/truststore.jks"), "password".toCharArray())
    .build();

    SSLFactoryUtils.reload(baseSslFactory, updatedSslFactory);
};

// initial update of ssl material to replace the dummies
sslUpdater.run();

// update ssl material every hour
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(sslUpdater, 1, 1, TimeUnit.HOURS);

Using PEM files

It is also possible to have ssl reloading based on PEM files instead of p12 files with a similar setup as shown on the other examples.
Just call the PemUtils to generate a KeyManager and TrustManager for the SSLFactory as shown below:

Runnable sslUpdater = () -> {
  X509ExtendedKeyManager keyManager = PemUtils.loadIdentityMaterial(Paths.get("/path/to/your/identity.pem"), "password".toCharArray());
  X509ExtendedTrustManager trustManager = PemUtils.loadTrustMaterial(Paths.get("/path/to/your/some-trusted-certificate.pem"));

  SSLFactory updatedSslFactory = SSLFactory.builder()
    .withIdentityMaterial(keyManager)
    .withTrustMaterial(trustManager)
    .build();

  SSLFactoryUtils.reload(baseSslFactory, updatedSslFactory);
};

UPDATE 09-12-2022

The current PR #4453 makes it possible to enable ssl reloading in the vertx server out of the box with the example server configuration below.

Vertx vertx = Vertx.vertx();
Router router = ...; // initialized Router
KeyCertOptions keyCertOptions = ...; // initialized KeyCertOptions
TrustOptions trustOptions = ...; // initialized TrustOptions

HttpServerOptions serverOptions = new HttpServerOptions()
        .setSsl(true)
        .setKeyCertOptions(keyCertOptions)
        .setTrustOptions(trustOptions);

HttpServer httpServer = vertx.createHttpServer(serverOptions);
httpServer.requestHandler(router)
        .listen(8443)
        .onSuccess(server -> System.out.println("HTTP server started on port " + server.actualPort()));

// the method below will reread the keystores/pem files and apply it to the running server
httpServer.reloadSsl();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants