From 4eb9842f40d9b8418105bd763a759ab146fec49b Mon Sep 17 00:00:00 2001 From: santosh Date: Fri, 19 Aug 2022 17:22:53 +0200 Subject: [PATCH 1/6] #12064 Auto-reload tls certs for druid endpoints --- .../server/initialization/TLSServerConfig.java | 16 ++++++++++++++++ .../initialization/jetty/JettyServerModule.java | 6 ++++++ 2 files changed, 22 insertions(+) diff --git a/server/src/main/java/org/apache/druid/server/initialization/TLSServerConfig.java b/server/src/main/java/org/apache/druid/server/initialization/TLSServerConfig.java index 1c83c41b1576..f5b55bf1bcbd 100644 --- a/server/src/main/java/org/apache/druid/server/initialization/TLSServerConfig.java +++ b/server/src/main/java/org/apache/druid/server/initialization/TLSServerConfig.java @@ -80,6 +80,12 @@ public class TLSServerConfig @JsonProperty private String crlPath; + @JsonProperty + private boolean reloadSslContext = false; + + @JsonProperty + private int reloadSslContextSeconds = 60; + public String getKeyStorePath() { return keyStorePath; @@ -170,6 +176,15 @@ public String getCrlPath() return crlPath; } + public int getReloadSslContextSeconds() + { + return reloadSslContextSeconds; + } + + public boolean isReloadSslContext() { + return reloadSslContext; + } + @Override public String toString() { @@ -189,6 +204,7 @@ public String toString() ", trustStoreAlgorithm='" + trustStoreAlgorithm + '\'' + ", validateHostnames='" + validateHostnames + '\'' + ", crlPath='" + crlPath + '\'' + + ", reloadSslContextSeconds='" + reloadSslContextSeconds + '\'' + '}'; } } diff --git a/server/src/main/java/org/apache/druid/server/initialization/jetty/JettyServerModule.java b/server/src/main/java/org/apache/druid/server/initialization/jetty/JettyServerModule.java index 5d4c6532e277..5f0b254a1dfa 100644 --- a/server/src/main/java/org/apache/druid/server/initialization/jetty/JettyServerModule.java +++ b/server/src/main/java/org/apache/druid/server/initialization/jetty/JettyServerModule.java @@ -75,6 +75,7 @@ import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.server.handler.ErrorHandler; import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.util.ssl.KeyStoreScanner; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler; @@ -347,6 +348,11 @@ static Server makeAndInitializeServer( } connector.setPort(node.getTlsPort()); serverConnectors.add(connector); + if(tlsServerConfig.isReloadSslContext()){ + KeyStoreScanner keyStoreScanner = new KeyStoreScanner(sslContextFactory); + keyStoreScanner.setScanInterval(tlsServerConfig.getReloadSslContextSeconds()); + server.addBean(keyStoreScanner); + } } else { sslContextFactory = null; } From 244ab191f5abb660be40485a81768f2ff37baeab Mon Sep 17 00:00:00 2001 From: santosh Date: Fri, 19 Aug 2022 17:33:31 +0200 Subject: [PATCH 2/6] #12064 Add missing toString param --- .../org/apache/druid/server/initialization/TLSServerConfig.java | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/main/java/org/apache/druid/server/initialization/TLSServerConfig.java b/server/src/main/java/org/apache/druid/server/initialization/TLSServerConfig.java index f5b55bf1bcbd..3d67105fe0fc 100644 --- a/server/src/main/java/org/apache/druid/server/initialization/TLSServerConfig.java +++ b/server/src/main/java/org/apache/druid/server/initialization/TLSServerConfig.java @@ -204,6 +204,7 @@ public String toString() ", trustStoreAlgorithm='" + trustStoreAlgorithm + '\'' + ", validateHostnames='" + validateHostnames + '\'' + ", crlPath='" + crlPath + '\'' + + ", reloadSslContext='" + reloadSslContext + '\'' + ", reloadSslContextSeconds='" + reloadSslContextSeconds + '\'' + '}'; } From d54936d4341c6624422b0787c6c3e437a4e300a3 Mon Sep 17 00:00:00 2001 From: santosh Date: Mon, 22 Aug 2022 16:07:29 +0200 Subject: [PATCH 3/6] #12064 Add tests and new jks Co-authored-by: zemin-piao --- .../util/http/client/FriendlyServersTest.java | 3 + .../jetty/JettyServerModule.java | 2 +- .../initialization/JettyCertRenewTest.java | 282 ++++++++++++++++++ server/src/test/resources/server-new.jks | Bin 0 -> 4047 bytes server/src/test/resources/truststore-new.jks | Bin 0 -> 3248 bytes 5 files changed, 286 insertions(+), 1 deletion(-) create mode 100644 server/src/test/java/org/apache/druid/server/initialization/JettyCertRenewTest.java create mode 100644 server/src/test/resources/server-new.jks create mode 100644 server/src/test/resources/truststore-new.jks diff --git a/core/src/test/java/org/apache/druid/java/util/http/client/FriendlyServersTest.java b/core/src/test/java/org/apache/druid/java/util/http/client/FriendlyServersTest.java index 60b770cf2e68..4df73564389e 100644 --- a/core/src/test/java/org/apache/druid/java/util/http/client/FriendlyServersTest.java +++ b/core/src/test/java/org/apache/druid/java/util/http/client/FriendlyServersTest.java @@ -31,6 +31,7 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.SslConnectionFactory; +import org.eclipse.jetty.util.ssl.KeyStoreScanner; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.jboss.netty.channel.ChannelException; import org.jboss.netty.handler.codec.http.HttpMethod; @@ -275,6 +276,8 @@ public void testFriendlySelfSignedHttpsServer() throws Exception sslConnector.setPort(0); server.setConnectors(new Connector[]{sslConnector}); + KeyStoreScanner keyStoreScanner = new KeyStoreScanner(sslContextFactory); + server.addBean(keyStoreScanner); server.start(); try { diff --git a/server/src/main/java/org/apache/druid/server/initialization/jetty/JettyServerModule.java b/server/src/main/java/org/apache/druid/server/initialization/jetty/JettyServerModule.java index 5f0b254a1dfa..9441eae89ddf 100644 --- a/server/src/main/java/org/apache/druid/server/initialization/jetty/JettyServerModule.java +++ b/server/src/main/java/org/apache/druid/server/initialization/jetty/JettyServerModule.java @@ -348,7 +348,7 @@ static Server makeAndInitializeServer( } connector.setPort(node.getTlsPort()); serverConnectors.add(connector); - if(tlsServerConfig.isReloadSslContext()){ + if (tlsServerConfig.isReloadSslContext()) { KeyStoreScanner keyStoreScanner = new KeyStoreScanner(sslContextFactory); keyStoreScanner.setScanInterval(tlsServerConfig.getReloadSslContextSeconds()); server.addBean(keyStoreScanner); diff --git a/server/src/test/java/org/apache/druid/server/initialization/JettyCertRenewTest.java b/server/src/test/java/org/apache/druid/server/initialization/JettyCertRenewTest.java new file mode 100644 index 000000000000..3ce9ce5c8574 --- /dev/null +++ b/server/src/test/java/org/apache/druid/server/initialization/JettyCertRenewTest.java @@ -0,0 +1,282 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.druid.server.initialization; + +import com.google.common.collect.ImmutableList; +import com.google.inject.Binder; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.Module; +import com.google.inject.multibindings.Multibinder; +import org.apache.druid.guice.GuiceInjectors; +import org.apache.druid.guice.Jerseys; +import org.apache.druid.guice.JsonConfigProvider; +import org.apache.druid.guice.LazySingleton; +import org.apache.druid.guice.LifecycleModule; +import org.apache.druid.guice.annotations.Self; +import org.apache.druid.initialization.Initialization; +import org.apache.druid.java.util.common.lifecycle.Lifecycle; +import org.apache.druid.java.util.http.client.HttpClientConfig; +import org.apache.druid.java.util.http.client.HttpClientInit; +import org.apache.druid.metadata.PasswordProvider; +import org.apache.druid.server.DruidNode; +import org.apache.druid.server.initialization.jetty.JettyServerInitializer; +import org.apache.druid.server.initialization.jetty.ServletFilterHolder; +import org.apache.druid.server.security.AuthTestUtils; +import org.apache.druid.server.security.AuthorizerMapper; +import org.eclipse.jetty.server.Server; +import org.joda.time.Duration; +import org.junit.Before; +import org.junit.Rule; +import org.junit.rules.TemporaryFolder; + +import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.EnumSet; +import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; + +public class JettyCertRenewTest extends JettyTest +{ + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + private HttpClientConfig sslConfig; + + private Injector injector; + + private LatchedRequestStateHolder latchedRequestState; + + @Override + public void setProperties() + { + // call super.setProperties first in case it is setting the same property as this class + super.setProperties(); + System.setProperty("druid.server.http.showDetailedJettyErrors", "true"); + } + + @Override + @Before + public void setup() throws Exception + { + setProperties(); + Injector injector = setupInjector(); + final DruidNode node = injector.getInstance(Key.get(DruidNode.class, Self.class)); + port = node.getPlaintextPort(); + tlsPort = node.getTlsPort(); + + lifecycle = injector.getInstance(Lifecycle.class); + lifecycle.start(); + BaseJettyTest.ClientHolder holder = injector.getInstance(BaseJettyTest.ClientHolder.class); + server = injector.getInstance(Server.class); + client = holder.getClient(); + + File keyStore = new File(JettyCertRenewTest.class.getClassLoader().getResource("server-new.jks").getFile()); + Files.copy(keyStore.toPath(), new File(folder.newFolder(), "server.jks").toPath()); + File trustStore = new File(JettyCertRenewTest.class.getClassLoader().getResource("truststore-new.jks").getFile()); + Files.copy(trustStore.toPath(), new File(folder.newFolder(), "truststore.jks").toPath()); + + Thread.sleep(2000); + } + + @Override + protected Injector setupInjector() + { + TLSServerConfig tlsConfig; + try { + File keyStore = new File(JettyCertRenewTest.class.getClassLoader().getResource("server.jks").getFile()); + Path tmpKeyStore = Files.copy(keyStore.toPath(), new File(folder.newFolder(), "server.jks").toPath()); + File trustStore = new File(JettyCertRenewTest.class.getClassLoader().getResource("truststore.jks").getFile()); + Path tmpTrustStore = Files.copy(trustStore.toPath(), new File(folder.newFolder(), "truststore.jks").toPath()); + PasswordProvider pp = () -> "druid123"; + tlsConfig = new TLSServerConfig() + { + @Override + public String getKeyStorePath() + { + return tmpKeyStore.toString(); + } + + @Override + public String getKeyStoreType() + { + return "jks"; + } + + @Override + public PasswordProvider getKeyStorePasswordProvider() + { + return pp; + } + + @Override + public PasswordProvider getKeyManagerPasswordProvider() + { + return pp; + } + + @Override + public String getTrustStorePath() + { + return tmpTrustStore.toString(); + } + + @Override + public String getTrustStoreAlgorithm() + { + return "PKIX"; + } + + @Override + public PasswordProvider getTrustStorePasswordProvider() + { + return pp; + } + + @Override + public String getCertAlias() + { + return "druid"; + } + + @Override + public boolean isRequireClientCertificate() + { + return false; + } + + @Override + public boolean isRequestClientCertificate() + { + return false; + } + + @Override + public boolean isValidateHostnames() + { + return false; + } + + @Override + public boolean isReloadSslContext() + { + return true; + } + + @Override + public int getReloadSslContextSeconds() + { + return 1; + } + }; + + sslConfig = + HttpClientConfig.builder() + .withSslContext( + HttpClientInit.sslContextWithTrustedKeyStore(tmpTrustStore.toString(), pp.getPassword()) + ) + .withWorkerCount(1) + .withReadTimeout(Duration.ZERO) + .build(); + } + catch (IOException e) { + throw new RuntimeException(e); + } + + final int ephemeralPort = ThreadLocalRandom.current().nextInt(49152, 65535); + + latchedRequestState = new LatchedRequestStateHolder(); + injector = Initialization.makeInjectorWithModules( + GuiceInjectors.makeStartupInjector(), + ImmutableList.of( + new Module() + { + @Override + public void configure(Binder binder) + { + JsonConfigProvider.bindInstance( + binder, + Key.get(DruidNode.class, Self.class), + new DruidNode("test", "localhost", false, ephemeralPort, ephemeralPort + 1, true, true) + ); + binder.bind(TLSServerConfig.class).toInstance(tlsConfig); + binder.bind(JettyServerInitializer.class).to(JettyServerInit.class).in(LazySingleton.class); + binder.bind(LatchedRequestStateHolder.class).toInstance(latchedRequestState); + + Multibinder multibinder = Multibinder.newSetBinder( + binder, + ServletFilterHolder.class + ); + + multibinder.addBinding().toInstance( + new ServletFilterHolder() + { + @Override + public String getPath() + { + return "/*"; + } + + @Override + public Map getInitParameters() + { + return null; + } + + @Override + public Class getFilterClass() + { + return DummyAuthFilter.class; + } + + @Override + public Filter getFilter() + { + return null; + } + + @Override + public EnumSet getDispatcherType() + { + return null; + } + } + ); + + + Jerseys.addResource(binder, SlowResource.class); + Jerseys.addResource(binder, LatchedResource.class); + Jerseys.addResource(binder, ExceptionResource.class); + Jerseys.addResource(binder, DefaultResource.class); + Jerseys.addResource(binder, DirectlyReturnResource.class); + binder.bind(AuthorizerMapper.class).toInstance(AuthTestUtils.TEST_AUTHORIZER_MAPPER); + LifecycleModule.register(binder, Server.class); + } + } + ) + ); + + return injector; + } +} diff --git a/server/src/test/resources/server-new.jks b/server/src/test/resources/server-new.jks new file mode 100644 index 0000000000000000000000000000000000000000..ffc46d578ebe01cc98e8252a6dbe40bdf49adb34 GIT binary patch literal 4047 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 q=Med$n-ciprQm2>Hh)<&N9aU literal 0 HcmV?d00001 diff --git a/server/src/test/resources/truststore-new.jks b/server/src/test/resources/truststore-new.jks new file mode 100644 index 0000000000000000000000000000000000000000..db6d765bcd51d1e4899b4d9079719b12b0dc360c GIT binary patch literal 3248 zcmezO_TO6u1_mZL=1nOo%}mkFEYU5>&o5zMU~D>iRG5{4HA2tSz>^xj9AnO#IGfOHB`3!hKf?PbTU_nDk191?an@7MiuOv0SD6u3nKQAP; zxTM(7z(5af8z-ZfVkSa?ZV6C@1K1aOi3N$t8L4{tMd^n8K=%O+W9DJYECKnZ%N}Sgi$ThTtatFQpo0yc4gPf6-fw_r^pTVGsiHoU;iIL%YoW~+EZ)a)aBqs-wX-jL_N}8@MY;#Kx@`Qs{=jq=Da9U#$NkCMm+)jde+d`b^r-Hf zl&eP|eIc&$PZYAj6=quU#a=V*u zWykv7ja3JFlzv5(^x52R2`rtp@uPzLmZc#o`7a*M8wMbCb_e`uP-{t}Txei+(?0*Ic^#{^>ftDbJ6q@m@dc-LX95zhG(Ht5B`E278WP zFOKIdi0}D$H0IWJ3!hKXE-S5Ne?@HS@K$m9H~Dy%)0C1q+}zxgGUv1E+Z=7!Yh!fz z>12U>SC&b=b9JBKl|RME?OIKjnW3ks!^$PTONDc+C)`?f}uk2u!q-L>L>zc41rq0U!)7K>0{1*B4+~U)fyNA9T zarw;kS-_ldu~#XMyHK35;*0mKLq=9tv>T6k1&A&)aoH97`t$0?3w~*peLnjo<@)JA zXRjL;DR#U!w?43D$$uthMh3>k$p(oAvcQBd%f}+dB9dE~T$b7LorUj+L(=WEo0X3B zCHD;ELDI@B5(Z)o*cI@D6bLgi{%2t|U{dSkN^v?5NJbAhQOQ;OoogM+o#oe zx1=qpiJv7|dFF9L>{1Oyfq4dMVyC1WJe%9^9C)G7(BG~nx}@Rw90s-0tZCajch5L~ z&1l!DZL20a-CXedtw(;4n#xJpnT)~4MGh7BYNXend!)7@=3GlM^PR)J%+VXyI5r5I zx~*dTP-*{C@7#~e4X+aUbFSrx=Rdojf5GHaahQnPrTa6wD`wV-?dAElyYZn9hvZF% z+uOc*ysF|qb5DV@m{+00=Gx0VRuSQCQ~uTF=$i8GTqZk-ao6c*JKHX3bZmPe_hV`6 z?H_F`pFNYmbiMmZmGXoCFIUZE@#9W@b2MD=Z=xT&|97dRt#TYQxWt;m(wY`LxNQG% zd9z0&XP?*_i`3Wb8z#HnTefGC*QR*)9q$ZlHd)WeY>1?Zr(SoGxE2 zJMrvx&HBwRpPb5S{CIT1@uv^?zuoCQB(C*J_t0gRc#jpEZhC8#olu|rzb`bziHE^y ztLYJ^j|qjOiMan_oe;J-)0``P!GhJL#+{l}D}CmO=|HaO1TpfJ^?X658d zq9s=hzUDdeZ1Obv+5hXr=85ZELwRp-v1-2d)7!0*=P^rrqtSgCMy-vHIX^CDJ$F(4 z`^muAg*iBx6?p1g!BBr&__5V$^J1((9$I^=|9^JpD1=<2}HI%KpC8LdM`>yW`&hg8gv XkV%*MlKtYQWA?0FnIE?{7EA&Ffju5& literal 0 HcmV?d00001 From f89f63124235d902e37ff7ff67c7a087cc0673d4 Mon Sep 17 00:00:00 2001 From: santosh Date: Mon, 22 Aug 2022 20:46:19 +0200 Subject: [PATCH 4/6] #12064 Refine tests --- .../initialization/TLSServerConfig.java | 3 +- .../initialization/JettyCertRenewTest.java | 199 ++++++++++++++---- 2 files changed, 161 insertions(+), 41 deletions(-) diff --git a/server/src/main/java/org/apache/druid/server/initialization/TLSServerConfig.java b/server/src/main/java/org/apache/druid/server/initialization/TLSServerConfig.java index 3d67105fe0fc..497d714a38bb 100644 --- a/server/src/main/java/org/apache/druid/server/initialization/TLSServerConfig.java +++ b/server/src/main/java/org/apache/druid/server/initialization/TLSServerConfig.java @@ -181,7 +181,8 @@ public int getReloadSslContextSeconds() return reloadSslContextSeconds; } - public boolean isReloadSslContext() { + public boolean isReloadSslContext() + { return reloadSslContext; } diff --git a/server/src/test/java/org/apache/druid/server/initialization/JettyCertRenewTest.java b/server/src/test/java/org/apache/druid/server/initialization/JettyCertRenewTest.java index 3ce9ce5c8574..031bdac1ee51 100644 --- a/server/src/test/java/org/apache/druid/server/initialization/JettyCertRenewTest.java +++ b/server/src/test/java/org/apache/druid/server/initialization/JettyCertRenewTest.java @@ -20,11 +20,13 @@ package org.apache.druid.server.initialization; import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.ListenableFuture; import com.google.inject.Binder; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.Module; import com.google.inject.multibindings.Multibinder; +import org.apache.commons.io.IOUtils; import org.apache.druid.guice.GuiceInjectors; import org.apache.druid.guice.Jerseys; import org.apache.druid.guice.JsonConfigProvider; @@ -32,9 +34,11 @@ import org.apache.druid.guice.LifecycleModule; import org.apache.druid.guice.annotations.Self; import org.apache.druid.initialization.Initialization; -import org.apache.druid.java.util.common.lifecycle.Lifecycle; +import org.apache.druid.java.util.http.client.HttpClient; import org.apache.druid.java.util.http.client.HttpClientConfig; import org.apache.druid.java.util.http.client.HttpClientInit; +import org.apache.druid.java.util.http.client.Request; +import org.apache.druid.java.util.http.client.response.InputStreamResponseHandler; import org.apache.druid.metadata.PasswordProvider; import org.apache.druid.server.DruidNode; import org.apache.druid.server.initialization.jetty.JettyServerInitializer; @@ -42,32 +46,56 @@ import org.apache.druid.server.security.AuthTestUtils; import org.apache.druid.server.security.AuthorizerMapper; import org.eclipse.jetty.server.Server; +import org.jboss.netty.handler.codec.http.HttpMethod; import org.joda.time.Duration; -import org.junit.Before; +import org.junit.Assert; import org.junit.Rule; +import org.junit.Test; import org.junit.rules.TemporaryFolder; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; import javax.servlet.DispatcherType; import javax.servlet.Filter; +import javax.ws.rs.core.MediaType; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.text.SimpleDateFormat; import java.util.EnumSet; +import java.util.Locale; import java.util.Map; import java.util.concurrent.ThreadLocalRandom; +import java.util.zip.GZIPOutputStream; -public class JettyCertRenewTest extends JettyTest +public class JettyCertRenewTest extends BaseJettyTest { @Rule public TemporaryFolder folder = new TemporaryFolder(); - private HttpClientConfig sslConfig; - private Injector injector; private LatchedRequestStateHolder latchedRequestState; + private Path tmpKeyStore; + + private Path tmpTrustStore; + + private PasswordProvider pp; + @Override public void setProperties() { @@ -76,29 +104,6 @@ public void setProperties() System.setProperty("druid.server.http.showDetailedJettyErrors", "true"); } - @Override - @Before - public void setup() throws Exception - { - setProperties(); - Injector injector = setupInjector(); - final DruidNode node = injector.getInstance(Key.get(DruidNode.class, Self.class)); - port = node.getPlaintextPort(); - tlsPort = node.getTlsPort(); - - lifecycle = injector.getInstance(Lifecycle.class); - lifecycle.start(); - BaseJettyTest.ClientHolder holder = injector.getInstance(BaseJettyTest.ClientHolder.class); - server = injector.getInstance(Server.class); - client = holder.getClient(); - - File keyStore = new File(JettyCertRenewTest.class.getClassLoader().getResource("server-new.jks").getFile()); - Files.copy(keyStore.toPath(), new File(folder.newFolder(), "server.jks").toPath()); - File trustStore = new File(JettyCertRenewTest.class.getClassLoader().getResource("truststore-new.jks").getFile()); - Files.copy(trustStore.toPath(), new File(folder.newFolder(), "truststore.jks").toPath()); - - Thread.sleep(2000); - } @Override protected Injector setupInjector() @@ -106,10 +111,10 @@ protected Injector setupInjector() TLSServerConfig tlsConfig; try { File keyStore = new File(JettyCertRenewTest.class.getClassLoader().getResource("server.jks").getFile()); - Path tmpKeyStore = Files.copy(keyStore.toPath(), new File(folder.newFolder(), "server.jks").toPath()); + tmpKeyStore = Files.copy(keyStore.toPath(), new File(folder.newFolder(), "server.jks").toPath()); File trustStore = new File(JettyCertRenewTest.class.getClassLoader().getResource("truststore.jks").getFile()); - Path tmpTrustStore = Files.copy(trustStore.toPath(), new File(folder.newFolder(), "truststore.jks").toPath()); - PasswordProvider pp = () -> "druid123"; + tmpTrustStore = Files.copy(trustStore.toPath(), new File(folder.newFolder(), "truststore.jks").toPath()); + pp = () -> "druid123"; tlsConfig = new TLSServerConfig() { @Override @@ -190,15 +195,6 @@ public int getReloadSslContextSeconds() return 1; } }; - - sslConfig = - HttpClientConfig.builder() - .withSslContext( - HttpClientInit.sslContextWithTrustedKeyStore(tmpTrustStore.toString(), pp.getPassword()) - ) - .withWorkerCount(1) - .withReadTimeout(Duration.ZERO) - .build(); } catch (IOException e) { throw new RuntimeException(e); @@ -279,4 +275,127 @@ public EnumSet getDispatcherType() return injector; } + + + @Test + public void testCertificateEndDateInvalid() throws Exception + { + SimpleDateFormat dateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", Locale.ENGLISH); + + Certificate[] certificatesBefore = getCertificates(); + for (Certificate certificate : certificatesBefore) { + X509Certificate real = (X509Certificate) certificate; + Assert.assertEquals(dateFormat.parse("Fri Mar 29 11:00:40 UTC 2030").toInstant(), real.getNotAfter().toInstant()); + } + + Assert.assertEquals(DEFAULT_RESPONSE_CONTENT, getResponseWithProperTrustStore()); + + // Replace the server and trustore keystores, wait for 3s and perform all the tests. + File keyStore = new File(JettyCertRenewTest.class.getClassLoader().getResource("server-new.jks").getFile()); + Files.copy(keyStore.toPath(), tmpKeyStore, StandardCopyOption.REPLACE_EXISTING); + File trustStore = new File(JettyCertRenewTest.class.getClassLoader().getResource("truststore-new.jks").getFile()); + Files.copy(trustStore.toPath(), tmpTrustStore, StandardCopyOption.REPLACE_EXISTING); + + Thread.sleep(3000); + + Certificate[] certificatesAfter = getCertificates(); + for (Certificate certificate : certificatesAfter) { + X509Certificate real = (X509Certificate) certificate; + Assert.assertEquals(dateFormat.parse("Thu Aug 19 13:38:51 UTC 2032").toInstant(), real.getNotAfter().toInstant()); + } + + Assert.assertEquals(DEFAULT_RESPONSE_CONTENT, getResponseWithProperTrustStore()); + } + + private static class AcceptAllForTestX509TrustManager implements X509TrustManager + { + private X509Certificate[] accepted; + + @Override + public void checkClientTrusted(X509Certificate[] xcs, String string) + { + } + + @Override + public void checkServerTrusted(X509Certificate[] xcs, String string) + { + accepted = xcs; + } + + @Override + public X509Certificate[] getAcceptedIssuers() + { + return accepted; + } + } + + private static class AcceptAllForTestHostnameVerifier implements HostnameVerifier + { + @Override + public boolean verify(String string, SSLSession ssls) + { + return true; + } + } + + private Certificate[] getCertificates() throws Exception + { + URL url = new URL("https://localhost:" + tlsPort + "/default/"); + + SSLContext sslCtx = SSLContext.getInstance("TLS"); + sslCtx.init(null, new TrustManager[]{new AcceptAllForTestX509TrustManager()}, null); + + HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); + + connection.setHostnameVerifier(new AcceptAllForTestHostnameVerifier()); + connection.setSSLSocketFactory(sslCtx.getSocketFactory()); + + connection.getResponseCode(); + + Certificate[] certificates = connection.getServerCertificates(); + + connection.disconnect(); + + return certificates; + } + + private HttpClientConfig getSslConfig() + { + return HttpClientConfig.builder() + .withSslContext( + HttpClientInit.sslContextWithTrustedKeyStore(tmpTrustStore.toString(), pp.getPassword()) + ) + .withWorkerCount(1) + .withReadTimeout(Duration.ZERO) + .build(); + } + + private String getResponseWithProperTrustStore() throws Exception + { + String text = "hello"; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(out)) { + gzipOutputStream.write(text.getBytes(Charset.defaultCharset())); + } + Request request = new Request(HttpMethod.GET, new URL("https://localhost:" + tlsPort + "/default/")); + request.setHeader("Content-Encoding", "gzip"); + request.setContent(MediaType.TEXT_PLAIN, out.toByteArray()); + + HttpClient client; + try { + client = HttpClientInit.createClient( + getSslConfig(), + lifecycle + ); + } + catch (Exception e) { + throw new RuntimeException(e); + } + + ListenableFuture go = client.go( + request, + new InputStreamResponseHandler() + ); + return IOUtils.toString(go.get(), StandardCharsets.UTF_8); + } } From ba2b25d8980e55afa5894719a9ebc937864e5fbe Mon Sep 17 00:00:00 2001 From: santosh Date: Mon, 22 Aug 2022 20:55:20 +0200 Subject: [PATCH 5/6] #12064 Add documentation --- docs/operations/tls-support.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/operations/tls-support.md b/docs/operations/tls-support.md index 169b68da8cff..d55c6cd3b0a5 100644 --- a/docs/operations/tls-support.md +++ b/docs/operations/tls-support.md @@ -50,6 +50,8 @@ values for the configs below, among others provided by Java implementation. |`druid.server.https.keyStoreType`|The type of the key store.|none|yes| |`druid.server.https.certAlias`|Alias of TLS/SSL certificate for the connector.|none|yes| |`druid.server.https.keyStorePassword`|The [Password Provider](../operations/password-provider.md) or String password for the Key Store.|none|yes| +|`druid.server.https.reloadSslContext`| Should druid server detect Key Store file change and reload.|false|no| +|`druid.server.https.reloadSslContextSeconds`| how frequently should druid server scan for Key Store file change.|60|yes| The following table contains configuration options related to client certificate authentication. From 877c149b9bd2021c9249ff9f96dc4edbbeb4438c Mon Sep 17 00:00:00 2001 From: Santosh Pingale Date: Wed, 24 Aug 2022 10:23:14 +0200 Subject: [PATCH 6/6] Apply suggestions from code review Co-authored-by: Frank Chen --- docs/operations/tls-support.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/operations/tls-support.md b/docs/operations/tls-support.md index d55c6cd3b0a5..2f912990b1fd 100644 --- a/docs/operations/tls-support.md +++ b/docs/operations/tls-support.md @@ -50,8 +50,8 @@ values for the configs below, among others provided by Java implementation. |`druid.server.https.keyStoreType`|The type of the key store.|none|yes| |`druid.server.https.certAlias`|Alias of TLS/SSL certificate for the connector.|none|yes| |`druid.server.https.keyStorePassword`|The [Password Provider](../operations/password-provider.md) or String password for the Key Store.|none|yes| -|`druid.server.https.reloadSslContext`| Should druid server detect Key Store file change and reload.|false|no| -|`druid.server.https.reloadSslContextSeconds`| how frequently should druid server scan for Key Store file change.|60|yes| +|`druid.server.https.reloadSslContext`| Should Druid server detect Key Store file change and reload.|false|no| +|`druid.server.https.reloadSslContextSeconds`| How frequently should Druid server scan for Key Store file change.|60|yes| The following table contains configuration options related to client certificate authentication.