From f7caf2876a7463957781c5cf4c40a2d7efd770cb Mon Sep 17 00:00:00 2001 From: Jeff Trent Date: Mon, 25 Sep 2023 10:56:25 -0400 Subject: [PATCH] fix subscription model, and add tests --- .../webserver/ConfiguredTlsManager.java | 22 +++++-- .../io/helidon/webserver/NettyWebServer.java | 8 ++- .../helidon/webserver/NettyWebServerTest.java | 37 ++++++++++- .../webserver/ServerConfigurationTest.java | 48 ++++++++++++++ .../spi/FakeReloadableTlsManager.java | 64 +++++++++++++++++++ .../webserver/spi/FakeTlsManagerProvider.java | 4 +- .../webserver/spi/TlsManagerProviderTest.java | 2 +- .../config-with-ssl-and-tls-manager.conf | 42 ++++++++++++ 8 files changed, 217 insertions(+), 10 deletions(-) create mode 100644 webserver/webserver/src/test/java/io/helidon/webserver/spi/FakeReloadableTlsManager.java create mode 100644 webserver/webserver/src/test/resources/config-with-ssl-and-tls-manager.conf diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/ConfiguredTlsManager.java b/webserver/webserver/src/main/java/io/helidon/webserver/ConfiguredTlsManager.java index 506cdc6967a..3b4c9add335 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/ConfiguredTlsManager.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/ConfiguredTlsManager.java @@ -50,7 +50,7 @@ public class ConfiguredTlsManager implements TlsManager { private final String name; private final String type; - private final Set> sslContextConsumers = new LinkedHashSet<>(); + private final Set> subscribers = new LinkedHashSet<>(); private volatile SSLContext sslContext; ConfiguredTlsManager() { @@ -96,7 +96,7 @@ public SSLContext sslContext() { @Override // TlsManager public void subscribe(Consumer sslContextConsumer) { - sslContextConsumers.add(Objects.requireNonNull(sslContextConsumer)); + subscribers.add(Objects.requireNonNull(sslContextConsumer)); } @Override // TlsManager @@ -129,7 +129,9 @@ public void init(WebServerTls tlsConfig) { * @param tlsConfig the tls config * @param keyManagers the key managers * @param trustManagers the trust managers + * @deprecated this method will removed in a future release. */ + @Deprecated protected void initSslContext(WebServerTls tlsConfig, KeyManager[] keyManagers, TrustManager[] trustManagers) { @@ -159,7 +161,7 @@ protected void reload(WebServerTls tlsConfig, initSslContext(tlsConfig, keyManagers, trustManagers); // notify subscribers - sslContextConsumers.forEach(c -> c.accept(sslContext)); + subscribers.forEach(c -> c.accept(sslContext)); } /** @@ -186,7 +188,9 @@ protected TrustManagerFactory trustAllTmf() { * * @param tlsConfig TLS config * @return a new trust manager factory + * @deprecated this method will removed in a future release. */ + @Deprecated protected TrustManagerFactory createTmf(WebServerTls tlsConfig) { try { return TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); @@ -195,8 +199,16 @@ protected TrustManagerFactory createTmf(WebServerTls tlsConfig) { } } - private void configureAndSet(WebServerTls tlsConfig, - SSLContext sslContext) { + /** + * Configure the {@link SSLContext} based upon configuration, and then store the instance. + * + * @param tlsConfig TLS config + * @param sslContext ssl context to store + * @deprecated this method will removed in a future release. + */ + @Deprecated + protected void configureAndSet(WebServerTls tlsConfig, + SSLContext sslContext) { SSLSessionContext serverSessionContext = sslContext.getServerSessionContext(); if (serverSessionContext != null) { int sessionCacheSize = tlsConfig.sessionCacheSize(); diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/NettyWebServer.java b/webserver/webserver/src/main/java/io/helidon/webserver/NettyWebServer.java index 28b7862ae30..309195d9092 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/NettyWebServer.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/NettyWebServer.java @@ -165,7 +165,13 @@ class NettyWebServer implements WebServer { bootstraps.put(name, bootstrap); // subscribe to updates - soConfig.tls().ifPresent(tlsConfig -> updateTls(tlsConfig, name)); + Optional webServerTls = soConfig.tls(); + webServerTls + .map(WebServerTls::manager) + .ifPresent(manager -> manager.subscribe(newCtx -> { + SslContext newSslCts = createSslContext(webServerTls.get(), newCtx); + childHandler.updateSslContext(newSslCts); + })); } // Log entry that also initializes NettyInitializer class diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/NettyWebServerTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/NettyWebServerTest.java index 7c168f18536..ffde40f0e5e 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/NettyWebServerTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/NettyWebServerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2022 Oracle and/or its affiliates. + * Copyright (c) 2017, 2023 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,17 +30,24 @@ import java.util.logging.Level; import java.util.logging.Logger; +import javax.net.ssl.SSLContext; + import io.helidon.common.http.DataChunk; import io.helidon.common.http.Http; import io.helidon.common.reactive.Multi; +import io.helidon.config.Config; +import io.helidon.config.ConfigSources; +import io.helidon.webserver.spi.FakeReloadableTlsManager; import org.hamcrest.collection.IsCollectionWithSize; import org.hamcrest.core.Is; import org.junit.jupiter.api.Test; import static io.helidon.config.testing.OptionalMatcher.present; +import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.sameInstance; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.core.AllOf.allOf; @@ -272,4 +279,32 @@ public void additionalCoupledPairedRoutingsDoWork() { assertThat(webServer.configuration().namedSocket("matched"), present()); } + + @Test + @SuppressWarnings("deprecation") + void tlsManagerReloadability() { + Config config = Config.builder().sources(ConfigSources.classpath("config-with-ssl-and-tls-manager.conf")).build(); + Config webServerConfig = config.get("webserver"); + assertThat(webServerConfig.exists(), is(true)); + + WebServer webServer = WebServer.builder().config(webServerConfig).build(); + assertThat(webServer.hasTls("secure"), is(true)); + + WebServerTls tlsConfig = webServer.configuration().namedSocket("secure").orElseThrow().tls().orElseThrow(); + assertThat(tlsConfig.manager(), instanceOf(FakeReloadableTlsManager.class)); + FakeReloadableTlsManager fake = (FakeReloadableTlsManager) tlsConfig.manager(); + assertThat(fake.subscribers().size(), is(1)); + + SSLContext sslContext = fake.sslContext(); + assertThat(sslContext, notNullValue()); + assertThat("should only change after reload", sslContext, sameInstance(fake.sslContext())); + + fake.reload(tlsConfig, null, null); + assertThat("sanity", fake.subscribers().size(), is(1)); + + SSLContext sslContextAfter = fake.sslContext(); + assertThat(sslContextAfter, notNullValue()); + assertThat("should be changed after reload", sslContextAfter, not(sameInstance(sslContext))); + } + } diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/ServerConfigurationTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/ServerConfigurationTest.java index d871557b839..779528ebfc4 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/ServerConfigurationTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/ServerConfigurationTest.java @@ -17,15 +17,23 @@ package io.helidon.webserver; import java.net.InetAddress; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import javax.net.ssl.SSLContext; import io.helidon.config.Config; import io.helidon.config.ConfigSources; +import io.helidon.webserver.spi.FakeReloadableTlsManager; import org.junit.jupiter.api.Test; +import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.CoreMatchers.sameInstance; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.hasSize; @@ -126,4 +134,44 @@ public void sslFromConfig() throws Exception { assertThat(sc.socket("secure").enabledSslProtocols(), contains("TLSv1.2")); assertThat(sc.socket("secure").ssl(), notNullValue()); } + + @Test + @SuppressWarnings({"deprecation", "removal"}) + public void tlsManagerWithConsumerSubscriptionsOnReload() { + Config config = Config.builder().sources(ConfigSources.classpath("config-with-ssl-and-tls-manager.conf")).build(); + ServerConfiguration sc = config.get("webserver").as(ServerConfiguration::create).get(); + SocketConfiguration sockCfg = sc.socket("secure"); + SSLContext sslCtx = sockCfg.ssl(); + + assertThat(sockCfg.port(), is(8443)); + assertThat(sockCfg.enabledSslProtocols(), contains("TLSv1.2")); + assertThat(sslCtx, notNullValue()); + + WebServerTls secureConfig = sc.socket("secure").tls().orElseThrow(); + assertThat(secureConfig.trustAll(), is(true)); + + TlsManager manager = secureConfig.manager(); + assertThat(manager, instanceOf(FakeReloadableTlsManager.class)); + + FakeReloadableTlsManager fake = (FakeReloadableTlsManager) manager; + assertThat(fake.tlsConfig(), sameInstance(secureConfig)); + assertThat(fake.sslContext(), sameInstance(sockCfg.ssl())); + assertThat(fake.subscribers().size(), is(0)); + + AtomicInteger counter = new AtomicInteger(); + AtomicReference sslCtxRef = new AtomicReference<>(); + fake.subscribe(sslContext -> { + assertThat(sslContext, notNullValue()); + sslCtxRef.set(sslContext); + counter.incrementAndGet(); + }); + assertThat(fake.subscribers().size(), is(1)); + assertThat(counter.get(), is(0)); + + fake.reload(secureConfig, null, null); + assertThat(counter.get(), is(1)); + assertThat(sslCtxRef, notNullValue()); + assertThat(sslCtxRef, not(sameInstance(sslCtx))); + } + } diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/spi/FakeReloadableTlsManager.java b/webserver/webserver/src/test/java/io/helidon/webserver/spi/FakeReloadableTlsManager.java new file mode 100644 index 00000000000..b6714b102fa --- /dev/null +++ b/webserver/webserver/src/test/java/io/helidon/webserver/spi/FakeReloadableTlsManager.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.webserver.spi; + +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.function.Consumer; + +import javax.net.ssl.SSLContext; + +import io.helidon.webserver.ConfiguredTlsManager; +import io.helidon.webserver.WebServerTls; + +@SuppressWarnings("deprecation") +public class FakeReloadableTlsManager extends ConfiguredTlsManager { + private final Set> subscribers = new LinkedHashSet<>(); + private WebServerTls tlsConfig; + + FakeReloadableTlsManager(String name) { + super(name, "fake-type"); + } + + @Override // TlsManager + public void init(WebServerTls tlsConfig) { + super.init(tlsConfig); + } + + @Override // TlsManager + public void subscribe(Consumer sslContextConsumer) { + super.subscribe(sslContextConsumer); + subscribers.add(sslContextConsumer); + } + + public Set> subscribers() { + return subscribers; + } + + @Override + protected void configureAndSet(WebServerTls tlsConfig, + SSLContext sslContext) { + super.configureAndSet(tlsConfig, sslContext); + + this.tlsConfig = tlsConfig; + } + + public WebServerTls tlsConfig() { + return tlsConfig; + } + +} diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/spi/FakeTlsManagerProvider.java b/webserver/webserver/src/test/java/io/helidon/webserver/spi/FakeTlsManagerProvider.java index f9b9b229f23..4a847fe9bfa 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/spi/FakeTlsManagerProvider.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/spi/FakeTlsManagerProvider.java @@ -17,11 +17,11 @@ package io.helidon.webserver.spi; import io.helidon.config.Config; -import io.helidon.webserver.ConfiguredTlsManager; import io.helidon.webserver.TlsManager; /** * Testing only - service loaded. + * * @deprecated */ @Deprecated @@ -40,7 +40,7 @@ public String configKey() { @Override public TlsManager create(Config config, String name) { - return new ConfiguredTlsManager(name, name) {}; + return new FakeReloadableTlsManager(name); } } diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/spi/TlsManagerProviderTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/spi/TlsManagerProviderTest.java index 4bc44029cbf..db00f93a364 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/spi/TlsManagerProviderTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/spi/TlsManagerProviderTest.java @@ -83,7 +83,7 @@ void goodConfig() { assertThat(configuredTlsManager.name(), equalTo("fake")); assertThat(configuredTlsManager.type(), - equalTo("fake")); + equalTo("fake-type")); } } diff --git a/webserver/webserver/src/test/resources/config-with-ssl-and-tls-manager.conf b/webserver/webserver/src/test/resources/config-with-ssl-and-tls-manager.conf new file mode 100644 index 00000000000..d4e9a37ecfa --- /dev/null +++ b/webserver/webserver/src/test/resources/config-with-ssl-and-tls-manager.conf @@ -0,0 +1,42 @@ +# +# Copyright (c) 2023 Oracle and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +webserver: { + sockets: { + secure: { + port: 8443, + bind-address: "127.0.0.1", + tls: { + trust-all: true + manager: { + fake: { + } + }, + protocols: [ + "TLSv1.2" + ], + private-key: { + keystore: { + resource { + resource-path: "certificate.p12" + } + passphrase: "helidon" + } + } + } + } + } +}