17

I have a background process that can update the keystore Tomcat uses for its SSL credentials. I would like to be able to have Tomcat reload this automatically without needing a manual restart.

Is it possible to have Tomcat reload this without restarting, or is there a programmatic way that it could be done instead?

5 Answers 5

7

I don't believe there is a way to do it automatically although your background process could restart tomcat automatically. The keystore only gets read once when the jvm is initialized. There might be a solution if you were to write your own handler that periodically re-checks the keystore but I personally haven't found any examples of this on the Internet.

9

You can restart individual Tomcat connector i.e. port restart like 8443 is possible after you change your jssecacert file.

Here is the complete code/method that I am using to restart tomcat connectors after I add/delete certificates.

// Stop and restart the SSL connection so that the tomcat server will
// re-read the certificates from the truststore file.
public void refreshTrustStore() throws Exception 
{
    try 
    {
        //following line should be replaced based on where you get your port number. You may pass in as argument to this method
        String httpsPort = configurationManager.getHttpsPort();
        String objectString = "*:type=Connector,port=" + httpsPort + ",*";

        final ObjectName objectNameQuery = new ObjectName(objectString); 

        for (final MBeanServer server: MBeanServerFactory.findMBeanServer(null))
        {
            if (!server.queryNames(objectNameQuery, null).isEmpty())
            {
                MBeanServer mbeanServer = server;
                ObjectName objectName = (ObjectName) server.queryNames(objectNameQuery, null).toArray()[0];

                mbeanServer.invoke(objectName, "stop", null, null);

                // Polling sleep to reduce delay to safe minimum.
                // Use currentTimeMillis() over nanoTime() to avoid issues
                // with migrating threads across sleep() calls.
                long start = System.currentTimeMillis();
                // Maximum of 6 seconds, 3x time required on an idle system.
                long max_duration = 6000L;
                long duration = 0L;
                do
                {
                    try
                    {
                        Thread.sleep(100);
                    }
                    catch (InterruptedException e)
                    {
                        Thread.currentThread().interrupt();
                    }

                    duration = (System.currentTimeMillis() - start);
                } while (duration < max_duration &&
                        server.queryNames(objectNameQuery, null).size() > 0);

                // Use below to get more accurate metrics.
                String message = "TrustStoreManager TrustStore Stop: took " + duration + "milliseconds";
                logger.information(message);

                mbeanServer.invoke(objectName, "start", null, null);

                break;
            }
        }
    } 
    catch (Exception exception) 
    {
        // Log and throw exception
            throw exception
    }
}
1
  • 1
    This will work only if connector is configured with bindOnInit="false" option.
    – anilech
    Oct 25, 2016 at 11:00
8

There is now a way to do this starting with tomcat v8.5.24.

They introduced 2 methods named:

  1. reloadSslHostConfig(String hostName) - to reload a specific host
  2. reloadSslHostConfigs() - reload all

They can be called in various ways:

  1. Using jmx
  2. Using manager service (in tomcat v9.x.x)
  3. By making custom protocol - I found this way during my research

Details of way 1 and way 2 are easily available in tomcat docs.

Details of how to go about using way 3:

  1. Make a class extending the protocol of your choice for eg. Http11NioProtocol
  2. Override the required methods and just call super in them to keep default behavior
  3. Make a thread in this class to call reloadSslHostConfigs method time to time
  4. Package this class in a jar and put that jar in tomcat's lib folder
  5. Edit protocol in connector in server.xml to use this custom defined protocol

Find sample code below:

Main protocol class:

package com.myown.connector;

import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentMap;

import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.net.ssl.SSLSessionContext;

import org.apache.coyote.http11.Http11NioProtocol;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.modeler.Registry;
import org.apache.tomcat.util.net.AbstractEndpoint;
import org.apache.tomcat.util.net.AbstractJsseEndpoint;
import org.apache.tomcat.util.net.GetSslConfig;
import org.apache.tomcat.util.net.SSLContext;
import org.apache.tomcat.util.net.SSLHostConfig;
import org.apache.tomcat.util.net.SSLHostConfigCertificate;
import org.apache.tomcat.util.net.SSLImplementation;
import org.apache.tomcat.util.net.SSLUtil;

public class ReloadProtocol extends Http11NioProtocol {

    private static final Log log = LogFactory.getLog(Http12ProtocolSSL.class);

    public ReloadProtocol() {
        super();
        RefreshSslConfigThread refresher = new 
              RefreshSslConfigThread(this.getEndpoint(), this);
        refresher.start();
    }

    @Override
    public void setKeystorePass(String s) {
        super.setKeystorePass(s);
    }

    @Override
    public void setKeyPass(String s) {
        super.setKeyPass(s);
    }

    @Override
    public void setTruststorePass(String p) {
        super.setTruststorePass(p);
    }

    class RefreshSslConfigThread extends Thread {

        AbstractJsseEndpoint<?> abstractJsseEndpoint = null;
        Http11NioProtocol protocol = null;

        public RefreshSslConfigThread(AbstractJsseEndpoint<?> abstractJsseEndpoint, Http11NioProtocol protocol) {
            this.abstractJsseEndpoint = abstractJsseEndpoint;
            this.protocol = protocol;
        }

        public void run() {
            int timeBetweenRefreshesInt = 1000000; // time in milli-seconds
            while (true) {
                try {
                        abstractJsseEndpoint.reloadSslHostConfigs();
                        System.out.println("Config Updated");
                } catch (Exception e) {
                    System.out.println("Problem while reloading.");
                }
                try {
                    Thread.sleep(timeBetweenRefreshesInt);
                } catch (InterruptedException e) {
                    System.out.println("Error while sleeping");
                }
            }
        }
   }
}

Connector in server.xml should mention this as the protocol:

<Connector protocol="com.myown.connector.ReloadProtocol"
 ..........

Hope this helps.

2

tomcat-reloading-connector is a ready to use solution for hot reloading certificates in Tomcat. You can just download the jar, plug it into your Tomcat (also works with embedded Tomcat and Spring Boot), configure it in server.xml and it will reload the certs automatically shortly after the files change.

It's actually similar to the solutions provided by Sagar Zaveri and Mahesh.M. However, it's somewhat more sophisticated (watches the cert folder and reloads on change), you don't have to copy code, it's licensed liberally and offers documentation as well as examples 😉

BTW on top of tomcat-reloading-connector, I built letsencrypt-tomcat that queries and refreshes certs via Let's Encrypt at runtime.

You can read more about both tomcat-reloading-connectorand letsencrypt-tomcat in this blog post.

Note that for now, tomcat-reloading-connector only works with Apache Portable Runtime (APR) based Native library for Tomcat, which uses certificates stored in PEM, not keystores. I'd recommend using APR anyways, as it provides much better performance as Tomcat's Java TLS implementation. With Docker APR is also rather easy to use, for example in thebitnami/tomcat image everything is preconfigured. letsencrypt-tomcat also ships everything necessary for using APR.

If you're still interested in using a Keystore this this should be rather easy to implement. Try creating an issue in tomcat-reloading-connector.

0

2024: You can now enable a built-in TLSCertificateReloadListener that monitors the expiration dates of TLS certificates and trigger automatic reloading of the TLS configuration a set number of days before the TLS certificate expires.

Available in tomcat version:

  • 11.0.x for 11.0.0-M12 onwards
  • 10.1.x for 10.1.14 onwards
  • 9.0.x for 9.0.81 onwards
  • 8.5.x for 8.5.94 onwards

See also Bug 65770.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .