Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can we avoid spring boot application restart, to refresh the certificates associated with its embedded tomcat container?

With any change to the SSL certificates (in its keystore), we need to restart the spring boot application. I want to update my key store entry periodically (may be every year), but want to avoid restarting the JVM. What would it take to achieve it. I wonder if writing custom KeyManager is an acceptable practice?

like image 339
mahesh kamath Avatar asked Sep 16 '16 08:09

mahesh kamath


People also ask

Does Spring Boot require restarting every time you change an application?

One of Spring Boot’s key features is creating a Java web application as a standalone, executable JAR file with embedded server like Tomcat. However, this feature comes with a drawback in development time: every changes you made to your project requires complete application restart to take effect.

How to deploy Spring Boot application without restart in Eclipse?

Spring Boot has a Developer tools (devtools) module helps to improve the productivity of developers. Let’s create Spring Boot project in Eclipse IDE. The name of the project is spring-boot-reload-changes-without-server-restart. Now running the above main class will deploy your application and server will be started on port 8080.

Where are the JAR files for Spring Boot DevTools?

By default, the jar files of Spring Boot DevTools are not included in your project’s package (JAR/WAR). If you no longer want to use automatic restart, just remove the spring-boot-devtools dependency in the pom.xml file.

What happens when you change the classpath in Spring Boot?

By default, any changes (create, update or delete) you made to any files in the project’s classpath (typically files under src/main directory) will trigger Spring DevTools to restart the application to update the changes. Actually, Spring Boot DevTools monitors changes for all entries under the project’s classpath.


3 Answers

Unfortunately, this is not possible.

BUT

You have several solutions here.

Reload Tomcat connector (a bit hacky)

You can restart Tomcat connector i.e. restart 8843 is possible after you change your jssecacert file.

But I think that it is still a hack.

Reverse proxy: Nginx, Apache

This is a way to go. Your application should be behind some reverse proxy (e.g. nginx). This will give you an additional flexibility and reduce load on your app. Nginx will handle https and translate it to plain http. Anyway, you'll have to restart nginx, but nginx restart is so fast that it will be no downtime. Moreover, you could configure script to do this for you.

like image 77
Yuri Avatar answered Oct 12 '22 21:10

Yuri


On Tomcat one can use local JMX to reload SSL context:

private static final String JMX_THREAD_POOL_NAME = "*:type=ThreadPool,name=*";
private static final String JMX_OPERATION_RELOAD_SSL_HOST_CONFIGS_NAME = "reloadSslHostConfigs"; 
private void reloadSSLConfigsOnConnectors() {
    try {
        MBeanServer server = ManagementFactory.getPlatformMBeanServer();
        ObjectName objectName = new ObjectName(JMX_THREAD_POOL_NAME);
        Set<ObjectInstance> allTP = server.queryMBeans(objectName, null);
        logger.info("MBeans found: {}", allTP.size());
        allTP.forEach(tp -> reloadSSLConfigOnThreadPoolJMX(server, tp));
    } catch (Exception ex) {
        logger.error("", ex);
    }
}

private void reloadSSLConfigOnThreadPoolJMX(MBeanServer server, ObjectInstance tp) {
    try {
        logger.info("Invoking operation SSL reload on {}", tp.getObjectName());
        server.invoke(tp.getObjectName(), JMX_OPERATION_RELOAD_SSL_HOST_CONFIGS_NAME, new Object[]{}, new String[]{});
        logger.trace("Successfully invoked");
    } catch (Exception ex) {
        logger.error("Invoking SSL reload", ex);
    }
}

I'm reloading all ThreadPool SSL context, but you really only need one: Tomcat:type=ThreadPool,name=https-jsse-nio-8443. I'm just afraid the name will change, so I cover all possibilities just in case.

like image 40
Undespairable Avatar answered Oct 12 '22 22:10

Undespairable


I have solved this problem in my Spring Boot application by getting a TomcatServletWebServerFactory in a bean and add my own connector customizer to it.

@Bean
public ServletWebServerFactory servletContainer() {

    TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
    // --- CUSTOMIZE SSL PORT IN ORDER TO BE ABLE TO RELOAD THE SSL HOST CONFIG
    tomcat.addConnectorCustomizers(new DefaultSSLConnectorCustomizer());            
    return tomcat;
}

My customizer extracts the protocol for https for later use

public class DefaultSSLConnectorCustomizer implements TomcatConnectorCustomizer {

private Http11NioProtocol protocol;

@Override
public void customize(Connector connector) {

    Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
    if ( connector.getSecure()) {
        //--- REMEMBER PROTOCOL WHICH WE NEED LATER IN ORDER TO RELOAD SSL CONFIG
        this.protocol = protocol;
    }
}

protected Http11NioProtocol getProtocol() {
    return protocol;
}

}

When I have updated the keystore with the new private key I conduct a SSL host config reload. This code goes here

@Component
public class TomcatUtil {

    public static final String DEFAULT_SSL_HOSTNAME_CONFIG_NAME = "_default_";

    private final Logger logger = LoggerFactory.getLogger(getClass());

    private ServletWebServerFactory servletWebServerFactory;

    public TomcatUtil(ServletWebServerFactory servletWebServerFactory) {

        this.servletWebServerFactory = servletWebServerFactory;
    }

    public void reloadSSLHostConfig() {

        TomcatServletWebServerFactory tomcatFactoty = (TomcatServletWebServerFactory) servletWebServerFactory;
        Collection<TomcatConnectorCustomizer> customizers = tomcatFactoty.getTomcatConnectorCustomizers();
        for (TomcatConnectorCustomizer tomcatConnectorCustomizer : customizers) {

            if (tomcatConnectorCustomizer instanceof DefaultSSLConnectorCustomizer) {
                DefaultSSLConnectorCustomizer customizer = (DefaultSSLConnectorCustomizer) tomcatConnectorCustomizer;
                Http11NioProtocol protocol = customizer.getProtocol();
                try {
                    protocol.reloadSslHostConfig(DEFAULT_SSL_HOSTNAME_CONFIG_NAME);
                    logger.info("Reloaded SSL host configuration");
                } catch (IllegalArgumentException e) {
                    logger.warn("Cannot reload SSL host configuration", e);
                }
            }
        }

    }
}

And finally

...
                renewServerCertificate();
                tomcatUtil.reloadSSLHostConfig();
like image 24
Frank Avatar answered Oct 12 '22 20:10

Frank