rahul
rahul

Reputation: 41

Reload Netty Server's SSL Context for gRPC

Can someone tell me how to reload the SSLContext when a server certificate it refreshed/renewed without restarting the gRPC server?

I have this code to build and start a gRPC server. The method certificateRefreshed() gets called whenever a certificate changes which is when I create a new SSL context, but this doesn't work unless I restart the grpc server.

public class ServerWithTls {
    Server server;
    SslContext sslContext;

    public ServerWithTls() {
        this.sslContext = getSslContext();

        NettyServerBuilder serverBuilder = NettyServerBuilder
            .forPort(settings.port())
            .executor(executorService)
            .addService(myService);
            .sslContext(this.sslContext);

        server = serverBuilder.build();
        server.start();
    }

    public io.netty.handler.ssl.SslContext getSslContext() {
        // returns ssl context based on cert and key
    }

    // gets notified when a server cert changes
    public void certificateRefreshed() {
        // create a new SSL context when cert changes
        this.sslContext = getSslContext();
    }

}

Upvotes: 4

Views: 1872

Answers (3)

Eric Anderson
Eric Anderson

Reputation: 26464

io.grpc.util.AdvancedTlsX509KeyManager is a newer convenience provided by gRPC to make this easier. It is a general-purpose KeyManager (not gRPC-specific) that can easily reload certificates.

You can use it with Netty's SslContext, but it is preferred to use TlsServerCredentials as the API is not tied to a particular transport.

Example usage:

AdvancedTlsX509KeyManager keyManager = new AdvancedTlsX509KeyManager();
keyManager.updateIdentityCredentialsFromFile(
    new File("key"), new File("cert"));
ServerCredentials creds = TlsServerCredentials.newBuilder()
    .keyManager(keyManager)
    .build();
// NettyServerBuilder.forPort(port, creds) also works,
// if you need Netty-specific configuration
Server server = Grpc.newServerBuilderForPort(port, creds)
    .executor(executorService)
    .addService(myService)
    .build()
    .start();

// Free to update the certs later
keyManager.updateIdentityCredentialsFromFile(
    new File("key"), new File("cert"));
// You can tell it to poll the files
keyManager.updateIdentityCredentialsFromFile(
    new File("key"), new File("cert"), 10, MINUTES, executor);

Upvotes: 1

Hakan54
Hakan54

Reputation: 3889

This question is similar to this question: Reloading a java.net.http.HttpClient's SSLContext

This option is unfortunately not available by default. After you have supplied the SSLContext to the Server and build the Server you cannot change the SSLContext. You will need to create a new SSLContext and a new Server.

I had the same challenge for one of my projects and I solved it by using a custom trustmanager and keymanager which wraps around the actual trustmanager and keymanager while having the capability of swapping the actual trustmanager and trustmanager. So you can use the following setup if you still want to accomplish it without the need of recreating the Server and SSLContext:

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);

SslContext sslContext = NettySslUtils.forServer(sslFactory).build();
          
Server server = NettyServerBuilder
    .forPort(8443)
    .executor(executorService)
    .addService(myService)
    .sslContext(sslContext)
    .build();

server.start();

See here for the documentation of this option: Reloading ssl at runtime

And here for an actual working example with Jetty (similar to Netty): Example swapping certificates at runtime with Jetty Server

You can add the library to your project with:

<dependency>
    <groupId>io.github.hakky54</groupId>
    <artifactId>sslcontext-kickstart-for-netty</artifactId>
    <version>7.4.4</version>
</dependency>

You can view the full documentation and other examples here: GitHub - SSLContext Kickstart

By the way I need to add a small disclaimer I am the maintainer of the library.

Upvotes: 0

Eric Anderson
Eric Anderson

Reputation: 26464

I'm unsure if there are easier alternatives, but I see two potentially possible ways.

  1. Make your own SslContext, mimicking DelegatingSslContext. You would swap to a different SslContext (especially during newEngine) when you want a different certificate.

  2. Use a KeyManagerFactory whose key material can change over time. I'm not aware of a pre-existing implementation of such a factory, so you probably would need to implement a KeyManagerFactorySpi that delegates to a KeyManagerFactory. You could then swap out the KeyManagerFactory over time.

I will warn that it would have been easy for me to miss something that would invalidate the approaches.

Upvotes: 0

Related Questions