Czy Tomcat może ponownie załadować swój certyfikat SSL bez ponownego uruchamiania?

12

Mam proces w tle, który może aktualizować magazyn kluczy, którego Tomcat używa dla swoich poświadczeń SSL. Chciałbym mieć możliwość automatycznego przeładowania Tomcat bez konieczności ręcznego restartu.

Czy jest możliwe, aby Tomcat przeładował to bez ponownego uruchamiania, czy jest programowy sposób, aby to zrobić?

sdeer
źródło

Odpowiedzi:

6

Nie wierzę, że istnieje sposób, aby to zrobić automatycznie, chociaż proces w tle mógłby automatycznie zrestartować tomcat. Magazyn kluczy jest odczytywany tylko raz przy inicjalizacji Jvm. Rozwiązaniem może być napisanie własnego modułu obsługi, który okresowo ponownie sprawdza magazyn kluczy, ale ja osobiście nie znalazłem żadnych przykładów tego w Internecie.

Mahnc
źródło
8

Możesz zrestartować pojedyncze złącze Tomcat, tzn. Po zmianie pliku jssecacert możliwe jest ponowne uruchomienie portu jak 8443.

Oto pełny kod / metoda, której używam do restartowania łączników tomcat po dodaniu / usunięciu certyfikatów.

// 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
źródło
1
Działa to tylko wtedy, gdy złącze jest skonfigurowane z bindOnInit="false"opcją.
anilech,
4

Jest teraz sposób, aby to zrobić, zaczynając od tomcat 8.5.5.

Wprowadzili 2 metody o nazwie:

  1. reloadSslHostConfig (String HostName) - aby ponownie załadować określonego hosta
  2. reloadSslHostConfigs () - przeładuj wszystko

Można je wywoływać na różne sposoby:

  1. Korzystanie z jmx
  2. Korzystanie z usługi menedżera (w tomcat v9.xx)
  3. Tworząc własny protokół - tak znalazłem podczas moich badań

Szczegóły sposobu 1 i 2 są łatwo dostępne w dokumentach tomcat.

Szczegółowe informacje na temat korzystania ze sposobu 3:

  1. Stwórz klasę rozszerzającą wybrany protokół np. Http11NioProtocol
  2. Zastąp wymagane metody i po prostu wywołaj w nich super, aby zachować domyślne zachowanie
  3. Utwórz wątek w tej klasie, aby od czasu do czasu wywoływać metodę reloadSslHostConfigs
  4. Spakuj tę klasę do słoika i umieść ten słoik w folderze lib tomcat
  5. Edytuj protokół w łączniku w pliku server.xml, aby użyć tego niestandardowego protokołu

Znajdź przykładowy kod poniżej:

Główna klasa protokołu:

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

Łącznik w server.xml powinien wspomnieć o tym jako protokole:

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

Mam nadzieję że to pomoże.

Mahesh.M
źródło