4646import java .io .IOException ;
4747import java .net .SocketAddress ;
4848import java .net .URI ;
49+ import java .nio .file .FileSystems ;
4950import java .nio .file .Files ;
5051import java .nio .file .InvalidPathException ;
5152import java .nio .file .Paths ;
53+ import java .nio .file .StandardWatchEventKinds ;
54+ import java .nio .file .WatchEvent ;
55+ import java .nio .file .WatchKey ;
56+ import java .nio .file .WatchService ;
5257import java .util .ArrayList ;
5358import java .util .Collection ;
5459import java .util .Collections ;
5560import java .util .List ;
5661import java .util .Optional ;
5762import java .util .concurrent .CountDownLatch ;
63+ import java .util .concurrent .ExecutorService ;
5864import java .util .concurrent .Executors ;
5965import java .util .concurrent .ScheduledExecutorService ;
6066import java .util .concurrent .ScheduledFuture ;
7480 name = "cassandra-management-api" ,
7581 description = "REST service for managing an Apache Cassandra or DSE node" )
7682public class Cli implements Runnable {
77- public static final String PROTOCOL_TLS_V1_2 = "TLSv1.2" ;
7883
7984 static {
8085 InternalLoggerFactory .setDefaultFactory (new Slf4JLoggerFactory ());
@@ -136,20 +141,26 @@ public class Cli implements Runnable {
136141 description = "Path to trust certs signed only by this CA" )
137142 private String tls_ca_cert_file ;
138143
144+ private File tlsCaCert ;
145+
139146 @ Path (writable = false )
140147 @ Option (
141148 name = {"--tlscert" },
142149 arity = 1 ,
143150 description = "Path to TLS certificate file" )
144151 private String tls_cert_file ;
145152
153+ private File tlsCert ;
154+
146155 @ Path (writable = false )
147156 @ Option (
148157 name = {"--tlskey" },
149158 arity = 1 ,
150159 description = "Path to TLS key file" )
151160 private String tls_key_file ;
152161
162+ private File tlsKey ;
163+
153164 private boolean useTls = false ;
154165 private File dbUnixSocketFile = null ;
155166 private File dbHomeDir = null ;
@@ -159,6 +170,8 @@ public class Cli implements Runnable {
159170 private final CountDownLatch shutdownLatch = new CountDownLatch (1 );
160171 private List <NettyJaxrsServer > servers = new ArrayList <>();
161172
173+ private SslContext sslContext ;
174+
162175 public Cli () {}
163176
164177 @ VisibleForTesting
@@ -387,6 +400,7 @@ void checkTLSDeps() {
387400 logger .error ("Specified CA Cert file does not exist: {}" , tls_ca_cert_file );
388401 System .exit (10 );
389402 }
403+ tlsCaCert = new File (tls_ca_cert_file );
390404 }
391405
392406 // CERT File Checks
@@ -406,6 +420,7 @@ void checkTLSDeps() {
406420 logger .error ("Specified Cert file does not exist: {}" , tls_cert_file );
407421 System .exit (13 );
408422 }
423+ tlsCert = new File (tls_cert_file );
409424 }
410425
411426 // KEY File Checks
@@ -424,6 +439,7 @@ void checkTLSDeps() {
424439 logger .error ("Specified Key file does not exist: {}" , tls_key_file );
425440 System .exit (16 );
426441 }
442+ tlsKey = new File (tls_key_file );
427443 }
428444
429445 useTls = hasAny ;
@@ -436,18 +452,86 @@ void preflightChecks() {
436452 checkUnixSocket ();
437453 }
438454
439- private NettyJaxrsServer startHTTPService (String hostname , int port ) throws SSLException {
455+ @ VisibleForTesting
456+ void createSSLContext () throws SSLException {
457+ this .sslContext =
458+ SslContextBuilder .forServer (tlsCert , tlsKey )
459+ .trustManager (tlsCaCert )
460+ .clientAuth (ClientAuth .REQUIRE )
461+ .ciphers (null , IdentityCipherSuiteFilter .INSTANCE )
462+ .build ();
463+ }
464+
465+ @ VisibleForTesting
466+ void createSSLWatcher () throws IOException {
467+ // Watch for tls_cert_file, tls_key_file and tls_ca_cert_file, add all their directories to
468+ // Filesystem Watcher
469+ WatchService watchService = FileSystems .getDefault ().newWatchService ();
470+ java .nio .file .Path tlsCertParent = tlsCert .toPath ().getParent ();
471+ java .nio .file .Path tlsKeyParent = tlsKey .toPath ().getParent ();
472+ java .nio .file .Path tlsCaCertParent = tlsCaCert .toPath ().getParent ();
473+
474+ tlsCertParent .register (
475+ watchService ,
476+ StandardWatchEventKinds .ENTRY_CREATE ,
477+ StandardWatchEventKinds .ENTRY_DELETE ,
478+ StandardWatchEventKinds .ENTRY_MODIFY );
479+ tlsKeyParent .register (
480+ watchService ,
481+ StandardWatchEventKinds .ENTRY_CREATE ,
482+ StandardWatchEventKinds .ENTRY_DELETE ,
483+ StandardWatchEventKinds .ENTRY_MODIFY );
484+ tlsCaCertParent .register (
485+ watchService ,
486+ StandardWatchEventKinds .ENTRY_CREATE ,
487+ StandardWatchEventKinds .ENTRY_DELETE ,
488+ StandardWatchEventKinds .ENTRY_MODIFY );
489+
490+ ExecutorService executorService = Executors .newSingleThreadExecutor ();
491+ executorService .execute (
492+ () -> {
493+ while (true ) {
494+ try {
495+ WatchKey key = watchService .take ();
496+ List <WatchEvent <?>> events = key .pollEvents ();
497+ boolean reloadNeeded = false ;
498+ for (WatchEvent <?> event : events ) {
499+ WatchEvent .Kind <?> kind = event .kind ();
500+
501+ WatchEvent <java .nio .file .Path > ev = (WatchEvent <java .nio .file .Path >) event ;
502+ java .nio .file .Path eventFilename = ev .context ();
503+
504+ if (tlsCertParent .resolve (eventFilename ).equals (tlsCert .toPath ())
505+ || tlsKeyParent .resolve (eventFilename ).equals (tlsKey .toPath ())
506+ || tlsCaCertParent .resolve (eventFilename ).equals (tlsCaCert )) {
507+ // Something in the TLS has been modified.. recreate SslContext
508+ reloadNeeded = true ;
509+ }
510+ }
511+ if (!key .reset ()) {
512+ // The watched directories have disappeared..
513+ break ;
514+ }
515+ if (reloadNeeded ) {
516+ logger .info ("Detected change in the SSL/TLS certificates, reloading." );
517+ createSSLContext ();
518+ }
519+ } catch (InterruptedException e ) {
520+ // Do something.. just log?
521+ logger .error ("Filesystem watcher received InterruptedException" , e );
522+ } catch (IOException e ) {
523+ logger .error ("Filesystem watcher received IOException" , e );
524+ }
525+ }
526+ });
527+ }
528+
529+ private NettyJaxrsServer startHTTPService (String hostname , int port ) throws IOException {
440530 NettyJaxrsServer server ;
441531
442532 if (useTls ) {
443- SslContext sslContext =
444- SslContextBuilder .forServer (new File (tls_cert_file ), new File (tls_key_file ))
445- .trustManager (new File (tls_ca_cert_file ))
446- .clientAuth (ClientAuth .REQUIRE )
447- .protocols (PROTOCOL_TLS_V1_2 )
448- .ciphers (null , IdentityCipherSuiteFilter .INSTANCE )
449- .build ();
450-
533+ createSSLContext ();
534+ createSSLWatcher ();
451535 server = new NettyJaxrsTLSServer (sslContext );
452536 } else {
453537 server = new NettyJaxrsServer ();
0 commit comments