From 5a750182bc75b672ccd61caf6a55fb42fb6cd146 Mon Sep 17 00:00:00 2001 From: "igor.petrenko" Date: Mon, 4 May 2026 12:57:30 +0300 Subject: [PATCH] CE-158 oap-http: autoreload certificates --- .../oap/http/server/nio/NioHttpServer.java | 46 +++++++++++++++++-- .../main/resources/META-INF/oap-module.oap | 1 + pom.xml | 2 +- 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/oap-http/oap-http/src/main/java/oap/http/server/nio/NioHttpServer.java b/oap-http/oap-http/src/main/java/oap/http/server/nio/NioHttpServer.java index 813b2c506d..c65589d02e 100644 --- a/oap-http/oap-http/src/main/java/oap/http/server/nio/NioHttpServer.java +++ b/oap-http/oap-http/src/main/java/oap/http/server/nio/NioHttpServer.java @@ -36,6 +36,8 @@ import lombok.SneakyThrows; import lombok.ToString; import lombok.extern.slf4j.Slf4j; +import oap.concurrent.scheduler.Scheduled; +import oap.concurrent.scheduler.Scheduler; import oap.http.server.nio.handlers.CompressionNioHandler; import oap.io.Closeables; import oap.util.Dates; @@ -44,9 +46,11 @@ import org.xnio.Options; import org.xnio.Xnio; import org.xnio.XnioWorker; +import org.xnio.ssl.JsseSslUtils; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; @@ -55,6 +59,7 @@ import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.util.ArrayList; @@ -80,7 +85,6 @@ public class NioHttpServer implements Closeable, AutoCloseable { public final ArrayList handlers = new ArrayList<>(); private final ConcurrentHashMap pathHandler = new ConcurrentHashMap<>(); private final AtomicLong requestId = new AtomicLong(); - private final KeyManager[] keyManagers; public int backlog = -1; public long idleTimeout = -1; public boolean tcpNodelay = true; @@ -94,9 +98,11 @@ public class NioHttpServer implements Closeable, AutoCloseable { public boolean alwaysSetDate = true; public boolean alwaysSetKeepAlive = true; public int pathHandlerCacheSize = 0; // without cache - + public long autoRefreshCertificates = Dates.h( 12 ); public Undertow undertow; + private KeyManager[] keyManagers; private XnioWorker xnioWorker; + private Scheduled autoRefreshCertificatesScheduled; public NioHttpServer( DefaultPort defaultPort ) { this.defaultPort = defaultPort; @@ -214,6 +220,10 @@ public void start() { addStats( undertow ); } + if( autoRefreshCertificates > 0 ) { + autoRefreshCertificatesScheduled = Scheduler.scheduleWithFixedDelay( autoRefreshCertificates, TimeUnit.MILLISECONDS, this::refreshCertificates ); + } + log.info( "server on ports: {} (statistics: {}, ioThreads: {}, workerThreads: {}) has started in {} ms", pathHandler.keySet(), statistics, undertow.getWorker().getMXBean().getIoThreadCount(), @@ -222,6 +232,7 @@ public void start() { ); } + @SneakyThrows private void addPortListener( int port, PathHandler portPathHandler, Undertow.Builder builder ) { Preconditions.checkNotNull( portPathHandler ); @@ -237,7 +248,8 @@ private void addPortListener( int port, PathHandler portPathHandler, Undertow.Bu handler = new GracefulShutdownHandler( handler ); if( port == defaultPort.httpsPort ) { - builder.addHttpsListener( port, "0.0.0.0", keyManagers, null, handler ); + SSLContext sslContext = JsseSslUtils.createSSLContext( keyManagers, null, new SecureRandom(), OptionMap.create( Options.SSL_PROTOCOL, "TLSv1.2" ) ); + builder.addHttpsListener( port, "0.0.0.0", sslContext, handler ); } else { builder.addHttpListener( port, "0.0.0.0", handler ); } @@ -351,6 +363,11 @@ public void preStop() { @Override public void close() throws IOException { + if( autoRefreshCertificatesScheduled != null ) { + Closeables.close( autoRefreshCertificatesScheduled ); + autoRefreshCertificatesScheduled = null; + } + preStop(); Closeables.close( xnioWorker ); @@ -360,6 +377,29 @@ public boolean hasHandler( Class handlerClass ) { return Lists.anyMatch( handlers, h -> h.getClass().equals( handlerClass ) ); } + public void refreshCertificates() { + try { + if( isHttpsEnabled() ) { + log.debug( "refreshCertificates location {}...", defaultPort.keyStore ); + + keyManagers = makeKeyManagers( defaultPort.keyStore, defaultPort.password ); + + List listenerInfo = undertow.getListenerInfo(); + + SSLContext sslContext = JsseSslUtils.createSSLContext( keyManagers, null, new SecureRandom(), OptionMap.create( Options.SSL_PROTOCOL, "TLSv1.2" ) ); + + listenerInfo + .stream() + .filter( li -> li.getSslContext() != null ) + .forEach( li -> li.setSslContext( sslContext ) ); + + log.debug( "refreshCertificates location {}... Done", defaultPort.keyStore ); + } + } catch( Exception e ) { + log.error( e.getMessage(), e ); + } + } + public enum PortType { HTTP, HTTPS } diff --git a/oap-http/oap-http/src/main/resources/META-INF/oap-module.oap b/oap-http/oap-http/src/main/resources/META-INF/oap-module.oap index 05216df16f..a790968887 100644 --- a/oap-http/oap-http/src/main/resources/META-INF/oap-module.oap +++ b/oap-http/oap-http/src/main/resources/META-INF/oap-module.oap @@ -30,6 +30,7 @@ services { additionalHttpPorts { httpprivate = 8081 } + autoRefreshCertificates = 12h // -1 - disable backlog = -1 idleTimeout = -1 diff --git a/pom.xml b/pom.xml index 67847caa59..041ad076fd 100644 --- a/pom.xml +++ b/pom.xml @@ -57,7 +57,7 @@ - 25.6.7 + 25.6.8 25.0.1 25.0.0