¿Puede Tomcat recargar su certificado SSL sin ser reiniciado?

11

Tengo un proceso en segundo plano que puede actualizar el almacén de claves que Tomcat utiliza para sus credenciales SSL. Me gustaría poder hacer que Tomcat vuelva a cargar esto automáticamente sin necesidad de reiniciar manualmente.

¿Es posible hacer que Tomcat vuelva a cargar esto sin reiniciar, o hay una forma programática de hacerlo?

sdeer
fuente

Respuestas:

6

No creo que haya una manera de hacerlo automáticamente, aunque su proceso en segundo plano podría reiniciar Tomcat automáticamente. El almacén de claves solo se lee una vez cuando se inicializa jvm. Puede haber una solución si tuviera que escribir su propio controlador que revise periódicamente el almacén de claves, pero personalmente no he encontrado ningún ejemplo de esto en Internet.

mahnsc
fuente
8

Puede reiniciar el conector Tomcat individual, es decir, es posible reiniciar el puerto como 8443 después de cambiar su archivo jssecacert.

Aquí está el código / método completo que estoy usando para reiniciar los conectores tomcat después de agregar / eliminar certificados.

// 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
    }
}
Sagar Zaveri
fuente
1
Esto funcionará solo si el conector está configurado con la bindOnInit="false"opción.
anilech
4

Ahora hay una manera de hacerlo comenzando con tomcat v8.5.24.

Introdujeron 2 métodos llamados:

  1. reloadSslHostConfig (String hostName): para volver a cargar un host específico
  2. reloadSslHostConfigs () - vuelve a cargar todo

Se pueden llamar de varias maneras:

  1. Usando jmx
  2. Uso del servicio de administrador (en tomcat v9.xx)
  3. Al hacer un protocolo personalizado, lo encontré durante mi investigación

Los detalles de la vía 1 y la vía 2 están fácilmente disponibles en tomcat docs.

Detalles sobre cómo usar el modo 3:

  1. Haga una clase que extienda el protocolo de su elección, por ejemplo. Http11NioProtocol
  2. Anule los métodos requeridos y simplemente llame a super en ellos para mantener el comportamiento predeterminado
  3. Haga un hilo en esta clase para llamar al método reloadSslHostConfigs de vez en cuando
  4. Empaquete esta clase en un frasco y colóquelo en la carpeta lib de tomcat
  5. Edite el protocolo en el conector en server.xml para usar este protocolo personalizado definido

Encuentre el código de muestra a continuación:

Clase de protocolo principal:

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

El conector en server.xml debería mencionar esto como el protocolo:

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

Espero que esto ayude.

Mahesh.M
fuente