2020
2121import io .netty .channel .group .ChannelGroup ;
2222import io .netty .handler .ssl .ClientAuth ;
23- import io .netty .handler .ssl .SslContext ;
24- import io .netty .handler .ssl .SslContextBuilder ;
25- import io .netty .handler .ssl .SslProvider ;
2623import io .netty .handler .timeout .IdleStateHandler ;
24+ import nl .altindag .ssl .SSLFactory ;
25+ import nl .altindag .ssl .exception .GenericSecurityException ;
26+ import nl .altindag .ssl .netty .util .NettySslUtils ;
27+ import nl .altindag .ssl .util .SSLFactoryUtils ;
28+ import org .apache .tinkerpop .gremlin .server .util .SSLStoreFilesModificationWatcher ;
2729import org .apache .tinkerpop .gremlin .util .MessageSerializer ;
2830import org .apache .tinkerpop .gremlin .util .message .RequestMessage ;
2931import org .apache .tinkerpop .gremlin .util .message .ResponseMessage ;
4446import org .slf4j .Logger ;
4547import org .slf4j .LoggerFactory ;
4648
47- import javax .net .ssl .KeyManagerFactory ;
48- import javax .net .ssl .SSLException ;
49- import javax .net .ssl .TrustManagerFactory ;
50-
5149import java .io .FileInputStream ;
5250import java .io .IOException ;
5351import java .io .InputStream ;
5452import java .lang .reflect .Constructor ;
5553import java .security .KeyStore ;
56- import java .security .KeyStoreException ;
57- import java .security .NoSuchAlgorithmException ;
58- import java .security .UnrecoverableKeyException ;
59- import java .security .cert .CertificateException ;
54+ import java .sql .Time ;
6055import java .util .Arrays ;
6156import java .util .Collections ;
6257import java .util .HashMap ;
6560import java .util .Optional ;
6661import java .util .concurrent .ExecutorService ;
6762import java .util .concurrent .ScheduledExecutorService ;
63+ import java .util .concurrent .TimeUnit ;
6864import java .util .stream .Stream ;
6965
7066/**
@@ -90,7 +86,7 @@ public abstract class AbstractChannelizer extends ChannelInitializer<SocketChann
9086
9187 protected Settings settings ;
9288 protected GremlinExecutor gremlinExecutor ;
93- protected Optional <SslContext > sslContext ;
89+ protected Optional <SSLFactory > sslFactory ;
9490 protected GraphManager graphManager ;
9591 protected ExecutorService gremlinExecutorService ;
9692 protected ScheduledExecutorService scheduledExecutorService ;
@@ -148,9 +144,25 @@ public void init(final ServerGremlinExecutor serverGremlinExecutor) {
148144 configureSerializers ();
149145
150146 // configure ssl if present
151- sslContext = settings .optionalSsl ().isPresent () && settings .ssl .enabled ?
152- Optional .ofNullable (createSSLContext (settings )) : Optional .empty ();
153- if (sslContext .isPresent ()) logger .info ("SSL enabled" );
147+ sslFactory = settings .optionalSsl ().isPresent () && settings .ssl .enabled ?
148+ Optional .ofNullable (createSSLFactoryBuilder (settings ).withSwappableTrustMaterial ().withSwappableIdentityMaterial ().build ()) : Optional .empty ();
149+
150+ if (sslFactory .isPresent ()) {
151+ logger .info ("SSL enabled" );
152+ // Every minute, check if keyStore/trustStore were modified, and if they were,
153+ // reload the SSLFactory which will reload the underlying KeyManager/TrustManager that Netty SSLHandler uses.
154+ scheduledExecutorService .schedule (
155+ new SSLStoreFilesModificationWatcher (settings .ssl .keyStore , settings .ssl .trustStore , () -> {
156+ SSLFactory newSslFactory = createSSLFactoryBuilder (settings ).build ();
157+ try {
158+ SSLFactoryUtils .reload (sslFactory .get (), newSslFactory );
159+ } catch (RuntimeException e ) {
160+ logger .error ("Failed to reload SSLFactory" , e );
161+ }
162+ }),
163+ 1 , TimeUnit .MINUTES
164+ );
165+ }
154166
155167 authenticator = createAuthenticator (settings .authentication );
156168 authorizer = createAuthorizer (settings .authorization );
@@ -168,7 +180,9 @@ public void init(final ServerGremlinExecutor serverGremlinExecutor) {
168180 public void initChannel (final SocketChannel ch ) throws Exception {
169181 final ChannelPipeline pipeline = ch .pipeline ();
170182
171- sslContext .ifPresent (sslContext -> pipeline .addLast (PIPELINE_SSL , sslContext .newHandler (ch .alloc ())));
183+ if (sslFactory .isPresent ()) {
184+ pipeline .addLast (PIPELINE_SSL , NettySslUtils .forServer (sslFactory .get ()).build ().newHandler (ch .alloc ()));
185+ }
172186
173187 // checks for no activity on a channel and triggers an event that is consumed by the OpSelectorHandler
174188 // and either closes the connection or sends a ping to see if the client is still alive
@@ -307,77 +321,54 @@ private void configureSerializers() {
307321 }
308322 }
309323
310- private SslContext createSSLContext (final Settings settings ) {
324+ private SSLFactory . Builder createSSLFactoryBuilder (final Settings settings ) {
311325 final Settings .SslSettings sslSettings = settings .ssl ;
312326
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
327+ final SSLFactory .Builder builder = SSLFactory .builder ();
323328 try {
324- final KeyManagerFactory kmf = KeyManagerFactory .getInstance (KeyManagerFactory .getDefaultAlgorithm ());
325-
326- // Load private key and signed cert
327329 if (null != sslSettings .keyStore ) {
328330 final String keyStoreType = null == sslSettings .keyStoreType ? KeyStore .getDefaultType () : sslSettings .keyStoreType ;
329- final KeyStore keystore = KeyStore .getInstance (keyStoreType );
330- final char [] password = null == sslSettings .keyStorePassword ? null : sslSettings .keyStorePassword .toCharArray ();
331+ final char [] keyStorePassword = null == sslSettings .keyStorePassword ? null : sslSettings .keyStorePassword .toCharArray ();
331332 try (final InputStream in = new FileInputStream (sslSettings .keyStore )) {
332- keystore . load (in , password );
333+ builder . withIdentityMaterial (in , keyStorePassword , keyStoreType );
333334 }
334- kmf .init (keystore , password );
335335 } else {
336336 throw new IllegalStateException ("keyStore must be configured when SSL is enabled." );
337337 }
338338
339- builder = SslContextBuilder .forServer (kmf );
340-
341339 // Load custom truststore for client auth certs
342340 if (null != sslSettings .trustStore ) {
343341 final String trustStoreType = null != sslSettings .trustStoreType ? sslSettings .trustStoreType
344- : sslSettings .keyStoreType != null ? sslSettings .keyStoreType : KeyStore .getDefaultType ();
345-
346- final KeyStore truststore = KeyStore .getInstance (trustStoreType );
347- final char [] password = null == sslSettings .trustStorePassword ? null : sslSettings .trustStorePassword .toCharArray ();
342+ : sslSettings .keyStoreType != null ? sslSettings .keyStoreType : KeyStore .getDefaultType ();
343+ final char [] trustStorePassword = null == sslSettings .trustStorePassword ? null : sslSettings .trustStorePassword .toCharArray ();
348344 try (final InputStream in = new FileInputStream (sslSettings .trustStore )) {
349- truststore . load (in , password );
345+ builder . withTrustMaterial (in , trustStorePassword , trustStoreType );
350346 }
351- final TrustManagerFactory tmf = TrustManagerFactory .getInstance (TrustManagerFactory .getDefaultAlgorithm ());
352- tmf .init (truststore );
353- builder .trustManager (tmf );
354347 }
355-
356- } catch (UnrecoverableKeyException | NoSuchAlgorithmException | KeyStoreException | CertificateException | IOException e ) {
348+ } catch (GenericSecurityException | IOException e ) {
357349 logger .error (e .getMessage ());
358350 throw new RuntimeException ("There was an error enabling SSL." , e );
359351 }
360352
361353 if (null != sslSettings .sslCipherSuites && !sslSettings .sslCipherSuites .isEmpty ()) {
362- builder .ciphers (sslSettings .sslCipherSuites );
354+ builder .withCiphers (sslSettings .sslCipherSuites . toArray ( new String [] {}) );
363355 }
364356
365357 if (null != sslSettings .sslEnabledProtocols && !sslSettings .sslEnabledProtocols .isEmpty ()) {
366- builder .protocols (sslSettings .sslEnabledProtocols .toArray (new String [] {}));
358+ builder .withProtocols (sslSettings .sslEnabledProtocols .toArray (new String [] {}));
367359 }
368-
360+
369361 if (null != sslSettings .needClientAuth && ClientAuth .OPTIONAL == sslSettings .needClientAuth ) {
370362 logger .warn ("needClientAuth = OPTIONAL is not a secure configuration. Setting to REQUIRE." );
371363 sslSettings .needClientAuth = ClientAuth .REQUIRE ;
372364 }
373365
374- builder .clientAuth (sslSettings .needClientAuth ).sslProvider (provider );
375-
376- try {
377- return builder .build ();
378- } catch (SSLException ssle ) {
379- logger .error (ssle .getMessage ());
380- throw new RuntimeException ("There was an error enabling SSL." , ssle );
366+ if (sslSettings .needClientAuth == ClientAuth .REQUIRE ) {
367+ builder .withNeedClientAuthentication (true );
381368 }
369+
370+ // The SSL provider will default to SslProvider.OPEN_SSL if available or SslProvider.JDK if not (see SslContext#defaultProvider)
371+
372+ return builder ;
382373 }
383374}
0 commit comments