Reputation: 41
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
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
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
Reputation: 26464
I'm unsure if there are easier alternatives, but I see two potentially possible ways.
Make your own SslContext
, mimicking DelegatingSslContext. You would swap to a different SslContext
(especially during newEngine
) when you want a different certificate.
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