Add support for SSL and bindable address to sidecar
Patch by Dinesh Joshi; reviewed by Vinay Chella and Chris Lohfink for CASSANDRA-15030
diff --git a/build.gradle b/build.gradle
index 5d4c771..a479649 100644
--- a/build.gradle
+++ b/build.gradle
@@ -49,7 +49,7 @@
     }
     test {
         resources {
-            srcDirs = [main.resources]
+            srcDirs = [main.resources, "src/test/resources"]
         }
     }
 }
@@ -129,6 +129,8 @@
 
 test {
     useJUnitPlatform()
+    systemProperty "javax.net.ssl.trustStore", "$projectDir/src/test/resources/certs/ca.p12"
+    systemProperty "javax.net.ssl.trustStorePassword", "password"
 }
 
 // copyDist gets called on every build
diff --git a/conf/sidecar.yaml b/conf/sidecar.yaml
index 8ffbcc6..f7e1ce3 100644
--- a/conf/sidecar.yaml
+++ b/conf/sidecar.yaml
@@ -7,7 +7,20 @@
   - port: 9042
 
 sidecar:
+  - host: 0.0.0.0
   - port: 9043
+#
+# Enable SSL configuration (Disabled by default)
+#
+#  - ssl:
+#      - enabled: true
+#      - keystore:
+#          - path: "path/to/keystore.p12"
+#          - password: password
+#      - truststore:
+#          - path: "path/to/truststore.p12"
+#          - password: password
+
 
 healthcheck:
-  - poll_freq_millis: 30000
\ No newline at end of file
+  - poll_freq_millis: 30000
diff --git a/src/main/java/org/apache/cassandra/sidecar/CassandraSidecarDaemon.java b/src/main/java/org/apache/cassandra/sidecar/CassandraSidecarDaemon.java
index 6ab682b..aaa39f3 100644
--- a/src/main/java/org/apache/cassandra/sidecar/CassandraSidecarDaemon.java
+++ b/src/main/java/org/apache/cassandra/sidecar/CassandraSidecarDaemon.java
@@ -20,13 +20,15 @@
 
 import com.google.inject.Guice;
 import com.google.inject.Inject;
+import com.google.inject.Singleton;
 import io.vertx.core.http.HttpServer;
 import org.apache.cassandra.sidecar.routes.HealthService;
+import org.apache.cassandra.sidecar.utils.SslUtils;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-
+@Singleton
 public class CassandraSidecarDaemon
 {
     private static final Logger logger = LoggerFactory.getLogger(CassandraSidecarDaemon.class);
@@ -45,9 +47,10 @@
     public void start()
     {
         banner();
+        validate();
         logger.info("Starting Cassandra Sidecar on port {}", config.getPort());
         healthService.start();
-        server.listen();
+        server.listen(config.getPort(), config.getHost());
     }
 
     public void stop()
@@ -69,6 +72,25 @@
                            "                                                                                      ");
     }
 
