|
21 | 21 | import io.netty.channel.group.ChannelGroup; |
22 | 22 | import io.netty.handler.ssl.ClientAuth; |
23 | 23 | import io.netty.handler.ssl.SslContext; |
24 | | -import io.netty.handler.ssl.SslContextBuilder; |
25 | 24 | import io.netty.handler.ssl.SslProvider; |
26 | 25 | import io.netty.handler.timeout.IdleStateHandler; |
| 26 | +import nl.altindag.ssl.SSLFactory; |
| 27 | +import nl.altindag.ssl.exception.GenericSecurityException; |
| 28 | +import nl.altindag.ssl.netty.util.NettySslUtils; |
| 29 | +import nl.altindag.ssl.util.SSLFactoryUtils; |
| 30 | +import org.apache.tinkerpop.gremlin.server.util.SSLStoreFilesModificationWatcher; |
27 | 31 | import org.apache.tinkerpop.gremlin.util.MessageSerializer; |
28 | 32 | import org.apache.tinkerpop.gremlin.util.message.RequestMessage; |
29 | 33 | import org.apache.tinkerpop.gremlin.util.message.ResponseMessage; |
|
44 | 48 | import org.slf4j.Logger; |
45 | 49 | import org.slf4j.LoggerFactory; |
46 | 50 |
|
47 | | -import javax.net.ssl.KeyManagerFactory; |
48 | 51 | import javax.net.ssl.SSLException; |
49 | | -import javax.net.ssl.TrustManagerFactory; |
50 | | - |
51 | 52 | import java.io.FileInputStream; |
52 | 53 | import java.io.IOException; |
53 | 54 | import java.io.InputStream; |
54 | 55 | import java.lang.reflect.Constructor; |
55 | 56 | import java.security.KeyStore; |
56 | | -import java.security.KeyStoreException; |
57 | | -import java.security.NoSuchAlgorithmException; |
58 | | -import java.security.UnrecoverableKeyException; |
59 | | -import java.security.cert.CertificateException; |
60 | 57 | import java.util.Arrays; |
61 | 58 | import java.util.Collections; |
62 | 59 | import java.util.HashMap; |
|
65 | 62 | import java.util.Optional; |
66 | 63 | import java.util.concurrent.ExecutorService; |
67 | 64 | import java.util.concurrent.ScheduledExecutorService; |
| 65 | +import java.util.concurrent.TimeUnit; |
68 | 66 | import java.util.stream.Stream; |
69 | 67 |
|
70 | 68 | /** |
@@ -148,8 +146,34 @@ public void init(final ServerGremlinExecutor serverGremlinExecutor) { |
148 | 146 | configureSerializers(); |
149 | 147 |
|
150 | 148 | // configure ssl if present |
151 | | - sslContext = settings.optionalSsl().isPresent() && settings.ssl.enabled ? |
152 | | - Optional.ofNullable(createSSLContext(settings)) : Optional.empty(); |
| 149 | + if (settings.optionalSsl().isPresent() && settings.ssl.enabled) { |
| 150 | + if (settings.ssl.getSslContext().isPresent()) { |
| 151 | + logger.info("Using the SslContext override"); |
| 152 | + this.sslContext = settings.ssl.getSslContext(); |
| 153 | + } else { |
| 154 | + final SSLFactory sslFactory = createSSLFactoryBuilder(settings).withSwappableTrustMaterial().withSwappableIdentityMaterial().build(); |
| 155 | + this.sslContext = Optional.of(createSSLContext(sslFactory)); |
| 156 | + |
| 157 | + if (settings.ssl.refreshInterval > 0) { |
| 158 | + // At the scheduled refreshInterval, check whether the keyStore or trustStore has been modified. If they were, |
| 159 | + // reload the SSLFactory which will reload the underlying KeyManager/TrustManager that Netty SSLHandler uses. |
| 160 | + scheduledExecutorService.scheduleAtFixedRate( |
| 161 | + new SSLStoreFilesModificationWatcher(settings.ssl.keyStore, settings.ssl.trustStore, () -> { |
| 162 | + SSLFactory newSslFactory = createSSLFactoryBuilder(settings).build(); |
| 163 | + try { |
| 164 | + SSLFactoryUtils.reload(sslFactory, newSslFactory); |
| 165 | + } catch (RuntimeException e) { |
| 166 | + logger.error("Failed to reload SSLFactory", e); |
| 167 | + } |
| 168 | + }), |
| 169 | + settings.ssl.refreshInterval, settings.ssl.refreshInterval, TimeUnit.MILLISECONDS |
| 170 | + ); |
| 171 | + } |
| 172 | + } |
| 173 | + } else { |
| 174 | + this.sslContext = Optional.empty(); |
| 175 | + } |
| 176 | + |
153 | 177 | if (sslContext.isPresent()) logger.info("SSL enabled"); |
154 | 178 |
|
155 | 179 | authenticator = createAuthenticator(settings.authentication); |
@@ -307,74 +331,59 @@ private void configureSerializers() { |
307 | 331 | } |
308 | 332 | } |
309 | 333 |
|
310 | | - private SslContext createSSLContext(final Settings settings) { |
| 334 | + private SSLFactory.Builder createSSLFactoryBuilder(final Settings settings) { |
311 | 335 | final Settings.SslSettings sslSettings = settings.ssl; |
312 | 336 |
|
313 | | - if (sslSettings.getSslContext().isPresent()) { |
314 | | - logger.info("Using the SslContext override"); |
315 | | - return sslSettings.getSslContext().get(); |
316 | | - } |
317 | | - |
318 | | - final SslProvider provider = SslProvider.JDK; |
319 | | - |
320 | | - final SslContextBuilder builder; |
321 | | - |
322 | | - // Build JSSE SSLContext |
| 337 | + final SSLFactory.Builder builder = SSLFactory.builder(); |
323 | 338 | try { |
324 | | - final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); |
325 | | - |
326 | | - // Load private key and signed cert |
327 | 339 | if (null != sslSettings.keyStore) { |
328 | 340 | final String keyStoreType = null == sslSettings.keyStoreType ? KeyStore.getDefaultType() : sslSettings.keyStoreType; |
329 | | - final KeyStore keystore = KeyStore.getInstance(keyStoreType); |
330 | 341 | final char[] password = null == sslSettings.keyStorePassword ? null : sslSettings.keyStorePassword.toCharArray(); |
331 | 342 | try (final InputStream in = new FileInputStream(sslSettings.keyStore)) { |
332 | | - keystore.load(in, password); |
| 343 | + builder.withIdentityMaterial(in, password, keyStoreType); |
333 | 344 | } |
334 | | - kmf.init(keystore, password); |
335 | 345 | } else { |
336 | 346 | throw new IllegalStateException("keyStore must be configured when SSL is enabled."); |
337 | 347 | } |
338 | 348 |
|
339 | | - builder = SslContextBuilder.forServer(kmf); |
340 | | - |
341 | 349 | // Load custom truststore for client auth certs |
342 | 350 | if (null != sslSettings.trustStore) { |
343 | 351 | final String trustStoreType = null != sslSettings.trustStoreType ? sslSettings.trustStoreType |
344 | | - : sslSettings.keyStoreType != null ? sslSettings.keyStoreType : KeyStore.getDefaultType(); |
345 | | - |
346 | | - final KeyStore truststore = KeyStore.getInstance(trustStoreType); |
| 352 | + : sslSettings.keyStoreType != null ? sslSettings.keyStoreType : KeyStore.getDefaultType(); |
347 | 353 | final char[] password = null == sslSettings.trustStorePassword ? null : sslSettings.trustStorePassword.toCharArray(); |
348 | 354 | try (final InputStream in = new FileInputStream(sslSettings.trustStore)) { |
349 | | - truststore.load(in, password); |
| 355 | + builder.withTrustMaterial(in, password, trustStoreType); |
350 | 356 | } |
351 | | - final TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); |
352 | | - tmf.init(truststore); |
353 | | - builder.trustManager(tmf); |
354 | 357 | } |
355 | | - |
356 | | - } catch (UnrecoverableKeyException | NoSuchAlgorithmException | KeyStoreException | CertificateException | IOException e) { |
| 358 | + } catch (GenericSecurityException | IOException e) { |
357 | 359 | logger.error(e.getMessage()); |
358 | 360 | throw new RuntimeException("There was an error enabling SSL.", e); |
359 | 361 | } |
360 | 362 |
|
361 | 363 | if (null != sslSettings.sslCipherSuites && !sslSettings.sslCipherSuites.isEmpty()) { |
362 | | - builder.ciphers(sslSettings.sslCipherSuites); |
| 364 | + builder.withCiphers(sslSettings.sslCipherSuites.toArray(new String[] {})); |
363 | 365 | } |
364 | 366 |
|
365 | 367 | if (null != sslSettings.sslEnabledProtocols && !sslSettings.sslEnabledProtocols.isEmpty()) { |
366 | | - builder.protocols(sslSettings.sslEnabledProtocols.toArray(new String[] {})); |
| 368 | + builder.withProtocols(sslSettings.sslEnabledProtocols.toArray(new String[] {})); |
367 | 369 | } |
368 | | - |
| 370 | + |
369 | 371 | if (null != sslSettings.needClientAuth && ClientAuth.OPTIONAL == sslSettings.needClientAuth) { |
370 | 372 | logger.warn("needClientAuth = OPTIONAL is not a secure configuration. Setting to REQUIRE."); |
371 | 373 | sslSettings.needClientAuth = ClientAuth.REQUIRE; |
372 | 374 | } |
373 | 375 |
|
374 | | - builder.clientAuth(sslSettings.needClientAuth).sslProvider(provider); |
| 376 | + if (sslSettings.needClientAuth == ClientAuth.REQUIRE) { |
| 377 | + builder.withNeedClientAuthentication(true); |
| 378 | + } |
| 379 | + |
| 380 | + return builder; |
| 381 | + } |
375 | 382 |
|
| 383 | + private static SslContext createSSLContext(final SSLFactory sslFactory) { |
376 | 384 | try { |
377 | | - return builder.build(); |
| 385 | + final SslProvider provider = SslProvider.JDK; |
| 386 | + return NettySslUtils.forServer(sslFactory).sslProvider(provider).build(); |
378 | 387 | } catch (SSLException ssle) { |
379 | 388 | logger.error(ssle.getMessage()); |
380 | 389 | throw new RuntimeException("There was an error enabling SSL.", ssle); |
|
0 commit comments