From 3ae78ae4f6f73c92517849f110ca2b8668f4759d Mon Sep 17 00:00:00 2001
From: "p.zindyaev"
Date: Fri, 22 May 2026 14:57:38 +0300
Subject: [PATCH] feature(ADS-3343): Prometheus metrics HTTPS support
---
.../prometheus/PrometheusMetricsProvider.java | 48 +++++++++++++++++--
.../PrometheusMetricsProviderConfigTest.java | 27 +++++++++++
2 files changed, 72 insertions(+), 3 deletions(-)
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();