+    private void validate()
+    {
+        if (config.isSslEnabled())
+        {
+            try
+            {
+                SslUtils.validateSslOpts(config.getKeyStorePath(), config.getKeystorePassword());
+
+                if (config.getTrustStorePath() != null && config.getTruststorePassword() != null)
+                    SslUtils.validateSslOpts(config.getTrustStorePath(), config.getTruststorePassword());
+            } catch (Exception e)
+            {
+                throw new RuntimeException("Invalid keystore parameters for SSL", e);
+            }
+        }
+
+    }
+
+
     public static void main(String[] args)
     {
         CassandraSidecarDaemon app = Guice.createInjector(new MainModule())
diff --git a/src/main/java/org/apache/cassandra/sidecar/Configuration.java b/src/main/java/org/apache/cassandra/sidecar/Configuration.java
index 8086164..ed20ea0 100644
--- a/src/main/java/org/apache/cassandra/sidecar/Configuration.java
+++ b/src/main/java/org/apache/cassandra/sidecar/Configuration.java
@@ -32,9 +32,21 @@
     /* Sidecar's HTTP REST API port */
     private final Integer port;
 
+    /* Sidecar's listen address */
+    private String host;
+
     /* Healthcheck frequency in miilis */
     private final Integer healthCheckFrequencyMillis;
 
+    /* SSL related settings */
+    private final String keyStorePath;
+    private final String keyStorePassword;
+
+    private final String trustStorePath;
+    private final String trustStorePassword;
+
+    private final boolean isSslEnabled;
+
     /**
      * Constructor
      *
@@ -42,14 +54,24 @@
      * @param cassandraPort
      * @param port
      * @param healthCheckFrequencyMillis
+     * @param trustStorePath
+     * @param trustStorePassword
      */
-    public Configuration(String cassandraHost, Integer cassandraPort, Integer port,
-                         Integer healthCheckFrequencyMillis)
+    public Configuration(String cassandraHost, Integer cassandraPort, String host, Integer port,
+                         Integer healthCheckFrequencyMillis, String keyStorePath, String keyStorePassword,
+                         String trustStorePath, String trustStorePassword, boolean isSslEnabled)
     {
         this.cassandraHost = cassandraHost;
         this.cassandraPort = cassandraPort;
+        this.host = host;
         this.port = port;
         this.healthCheckFrequencyMillis = healthCheckFrequencyMillis;
+
+        this.keyStorePath = keyStorePath;
+        this.keyStorePassword = keyStorePassword;
+        this.trustStorePath = trustStorePath;
+        this.trustStorePassword = trustStorePassword;
+        this.isSslEnabled = isSslEnabled;
     }
 
     /**
@@ -73,6 +95,16 @@
     }
 
     /**
+     *  Sidecar's listen address
+     *
+     * @return
+     */
+    public String getHost()
+    {
+        return host;
+    }
+
+    /**
      * Get the Sidecar's REST HTTP API port
      *
      * @return
@@ -91,4 +123,137 @@
     {
         return healthCheckFrequencyMillis;
     }
+
+    /**
+     * Get the SSL status
+     *
+     * @return
+     */
+    public boolean isSslEnabled()
+    {
+        return isSslEnabled;
+    }
+
+    /**
+     * Get the Keystore Path
+     *
+     * @return
+     */
+    public String getKeyStorePath()
+    {
+        return keyStorePath;
+    }
+
+    /**
+     * Get the Keystore password
+     *
+     * @return
+     */
+    public String getKeystorePassword()
+    {
+        return keyStorePassword;
+    }
+
+    /**
+     * Get the Truststore Path
+     *
+     * @return
+     */
+    public String getTrustStorePath()
+    {
+        return trustStorePath;
+    }
+
+    /**
+     * Get the Truststore password
+     *
+     * @return
+     */
+    public String getTruststorePassword()
+    {
+        return trustStorePassword;
+    }
+
+    /**
+     * Configuration Builder
+     */
+    public static class Builder
+    {
+        private String cassandraHost;
+        private Integer cassandraPort;
+        private String host;
+        private Integer port;
+        private Integer healthCheckFrequencyMillis;
+        private String keyStorePath;
+        private String keyStorePassword;
+        private String trustStorePath;
+        private String trustStorePassword;
+        private boolean isSslEnabled;
+
+        public Builder setCassandraHost(String host)
+        {
+            this.cassandraHost = host;
+            return this;
+        }
+
+        public Builder setCassandraPort(Integer port)
+        {
+            this.cassandraPort = port;
+            return this;
+        }
+
+        public Builder setHost(String host)
+        {
+            this.host = host;
+            return this;
+        }
+
+        public Builder setPort(Integer port)
+        {
+            this.port = port;
+            return this;
+        }
+
+        public Builder setHealthCheckFrequency(Integer freqMillis)
+        {
+            this.healthCheckFrequencyMillis = freqMillis;
+            return this;
+        }
+
+        public Builder setKeyStorePath(String path)
+        {
+            this.keyStorePath = path;
+            return this;
+        }
+
+        public Builder setKeyStorePassword(String password)
+        {
+            this.keyStorePassword = password;
+            return this;
+        }
+
+        public Builder setTrustStorePath(String path)
+        {
+            this.trustStorePath = path;
+            return this;
+        }
+
+        public Builder setTrustStorePassword(String password)
+        {
+            this.trustStorePassword = password;
+            return this;
+        }
+
+        public Builder setSslEnabled(boolean enabled)
+        {
+            this.isSslEnabled = enabled;
+            return this;
+        }
+
+        public Configuration build()
+        {
+            return new Configuration(cassandraHost, cassandraPort, host, port, healthCheckFrequencyMillis,
+                                     keyStorePath, keyStorePassword, trustStorePath, trustStorePassword, isSslEnabled);
+        }
+    }
 }
diff --git a/src/main/java/org/apache/cassandra/sidecar/MainModule.java b/src/main/java/org/apache/cassandra/sidecar/MainModule.java
index 59113f2..b161bac 100644
--- a/src/main/java/org/apache/cassandra/sidecar/MainModule.java
+++ b/src/main/java/org/apache/cassandra/sidecar/MainModule.java
@@ -25,6 +25,7 @@
 import io.vertx.core.VertxOptions;
 import io.vertx.core.http.HttpServer;
 import io.vertx.core.http.HttpServerOptions;
+import io.vertx.core.net.JksOptions;
 import io.vertx.ext.dropwizard.DropwizardMetricsOptions;
 import io.vertx.ext.web.Router;
 import io.vertx.ext.web.handler.LoggerHandler;
@@ -39,12 +40,6 @@
 
 public class MainModule extends AbstractModule
 {
-    @Override
-    protected void configure()
-    {
-        bind(CassandraSidecarDaemon.class).in(Singleton.class);
-    }
-
     @Provides
     @Singleton
     public Vertx getVertx()
@@ -67,11 +62,26 @@
 
     @Provides
     @Singleton
-    public HttpServer vertxServer(Vertx vertx, Configuration config, Router router)
+    public HttpServer vertxServer(Vertx vertx, Router router, Configuration conf)
     {
-        HttpServer server = vertx.createHttpServer(new HttpServerOptions()
-                .setPort(config.getPort())
-                .setLogActivity(true));
+        HttpServerOptions options = new HttpServerOptions().setLogActivity(true);
+
+        if (conf.isSslEnabled())
+        {
+            options.setKeyStoreOptions(new JksOptions()
+                                       .setPath(conf.getKeyStorePath())
+                                       .setPassword(conf.getKeystorePassword()))
+                   .setSsl(conf.isSslEnabled());
+
+            if (conf.getTrustStorePath() != null && conf.getTruststorePassword() != null)
+            {
+                options.setTrustStoreOptions(new JksOptions()
+                                             .setPath(conf.getTrustStorePath())
+                                             .setPassword(conf.getTruststorePassword()));
+            }
+        }
+
+        HttpServer server = vertx.createHttpServer(options);
         server.requestHandler(router);
         return server;
     }
@@ -102,10 +112,17 @@
         File propFile = new File("sidecar.yaml");
         YAMLConfiguration yamlConf = confs.fileBased(YAMLConfiguration.class, propFile);
 
-        return new Configuration(
-                yamlConf.get(String.class, "cassandra.host"),
-                yamlConf.get(Integer.class, "cassandra.port"),
-                yamlConf.get(Integer.class, "sidecar.port"),
-                yamlConf.get(Integer.class, "healthcheck.poll_freq_millis"));
+        return new Configuration.Builder()
+                           .setCassandraHost(yamlConf.get(String.class, "cassandra.host"))
+                           .setCassandraPort(yamlConf.get(Integer.class, "cassandra.port"))
+                           .setHost(yamlConf.get(String.class, "sidecar.host"))
+                           .setPort(yamlConf.get(Integer.class, "sidecar.port"))
+                           .setHealthCheckFrequency(yamlConf.get(Integer.class, "healthcheck.poll_freq_millis"))
+                           .setKeyStorePath(yamlConf.get(String.class, "sidecar.ssl.keystore.path", null))
+                           .setKeyStorePassword(yamlConf.get(String.class, "sidecar.ssl.keystore.password", null))
+                           .setTrustStorePath(yamlConf.get(String.class, "sidecar.ssl.truststore.path", null))
+                           .setTrustStorePassword(yamlConf.get(String.class, "sidecar.ssl.truststore.password", null))
+                           .setSslEnabled(yamlConf.get(Boolean.class, "sidecar.ssl.enabled", false))
+                           .build();
     }
 }
diff --git a/src/main/java/org/apache/cassandra/sidecar/utils/SslUtils.java b/src/main/java/org/apache/cassandra/sidecar/utils/SslUtils.java
new file mode 100644
index 0000000..8855f45
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/utils/SslUtils.java
@@ -0,0 +1,59 @@
+/*
+ * 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.cassandra.sidecar.utils;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+
+/**
+ * Utility class for SSL related operations
+ */
+public class SslUtils
+{
+    /**
+     * Given the parameters, validate the keystore can be loaded and is usable
+     *
+     * @param keyStorePath
+     * @param keystorePassword
+     * @throws KeyStoreException
+     * @throws NoSuchAlgorithmException
+     * @throws IOException
+     * @throws CertificateException
+     */
+    public static void validateSslOpts(String keyStorePath, String keystorePassword) throws KeyStoreException,
+                                                                                            NoSuchAlgorithmException,
+                                                                                            IOException, CertificateException
+    {
+        final KeyStore ks;
+
+        if (keyStorePath.endsWith("p12"))
+            ks = KeyStore.getInstance("PKCS12");
+        else if (keyStorePath.endsWith("jks"))
+            ks = KeyStore.getInstance("JKS");
+        else
+            throw new IllegalArgumentException("Unrecognized keystore format extension: "
+                                               + keyStorePath.substring(keyStorePath.length() - 3));
+
+        ks.load(new FileInputStream(keyStorePath), keystorePassword.toCharArray());
+    }
+}
diff --git a/src/test/java/org/apache/cassandra/sidecar/AbstractHealthServiceTest.java b/src/test/java/org/apache/cassandra/sidecar/AbstractHealthServiceTest.java
new file mode 100644
index 0000000..90f077f
--- /dev/null
+++ b/src/test/java/org/apache/cassandra/sidecar/AbstractHealthServiceTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.cassandra.sidecar;
+
+import org.junit.Assert;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import io.vertx.core.Vertx;
+import io.vertx.core.http.HttpServer;
+import io.vertx.ext.web.client.WebClient;
+import io.vertx.ext.web.codec.BodyCodec;
+import io.vertx.junit5.VertxTestContext;
+import org.apache.cassandra.sidecar.mocks.MockHealthCheck;
+import org.apache.cassandra.sidecar.routes.HealthService;
+
+abstract public class AbstractHealthServiceTest
+{
+    private MockHealthCheck check;
+    private HealthService service;
+    private Vertx vertx;
+    private Configuration config;
+
+    public abstract AbstractModule getTestModule();
+    public abstract boolean isSslEnabled();
+
+    @BeforeEach
+    void setUp()
+    {
+        Injector injector = Guice.createInjector(getTestModule());
+        HttpServer server = injector.getInstance(HttpServer.class);
+
+        check = injector.getInstance(MockHealthCheck.class);
+        service = injector.getInstance(HealthService.class);
+        vertx = injector.getInstance(Vertx.class);
+        config = injector.getInstance(Configuration.class);
+
+        server.listen(config.getPort());
+    }
+
+    @AfterEach
+    void tearDown()
+    {
+        vertx.close();
+    }
+
+    @DisplayName("Should return HTTP 200 OK when check=True")
+    @Test
+    public void testHealthCheckReturns200OK(VertxTestContext testContext)
+    {
+        check.setStatus(true);
+        service.refreshNow();
+
+        WebClient client = WebClient.create(vertx);
+
+        client.get(config.getPort(), "localhost", "/api/v1/__health")
+              .as(BodyCodec.string())
+              .ssl(isSslEnabled())
+              .send(testContext.succeeding(response -> testContext.verify(() -> {
+                  Assert.assertEquals(200, response.statusCode());
+                  testContext.completeNow();
+              })));
+    }
+
+    @DisplayName("Should return HTTP 503 Failure when check=False")
+    @Test
+    public void testHealthCheckReturns503Failure(VertxTestContext testContext)
+    {
+        check.setStatus(false);
+        service.refreshNow();
+
+        WebClient client = WebClient.create(vertx);
+
+        client.get(config.getPort(), "localhost", "/api/v1/__health")
+              .as(BodyCodec.string())
+              .ssl(isSslEnabled())
+              .send(testContext.succeeding(response -> testContext.verify(() -> {
+                  Assert.assertEquals(503, response.statusCode());
+                  testContext.completeNow();
+              })));
+    }
+}
diff --git a/src/test/java/org/apache/cassandra/sidecar/HealthServiceSslTest.java b/src/test/java/org/apache/cassandra/sidecar/HealthServiceSslTest.java
new file mode 100644
index 0000000..d89d861
--- /dev/null
+++ b/src/test/java/org/apache/cassandra/sidecar/HealthServiceSslTest.java
@@ -0,0 +1,42 @@
+/*
+ * 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.cassandra.sidecar;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import com.google.inject.AbstractModule;
+import io.vertx.core.Vertx;
+import io.vertx.junit5.VertxExtension;
+
+@DisplayName("Health Service SSL Test")
+@ExtendWith(VertxExtension.class)
+public class HealthServiceSslTest extends AbstractHealthServiceTest
+{
+
+    public AbstractModule getTestModule()
+    {
+        return new TestSslModule(Vertx.vertx());
+    }
+
+    public boolean isSslEnabled()
+    {
+        return true;
+    }
+}
diff --git a/src/test/java/org/apache/cassandra/sidecar/HealthServiceTest.java b/src/test/java/org/apache/cassandra/sidecar/HealthServiceTest.java
index 9a39c44..67a6220 100644
--- a/src/test/java/org/apache/cassandra/sidecar/HealthServiceTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/HealthServiceTest.java
@@ -18,88 +18,25 @@
 
 package org.apache.cassandra.sidecar;
 
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import io.vertx.core.Vertx;
-import io.vertx.core.http.HttpServer;
-import io.vertx.ext.web.Router;
-import io.vertx.ext.web.client.WebClient;
-import io.vertx.ext.web.codec.BodyCodec;
-import io.vertx.junit5.VertxExtension;
-import io.vertx.junit5.VertxTestContext;
-import org.apache.cassandra.sidecar.mocks.MockHealthCheck;
-import org.apache.cassandra.sidecar.routes.HealthService;
-
-import org.junit.Assert;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 
+import com.google.inject.AbstractModule;
+import io.vertx.core.Vertx;
+import io.vertx.junit5.VertxExtension;
+
 @DisplayName("Health Service Test")
 @ExtendWith(VertxExtension.class)
-public class HealthServiceTest
+public class HealthServiceTest extends AbstractHealthServiceTest
 {
-    private MockHealthCheck check;
-    private HealthService service;
-    private Vertx vertx;
-    private Configuration config;
 
-    @BeforeEach
-    void setUp()
+    public AbstractModule getTestModule()
     {
-        Injector injector = Guice.createInjector(new TestModule(Vertx.vertx()));
-        HttpServer server = injector.getInstance(HttpServer.class);
-        Router router = injector.getInstance(Router.class);
-
-        check = injector.getInstance(MockHealthCheck.class);
-        service = injector.getInstance(HealthService.class);
-        vertx = injector.getInstance(Vertx.class);
-        config = injector.getInstance(Configuration.class);
-
-        server.listen(config.getPort());
+        return new TestModule(Vertx.vertx());
     }
 
-    @AfterEach
-    void tearDown()
+    public boolean isSslEnabled()
     {
-        vertx.close();
-    }
-
-    @DisplayName("Should return HTTP 200 OK when check=True")
-    @Test
-    public void testHealthCheckReturns200OK(VertxTestContext testContext)
-    {
-        check.setStatus(true);
-        service.refreshNow();
-
-        WebClient client = WebClient.create(vertx);
-
-        client.get(config.getPort(), "localhost", "/api/v1/__health")
-              .as(BodyCodec.string())
-              .send(testContext.succeeding(response -> testContext.verify(() -> {
-                  System.out.println(response.statusCode());
-                  Assert.assertEquals(200, response.statusCode());
-                  testContext.completeNow();
-              })));
-    }
-
-    @DisplayName("Should return HTTP 503 Failure when check=False")
-    @Test
-    public void testHealthCheckReturns503Failure(VertxTestContext testContext)
-    {
-        check.setStatus(false);
-        service.refreshNow();
-
-        WebClient client = WebClient.create(vertx);
-
-        client.get(config.getPort(), "localhost", "/api/v1/__health")
-              .as(BodyCodec.string())
-              .send(testContext.succeeding(response -> testContext.verify(() -> {
-                  System.out.println(response.statusCode());
-                  Assert.assertEquals(503, response.statusCode());
-                  testContext.completeNow();
-              })));
+        return false;
     }
 }
diff --git a/src/test/java/org/apache/cassandra/sidecar/TestModule.java b/src/test/java/org/apache/cassandra/sidecar/TestModule.java
index 01061ba..0bb17ad 100644
--- a/src/test/java/org/apache/cassandra/sidecar/TestModule.java
+++ b/src/test/java/org/apache/cassandra/sidecar/TestModule.java
@@ -24,6 +24,7 @@
 import io.vertx.core.Vertx;
 import io.vertx.core.http.HttpServer;
 import io.vertx.core.http.HttpServerOptions;
+import io.vertx.core.net.JksOptions;
 import io.vertx.ext.web.Router;
 import io.vertx.ext.web.handler.LoggerHandler;
 import org.apache.cassandra.sidecar.mocks.MockHealthCheck;
@@ -38,12 +39,6 @@
         this.vertx = vertx;
     }
 
-    @Override
-    protected void configure()
-    {
-        bind(CassandraSidecarDaemon.class).in(Singleton.class);
-    }
-
     @Provides
     @Singleton
     public Vertx getVertx()
@@ -67,11 +62,14 @@
 
     @Provides
     @Singleton
-    public HttpServer vertxServer(Vertx vertx, Configuration config, Router router)
+    public HttpServer vertxServer(Vertx vertx, Router router, Configuration conf)
     {
-        HttpServer server = vertx.createHttpServer(new HttpServerOptions()
-                                                   .setPort(config.getPort())
-                                                   .setLogActivity(true));
+        HttpServerOptions options = new HttpServerOptions().setLogActivity(true);
+        options.setKeyStoreOptions(new JksOptions()
+                                       .setPath(conf.getKeyStorePath())
+                                       .setPassword(conf.getKeystorePassword()))
+                   .setSsl(conf.isSslEnabled());
+        HttpServer server = vertx.createHttpServer(options);
         server.requestHandler(router);
         return server;
     }
@@ -90,10 +88,18 @@
     @Singleton
     public Configuration configuration()
     {
-        return new Configuration(
-        "INVALID_FOR_TEST",
-        0,
-        6475,
-        1000);
+        return abstractConfig();
+    }
+
+    protected Configuration abstractConfig()
+    {
+        return new Configuration.Builder()
+                           .setCassandraHost("INVALID_FOR_TEST")
+                           .setCassandraPort(0)
+                           .setHost("127.0.0.1")
+                           .setPort(6475)
+                           .setHealthCheckFrequency(1000)
+                           .setSslEnabled(false)
+                           .build();
     }
 }
diff --git a/src/test/java/org/apache/cassandra/sidecar/TestSslModule.java b/src/test/java/org/apache/cassandra/sidecar/TestSslModule.java
new file mode 100644
index 0000000..078327f
--- /dev/null
+++ b/src/test/java/org/apache/cassandra/sidecar/TestSslModule.java
@@ -0,0 +1,52 @@
+/*
+ * 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.cassandra.sidecar;
+
+import io.vertx.core.Vertx;
+
+public class TestSslModule extends TestModule
+{
+    public TestSslModule(Vertx vertx)
+    {
+        super(vertx);
+    }
+
+    @Override
+    public Configuration abstractConfig()
+    {
+        final String keyStorePath = TestSslModule.class.getClassLoader().getResource("certs/test.p12").getPath();
+        final String keyStorePassword = "password";
+
+        final String trustStorePath = TestSslModule.class.getClassLoader().getResource("certs/ca.p12").getPath();
+        final String trustStorePassword = "password";
+
+        return new Configuration.Builder()
+                           .setCassandraHost("INVALID_FOR_TEST")
+                           .setCassandraPort(0)
+                           .setHost("127.0.0.1")
+                           .setPort(6475)
+                           .setHealthCheckFrequency(1000)
+                           .setKeyStorePath(keyStorePath)
+                           .setKeyStorePassword(keyStorePassword)
+                           .setTrustStorePath(trustStorePath)
+                           .setTrustStorePassword(trustStorePassword)
+                           .setSslEnabled(true)
+                           .build();
+    }
+}
diff --git a/src/test/resources/certs/ca.p12 b/src/test/resources/certs/ca.p12
new file mode 100644
index 0000000..39a1899
--- /dev/null
+++ b/src/test/resources/certs/ca.p12
Binary files differ
diff --git a/src/test/resources/certs/test.p12 b/src/test/resources/certs/test.p12
new file mode 100644
index 0000000..3436057
--- /dev/null
+++ b/src/test/resources/certs/test.p12
Binary files differ