From edfaccf0bf86c07730b90ec95809f34a9655c51f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niccol=C3=B2=20Maltoni?= Date: Fri, 30 Aug 2024 18:44:11 +0200 Subject: [PATCH] Revert "revert(server): desperate" This reverts commit a6bad9647eeb325d1d0221c45287c77ce3042195. --- .../api/CertificatesResource.java | 6 +- .../carapaceproxy/api/ListenersResource.java | 32 +- .../org/carapaceproxy/client/EndpointKey.java | 5 + .../configstore/ConfigurationStoreUtils.java | 4 +- .../core/DisposableChannelListener.java | 213 ++++++++ .../carapaceproxy/core/HttpProxyServer.java | 4 +- .../org/carapaceproxy/core/Listeners.java | 474 +++--------------- .../core/ProxyRequestsManager.java | 2 +- .../core/RuntimeServerConfiguration.java | 13 +- .../carapaceproxy/core/TrustStoreManager.java | 4 +- .../ssl}/CertificatesUtils.java | 2 +- .../org/carapaceproxy/core/ssl/SniMapper.java | 140 ++++++ .../core/ssl/SslContextConfigurator.java | 143 ++++++ .../core/stats/ListenerStats.java | 22 + .../core/stats/PrometheusListenerStats.java | 56 +++ .../stats}/PrometheusUtils.java | 2 +- .../server/backends/BackendHealthManager.java | 2 +- .../cache/CacheByteBufMemoryUsageMetric.java | 2 +- .../server/cache/CacheStats.java | 2 +- .../server/cache/ContentsCache.java | 2 +- .../server/certificates/ACMEClient.java | 2 +- .../DynamicCertificatesManager.java | 6 +- .../server/certificates/Route53Client.java | 2 +- .../carapaceproxy/server/config/HostPort.java | 13 + .../config/NetworkListenerConfiguration.java | 7 +- .../config/SSLCertificateConfiguration.java | 4 +- .../carapaceproxy/api/StartAPIServerTest.java | 6 +- .../org/carapaceproxy/api/UseAdminServer.java | 6 +- .../carapaceproxy/core/MaxHeaderSizeTest.java | 19 +- .../listeners/ListenerConfigurationTest.java | 8 +- .../server/certificates/CertificatesTest.java | 4 +- .../certificates/CertificatesUtilsTest.java | 4 +- .../utils/CertificatesTestUtils.java | 2 +- 33 files changed, 717 insertions(+), 496 deletions(-) create mode 100644 carapace-server/src/main/java/org/carapaceproxy/core/DisposableChannelListener.java rename carapace-server/src/main/java/org/carapaceproxy/{utils => core/ssl}/CertificatesUtils.java (99%) create mode 100644 carapace-server/src/main/java/org/carapaceproxy/core/ssl/SniMapper.java create mode 100644 carapace-server/src/main/java/org/carapaceproxy/core/ssl/SslContextConfigurator.java create mode 100644 carapace-server/src/main/java/org/carapaceproxy/core/stats/ListenerStats.java create mode 100644 carapace-server/src/main/java/org/carapaceproxy/core/stats/PrometheusListenerStats.java rename carapace-server/src/main/java/org/carapaceproxy/{utils => core/stats}/PrometheusUtils.java (98%) diff --git a/carapace-server/src/main/java/org/carapaceproxy/api/CertificatesResource.java b/carapace-server/src/main/java/org/carapaceproxy/api/CertificatesResource.java index cc7389e47..d97ea9f77 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/api/CertificatesResource.java +++ b/carapace-server/src/main/java/org/carapaceproxy/api/CertificatesResource.java @@ -28,8 +28,8 @@ import static org.carapaceproxy.utils.APIUtils.certificateModeToString; import static org.carapaceproxy.utils.APIUtils.certificateStateToString; import static org.carapaceproxy.utils.APIUtils.stringToCertificateMode; -import static org.carapaceproxy.utils.CertificatesUtils.createKeystore; -import static org.carapaceproxy.utils.CertificatesUtils.loadKeyStoreFromFile; +import static org.carapaceproxy.core.ssl.CertificatesUtils.createKeystore; +import static org.carapaceproxy.core.ssl.CertificatesUtils.loadKeyStoreFromFile; import java.io.IOException; import java.io.InputStream; import java.security.GeneralSecurityException; @@ -74,7 +74,7 @@ import org.carapaceproxy.server.config.ConfigurationNotValidException; import org.carapaceproxy.server.config.SSLCertificateConfiguration; import org.carapaceproxy.server.config.SSLCertificateConfiguration.CertificateMode; -import org.carapaceproxy.utils.CertificatesUtils; +import org.carapaceproxy.core.ssl.CertificatesUtils; /** * Access to certificates diff --git a/carapace-server/src/main/java/org/carapaceproxy/api/ListenersResource.java b/carapace-server/src/main/java/org/carapaceproxy/api/ListenersResource.java index 7dbab0f8d..bfde6bda9 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/api/ListenersResource.java +++ b/carapace-server/src/main/java/org/carapaceproxy/api/ListenersResource.java @@ -27,9 +27,7 @@ import javax.ws.rs.Path; import javax.ws.rs.Produces; import lombok.Data; -import org.carapaceproxy.client.EndpointKey; import org.carapaceproxy.core.HttpProxyServer; -import org.carapaceproxy.server.config.NetworkListenerConfiguration; /** * Access to listeners @@ -59,21 +57,21 @@ public static final class ListenerBean { @GET public Map getAllListeners() { HttpProxyServer server = (HttpProxyServer) context.getAttribute("server"); - - return server.getListeners().getListeningChannels().entrySet().stream().map(listener -> { - NetworkListenerConfiguration config = listener.getValue().getConfig(); - int port = listener.getKey().port(); - ListenerBean bean = new ListenerBean( - config.getHost(), - port, - config.isSsl(), - config.getSslCiphers(), - config.getSslProtocols(), - config.getDefaultCertificate(), - (int) listener.getValue().getTotalRequests().get() - ); - return Map.entry(EndpointKey.make(config.getHost(), port).getHostPort(), bean); - }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + return server.getListeners() + .getListeningChannels() + .values() + .stream() + .collect(Collectors.toMap( + channel -> channel.getHostPort().toString(), + channel -> new ListenerBean( + channel.getHostPort().host(), + channel.getHostPort().port(), + channel.getConfig().isSsl(), + channel.getConfig().getSslCiphers(), + channel.getConfig().getSslProtocols(), + channel.getConfig().getDefaultCertificate(), + channel.getTotalRequests() + ))); } } diff --git a/carapace-server/src/main/java/org/carapaceproxy/client/EndpointKey.java b/carapace-server/src/main/java/org/carapaceproxy/client/EndpointKey.java index f780dab1e..e99705406 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/client/EndpointKey.java +++ b/carapace-server/src/main/java/org/carapaceproxy/client/EndpointKey.java @@ -20,6 +20,7 @@ package org.carapaceproxy.client; import lombok.Data; +import org.carapaceproxy.server.config.HostPort; /** * Identifier of an endpoint @@ -32,6 +33,10 @@ public final class EndpointKey { private final String host; private final int port; + public static EndpointKey make(HostPort hostPort) { + return new EndpointKey(hostPort.host(), hostPort.port()); + } + public static EndpointKey make(String host, int port) { return new EndpointKey(host, port); } diff --git a/carapace-server/src/main/java/org/carapaceproxy/configstore/ConfigurationStoreUtils.java b/carapace-server/src/main/java/org/carapaceproxy/configstore/ConfigurationStoreUtils.java index 5ec039691..0a6ddd04e 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/configstore/ConfigurationStoreUtils.java +++ b/carapace-server/src/main/java/org/carapaceproxy/configstore/ConfigurationStoreUtils.java @@ -28,8 +28,8 @@ import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Base64; -import static org.carapaceproxy.utils.CertificatesUtils.createKeystore; -import static org.carapaceproxy.utils.CertificatesUtils.readChainFromKeystore; +import static org.carapaceproxy.core.ssl.CertificatesUtils.createKeystore; +import static org.carapaceproxy.core.ssl.CertificatesUtils.readChainFromKeystore; public final class ConfigurationStoreUtils { diff --git a/carapace-server/src/main/java/org/carapaceproxy/core/DisposableChannelListener.java b/carapace-server/src/main/java/org/carapaceproxy/core/DisposableChannelListener.java new file mode 100644 index 000000000..b22b5686b --- /dev/null +++ b/carapace-server/src/main/java/org/carapaceproxy/core/DisposableChannelListener.java @@ -0,0 +1,213 @@ +package org.carapaceproxy.core; + +import static reactor.netty.ConnectionObserver.State.CONNECTED; +import io.netty.channel.ChannelOption; +import io.netty.channel.epoll.Epoll; +import io.netty.channel.epoll.EpollChannelOption; +import io.netty.channel.socket.nio.NioChannelOption; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.timeout.IdleStateHandler; +import java.io.File; +import java.net.InetSocketAddress; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.concurrent.ConcurrentMap; +import java.util.function.Function; +import jdk.net.ExtendedSocketOptions; +import org.carapaceproxy.core.ssl.SslContextConfigurator; +import org.carapaceproxy.core.stats.ListenerStats; +import org.carapaceproxy.server.config.HostPort; +import org.carapaceproxy.server.config.NetworkListenerConfiguration; +import org.carapaceproxy.utils.CarapaceLogger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.netty.DisposableChannel; +import reactor.netty.FutureMono; +import reactor.netty.http.HttpProtocol; +import reactor.netty.http.server.HttpServer; + +/** + * This class models a single listener for a {@link HttpProxyServer}. + *
+ * This is meant to be a one-use class: once built, it can only be started and stopped. + */ +public class DisposableChannelListener { + private static final Logger LOG = LoggerFactory.getLogger(DisposableChannelListener.class); + + private final HttpProxyServer parent; + private final HostPort hostPort; + private final NetworkListenerConfiguration configuration; + private final ListenerStats stats; + private final ConcurrentMap sslContextsCache; + private ListenerStats.StatCounter totalRequests; + private DisposableChannel listeningChannel; + + public DisposableChannelListener(final HostPort hostPort, final HttpProxyServer parent, final NetworkListenerConfiguration configuration, final ListenerStats stats, final ConcurrentMap sslContextsCache) { + this.parent = parent; + this.hostPort = hostPort; + this.configuration = configuration; + // todo I think we need to address this at some point + // this.configuration = getCurrentConfiguration().getListener(hostPort); + // requireNonNull(this.configuration, "Parent server configuration doesn't define any listener for " + hostPort); + this.sslContextsCache = sslContextsCache; + this.listeningChannel = null; + this.stats = stats; + this.totalRequests = null; + } + + private RuntimeServerConfiguration getCurrentConfiguration() { + return parent.getCurrentConfiguration(); + } + + public NetworkListenerConfiguration getConfig() { + return configuration; + } + + public HostPort getHostPort() { + return hostPort; + } + + private File getBasePath() { + return parent.getBasePath(); + } + + public void start() { + final var hostPort = this.hostPort.offsetPort(parent.getListenersOffsetPort()); + final var config = this.configuration; + final var currentConfiguration = getCurrentConfiguration(); + final var clients = stats.clients(); + totalRequests = stats.requests(hostPort); + LOG.info("Starting listener at {} ssl:{}", hostPort, config.isSsl()); + var httpServer = HttpServer.create() + .host(hostPort.host()) + .port(hostPort.port()) + .protocol(config.getProtocols().toArray(HttpProtocol[]::new)); + if (config.isSsl()) { + httpServer = httpServer.secure(new SslContextConfigurator(parent, config, hostPort, sslContextsCache)); + } + httpServer = httpServer + .metrics(true, Function.identity()) + .forwarded(ForwardedStrategy.of(config.getForwardedStrategy(), config.getTrustedIps())) + .option(ChannelOption.SO_BACKLOG, config.getSoBacklog()) + .childOption(ChannelOption.SO_KEEPALIVE, config.isKeepAlive()) + .runOn(parent.getEventLoopGroup()) + .childOption(Epoll.isAvailable() + ? EpollChannelOption.TCP_KEEPIDLE + : NioChannelOption.of(ExtendedSocketOptions.TCP_KEEPIDLE), config.getKeepAliveIdle()) + .childOption(Epoll.isAvailable() + ? EpollChannelOption.TCP_KEEPINTVL + : NioChannelOption.of(ExtendedSocketOptions.TCP_KEEPINTERVAL), config.getKeepAliveInterval()) + .childOption(Epoll.isAvailable() + ? EpollChannelOption.TCP_KEEPCNT + : NioChannelOption.of(ExtendedSocketOptions.TCP_KEEPCOUNT), config.getKeepAliveCount()) + .maxKeepAliveRequests(config.getMaxKeepAliveRequests()) + .doOnChannelInit((observer, channel, remoteAddress) -> { + final var handler = new IdleStateHandler(0, 0, currentConfiguration.getClientsIdleTimeoutSeconds()); + channel.pipeline().addFirst("idleStateHandler", handler); + /* // todo + if (config.isSsl()) { + SniHandler sni = new SniHandler(listeningChannel) { + @Override + protected SslHandler newSslHandler(SslContext context, ByteBufAllocator allocator) { + SslHandler handler = super.newSslHandler(context, allocator); + if (currentConfiguration.isOcspEnabled() && OpenSsl.isOcspSupported()) { + Certificate cert = (Certificate) context.attributes().attr(AttributeKey.valueOf(OCSP_CERTIFICATE_CHAIN)).get(); + if (cert != null) { + try { + ReferenceCountedOpenSslEngine engine = (ReferenceCountedOpenSslEngine) handler.engine(); + engine.setOcspResponse(parent.getOcspStaplingManager().getOcspResponseForCertificate(cert)); // setting proper ocsp response + } catch (IOException ex) { + LOG.error("Error setting OCSP response.", ex); + } + } else { + LOG.error("Cannot set OCSP response without the certificate"); + } + } + return handler; + } + }; + channel.pipeline().addFirst(sni); + } */ + }) + .doOnConnection(conn -> { + clients.increment(); + conn.channel().closeFuture().addListener(e -> clients.decrement()); + config.getGroup().add(conn.channel()); + }) + .childObserve((connection, state) -> { + if (state == CONNECTED) { + UriCleanerHandler.INSTANCE.addToPipeline(connection.channel()); + } + }) + .httpRequestDecoder(option -> option.maxHeaderSize(currentConfiguration.getMaxHeaderSize())) + .handle((request, response) -> { + if (CarapaceLogger.isLoggingDebugEnabled()) { + CarapaceLogger.debug("Receive request " + request.uri() + + " From " + request.remoteAddress() + + " Timestamp " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss.SSS"))); + } + totalRequests.increment(); + ProxyRequest proxyRequest = new ProxyRequest(request, response, hostPort); + return parent.getProxyRequestsManager().processRequest(proxyRequest); + }); + if (currentConfiguration.getResponseCompressionThreshold() >= 0) { + CarapaceLogger.debug("Response compression enabled with min size = {0} bytes for listener {1}", + currentConfiguration.getResponseCompressionThreshold(), hostPort + ); + httpServer = httpServer.compress(currentConfiguration.getResponseCompressionThreshold()); + } else { + CarapaceLogger.debug("Response compression disabled for listener {0}", hostPort); + } + // Initialization of event loop groups, native transport libraries and the native libraries for the security + httpServer.warmup().block(); + this.listeningChannel = httpServer.bindNow(); // blocking + LOG.info("started listener at {}: {}", hostPort, this.listeningChannel); + } + + /** + * Stop the listener and free the port. + * + * @see DisposableChannel#disposeNow(Duration) + */ + public void stop() throws InterruptedException { + if (this.isStarted()) { + this.listeningChannel.disposeNow(Duration.ofSeconds(10)); + FutureMono.from(this.configuration.getGroup().close()).block(Duration.ofSeconds(10)); + } + } + + /** + * Check whether the listener is actually started. + * + * @return true is the server was started, or false if it was never started, or if it was stopped + */ + public boolean isStarted() { + return this.listeningChannel != null && !this.listeningChannel.isDisposed(); + } + + /** + * The actual HTTP port used by the channel might seldom differ from {@link #getHostPort() the one declared}. + *
+ * A common example is unit tests, but there might also be real-world cases. + * + * @return the actual port from the {@link DisposableChannel} + */ + public int getActualPort() { + if (this.listeningChannel != null) { + if (this.listeningChannel.address() instanceof InetSocketAddress address) { + return address.getPort(); + } + LOG.warn("Unexpected listening channel address type {}", this.listeningChannel.address().getClass()); + } + return hostPort.port(); + } + + public int getTotalRequests() { + return totalRequests.get(); + } + + public void clear() { + this.sslContextsCache.clear(); + } +} diff --git a/carapace-server/src/main/java/org/carapaceproxy/core/HttpProxyServer.java b/carapace-server/src/main/java/org/carapaceproxy/core/HttpProxyServer.java index b7a245ae0..b1ee44156 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/core/HttpProxyServer.java +++ b/carapace-server/src/main/java/org/carapaceproxy/core/HttpProxyServer.java @@ -227,11 +227,11 @@ public class HttpProxyServer implements AutoCloseable { @Getter private int listenersOffsetPort = 0; - public static HttpProxyServer buildForTests(String host, int port, EndpointMapper mapper, File baseDir) throws ConfigurationNotValidException { + public static HttpProxyServer buildForTests(String host, int port, EndpointMapper mapper, File baseDir) throws ConfigurationNotValidException, InterruptedException { HttpProxyServer res = new HttpProxyServer(mapper, baseDir.getAbsoluteFile()); res.currentConfiguration.addListener(new NetworkListenerConfiguration(host, port)); res.proxyRequestsManager.reloadConfiguration(res.currentConfiguration, mapper.getBackends().values()); - + // res.getListeners().reloadConfiguration(res.currentConfiguration); // todo maybe not needed return res; } diff --git a/carapace-server/src/main/java/org/carapaceproxy/core/Listeners.java b/carapace-server/src/main/java/org/carapaceproxy/core/Listeners.java index c029c6049..45742c43e 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/core/Listeners.java +++ b/carapace-server/src/main/java/org/carapaceproxy/core/Listeners.java @@ -19,140 +19,59 @@ */ package org.carapaceproxy.core; -import static org.carapaceproxy.utils.CertificatesUtils.loadKeyStoreData; -import static org.carapaceproxy.utils.CertificatesUtils.loadKeyStoreFromFile; -import static org.carapaceproxy.utils.CertificatesUtils.readChainFromKeystore; -import static reactor.netty.ConnectionObserver.State.CONNECTED; -import io.netty.buffer.ByteBufAllocator; -import io.netty.channel.ChannelOption; -import io.netty.channel.epoll.Epoll; -import io.netty.channel.epoll.EpollChannelOption; -import io.netty.channel.socket.nio.NioChannelOption; -import io.netty.handler.ssl.OpenSsl; -import io.netty.handler.ssl.OpenSslCachingX509KeyManagerFactory; -import io.netty.handler.ssl.ReferenceCountedOpenSslEngine; -import io.netty.handler.ssl.SniHandler; import io.netty.handler.ssl.SslContext; -import io.netty.handler.ssl.SslContextBuilder; -import io.netty.handler.ssl.SslHandler; -import io.netty.handler.ssl.SslProvider; -import io.netty.handler.timeout.IdleStateHandler; -import io.netty.util.Attribute; -import io.netty.util.AttributeKey; -import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.Promise; -import io.prometheus.client.Counter; -import io.prometheus.client.Gauge; -import java.io.File; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.security.GeneralSecurityException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.time.Duration; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.function.Consumer; -import java.util.function.Function; -import javax.annotation.Nullable; -import javax.net.ssl.KeyManagerFactory; -import jdk.net.ExtendedSocketOptions; -import lombok.Data; +import org.carapaceproxy.core.ssl.CertificatesUtils; +import org.carapaceproxy.core.stats.PrometheusListenerStats; import org.carapaceproxy.server.config.ConfigurationNotValidException; import org.carapaceproxy.server.config.HostPort; import org.carapaceproxy.server.config.NetworkListenerConfiguration; import org.carapaceproxy.server.config.SSLCertificateConfiguration; -import org.carapaceproxy.utils.CarapaceLogger; -import org.carapaceproxy.utils.CertificatesUtils; -import org.carapaceproxy.utils.PrometheusUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import reactor.netty.DisposableServer; -import reactor.netty.FutureMono; -import reactor.netty.http.HttpProtocol; -import reactor.netty.http.server.HttpServer; /** * Collection of listeners waiting for incoming clients requests on the configured HTTP ports. *
* While the {@link RuntimeServerConfiguration} is actually mutable, this class won't watch it for updates; - * the caller should request a {@link #reloadCurrentConfiguration() reload of the configuration} manually instead. + * the caller should request a {@link #reloadConfiguration() reload of the configuration} manually instead. * * @author enrico.olivelli */ public class Listeners { public static final String OCSP_CERTIFICATE_CHAIN = "ocsp-certificate"; - - public static final Logger LOG = LoggerFactory.getLogger(Listeners.class); - - private static final Gauge CURRENT_CONNECTED_CLIENTS_GAUGE = PrometheusUtils.createGauge( - "clients", "current_connected", "currently connected clients" - ).register(); - - private static final Counter TOTAL_REQUESTS_PER_LISTENER_COUNTER = PrometheusUtils.createCounter( - "listeners", "requests_total", "total requests", "listener" - ).register(); + private static final Logger LOG = LoggerFactory.getLogger(Listeners.class); private final HttpProxyServer parent; + private RuntimeServerConfiguration currentConfiguration; private final ConcurrentMap sslContexts = new ConcurrentHashMap<>(); - private final ConcurrentMap listeningChannels = new ConcurrentHashMap<>(); - private final File basePath; + private final ConcurrentMap listeningChannels = new ConcurrentHashMap<>(); private boolean started; - private RuntimeServerConfiguration currentConfiguration; - public Listeners(HttpProxyServer parent) { this.parent = parent; this.currentConfiguration = parent.getCurrentConfiguration(); - this.basePath = parent.getBasePath(); } public int getLocalPort() { - for (ListeningChannel c : listeningChannels.values()) { - InetSocketAddress addr = (InetSocketAddress) c.getChannel().address(); - return addr.getPort(); + for (final var channel : listeningChannels.values()) { + return channel.getActualPort(); } return -1; } - public Map getListeningChannels() { + public ConcurrentMap getListeningChannels() { return listeningChannels; } public void start() throws InterruptedException, ConfigurationNotValidException { started = true; - reloadConfiguration(currentConfiguration); - } - - public void stop() { - for (HostPort key : listeningChannels.keySet()) { - try { - stopListener(key); - } catch (InterruptedException ex) { - LOG.error("Interrupted while stopping a listener", ex); - Thread.currentThread().interrupt(); - } - } - } - - private void stopListener(HostPort hostport) throws InterruptedException { - ListeningChannel channel = listeningChannels.remove(hostport); - if (channel != null) { - channel.channel.disposeNow(Duration.ofSeconds(10)); - FutureMono.from(channel.getConfig().getGroup().close()).block(Duration.ofSeconds(10)); - } + reloadCurrentConfiguration(); } /** @@ -171,7 +90,7 @@ public void reloadCurrentConfiguration() throws InterruptedException { * @throws InterruptedException if it is interrupted while starting or stopping a listener * @see #reloadCurrentConfiguration() */ - void reloadConfiguration(RuntimeServerConfiguration newConfiguration) throws InterruptedException { + void reloadConfiguration(final RuntimeServerConfiguration newConfiguration) throws InterruptedException { if (!started) { this.currentConfiguration = newConfiguration; return; @@ -180,22 +99,23 @@ void reloadConfiguration(RuntimeServerConfiguration newConfiguration) throws Int sslContexts.clear(); // stop dropped listeners, start new one - List listenersToStop = new ArrayList<>(); - List listenersToRestart = new ArrayList<>(); - for (Map.Entry channel : listeningChannels.entrySet()) { - HostPort key = channel.getKey(); - NetworkListenerConfiguration actualListenerConfig = currentConfiguration.getListener(key); - NetworkListenerConfiguration newConfigurationForListener = newConfiguration.getListener(key); + List listenersToStop = new ArrayList<>(); + List listenersToRestart = new ArrayList<>(); + for (Map.Entry channel : listeningChannels.entrySet()) { + final var hostPort = channel.getKey(); + final var listener = channel.getValue(); + final var actualListenerConfig = currentConfiguration.getListener(hostPort); + final var newConfigurationForListener = newConfiguration.getListener(hostPort); if (newConfigurationForListener == null) { - LOG.info("listener: {} is to be shut down", key); - listenersToStop.add(key); + LOG.info("listener: {} is to be shut down", hostPort); + listenersToStop.add(listener); } else if (!newConfigurationForListener.equals(actualListenerConfig) - || newConfiguration.getResponseCompressionThreshold() != currentConfiguration.getResponseCompressionThreshold() - || newConfiguration.getMaxHeaderSize() != currentConfiguration.getMaxHeaderSize()) { - LOG.info("listener: {} is to be restarted", key); - listenersToRestart.add(key); + || newConfiguration.getResponseCompressionThreshold() != currentConfiguration.getResponseCompressionThreshold() + || newConfiguration.getMaxHeaderSize() != currentConfiguration.getMaxHeaderSize()) { + LOG.info("listener: {} is to be restarted", hostPort); + listenersToRestart.add(listener); } - channel.getValue().clear(); + listener.clear(); } List listenersToStart = new ArrayList<>(); for (NetworkListenerConfiguration config : newConfiguration.getListeners()) { @@ -210,22 +130,23 @@ void reloadConfiguration(RuntimeServerConfiguration newConfiguration) throws Int currentConfiguration = newConfiguration; try { - for (HostPort hostport : listenersToStop) { - LOG.info("Stopping {}", hostport); - stopListener(hostport); + for (final var listener : listenersToStop) { + final var hostPort = listener.getHostPort(); + LOG.info("Stopping {}", hostPort); + listener.stop(); + listeningChannels.remove(hostPort); } - for (HostPort hostport : listenersToRestart) { - LOG.info("Restart {}", hostport); - stopListener(hostport); - NetworkListenerConfiguration newConfigurationForListener = currentConfiguration.getListener(hostport); - bootListener(newConfigurationForListener); + for (final var listener : listenersToRestart) { + final var hostPort = listener.getHostPort(); + LOG.info("Restart {}", hostPort); + listener.stop(); + bootListener(hostPort, currentConfiguration.getListener(hostPort)); } - for (HostPort hostport : listenersToStart) { - LOG.info("Starting {}", hostport); - NetworkListenerConfiguration newConfigurationForListener = currentConfiguration.getListener(hostport); - bootListener(newConfigurationForListener); + for (HostPort hostPort : listenersToStart) { + LOG.info("Starting {}", hostPort); + bootListener(hostPort, currentConfiguration.getListener(hostPort)); } } catch (InterruptedException stopMe) { Thread.currentThread().interrupt(); @@ -233,314 +154,31 @@ void reloadConfiguration(RuntimeServerConfiguration newConfiguration) throws Int } } - private void bootListener(NetworkListenerConfiguration config) throws InterruptedException { - HostPort hostPort = new HostPort(config.getHost(), config.getPort() + parent.getListenersOffsetPort()); - ListeningChannel listeningChannel = new ListeningChannel(hostPort, config); - LOG.info("Starting listener at {}:{} ssl:{}", hostPort.host(), hostPort.port(), config.isSsl()); - - // Listener setup - HttpServer httpServer = HttpServer.create() - .host(hostPort.host()) - .port(hostPort.port()) - .protocol(config.getProtocols().toArray(HttpProtocol[]::new)); - - if (config.isSsl()) { - final HttpProxyServer parent1 = parent; - final NetworkListenerConfiguration listenerConfiguration1 = config; - final HostPort hostPort1 = hostPort; - httpServer = httpServer.secure(new Consumer<>() { - private final HttpProxyServer parent = parent1; - private final RuntimeServerConfiguration runtimeConfiguration = parent1.getCurrentConfiguration(); - private final NetworkListenerConfiguration listenerConfiguration = listenerConfiguration1; - private final HostPort hostPort = hostPort1; - - @Override - public void accept(final reactor.netty.tcp.SslProvider.SslContextSpec sslContextSpec) { - final SslContext sslContext; - try { - if (!listenerConfiguration.isSsl()) { - throw new ConfigurationNotValidException("SSL not enabled"); - } - final var defaultSslConfiguration = getDefaultSslConfiguration(); - if (defaultSslConfiguration == null) { - throw new ConfigurationNotValidException("Unable to boot SSL context for listener " + listenerConfiguration.getHost() + ": no default certificate setup."); - } - final var keyStore = getKeyStore(hostPort, defaultSslConfiguration); - // todo compute key and store into cache - sslContext = SslContextBuilder - .forServer(getKeyFactory(keyStore, defaultSslConfiguration)) - .enableOcsp(isEnableOcsp()) - .trustManager(parent.getTrustStoreManager().getTrustManagerFactory()) - .sslProvider(SslProvider.OPENSSL) - .protocols(listenerConfiguration.getSslProtocols()) - .ciphers(getSslCiphers()) - .build(); - final var chain = readChainFromKeystore(keyStore); - if (isEnableOcsp() && chain.length > 0) { - parent.getOcspStaplingManager().addCertificateForStapling(chain); - Attribute attr = sslContext.attributes().attr(AttributeKey.valueOf(OCSP_CERTIFICATE_CHAIN)); - attr.set(chain[0]); - // todo i'm not sure if `.enableOcsp(isEnableOcsp())` and this part are enough, - // or if I should plug a `SniHandler` into the channel pipeline like we did in `Listeners` - } - } catch (final ConfigurationNotValidException | IOException | GeneralSecurityException e) { - throw new RuntimeException(e); - } - sslContextSpec.sslContext(sslContext)/* .setSniAsyncMappings(new SniMapper(parent, runtimeConfiguration, listenerConfiguration, hostPort)) */; - } - - @Nullable - private SSLCertificateConfiguration getDefaultSslConfiguration() { - return runtimeConfiguration.getCertificates().get(getDefaultCertificate()); - } - - private KeyStore getKeyStore(final HostPort hostPort, final SSLCertificateConfiguration sslConfiguration) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, ConfigurationNotValidException, UnrecoverableKeyException { - final var keyStoreContent = getCertificateForDomain(sslConfiguration); - final KeyStore keyStore; - if (keyStoreContent != null) { - LOG.debug("start SSL with dynamic certificate id {}, on listener {}", sslConfiguration.getId(), hostPort); - keyStore = loadKeyStoreData(keyStoreContent, sslConfiguration.getPassword()); - } else { - LOG.debug("start SSL with certificate id {}, on listener {} file={}", sslConfiguration.getId(), hostPort, sslConfiguration.getFile()); - keyStore = loadKeyStoreFromFile(sslConfiguration.getFile(), sslConfiguration.getPassword(), getBasePath()); - } - return keyStore; - } - - private KeyManagerFactory getKeyFactory(final KeyStore keyStore, final SSLCertificateConfiguration defaultSslConfiguration) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException { - final var keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - final var keyFactoryInstance = new OpenSslCachingX509KeyManagerFactory(keyFactory); - keyFactoryInstance.init(keyStore, defaultSslConfiguration.getPassword().toCharArray()); - return keyFactoryInstance; - } - - private boolean isEnableOcsp() { - return runtimeConfiguration.isOcspEnabled() && OpenSsl.isOcspSupported(); - } - - private List getSslCiphers() { - final var sslCiphers = listenerConfiguration.getSslCiphers(); - if (sslCiphers != null && !sslCiphers.isEmpty()) { - LOG.debug("required sslCiphers {}", sslCiphers); - return Arrays.asList(sslCiphers.split(",")); - } - return null; - } - - private String getDefaultCertificate() { - return listenerConfiguration.getDefaultCertificate(); - } - - private byte[] getCertificateForDomain(final SSLCertificateConfiguration sslConfiguration) { - return parent.getDynamicCertificatesManager().getCertificateForDomain(sslConfiguration.getId()); - } - - private File getBasePath() { - return parent.getBasePath(); - } - }); - } - - httpServer = httpServer - .metrics(true, Function.identity()) - .forwarded(ForwardedStrategy.of(config.getForwardedStrategy(), config.getTrustedIps())) - .option(ChannelOption.SO_BACKLOG, config.getSoBacklog()) - .childOption(ChannelOption.SO_KEEPALIVE, config.isKeepAlive()) - .runOn(parent.getEventLoopGroup()) - .childOption(Epoll.isAvailable() - ? EpollChannelOption.TCP_KEEPIDLE - : NioChannelOption.of(ExtendedSocketOptions.TCP_KEEPIDLE), config.getKeepAliveIdle()) - .childOption(Epoll.isAvailable() - ? EpollChannelOption.TCP_KEEPINTVL - : NioChannelOption.of(ExtendedSocketOptions.TCP_KEEPINTERVAL), config.getKeepAliveInterval()) - .childOption(Epoll.isAvailable() - ? EpollChannelOption.TCP_KEEPCNT - : NioChannelOption.of(ExtendedSocketOptions.TCP_KEEPCOUNT), config.getKeepAliveCount()) - .maxKeepAliveRequests(config.getMaxKeepAliveRequests()) - .doOnChannelInit((observer, channel, remoteAddress) -> { - channel.pipeline().addFirst("idleStateHandler", new IdleStateHandler(0, 0, currentConfiguration.getClientsIdleTimeoutSeconds())); - if (config.isSsl()) { - SniHandler sni = new SniHandler(listeningChannel) { - @Override - protected SslHandler newSslHandler(SslContext context, ByteBufAllocator allocator) { - SslHandler handler = super.newSslHandler(context, allocator); - if (currentConfiguration.isOcspEnabled() && OpenSsl.isOcspSupported()) { - Certificate cert = (Certificate) context.attributes().attr(AttributeKey.valueOf(OCSP_CERTIFICATE_CHAIN)).get(); - if (cert != null) { - try { - ReferenceCountedOpenSslEngine engine = (ReferenceCountedOpenSslEngine) handler.engine(); - engine.setOcspResponse(parent.getOcspStaplingManager().getOcspResponseForCertificate(cert)); // setting proper ocsp response - } catch (IOException ex) { - LOG.error("Error setting OCSP response.", ex); - } - } else { - LOG.error("Cannot set OCSP response without the certificate"); - } - } - return handler; - } - }; - channel.pipeline().addFirst(sni); - } - }) - .doOnConnection(conn -> { - CURRENT_CONNECTED_CLIENTS_GAUGE.inc(); - conn.channel().closeFuture().addListener(e -> CURRENT_CONNECTED_CLIENTS_GAUGE.dec()); - config.getGroup().add(conn.channel()); - }) - .childObserve((connection, state) -> { - if (state == CONNECTED) { - UriCleanerHandler.INSTANCE.addToPipeline(connection.channel()); - } - }) - .httpRequestDecoder(option -> option.maxHeaderSize(currentConfiguration.getMaxHeaderSize())) - .handle((request, response) -> { // Custom request-response handling - if (CarapaceLogger.isLoggingDebugEnabled()) { - CarapaceLogger.debug("Receive request " + request.uri() - + " From " + request.remoteAddress() - + " Timestamp " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss.SSS"))); - } - - ListeningChannel channel = listeningChannels.get(hostPort); - if (channel != null) { - channel.incRequests(); - } - ProxyRequest proxyRequest = new ProxyRequest(request, response, hostPort); - return parent.getProxyRequestsManager().processRequest(proxyRequest); - }); - - // response compression - if (currentConfiguration.getResponseCompressionThreshold() >= 0) { - CarapaceLogger.debug("Response compression enabled with min size = {0} bytes for listener {1}", - currentConfiguration.getResponseCompressionThreshold(), hostPort - ); - httpServer = httpServer.compress(currentConfiguration.getResponseCompressionThreshold()); - } else { - CarapaceLogger.debug("Response compression disabled for listener {0}", hostPort); - } - - // Initialization of event loop groups, native transport libraries and the native libraries for the security - httpServer.warmup().block(); - - // Listener startup - DisposableServer channel = httpServer.bindNow(); // blocking - listeningChannel.setChannel(channel); - listeningChannels.put(hostPort, listeningChannel); - LOG.info("started listener at {}: {}", hostPort, channel); + private void bootListener(final HostPort hostPort, final NetworkListenerConfiguration configuration) { + final var newListener = new DisposableChannelListener( + hostPort, this.parent, configuration, PrometheusListenerStats.INSTANCE, sslContexts + ); + listeningChannels.put(hostPort, newListener); + newListener.start(); } - @Data - public final class ListeningChannel/* implements io.netty.util.AsyncMapping */ { - - private final HostPort hostPort; - private final NetworkListenerConfiguration config; - private final Counter.Child totalRequests; - private final Map listenerSslContexts = new HashMap<>(); - DisposableServer channel; - - public ListeningChannel(HostPort hostPort, NetworkListenerConfiguration config) { - this.hostPort = hostPort; - this.config = config; - totalRequests = TOTAL_REQUESTS_PER_LISTENER_COUNTER.labels(hostPort.host() + "_" + hostPort.port()); - } - - public void incRequests() { - totalRequests.inc(); - } - - public void clear() { - this.listenerSslContexts.clear(); - } - - /* @Override - public Future map(String sniHostname, Promise promise) { - String key = config.getHost() + ":" + hostPort.port() + "+" + sniHostname; - if (LOG.isDebugEnabled()) { - LOG.debug("resolve SNI mapping {}, key: {}", sniHostname, key); - } - try { - SslContext sslContext = listenerSslContexts.get(key); - if (sslContext != null) { - return promise.setSuccess(sslContext); - } - - sslContext = sslContexts.computeIfAbsent(key, (k) -> { - try { - SSLCertificateConfiguration choosen = chooseCertificate(sniHostname, config.getDefaultCertificate()); - if (choosen == null) { - throw new ConfigurationNotValidException("cannot find a certificate for snihostname " + sniHostname - + ", with default cert for listener as '" + config.getDefaultCertificate() - + "', available " + currentConfiguration.getCertificates().keySet()); - } - return bootSslContext(config, choosen); - } catch (ConfigurationNotValidException ex) { - throw new RuntimeException(ex); - } - }); - listenerSslContexts.put(key, sslContext); - return promise.setSuccess(sslContext); - } catch (final RuntimeException err) { - LOG.error("Error booting certificate for SNI hostname {}, on listener {}", sniHostname, config); - if (err.getCause() instanceof ConfigurationNotValidException configurationNotValidException) { - return promise.setFailure(configurationNotValidException); - } - return promise.setFailure(new ConfigurationNotValidException(err)); - } - } */ - - /* private SslContext bootSslContext(NetworkListenerConfiguration listener, SSLCertificateConfiguration certificate) throws ConfigurationNotValidException { - int port = listener.getPort() + parent.getListenersOffsetPort(); - String sslCiphers = listener.getSslCiphers(); - + public void stop() { + for (final var channel : listeningChannels.values()) { try { - // Try to find certificate data on db - byte[] keystoreContent = parent.getDynamicCertificatesManager().getCertificateForDomain(certificate.getId()); - KeyStore keystore; - if (keystoreContent != null) { - LOG.debug("start SSL with dynamic certificate id {}, on listener {}:{}", certificate.getId(), listener.getHost(), port); - keystore = loadKeyStoreData(keystoreContent, certificate.getPassword()); - } else { - if (certificate.isDynamic()) { // fallback to default certificate - certificate = currentConfiguration.getCertificates().get(listener.getDefaultCertificate()); - if (certificate == null) { - throw new ConfigurationNotValidException("Unable to boot SSL context for listener " + listener.getHost() + ": no default certificate setup."); - } - } - LOG.debug("start SSL with certificate id {}, on listener {}:{} file={}", certificate.getId(), listener.getHost(), port, certificate.getFile()); - keystore = loadKeyStoreFromFile(certificate.getFile(), certificate.getPassword(), basePath); - } - KeyManagerFactory keyFactory = new OpenSslCachingX509KeyManagerFactory(KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())); - keyFactory.init(keystore, certificate.getPassword().toCharArray()); - - List ciphers = null; - if (sslCiphers != null && !sslCiphers.isEmpty()) { - LOG.debug("required sslCiphers {}", sslCiphers); - ciphers = Arrays.asList(sslCiphers.split(",")); - } - SslContext sslContext = SslContextBuilder - .forServer(keyFactory) - .enableOcsp(currentConfiguration.isOcspEnabled() && OpenSsl.isOcspSupported()) - .trustManager(parent.getTrustStoreManager().getTrustManagerFactory()) - .sslProvider(SslProvider.OPENSSL) - .protocols(listener.getSslProtocols()) - .ciphers(ciphers).build(); - - Certificate[] chain = readChainFromKeystore(keystore); - if (currentConfiguration.isOcspEnabled() && OpenSsl.isOcspSupported() && chain.length > 0) { - parent.getOcspStaplingManager().addCertificateForStapling(chain); - Attribute attr = sslContext.attributes().attr(AttributeKey.valueOf(OCSP_CERTIFICATE_CHAIN)); - attr.set(chain[0]); - } - - return sslContext; - } catch (IOException | GeneralSecurityException err) { - LOG.error("ERROR booting listener ", err); - throw new ConfigurationNotValidException(err); + channel.stop(); + } catch (InterruptedException ex) { + LOG.error("Interrupted while stopping a listener", ex); + Thread.currentThread().interrupt(); } - } */ + } + listeningChannels.clear(); } public SSLCertificateConfiguration chooseCertificate(String sniHostname, String defaultCertificate) { + return chooseCertificate(parent.getCurrentConfiguration(), sniHostname, defaultCertificate); + } + + public static SSLCertificateConfiguration chooseCertificate(final RuntimeServerConfiguration currentConfiguration, String sniHostname, String defaultCertificate) { if (sniHostname == null) { sniHostname = ""; } diff --git a/carapace-server/src/main/java/org/carapaceproxy/core/ProxyRequestsManager.java b/carapace-server/src/main/java/org/carapaceproxy/core/ProxyRequestsManager.java index f1d228d97..770ece29c 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/core/ProxyRequestsManager.java +++ b/carapace-server/src/main/java/org/carapaceproxy/core/ProxyRequestsManager.java @@ -60,6 +60,7 @@ import org.carapaceproxy.EndpointStats; import org.carapaceproxy.SimpleHTTPResponse; import org.carapaceproxy.client.EndpointKey; +import org.carapaceproxy.core.stats.PrometheusUtils; import org.carapaceproxy.server.cache.ContentsCache; import org.carapaceproxy.server.config.BackendConfiguration; import org.carapaceproxy.server.config.ConnectionPoolConfiguration; @@ -68,7 +69,6 @@ import org.carapaceproxy.server.mapper.MapResult; import org.carapaceproxy.utils.CarapaceLogger; import org.carapaceproxy.utils.HttpUtils; -import org.carapaceproxy.utils.PrometheusUtils; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; diff --git a/carapace-server/src/main/java/org/carapaceproxy/core/RuntimeServerConfiguration.java b/carapace-server/src/main/java/org/carapaceproxy/core/RuntimeServerConfiguration.java index 0cad17387..f3778acff 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/core/RuntimeServerConfiguration.java +++ b/carapace-server/src/main/java/org/carapaceproxy/core/RuntimeServerConfiguration.java @@ -31,6 +31,7 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -66,7 +67,7 @@ public class RuntimeServerConfiguration { private static final Logger LOG = Logger.getLogger(RuntimeServerConfiguration.class.getName()); - private final List listeners = new ArrayList<>(); + private final Map listeners = new LinkedHashMap<>(); private final Map certificates = new HashMap<>(); private final List requestFilters = new ArrayList<>(); private final Map connectionPools = new HashMap<>(); @@ -455,7 +456,7 @@ public void addListener(NetworkListenerConfiguration listener) throws Configurat throw new ConfigurationNotValidException(ex); } } - listeners.add(listener); + listeners.put(listener.getKey(), listener); } public void addCertificate(SSLCertificateConfiguration certificate) throws ConfigurationNotValidException { @@ -470,7 +471,7 @@ void addRequestFilter(RequestFilterConfiguration config) { } public List getListeners() { - return listeners; + return List.copyOf(listeners.values()); } public Map getCertificates() { @@ -482,10 +483,6 @@ public List getRequestFilters() { } NetworkListenerConfiguration getListener(HostPort hostPort) { - return listeners - .stream() - .filter(s -> s.getHost().equalsIgnoreCase(hostPort.host()) && s.getPort() == hostPort.port()) - .findFirst() - .orElse(null); + return listeners.getOrDefault(hostPort, null); } } diff --git a/carapace-server/src/main/java/org/carapaceproxy/core/TrustStoreManager.java b/carapace-server/src/main/java/org/carapaceproxy/core/TrustStoreManager.java index 9a29daeb6..4d77da5fd 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/core/TrustStoreManager.java +++ b/carapace-server/src/main/java/org/carapaceproxy/core/TrustStoreManager.java @@ -1,7 +1,7 @@ package org.carapaceproxy.core; import lombok.Getter; -import org.carapaceproxy.utils.CertificatesUtils; +import org.carapaceproxy.core.ssl.CertificatesUtils; import javax.net.ssl.TrustManagerFactory; import java.io.File; @@ -16,7 +16,7 @@ import java.util.logging.Level; import java.util.logging.Logger; -import static org.carapaceproxy.utils.CertificatesUtils.loadKeyStoreFromFile; +import static org.carapaceproxy.core.ssl.CertificatesUtils.loadKeyStoreFromFile; public class TrustStoreManager { diff --git a/carapace-server/src/main/java/org/carapaceproxy/utils/CertificatesUtils.java b/carapace-server/src/main/java/org/carapaceproxy/core/ssl/CertificatesUtils.java similarity index 99% rename from carapace-server/src/main/java/org/carapaceproxy/utils/CertificatesUtils.java rename to carapace-server/src/main/java/org/carapaceproxy/core/ssl/CertificatesUtils.java index 7e72e824b..aeaa6ef07 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/utils/CertificatesUtils.java +++ b/carapace-server/src/main/java/org/carapaceproxy/core/ssl/CertificatesUtils.java @@ -17,7 +17,7 @@ * under the License. * */ -package org.carapaceproxy.utils; +package org.carapaceproxy.core.ssl; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; diff --git a/carapace-server/src/main/java/org/carapaceproxy/core/ssl/SniMapper.java b/carapace-server/src/main/java/org/carapaceproxy/core/ssl/SniMapper.java new file mode 100644 index 000000000..a9bbf353b --- /dev/null +++ b/carapace-server/src/main/java/org/carapaceproxy/core/ssl/SniMapper.java @@ -0,0 +1,140 @@ +package org.carapaceproxy.core.ssl; + +import static org.carapaceproxy.core.ssl.CertificatesUtils.loadKeyStoreData; +import static org.carapaceproxy.core.ssl.CertificatesUtils.loadKeyStoreFromFile; +import static org.carapaceproxy.core.ssl.CertificatesUtils.readChainFromKeystore; +import io.netty.handler.ssl.OpenSsl; +import io.netty.handler.ssl.OpenSslCachingX509KeyManagerFactory; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.util.AsyncMapping; +import io.netty.util.Attribute; +import io.netty.util.AttributeKey; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.Promise; +import java.io.File; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.util.Arrays; +import java.util.List; +import javax.net.ssl.KeyManagerFactory; +import org.carapaceproxy.core.HttpProxyServer; +import org.carapaceproxy.core.Listeners; +import org.carapaceproxy.core.RuntimeServerConfiguration; +import org.carapaceproxy.server.config.ConfigurationNotValidException; +import org.carapaceproxy.server.config.HostPort; +import org.carapaceproxy.server.config.NetworkListenerConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.netty.tcp.SslProvider; + +public final class SniMapper implements AsyncMapping { + private static final Logger LOG = LoggerFactory.getLogger(SniMapper.class); + private final HttpProxyServer parent; + private final RuntimeServerConfiguration runtimeConfiguration; + private final NetworkListenerConfiguration listenerConfiguration; + private final HostPort hostPort; + + public SniMapper( + final HttpProxyServer parent, + final RuntimeServerConfiguration runtimeConfiguration, + final NetworkListenerConfiguration listenerConfiguration, + final HostPort hostPort + ) { + this.parent = parent; + this.runtimeConfiguration = runtimeConfiguration; + this.listenerConfiguration = listenerConfiguration; + this.hostPort = hostPort; + } + + private boolean isEnableOcsp() { + return runtimeConfiguration.isOcspEnabled() && OpenSsl.isOcspSupported(); + } + + private List getSslCiphers() { + final var sslCiphers = listenerConfiguration.getSslCiphers(); + if (sslCiphers != null && !sslCiphers.isEmpty()) { + LOG.debug("required sslCiphers {}", sslCiphers); + return Arrays.asList(sslCiphers.split(",")); + } + return null; + } + + @Override + public Future map(final String sniHostname, final Promise promise) { + try { + final var key = computeKey(sniHostname); + LOG.debug("resolve SNI mapping {}, key: {}", sniHostname, key); + try { + final var defaultCertificate = listenerConfiguration.getDefaultCertificate(); + var chosen = Listeners.chooseCertificate(runtimeConfiguration, sniHostname, defaultCertificate); + if (chosen == null) { + throw new ConfigurationNotValidException("cannot find a certificate for snihostname " + sniHostname + + ", with default cert for listener as '" + defaultCertificate + + "', available " + runtimeConfiguration.getCertificates().keySet()); + } + int port = listenerConfiguration.getPort() + parent.getListenersOffsetPort(); + try { + // Try to find certificate data on db + byte[] keystoreContent = parent.getDynamicCertificatesManager().getCertificateForDomain(chosen.getId()); + final KeyStore keystore; + if (keystoreContent != null) { + LOG.debug("start SSL with dynamic certificate id {}, on listener {}:{}", chosen.getId(), listenerConfiguration.getHost(), port); + keystore = loadKeyStoreData(keystoreContent, chosen.getPassword()); + } else { + if (chosen.isDynamic()) { // fallback to default certificate + chosen = runtimeConfiguration.getCertificates().get(listenerConfiguration.getDefaultCertificate()); + if (chosen == null) { + return promise.setFailure(new ConfigurationNotValidException("Unable to boot SSL context for listener " + listenerConfiguration.getHost() + ": no default certificate setup.")); + } + } + LOG.debug("start SSL with certificate id {}, on listener {}:{} file={}", chosen.getId(), listenerConfiguration.getHost(), port, chosen.getFile()); + keystore = loadKeyStoreFromFile(chosen.getFile(), chosen.getPassword(), basePath()); + } + KeyManagerFactory keyFactory = new OpenSslCachingX509KeyManagerFactory(KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())); + keyFactory.init(keystore, chosen.getPassword().toCharArray()); + + SslContext sslContext = SslContextBuilder + .forServer(keyFactory) + .enableOcsp(isEnableOcsp()) + .trustManager(parent.getTrustStoreManager().getTrustManagerFactory()) + .sslProvider(io.netty.handler.ssl.SslProvider.OPENSSL) + .protocols(listenerConfiguration.getSslProtocols()) + .ciphers(getSslCiphers()) + .build(); + + Certificate[] chain = readChainFromKeystore(keystore); + if (runtimeConfiguration.isOcspEnabled() && OpenSsl.isOcspSupported() && chain.length > 0) { + parent.getOcspStaplingManager().addCertificateForStapling(chain); + Attribute attr = sslContext.attributes().attr(AttributeKey.valueOf(Listeners.OCSP_CERTIFICATE_CHAIN)); + attr.set(chain[0]); + } + + return promise.setSuccess(SslProvider.builder().sslContext(sslContext).build()); + } catch (IOException | GeneralSecurityException err) { + LOG.error("ERROR booting listener", err); + return promise.setFailure(new ConfigurationNotValidException(err)); + } + } catch (RuntimeException err) { + if (err.getCause() instanceof ConfigurationNotValidException) { + throw (ConfigurationNotValidException) err.getCause(); + } + throw new ConfigurationNotValidException(err); + } + } catch (ConfigurationNotValidException err) { + LOG.error("Error booting certificate for SNI hostname {}, on listener {}", sniHostname, listenerConfiguration); + return promise.setFailure(err); + } + } + + private File basePath() { + return parent.getBasePath(); + } + + private String computeKey(final String sniHostname) { + return listenerConfiguration.getHost() + ":" + hostPort.port() + "+" + sniHostname; + } + +} diff --git a/carapace-server/src/main/java/org/carapaceproxy/core/ssl/SslContextConfigurator.java b/carapace-server/src/main/java/org/carapaceproxy/core/ssl/SslContextConfigurator.java new file mode 100644 index 000000000..c3f81ef92 --- /dev/null +++ b/carapace-server/src/main/java/org/carapaceproxy/core/ssl/SslContextConfigurator.java @@ -0,0 +1,143 @@ +package org.carapaceproxy.core.ssl; + +import static org.carapaceproxy.core.ssl.CertificatesUtils.loadKeyStoreData; +import static org.carapaceproxy.core.ssl.CertificatesUtils.loadKeyStoreFromFile; +import static org.carapaceproxy.core.ssl.CertificatesUtils.readChainFromKeystore; +import io.netty.handler.ssl.OpenSsl; +import io.netty.handler.ssl.OpenSslCachingX509KeyManagerFactory; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.util.Attribute; +import io.netty.util.AttributeKey; +import java.io.File; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ConcurrentMap; +import java.util.function.Consumer; +import javax.annotation.Nullable; +import javax.net.ssl.KeyManagerFactory; +import org.carapaceproxy.core.HttpProxyServer; +import org.carapaceproxy.core.Listeners; +import org.carapaceproxy.core.RuntimeServerConfiguration; +import org.carapaceproxy.server.config.ConfigurationNotValidException; +import org.carapaceproxy.server.config.HostPort; +import org.carapaceproxy.server.config.NetworkListenerConfiguration; +import org.carapaceproxy.server.config.SSLCertificateConfiguration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import reactor.netty.tcp.SslProvider; + +public final class SslContextConfigurator implements Consumer { + private static final Logger LOG = LoggerFactory.getLogger(SslContextConfigurator.class); + + private final HttpProxyServer parent; + private final RuntimeServerConfiguration runtimeConfiguration; + private final NetworkListenerConfiguration listenerConfiguration; + private final HostPort hostPort; + private final ConcurrentMap sslContextsCache; + + public SslContextConfigurator( + final HttpProxyServer parent, + final NetworkListenerConfiguration listenerConfiguration, + final HostPort hostPort, + final ConcurrentMap sslContextsCache + ) { + this.parent = parent; + this.runtimeConfiguration = parent.getCurrentConfiguration(); + this.listenerConfiguration = listenerConfiguration; + this.hostPort = hostPort; + this.sslContextsCache = sslContextsCache; + } + + @Override + public void accept(final SslProvider.SslContextSpec sslContextSpec) { + final SslContext sslContext; + try { + if (!listenerConfiguration.isSsl()) { + throw new ConfigurationNotValidException("SSL not enabled"); + } + final var defaultSslConfiguration = getDefaultSslConfiguration(); + if (defaultSslConfiguration == null) { + throw new ConfigurationNotValidException("Unable to boot SSL context for listener " + listenerConfiguration.getHost() + ": no default certificate setup."); + } + final var keyStore = getKeyStore(hostPort, defaultSslConfiguration); + // todo compute key and store into cache + sslContext = SslContextBuilder + .forServer(getKeyFactory(keyStore, defaultSslConfiguration)) + .enableOcsp(isEnableOcsp()) + .trustManager(parent.getTrustStoreManager().getTrustManagerFactory()) + .sslProvider(io.netty.handler.ssl.SslProvider.OPENSSL) + .protocols(listenerConfiguration.getSslProtocols()) + .ciphers(getSslCiphers()) + .build(); + final var chain = readChainFromKeystore(keyStore); + if (isEnableOcsp() && chain.length > 0) { + parent.getOcspStaplingManager().addCertificateForStapling(chain); + Attribute attr = sslContext.attributes().attr(AttributeKey.valueOf(Listeners.OCSP_CERTIFICATE_CHAIN)); + attr.set(chain[0]); + // todo i'm not sure if `.enableOcsp(isEnableOcsp())` and this part are enough, + // or if I should plug a `SniHandler` into the channel pipeline like we did in `Listeners` + } + } catch (final ConfigurationNotValidException | IOException | GeneralSecurityException e) { + throw new RuntimeException(e); + } + sslContextSpec.sslContext(sslContext).setSniAsyncMappings(new SniMapper(parent, runtimeConfiguration, listenerConfiguration, hostPort)); + } + + @Nullable + private SSLCertificateConfiguration getDefaultSslConfiguration() { + return runtimeConfiguration.getCertificates().get(getDefaultCertificate()); + } + + private KeyStore getKeyStore(final HostPort hostPort, final SSLCertificateConfiguration sslConfiguration) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, ConfigurationNotValidException, UnrecoverableKeyException { + final var keyStoreContent = getCertificateForDomain(sslConfiguration); + final KeyStore keyStore; + if (keyStoreContent != null) { + LOG.debug("start SSL with dynamic certificate id {}, on listener {}", sslConfiguration.getId(), hostPort); + keyStore = loadKeyStoreData(keyStoreContent, sslConfiguration.getPassword()); + } else { + LOG.debug("start SSL with certificate id {}, on listener {} file={}", sslConfiguration.getId(), hostPort, sslConfiguration.getFile()); + keyStore = loadKeyStoreFromFile(sslConfiguration.getFile(), sslConfiguration.getPassword(), getBasePath()); + } + return keyStore; + } + + private static KeyManagerFactory getKeyFactory(final KeyStore keyStore, final SSLCertificateConfiguration defaultSslConfiguration) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException { + final var keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + final var keyFactoryInstance = new OpenSslCachingX509KeyManagerFactory(keyFactory); + keyFactoryInstance.init(keyStore, defaultSslConfiguration.getPassword().toCharArray()); + return keyFactoryInstance; + } + + private boolean isEnableOcsp() { + return runtimeConfiguration.isOcspEnabled() && OpenSsl.isOcspSupported(); + } + + private List getSslCiphers() { + final var sslCiphers = listenerConfiguration.getSslCiphers(); + if (sslCiphers != null && !sslCiphers.isEmpty()) { + LOG.debug("required sslCiphers {}", sslCiphers); + return Arrays.asList(sslCiphers.split(",")); + } + return null; + } + + private String getDefaultCertificate() { + return listenerConfiguration.getDefaultCertificate(); + } + + private byte[] getCertificateForDomain(final SSLCertificateConfiguration sslConfiguration) { + return parent.getDynamicCertificatesManager().getCertificateForDomain(sslConfiguration.getId()); + } + + private File getBasePath() { + return parent.getBasePath(); + } +} diff --git a/carapace-server/src/main/java/org/carapaceproxy/core/stats/ListenerStats.java b/carapace-server/src/main/java/org/carapaceproxy/core/stats/ListenerStats.java new file mode 100644 index 000000000..0cf2ca825 --- /dev/null +++ b/carapace-server/src/main/java/org/carapaceproxy/core/stats/ListenerStats.java @@ -0,0 +1,22 @@ +package org.carapaceproxy.core.stats; + +import org.carapaceproxy.server.config.HostPort; + +public interface ListenerStats { + StatCounter requests(HostPort hostPort); + + StatGauge clients(); + + interface StatCounter { + void increment(); + + int get(); + } + + interface StatGauge extends StatCounter { + @Override + void increment(); + + void decrement(); + } +} diff --git a/carapace-server/src/main/java/org/carapaceproxy/core/stats/PrometheusListenerStats.java b/carapace-server/src/main/java/org/carapaceproxy/core/stats/PrometheusListenerStats.java new file mode 100644 index 000000000..8fb0ede23 --- /dev/null +++ b/carapace-server/src/main/java/org/carapaceproxy/core/stats/PrometheusListenerStats.java @@ -0,0 +1,56 @@ +package org.carapaceproxy.core.stats; + +import io.prometheus.client.Counter; +import io.prometheus.client.Gauge; +import org.carapaceproxy.server.config.HostPort; + +public class PrometheusListenerStats implements ListenerStats { + public static final ListenerStats INSTANCE = new PrometheusListenerStats(); + private static final Gauge CURRENT_CONNECTED_CLIENTS_GAUGE = PrometheusUtils.createGauge( + "clients", "current_connected", "currently connected clients" + ).register(); + private static final Counter TOTAL_REQUESTS_PER_LISTENER_COUNTER = PrometheusUtils.createCounter( + "listeners", "requests_total", "total requests", "listener" + ).register(); + + private PrometheusListenerStats() { + } + + @Override + public StatCounter requests(final HostPort hostPort) { + return new StatCounter() { + private final Counter.Child counter = TOTAL_REQUESTS_PER_LISTENER_COUNTER + .labels(hostPort.host() + "_" + hostPort.port()); + + @Override + public void increment() { + counter.inc(); + } + + @Override + public int get() { + return (int) counter.get(); + } + }; + } + + @Override + public StatGauge clients() { + return new StatGauge() { + @Override + public void increment() { + CURRENT_CONNECTED_CLIENTS_GAUGE.inc(); + } + + @Override + public int get() { + return (int) CURRENT_CONNECTED_CLIENTS_GAUGE.get(); + } + + @Override + public void decrement() { + CURRENT_CONNECTED_CLIENTS_GAUGE.dec(); + } + }; + } +} diff --git a/carapace-server/src/main/java/org/carapaceproxy/utils/PrometheusUtils.java b/carapace-server/src/main/java/org/carapaceproxy/core/stats/PrometheusUtils.java similarity index 98% rename from carapace-server/src/main/java/org/carapaceproxy/utils/PrometheusUtils.java rename to carapace-server/src/main/java/org/carapaceproxy/core/stats/PrometheusUtils.java index 5949704aa..a97146c31 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/utils/PrometheusUtils.java +++ b/carapace-server/src/main/java/org/carapaceproxy/core/stats/PrometheusUtils.java @@ -17,7 +17,7 @@ under the License. */ -package org.carapaceproxy.utils; +package org.carapaceproxy.core.stats; import io.prometheus.client.Counter; import io.prometheus.client.Gauge; diff --git a/carapace-server/src/main/java/org/carapaceproxy/server/backends/BackendHealthManager.java b/carapace-server/src/main/java/org/carapaceproxy/server/backends/BackendHealthManager.java index 22f4331bc..9fcc0b67b 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/server/backends/BackendHealthManager.java +++ b/carapace-server/src/main/java/org/carapaceproxy/server/backends/BackendHealthManager.java @@ -36,7 +36,7 @@ import org.carapaceproxy.server.mapper.EndpointMapper; import org.carapaceproxy.core.RuntimeServerConfiguration; import org.carapaceproxy.server.config.BackendConfiguration; -import org.carapaceproxy.utils.PrometheusUtils; +import org.carapaceproxy.core.stats.PrometheusUtils; /** * Keeps status about backends diff --git a/carapace-server/src/main/java/org/carapaceproxy/server/cache/CacheByteBufMemoryUsageMetric.java b/carapace-server/src/main/java/org/carapaceproxy/server/cache/CacheByteBufMemoryUsageMetric.java index 6a96ed461..5da64407d 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/server/cache/CacheByteBufMemoryUsageMetric.java +++ b/carapace-server/src/main/java/org/carapaceproxy/server/cache/CacheByteBufMemoryUsageMetric.java @@ -4,7 +4,7 @@ import io.netty.buffer.UnpooledByteBufAllocator; import io.prometheus.client.Gauge; import org.carapaceproxy.core.HttpProxyServer; -import org.carapaceproxy.utils.PrometheusUtils; +import org.carapaceproxy.core.stats.PrometheusUtils; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; diff --git a/carapace-server/src/main/java/org/carapaceproxy/server/cache/CacheStats.java b/carapace-server/src/main/java/org/carapaceproxy/server/cache/CacheStats.java index 6ba58e0b7..2247d4219 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/server/cache/CacheStats.java +++ b/carapace-server/src/main/java/org/carapaceproxy/server/cache/CacheStats.java @@ -22,7 +22,7 @@ import com.google.common.annotations.VisibleForTesting; import io.prometheus.client.Counter; import io.prometheus.client.Gauge; -import org.carapaceproxy.utils.PrometheusUtils; +import org.carapaceproxy.core.stats.PrometheusUtils; /** * Overall statistics about cache diff --git a/carapace-server/src/main/java/org/carapaceproxy/server/cache/ContentsCache.java b/carapace-server/src/main/java/org/carapaceproxy/server/cache/ContentsCache.java index 298251ce2..5390c86a1 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/server/cache/ContentsCache.java +++ b/carapace-server/src/main/java/org/carapaceproxy/server/cache/ContentsCache.java @@ -42,7 +42,7 @@ import lombok.Data; import org.carapaceproxy.core.ProxyRequest; import org.carapaceproxy.core.RuntimeServerConfiguration; -import org.carapaceproxy.utils.PrometheusUtils; +import org.carapaceproxy.core.stats.PrometheusUtils; import reactor.netty.http.client.HttpClientResponse; /** diff --git a/carapace-server/src/main/java/org/carapaceproxy/server/certificates/ACMEClient.java b/carapace-server/src/main/java/org/carapaceproxy/server/certificates/ACMEClient.java index 03781d7c1..8edcbb1bb 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/server/certificates/ACMEClient.java +++ b/carapace-server/src/main/java/org/carapaceproxy/server/certificates/ACMEClient.java @@ -28,7 +28,7 @@ import java.util.Map; import java.util.stream.Collectors; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.carapaceproxy.utils.CertificatesUtils; +import org.carapaceproxy.core.ssl.CertificatesUtils; import org.shredzone.acme4j.Account; import org.shredzone.acme4j.AccountBuilder; import org.shredzone.acme4j.Authorization; diff --git a/carapace-server/src/main/java/org/carapaceproxy/server/certificates/DynamicCertificatesManager.java b/carapace-server/src/main/java/org/carapaceproxy/server/certificates/DynamicCertificatesManager.java index 9493f4ba9..2a8360c63 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/server/certificates/DynamicCertificatesManager.java +++ b/carapace-server/src/main/java/org/carapaceproxy/server/certificates/DynamicCertificatesManager.java @@ -32,8 +32,8 @@ import static org.carapaceproxy.server.certificates.DynamicCertificateState.VERIFYING; import static org.carapaceproxy.server.certificates.DynamicCertificateState.WAITING; import static org.carapaceproxy.server.config.SSLCertificateConfiguration.CertificateMode.MANUAL; -import static org.carapaceproxy.utils.CertificatesUtils.isCertificateExpired; -import static org.carapaceproxy.utils.CertificatesUtils.readChainFromKeystore; +import static org.carapaceproxy.core.ssl.CertificatesUtils.isCertificateExpired; +import static org.carapaceproxy.core.ssl.CertificatesUtils.readChainFromKeystore; import com.google.common.annotations.VisibleForTesting; import java.io.File; import java.io.FileOutputStream; @@ -70,9 +70,9 @@ import org.carapaceproxy.configstore.ConfigurationStore; import org.carapaceproxy.core.HttpProxyServer; import org.carapaceproxy.core.RuntimeServerConfiguration; +import org.carapaceproxy.core.ssl.CertificatesUtils; import org.carapaceproxy.server.config.ConfigurationNotValidException; import org.carapaceproxy.server.config.SSLCertificateConfiguration; -import org.carapaceproxy.utils.CertificatesUtils; import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder; import org.shredzone.acme4j.Order; import org.shredzone.acme4j.Status; diff --git a/carapace-server/src/main/java/org/carapaceproxy/server/certificates/Route53Client.java b/carapace-server/src/main/java/org/carapaceproxy/server/certificates/Route53Client.java index 43f3efbf6..1df433094 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/server/certificates/Route53Client.java +++ b/carapace-server/src/main/java/org/carapaceproxy/server/certificates/Route53Client.java @@ -26,7 +26,7 @@ import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.logging.Logger; -import org.carapaceproxy.utils.CertificatesUtils; +import org.carapaceproxy.core.ssl.CertificatesUtils; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.regions.Region; diff --git a/carapace-server/src/main/java/org/carapaceproxy/server/config/HostPort.java b/carapace-server/src/main/java/org/carapaceproxy/server/config/HostPort.java index 1c8a73004..534316ec8 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/server/config/HostPort.java +++ b/carapace-server/src/main/java/org/carapaceproxy/server/config/HostPort.java @@ -1,4 +1,17 @@ package org.carapaceproxy.server.config; public record HostPort(String host, int port) { + + public HostPort offsetPort(final int offset) { + final var newPort = this.port + offset; + if (newPort < 0 || newPort > 65535) { + throw new IllegalArgumentException("Offset " + offset + " produces out-of-range HTTP port " + newPort); + } + return new HostPort(host, newPort); + } + + @Override + public String toString() { + return host + ":" + port; + } } diff --git a/carapace-server/src/main/java/org/carapaceproxy/server/config/NetworkListenerConfiguration.java b/carapace-server/src/main/java/org/carapaceproxy/server/config/NetworkListenerConfiguration.java index 0fa651d39..8410eee6d 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/server/config/NetworkListenerConfiguration.java +++ b/carapace-server/src/main/java/org/carapaceproxy/server/config/NetworkListenerConfiguration.java @@ -159,11 +159,6 @@ public HostPort getKey() { } public static Set getDefaultHttpProtocols(final boolean ssl) { - // return Set.of(HTTP11.name(), (ssl ? H2 : H2C).name()); - if (ssl) { - // todo: until #410 is done, we will allow HTTP/2 only in clear-text - return Set.of(HTTP11.name()); - } - return Set.of(HTTP11.name(), H2C.name()); + return Set.of(HTTP11.name(), (ssl ? H2 : H2C).name()); } } diff --git a/carapace-server/src/main/java/org/carapaceproxy/server/config/SSLCertificateConfiguration.java b/carapace-server/src/main/java/org/carapaceproxy/server/config/SSLCertificateConfiguration.java index 56690dde4..c4ad0b79f 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/server/config/SSLCertificateConfiguration.java +++ b/carapace-server/src/main/java/org/carapaceproxy/server/config/SSLCertificateConfiguration.java @@ -28,7 +28,7 @@ import java.util.Set; import java.util.stream.Collectors; import lombok.Data; -import org.carapaceproxy.utils.CertificatesUtils; +import org.carapaceproxy.core.ssl.CertificatesUtils; /** * Configuration for a TLS Certificate @@ -38,7 +38,7 @@ @Data public class SSLCertificateConfiguration { - public enum CertificateMode { + public static enum CertificateMode { STATIC, ACME, MANUAL } diff --git a/carapace-server/src/test/java/org/carapaceproxy/api/StartAPIServerTest.java b/carapace-server/src/test/java/org/carapaceproxy/api/StartAPIServerTest.java index e438945fe..348b0a31c 100644 --- a/carapace-server/src/test/java/org/carapaceproxy/api/StartAPIServerTest.java +++ b/carapace-server/src/test/java/org/carapaceproxy/api/StartAPIServerTest.java @@ -24,8 +24,8 @@ import static org.carapaceproxy.utils.APIUtils.certificateStateToString; import static org.carapaceproxy.utils.CertificatesTestUtils.generateSampleChain; import static org.carapaceproxy.utils.CertificatesTestUtils.uploadCertificate; -import static org.carapaceproxy.utils.CertificatesUtils.KEYSTORE_PW; -import static org.carapaceproxy.utils.CertificatesUtils.createKeystore; +import static org.carapaceproxy.core.ssl.CertificatesUtils.KEYSTORE_PW; +import static org.carapaceproxy.core.ssl.CertificatesUtils.createKeystore; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; @@ -62,7 +62,7 @@ import org.carapaceproxy.server.filters.XTlsCipherRequestFilter; import org.carapaceproxy.server.filters.XTlsProtocolRequestFilter; import org.carapaceproxy.server.mapper.requestmatcher.MatchAllRequestMatcher; -import org.carapaceproxy.utils.CertificatesUtils; +import org.carapaceproxy.core.ssl.CertificatesUtils; import org.carapaceproxy.utils.RawHttpClient; import org.carapaceproxy.utils.TestUtils; import org.junit.Assert; diff --git a/carapace-server/src/test/java/org/carapaceproxy/api/UseAdminServer.java b/carapace-server/src/test/java/org/carapaceproxy/api/UseAdminServer.java index 87086681f..78222eaa5 100644 --- a/carapace-server/src/test/java/org/carapaceproxy/api/UseAdminServer.java +++ b/carapace-server/src/test/java/org/carapaceproxy/api/UseAdminServer.java @@ -19,18 +19,18 @@ */ package org.carapaceproxy.api; +import static org.carapaceproxy.core.HttpProxyServer.buildForTests; +import static org.junit.Assert.assertNull; import java.io.File; import java.io.IOException; import java.util.Properties; import org.carapaceproxy.configstore.PropertiesConfigurationStore; import org.carapaceproxy.core.HttpProxyServer; -import static org.carapaceproxy.core.HttpProxyServer.buildForTests; import org.carapaceproxy.server.config.ConfigurationChangeInProgressException; import org.carapaceproxy.server.config.ConfigurationNotValidException; import org.carapaceproxy.utils.RawHttpClient; import org.carapaceproxy.utils.TestEndpointMapper; import org.junit.After; -import static org.junit.Assert.assertNull; import org.junit.Before; import org.junit.Rule; import org.junit.rules.TemporaryFolder; @@ -67,7 +67,7 @@ public void buildNewServer() throws Exception { } @After - public void stopServer() throws Exception { + public void stopServer() { if (server != null) { server.close(); server = null; diff --git a/carapace-server/src/test/java/org/carapaceproxy/core/MaxHeaderSizeTest.java b/carapace-server/src/test/java/org/carapaceproxy/core/MaxHeaderSizeTest.java index 77a5a5a69..12425c5bb 100644 --- a/carapace-server/src/test/java/org/carapaceproxy/core/MaxHeaderSizeTest.java +++ b/carapace-server/src/test/java/org/carapaceproxy/core/MaxHeaderSizeTest.java @@ -1,19 +1,20 @@ package org.carapaceproxy.core; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static org.junit.Assert.assertEquals; import com.github.tomakehurst.wiremock.junit.WireMockRule; -import org.carapaceproxy.api.UseAdminServer; -import org.carapaceproxy.utils.TestUtils; -import org.junit.Rule; -import org.junit.Test; - import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.Properties; - -import static com.github.tomakehurst.wiremock.client.WireMock.*; -import static org.junit.Assert.assertEquals; +import org.carapaceproxy.api.UseAdminServer; +import org.carapaceproxy.utils.TestUtils; +import org.junit.Rule; +import org.junit.Test; public class MaxHeaderSizeTest extends UseAdminServer { @@ -78,7 +79,7 @@ public void test() throws Exception { HttpRequest request = HttpRequest.newBuilder() .GET() - .uri(URI.create("http://localhost:" + 8086 +"/index.html")) + .uri(URI.create("http://localhost:" + 8086 + "/index.html")) .setHeader("custom-header", "test") .setHeader("token", "eyJhbGciOiJIUzI1NiJ9.eyJSb") .setHeader("token1", "eyJhbGciOiJIUzI1NiJ9.eyJSb") diff --git a/carapace-server/src/test/java/org/carapaceproxy/listeners/ListenerConfigurationTest.java b/carapace-server/src/test/java/org/carapaceproxy/listeners/ListenerConfigurationTest.java index 8dcff3553..646341d2e 100644 --- a/carapace-server/src/test/java/org/carapaceproxy/listeners/ListenerConfigurationTest.java +++ b/carapace-server/src/test/java/org/carapaceproxy/listeners/ListenerConfigurationTest.java @@ -6,8 +6,8 @@ import java.util.Map; import java.util.Properties; import org.carapaceproxy.configstore.PropertiesConfigurationStore; +import org.carapaceproxy.core.DisposableChannelListener; import org.carapaceproxy.core.HttpProxyServer; -import org.carapaceproxy.core.Listeners; import org.carapaceproxy.server.config.ConfigurationChangeInProgressException; import org.carapaceproxy.server.config.HostPort; import org.junit.Rule; @@ -36,7 +36,7 @@ public void testListenerKeepAliveConfiguration() throws Exception { HostPort listenerKey = new HostPort("localhost", 8080); { - Map listeners = server.getListeners().getListeningChannels(); + Map listeners = server.getListeners().getListeningChannels(); //check default configuration assertTrue(listeners.get(listenerKey).getConfig().isKeepAlive()); @@ -56,7 +56,7 @@ public void testListenerKeepAliveConfiguration() throws Exception { reloadConfiguration(configuration, server); - Map listeners = server.getListeners().getListeningChannels(); + Map listeners = server.getListeners().getListeningChannels(); assertEquals(1, listeners.size()); assertFalse(listeners.get(listenerKey).getConfig().isKeepAlive()); @@ -77,7 +77,7 @@ public void testListenerKeepAliveConfiguration() throws Exception { configuration.put("listener.1.enabled", "true"); reloadConfiguration(configuration, server); - Map listeners = server.getListeners().getListeningChannels(); + Map listeners = server.getListeners().getListeningChannels(); assertTrue(listeners.get(listenerKey).getConfig().isKeepAlive()); assertEquals(10, listeners.get(listenerKey).getConfig().getSoBacklog()); diff --git a/carapace-server/src/test/java/org/carapaceproxy/server/certificates/CertificatesTest.java b/carapace-server/src/test/java/org/carapaceproxy/server/certificates/CertificatesTest.java index fc18da5cd..a9c7703c5 100644 --- a/carapace-server/src/test/java/org/carapaceproxy/server/certificates/CertificatesTest.java +++ b/carapace-server/src/test/java/org/carapaceproxy/server/certificates/CertificatesTest.java @@ -28,7 +28,7 @@ import static org.carapaceproxy.server.certificates.DynamicCertificatesManager.DEFAULT_KEYPAIRS_SIZE; import static org.carapaceproxy.utils.CertificatesTestUtils.generateSampleChain; import static org.carapaceproxy.utils.CertificatesTestUtils.uploadCertificate; -import static org.carapaceproxy.utils.CertificatesUtils.createKeystore; +import static org.carapaceproxy.core.ssl.CertificatesUtils.createKeystore; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertArrayEquals; @@ -82,7 +82,7 @@ import org.carapaceproxy.configstore.ConfigurationStore; import org.carapaceproxy.server.certificates.ocsp.OcspStaplingManager; import org.carapaceproxy.server.config.SSLCertificateConfiguration; -import org.carapaceproxy.utils.CertificatesUtils; +import org.carapaceproxy.core.ssl.CertificatesUtils; import org.carapaceproxy.utils.HttpTestUtils; import org.carapaceproxy.utils.RawHttpClient; import org.carapaceproxy.utils.RawHttpClient.HttpResponse; diff --git a/carapace-server/src/test/java/org/carapaceproxy/server/certificates/CertificatesUtilsTest.java b/carapace-server/src/test/java/org/carapaceproxy/server/certificates/CertificatesUtilsTest.java index 195baf99f..d97981c90 100644 --- a/carapace-server/src/test/java/org/carapaceproxy/server/certificates/CertificatesUtilsTest.java +++ b/carapace-server/src/test/java/org/carapaceproxy/server/certificates/CertificatesUtilsTest.java @@ -32,10 +32,10 @@ import java.util.Arrays; import org.junit.Test; import org.shredzone.acme4j.util.KeyPairUtils; -import static org.carapaceproxy.utils.CertificatesUtils.compareChains; +import static org.carapaceproxy.core.ssl.CertificatesUtils.compareChains; import java.security.cert.X509Certificate; import java.util.Date; -import org.carapaceproxy.utils.CertificatesUtils; +import org.carapaceproxy.core.ssl.CertificatesUtils; /** * diff --git a/carapace-server/src/test/java/org/carapaceproxy/utils/CertificatesTestUtils.java b/carapace-server/src/test/java/org/carapaceproxy/utils/CertificatesTestUtils.java index 4ee558075..d339162d3 100644 --- a/carapace-server/src/test/java/org/carapaceproxy/utils/CertificatesTestUtils.java +++ b/carapace-server/src/test/java/org/carapaceproxy/utils/CertificatesTestUtils.java @@ -20,7 +20,7 @@ package org.carapaceproxy.utils; import static org.carapaceproxy.server.certificates.DynamicCertificatesManager.DEFAULT_KEYPAIRS_SIZE; -import static org.carapaceproxy.utils.CertificatesUtils.createKeystore; +import static org.carapaceproxy.core.ssl.CertificatesUtils.createKeystore; import static org.shredzone.acme4j.util.KeyPairUtils.createKeyPair; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream;