diff --git a/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/main/java/org/apache/zookeeper/metrics/prometheus/PrometheusMetricsProvider.java b/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/main/java/org/apache/zookeeper/metrics/prometheus/PrometheusMetricsProvider.java index af4bff9d218..b79f4bfc540 100644 --- a/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/main/java/org/apache/zookeeper/metrics/prometheus/PrometheusMetricsProvider.java +++ b/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/main/java/org/apache/zookeeper/metrics/prometheus/PrometheusMetricsProvider.java @@ -23,7 +23,6 @@ import io.prometheus.client.exporter.MetricsServlet; import io.prometheus.client.hotspot.DefaultExports; import java.io.IOException; -import java.net.InetSocketAddress; import java.util.Enumeration; import java.util.Objects; import java.util.Optional; @@ -54,10 +53,16 @@ import org.apache.zookeeper.server.RateLogger; import org.eclipse.jetty.security.ConstraintMapping; import org.eclipse.jetty.security.ConstraintSecurityHandler; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.SecureRequestCustomizer; import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.util.security.Constraint; +import org.eclipse.jetty.util.ssl.SslContextFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -102,6 +107,9 @@ public class PrometheusMetricsProvider implements MetricsProvider { private final RateLogger rateLogger = new RateLogger(LOG, 60 * 1000); private String host = "0.0.0.0"; private int port = 7000; + private boolean sslEnabled = false; + private String keyStoreLocation; + private String keyStorePassword; private boolean exportJvmInfo = true; private Server server; private final MetricsServletImpl servlet = new MetricsServletImpl(); @@ -113,9 +121,11 @@ public class PrometheusMetricsProvider implements MetricsProvider { @Override public void configure(Properties configuration) throws MetricsProviderLifeCycleException { - LOG.info("Initializing metrics, configuration: {}", configuration); this.host = configuration.getProperty("httpHost", "0.0.0.0"); this.port = Integer.parseInt(configuration.getProperty("httpPort", "7000")); + this.sslEnabled = Boolean.parseBoolean(configuration.getProperty("sslEnabled", "false")); + this.keyStoreLocation = configuration.getProperty("keyStore.location"); + this.keyStorePassword = configuration.getProperty("keyStore.password"); this.exportJvmInfo = Boolean.parseBoolean(configuration.getProperty("exportJvmInfo", "true")); this.numWorkerThreads = Integer.parseInt( configuration.getProperty(NUM_WORKER_THREADS, "1")); @@ -123,6 +133,11 @@ public void configure(Properties configuration) throws MetricsProviderLifeCycleE configuration.getProperty(MAX_QUEUE_SIZE, "1000000")); this.workerShutdownTimeoutMs = Long.parseLong( configuration.getProperty(WORKER_SHUTDOWN_TIMEOUT_MS, "1000")); + LOG.info("Initializing metrics, configuration: httpHost: {}, httpPort: {}, sslEnabled: {}, exportJvmInfo: {}", + this.host, + this.port, + this.sslEnabled, + this.exportJvmInfo); } @Override @@ -134,7 +149,34 @@ public void start() throws MetricsProviderLifeCycleException { if (exportJvmInfo) { DefaultExports.initialize(); } - server = new Server(new InetSocketAddress(host, port)); + server = new Server(); + ServerConnector connector; + if (sslEnabled) { + LOG.info("SSL enabled for /metrics endpoint"); + if (keyStoreLocation == null || keyStoreLocation.isEmpty()) { + throw new MetricsProviderLifeCycleException("keyStore.location is not configured"); + } + if (keyStorePassword == null || keyStorePassword.isEmpty()) { + throw new MetricsProviderLifeCycleException("keyStore.password is not configured"); + } + SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); + sslContextFactory.setKeyStorePath(keyStoreLocation); + sslContextFactory.setKeyStorePassword(keyStorePassword); + + HttpConfiguration httpsConfig = new HttpConfiguration(); + httpsConfig.setSecureScheme("https"); + httpsConfig.setSecurePort(port); + httpsConfig.addCustomizer(new SecureRequestCustomizer()); + + connector = new ServerConnector(server, + new SslConnectionFactory(sslContextFactory, "http/1.1"), + new HttpConnectionFactory(httpsConfig)); + } else { + connector = new ServerConnector(server, new HttpConnectionFactory(new HttpConfiguration())); + } + connector.setHost(host); + connector.setPort(port); + server.setConnectors(new ServerConnector[]{connector}); ServletContextHandler context = new ServletContextHandler(); context.setContextPath("/"); constrainTraceMethod(context); diff --git a/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/java/org/apache/zookeeper/metrics/prometheus/PrometheusMetricsProviderConfigTest.java b/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/java/org/apache/zookeeper/metrics/prometheus/PrometheusMetricsProviderConfigTest.java index cd275f173db..da2bbd8bb93 100644 --- a/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/java/org/apache/zookeeper/metrics/prometheus/PrometheusMetricsProviderConfigTest.java +++ b/zookeeper-metrics-providers/zookeeper-prometheus-metrics/src/test/java/org/apache/zookeeper/metrics/prometheus/PrometheusMetricsProviderConfigTest.java @@ -52,6 +52,33 @@ public void testInvalidAddr() { }); } + @Test + public void testInvalidKeystoreLocation() { + assertThrows(MetricsProviderLifeCycleException.class, () -> { + CollectorRegistry.defaultRegistry.clear(); + PrometheusMetricsProvider provider = new PrometheusMetricsProvider(); + Properties configuration = new Properties(); + configuration.setProperty("sslEnabled", "true"); + configuration.setProperty("keyStore.location", ""); + provider.configure(configuration); + provider.start(); + }); + } + + @Test + public void testInvalidKeystorePassword() { + assertThrows(MetricsProviderLifeCycleException.class, () -> { + CollectorRegistry.defaultRegistry.clear(); + PrometheusMetricsProvider provider = new PrometheusMetricsProvider(); + Properties configuration = new Properties(); + configuration.setProperty("sslEnabled", "true"); + configuration.setProperty("keyStore.location", "/tmp/key.jks"); + configuration.setProperty("keyStore.password", ""); + provider.configure(configuration); + provider.start(); + }); + } + @Test public void testValidConfig() throws MetricsProviderLifeCycleException { CollectorRegistry.defaultRegistry.clear();