From d5167c77ace07d2d2bdb31ee8f097b57a50ba024 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20Paksy?= Date: Wed, 11 Mar 2026 15:17:00 +0100 Subject: [PATCH 1/8] ZOOKEEPER-5024: Allow to set TLS version and ciphers for PrometheusMetricsProvider Since PrometheusMetricsProvider only receives those configs which are prefixed with `metricsProvider`, introduced two new properties: - metricsProvider.ssl.enabledProtocols - metricsProvider.ssl.ciphersuites --- .../prometheus/PrometheusMetricsProvider.java | 31 +- .../PrometheusMetricsProviderConfigTest.java | 285 +++++++++++++++--- 2 files changed, 263 insertions(+), 53 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 1fbb34169c7..cda6256806a 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 @@ -37,7 +37,6 @@ import org.apache.zookeeper.metrics.Gauge; import org.apache.zookeeper.metrics.GaugeSet; import org.apache.zookeeper.metrics.MetricsContext; -import org.apache.zookeeper.metrics.MetricsContext.DetailLevel; import org.apache.zookeeper.metrics.MetricsProvider; import org.apache.zookeeper.metrics.MetricsProviderLifeCycleException; import org.apache.zookeeper.metrics.Summary; @@ -83,8 +82,14 @@ public class PrometheusMetricsProvider implements MetricsProvider { private String trustStoreType; private boolean needClientAuth = true; // Secure default private boolean wantClientAuth = true; // Secure default + private String enabledProtocols; + private String cipherSuites; // Constants for configuration + public static final String HTTP_HOST = "httpHost"; + public static final String HTTP_PORT = "httpPort"; + public static final String EXPORT_JVM_INFO = "exportJvmInfo"; + public static final String HTTPS_PORT = "httpsPort"; public static final String NUM_WORKER_THREADS = "numWorkerThreads"; public static final String SSL_KEYSTORE_LOCATION = "ssl.keyStore.location"; public static final String SSL_KEYSTORE_PASSWORD = "ssl.keyStore.password"; @@ -94,6 +99,8 @@ public class PrometheusMetricsProvider implements MetricsProvider { public static final String SSL_TRUSTSTORE_TYPE = "ssl.trustStore.type"; public static final String SSL_NEED_CLIENT_AUTH = "ssl.need.client.auth"; public static final String SSL_WANT_CLIENT_AUTH = "ssl.want.client.auth"; + public static final String SSL_ENABLED_PROTOCOLS = "ssl.enabledProtocols"; + public static final String SSL_ENABLED_CIPHERS = "ssl.ciphersuites"; public static final int SCAN_INTERVAL = 60 * 10; // 10 minutes /** @@ -114,10 +121,10 @@ protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws public void configure(Properties configuration) throws MetricsProviderLifeCycleException { LOG.info("Initializing Prometheus metrics with Jetty, configuration: {}", configuration); - this.host = configuration.getProperty("httpHost", "0.0.0.0"); - this.httpPort = Integer.parseInt(configuration.getProperty("httpPort", "-1")); - this.httpsPort = Integer.parseInt(configuration.getProperty("httpsPort", "-1")); - this.exportJvmInfo = Boolean.parseBoolean(configuration.getProperty("exportJvmInfo", "true")); + this.host = configuration.getProperty(HTTP_HOST, "0.0.0.0"); + this.httpPort = Integer.parseInt(configuration.getProperty(HTTP_PORT, "-1")); + this.httpsPort = Integer.parseInt(configuration.getProperty(HTTPS_PORT, "-1")); + this.exportJvmInfo = Boolean.parseBoolean(configuration.getProperty(EXPORT_JVM_INFO, "true")); this.numWorkerThreads = Integer.parseInt(configuration.getProperty(NUM_WORKER_THREADS, "10")); // If httpsPort is specified, parse all SSL properties @@ -130,6 +137,8 @@ public void configure(Properties configuration) throws MetricsProviderLifeCycleE this.trustStoreType = configuration.getProperty(SSL_TRUSTSTORE_TYPE, "PKCS12"); this.needClientAuth = Boolean.parseBoolean(configuration.getProperty(SSL_NEED_CLIENT_AUTH, "true")); this.wantClientAuth = Boolean.parseBoolean(configuration.getProperty(SSL_WANT_CLIENT_AUTH, "true")); + this.enabledProtocols = configuration.getProperty(SSL_ENABLED_PROTOCOLS); + this.cipherSuites = configuration.getProperty(SSL_ENABLED_CIPHERS); } // Validate that at least one port is configured. @@ -232,6 +241,18 @@ private SslContextFactory.Server createSslContextFactory() { sslContextFactory.setNeedClientAuth(this.needClientAuth); sslContextFactory.setWantClientAuth(this.wantClientAuth); + if (enabledProtocols != null) { + LOG.info("Setting enabled protocols: '{}'", enabledProtocols); + String[] enabledProtocolsArray = enabledProtocols.split(","); + sslContextFactory.setIncludeProtocols(enabledProtocolsArray); + } + + if (cipherSuites != null) { + LOG.info("Setting enabled cipherSuites: '{}'", cipherSuites); + String[] cipherSuitesArray = cipherSuites.split(","); + sslContextFactory.setIncludeCipherSuites(cipherSuitesArray); + } + return sslContextFactory; } 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 c151854b05a..bb24ab75d29 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 @@ -18,89 +18,278 @@ package org.apache.zookeeper.metrics.prometheus; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; import java.util.Properties; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; import org.apache.zookeeper.metrics.MetricsProviderLifeCycleException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; public class PrometheusMetricsProviderConfigTest extends PrometheusMetricsTestBase { + private static final String KEYSTORE_TYPE_JKS = "JKS"; + private static final String PASSWORD = "testpass"; + + private String keyStorePath; + private String trustStorePath; + private PrometheusMetricsProvider provider; + + @BeforeEach + public void setup() { + String testDataPath = System.getProperty("test.data.dir", "src/test/resources/data"); + keyStorePath = testDataPath + "/ssl/server_keystore.jks"; + trustStorePath = testDataPath + "/ssl/server_truststore.jks"; + } + + @AfterEach + public void tearDown() { + if (provider != null) { + provider.stop(); + } + } + @Test - public void testInvalidPort() { - assertThrows(MetricsProviderLifeCycleException.class, () -> { - PrometheusMetricsProvider provider = new PrometheusMetricsProvider(); - Properties configuration = new Properties(); - configuration.setProperty("httpPort", "65536"); - configuration.setProperty("exportJvmInfo", "false"); - provider.configure(configuration); - provider.start(); - }); + public void testInvalidPort() throws MetricsProviderLifeCycleException { + provider = new PrometheusMetricsProvider(); + Properties configuration = new Properties(); + configuration.setProperty(PrometheusMetricsProvider.HTTP_PORT, "65536"); + configuration.setProperty(PrometheusMetricsProvider.EXPORT_JVM_INFO, "false"); + provider.configure(configuration); + + MetricsProviderLifeCycleException exception = + assertThrows(MetricsProviderLifeCycleException.class, provider::start); + + assertEquals("Failed to start Prometheus Jetty server", exception.getMessage()); + assertNotNull(exception.getCause()); + assertEquals("port out of range:65536", exception.getCause().getMessage()); } @Test - public void testInvalidAddr() { - assertThrows(MetricsProviderLifeCycleException.class, () -> { - PrometheusMetricsProvider provider = new PrometheusMetricsProvider(); - Properties configuration = new Properties(); - configuration.setProperty("httpHost", "master"); - provider.configure(configuration); - provider.start(); - }); + public void testNoPortSet() { + provider = new PrometheusMetricsProvider(); + Properties configuration = new Properties(); + // Do not set HTTP port and HTTPS port here. + + MetricsProviderLifeCycleException exception = + assertThrows(MetricsProviderLifeCycleException.class, () -> provider.configure(configuration)); + + assertEquals("Either httpPort or httpsPort must be configured for Prometheus exporter.", + exception.getMessage()); + } + + @Test + public void testInvalidAddr() throws MetricsProviderLifeCycleException { + provider = new PrometheusMetricsProvider(); + Properties configuration = new Properties(); + configuration.setProperty(PrometheusMetricsProvider.HTTP_HOST, "master"); + configuration.setProperty(PrometheusMetricsProvider.HTTP_PORT, "0"); + provider.configure(configuration); + + MetricsProviderLifeCycleException exception = + assertThrows(MetricsProviderLifeCycleException.class, provider::start); + + assertEquals("Failed to start Prometheus Jetty server", exception.getMessage()); + assertNotNull(exception.getCause()); + assertEquals("Failed to bind to master:0", exception.getCause().getMessage()); } @Test public void testValidConfig() throws MetricsProviderLifeCycleException { - PrometheusMetricsProvider provider = new PrometheusMetricsProvider(); + provider = new PrometheusMetricsProvider(); Properties configuration = new Properties(); - configuration.setProperty("httpHost", "0.0.0.0"); - configuration.setProperty("httpPort", "0"); + configuration.setProperty(PrometheusMetricsProvider.HTTP_HOST, "0.0.0.0"); + configuration.setProperty(PrometheusMetricsProvider.HTTP_PORT, "0"); provider.configure(configuration); provider.start(); } @Test public void testValidSslConfig() throws MetricsProviderLifeCycleException { - PrometheusMetricsProvider provider = new PrometheusMetricsProvider(); + provider = new PrometheusMetricsProvider(); Properties configuration = new Properties(); - String testDataPath = System.getProperty("test.data.dir", "src/test/resources/data"); - configuration.setProperty("httpHost", "127.0.0.1"); - configuration.setProperty("httpsPort", "0"); - configuration.setProperty("ssl.keyStore.location", testDataPath + "/ssl/server_keystore.jks"); - configuration.setProperty("ssl.keyStore.password", "testpass"); - configuration.setProperty("ssl.trustStore.location", testDataPath + "/ssl/server_truststore.jks"); - configuration.setProperty("ssl.trustStore.password", "testpass"); + + configuration.setProperty(PrometheusMetricsProvider.HTTP_HOST, "127.0.0.1"); + configuration.setProperty(PrometheusMetricsProvider.HTTPS_PORT, "0"); + configuration.setProperty(PrometheusMetricsProvider.SSL_KEYSTORE_LOCATION, keyStorePath); + configuration.setProperty(PrometheusMetricsProvider.SSL_KEYSTORE_PASSWORD, PASSWORD); + configuration.setProperty(PrometheusMetricsProvider.SSL_TRUSTSTORE_LOCATION, trustStorePath); + configuration.setProperty(PrometheusMetricsProvider.SSL_TRUSTSTORE_PASSWORD, PASSWORD); provider.configure(configuration); provider.start(); } @Test public void testValidHttpsAndHttpConfig() throws MetricsProviderLifeCycleException { - PrometheusMetricsProvider provider = new PrometheusMetricsProvider(); + provider = new PrometheusMetricsProvider(); Properties configuration = new Properties(); - String testDataPath = System.getProperty("test.data.dir", "src/test/resources/data"); - configuration.setProperty("httpPort", "0"); - configuration.setProperty("httpsPort", "0"); - configuration.setProperty("ssl.keyStore.location", testDataPath + "/ssl/server_keystore.jks"); - configuration.setProperty("ssl.keyStore.password", "testpass"); - configuration.setProperty("ssl.trustStore.location", testDataPath + "/ssl/server_truststore.jks"); - configuration.setProperty("ssl.trustStore.password", "testpass"); + configuration.setProperty(PrometheusMetricsProvider.HTTP_PORT, "0"); + configuration.setProperty(PrometheusMetricsProvider.HTTPS_PORT, "0"); + configuration.setProperty(PrometheusMetricsProvider.SSL_KEYSTORE_LOCATION, keyStorePath); + configuration.setProperty(PrometheusMetricsProvider.SSL_KEYSTORE_PASSWORD, PASSWORD); + configuration.setProperty(PrometheusMetricsProvider.SSL_TRUSTSTORE_LOCATION, trustStorePath); + configuration.setProperty(PrometheusMetricsProvider.SSL_TRUSTSTORE_PASSWORD, PASSWORD); provider.configure(configuration); provider.start(); } @Test public void testInvalidSslConfig() throws MetricsProviderLifeCycleException { - assertThrows(MetricsProviderLifeCycleException.class, () -> { - PrometheusMetricsProvider provider = new PrometheusMetricsProvider(); - Properties configuration = new Properties(); - String testDataPath = System.getProperty("test.data.dir", "src/test/resources/data"); - configuration.setProperty("httpsPort", "50514"); - // keystore missing - configuration.setProperty("ssl.keyStore.password", "testpass"); - configuration.setProperty("ssl.trustStore.location", testDataPath + "/ssl/server_truststore.jks"); - configuration.setProperty("ssl.trustStore.password", "testpass"); - provider.configure(configuration); - provider.start(); - }); + provider = new PrometheusMetricsProvider(); + Properties configuration = new Properties(); + configuration.setProperty(PrometheusMetricsProvider.HTTPS_PORT, "50514"); + // keystore missing + configuration.setProperty(PrometheusMetricsProvider.SSL_KEYSTORE_PASSWORD, PASSWORD); + configuration.setProperty(PrometheusMetricsProvider.SSL_TRUSTSTORE_LOCATION, trustStorePath); + configuration.setProperty(PrometheusMetricsProvider.SSL_TRUSTSTORE_PASSWORD, PASSWORD); + provider.configure(configuration); + + MetricsProviderLifeCycleException exception = + assertThrows(MetricsProviderLifeCycleException.class, provider::start); + + assertEquals("Failed to start Prometheus Jetty server", exception.getMessage()); + assertNotNull(exception.getCause()); + assertEquals( + "SSL/TLS is enabled, but 'ssl.keyStore.location' is not set.", + exception.getCause().getMessage()); + } + + @Test + public void testHandshakeWithSupportedProtocol() throws Exception { + provider = new PrometheusMetricsProvider(); + Properties configuration = new Properties(); + configuration.setProperty(PrometheusMetricsProvider.HTTP_HOST, "127.0.0.1"); + configuration.setProperty(PrometheusMetricsProvider.HTTPS_PORT, "7000"); + configuration.setProperty(PrometheusMetricsProvider.SSL_KEYSTORE_LOCATION, keyStorePath); + configuration.setProperty(PrometheusMetricsProvider.SSL_KEYSTORE_PASSWORD, PASSWORD); + configuration.setProperty(PrometheusMetricsProvider.SSL_TRUSTSTORE_LOCATION, trustStorePath); + configuration.setProperty(PrometheusMetricsProvider.SSL_TRUSTSTORE_PASSWORD, PASSWORD); + configuration.setProperty(PrometheusMetricsProvider.SSL_ENABLED_PROTOCOLS, "TLSv1.3"); + provider.configure(configuration); + provider.start(); + + // Use a raw SSLSocket to verify the handshake + SSLContext sslContext = createSSLContext(keyStorePath, PASSWORD.toCharArray(), "TLSv1.3"); + SSLSocketFactory factory = sslContext.getSocketFactory(); + + try (SSLSocket socket = (SSLSocket) factory.createSocket("localhost", 7000)) { + socket.startHandshake(); + String negotiatedProtocol = socket.getSession().getProtocol(); + + // Verify that we actually landed on the protocol we expected + assertEquals("TLSv1.3", negotiatedProtocol, + "The negotiated protocol should be TLSv1.3."); + } + } + + @Test + public void testHandshakeWithUnsupportedProtocolFails() throws Exception { + provider = new PrometheusMetricsProvider(); + Properties configuration = new Properties(); + configuration.setProperty(PrometheusMetricsProvider.HTTP_HOST, "127.0.0.1"); + configuration.setProperty(PrometheusMetricsProvider.HTTPS_PORT, "7000"); + configuration.setProperty(PrometheusMetricsProvider.SSL_KEYSTORE_LOCATION, keyStorePath); + configuration.setProperty(PrometheusMetricsProvider.SSL_KEYSTORE_PASSWORD, PASSWORD); + configuration.setProperty(PrometheusMetricsProvider.SSL_TRUSTSTORE_LOCATION, trustStorePath); + configuration.setProperty(PrometheusMetricsProvider.SSL_TRUSTSTORE_PASSWORD, PASSWORD); + configuration.setProperty(PrometheusMetricsProvider.SSL_ENABLED_PROTOCOLS, "TLSv1.3"); + provider.configure(configuration); + provider.start(); + + SSLContext sslContext = createSSLContext(keyStorePath, PASSWORD.toCharArray(), "TLSv1.1"); + SSLSocketFactory factory = sslContext.getSocketFactory(); + + try (SSLSocket socket = (SSLSocket) factory.createSocket("localhost", 7000)) { + SSLHandshakeException exception = assertThrows(SSLHandshakeException.class, socket::startHandshake); + assertEquals( + "No appropriate protocol (protocol is disabled or cipher suites are inappropriate)", + exception.getMessage(), + "The handshake should have failed due to a protocol mismatch."); + } + } + + @Test + public void testCipherMismatchFails() throws Exception { + provider = new PrometheusMetricsProvider(); + Properties configuration = new Properties(); + configuration.setProperty(PrometheusMetricsProvider.HTTP_HOST, "127.0.0.1"); + configuration.setProperty(PrometheusMetricsProvider.HTTPS_PORT, "7000"); + configuration.setProperty(PrometheusMetricsProvider.SSL_KEYSTORE_LOCATION, keyStorePath); + configuration.setProperty(PrometheusMetricsProvider.SSL_KEYSTORE_PASSWORD, PASSWORD); + configuration.setProperty(PrometheusMetricsProvider.SSL_TRUSTSTORE_LOCATION, trustStorePath); + configuration.setProperty(PrometheusMetricsProvider.SSL_TRUSTSTORE_PASSWORD, PASSWORD); + System.setProperty(PrometheusMetricsProvider.SSL_ENABLED_CIPHERS, + "TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384"); + provider.configure(configuration); + provider.start(); + + SSLContext sslContext = createSSLContext(keyStorePath, PASSWORD.toCharArray(), "TLSv1.2"); + SSLSocketFactory factory = sslContext.getSocketFactory(); + + try (SSLSocket socket = (SSLSocket) factory.createSocket("localhost", 7000)) { + // Force the client to use a cipher NOT enabled for the AdminServer + String[] unsupportedCiphers = new String[]{"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"}; + socket.setEnabledCipherSuites(unsupportedCiphers); + + assertThrows(SSLHandshakeException.class, socket::startHandshake, + "The handshake should have failed due to a cipher mismatch."); + } + } + + private SSLContext createSSLContext(String keystorePath, char[] password, String protocol) + throws Exception { + KeyManager[] keyManagers = getKeyManagers(keystorePath, password); + TrustManager[] trustAllCerts = getTrustAllCerts(); + + SSLContext sslContext = SSLContext.getInstance(protocol); + sslContext.init(keyManagers, trustAllCerts, null); + + return sslContext; + } + + private static KeyManager[] getKeyManagers(String keystorePath, char[] password) throws KeyStoreException, + IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException { + KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE_JKS); + try (FileInputStream fis = new FileInputStream(keystorePath)) { + keyStore.load(fis, password); + } + + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(keyStore, password); + return kmf.getKeyManagers(); + } + + public TrustManager[] getTrustAllCerts() { + // This is OK for testing. + return new TrustManager[]{ + new X509TrustManager() { + public X509Certificate[] getAcceptedIssuers() { + return null; + } + + public void checkClientTrusted(X509Certificate[] certs, String authType) { + } + + public void checkServerTrusted(X509Certificate[] certs, String authType) { + } + } + }; } } From 6b0ca9825757fbcf1903648bf127d01c812ec794 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20Paksy?= Date: Wed, 11 Mar 2026 15:44:31 +0100 Subject: [PATCH 2/8] ZOOKEEPER-5024: Documented new metricsProvider.ssl.ciphersuites and metricsProvider.ssl.enabledProtocols properties --- .../src/main/resources/markdown/zookeeperAdmin.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md b/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md index c1e1684aea7..bedc2b5d260 100644 --- a/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md +++ b/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md @@ -2296,7 +2296,17 @@ options are used to configure the [AdminServer](#sc_adminserver). **New in 3.7.1:** The timeout in ms for Prometheus worker threads shutdown. Default value is 1000ms. - + +* *metricsProvider.ssl.ciphersuites* : + **New in 3.10.0:** + The enabled cipher suites to be used in TLS negotiation for PrometheusMetricsProvider. + Default value is Jetty default. + +* *metricsProvider.ssl.enabledProtocols* : + **New in 3.10.0:** + The enabled protocols to be used in TLS negotiation for PrometheusMetricsProvider. + Default value is Jetty default. + ### Communication using the Netty framework From e0c1d9e4b9d4210bc927dbfdfb5b249d15b9f763 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20Paksy?= Date: Wed, 11 Mar 2026 16:46:04 +0100 Subject: [PATCH 3/8] ZOOKEEPER-5024: Documented missing metricsProvider.httpsPort and ssl properties --- .../main/resources/markdown/zookeeperAdmin.md | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md b/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md index bedc2b5d260..b0f32d67a0b 100644 --- a/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md +++ b/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md @@ -2274,8 +2274,16 @@ options are used to configure the [AdminServer](#sc_adminserver). **New in 3.8.0:** Prometheus.io exporter will start a Jetty server and listen this address, default is "0.0.0.0" * *metricsProvider.httpPort* : - Prometheus.io exporter will start a Jetty server and bind to this port, it defaults to 7000. - Prometheus end point will be http://hostname:httPort/metrics. + Prometheus.io exporter will start a Jetty server and bind to this port. + Prometheus end point will be `http://hostname:httpPort/metrics`. + If omitted no HTTP port will be opened. + * Note: Either HTTP or HTTPS port has to be specified or both. + +* *metricsProvider.httpsPort* : + Prometheus.io exporter will start a Jetty server and bind to this port. + Prometheus end point will be `https://hostname:httpsPort/metrics`. + If omitted no HTTPS port will be opened. + * Note: Either HTTP or HTTPS port has to be specified or both. * *metricsProvider.exportJvmInfo* : If this property is set to **true** Prometheus.io will export useful metrics about the JVM. @@ -2297,6 +2305,24 @@ options are used to configure the [AdminServer](#sc_adminserver). The timeout in ms for Prometheus worker threads shutdown. Default value is 1000ms. +* *metricsProvider.ssl.keyStore.location* and *metricsProvider.ssl.keyStore.password*: + Specifies the file path to a Java keystore containing the local + credentials to be used for PrometheusMetricsProvider TLS connections and the + password to unlock the file. + +* *metricsProvider.ssl.keyStore.type*: + Specifies the file format of the PrometheusMetricsProvider keystore. Values: JKS, PEM, PKCS12 or null (detect by filename). + Default: null. + +* *metricsProvider.ssl.trustStore.location* and *metricsProvider.ssl.trustStore.password*: + Specifies the file path to a Java truststore containing the remote + credentials to be used for PrometheusMetricsProvider TLS connections and the + password to unlock the file. + +* *metricsProvider.ssl.trustStore.type*: + Specifies the file format of the PrometheusMetricsProvider truststore. Values: JKS, PEM, PKCS12 or null (detect by filename). + Default: null. + * *metricsProvider.ssl.ciphersuites* : **New in 3.10.0:** The enabled cipher suites to be used in TLS negotiation for PrometheusMetricsProvider. From 146b527de363c116c80d8b5d0cfc10d1def2986e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20Paksy?= Date: Wed, 11 Mar 2026 16:52:34 +0100 Subject: [PATCH 4/8] ZOOKEEPER-5024: Documented missing metricsProvider client auth properties --- .../src/main/resources/markdown/zookeeperAdmin.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md b/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md index b0f32d67a0b..9e5bbf591ea 100644 --- a/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md +++ b/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md @@ -2323,6 +2323,16 @@ options are used to configure the [AdminServer](#sc_adminserver). Specifies the file format of the PrometheusMetricsProvider truststore. Values: JKS, PEM, PKCS12 or null (detect by filename). Default: null. +* *metricsProvider.ssl.need.client.auth*: + Specifies options to authenticate SSL connections from clients. + When set to true, PrometheusMetricsProvider will "require" client authentication. + Default: true + +* *metricsProvider.ssl.want.client.auth*: + Specifies options to authenticate SSL connections from clients. + When set to true, PrometheusMetricsProvider will "request" client authentication. + Default: true + * *metricsProvider.ssl.ciphersuites* : **New in 3.10.0:** The enabled cipher suites to be used in TLS negotiation for PrometheusMetricsProvider. From 9a7303c10a6461abad64aa362336dec35bc70907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20Paksy?= Date: Wed, 11 Mar 2026 16:55:54 +0100 Subject: [PATCH 5/8] ZOOKEEPER-5024: Fix markdown indentation --- zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md b/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md index 9e5bbf591ea..eaf36e57af7 100644 --- a/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md +++ b/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md @@ -2277,7 +2277,7 @@ options are used to configure the [AdminServer](#sc_adminserver). Prometheus.io exporter will start a Jetty server and bind to this port. Prometheus end point will be `http://hostname:httpPort/metrics`. If omitted no HTTP port will be opened. - * Note: Either HTTP or HTTPS port has to be specified or both. + * Note: Either HTTP or HTTPS port has to be specified or both. * *metricsProvider.httpsPort* : Prometheus.io exporter will start a Jetty server and bind to this port. From c8ff1a51192d0c0ecdfef27d6d3ecd2f7ff0b636 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20Paksy?= Date: Thu, 12 Mar 2026 09:06:42 +0100 Subject: [PATCH 6/8] ZOOKEEPER-5024: Changed logging to DEBUG The logging of setting the enabled protocols and ciphers in PrometheusMetricsProvider is now changed to DEBUG. --- .../metrics/prometheus/PrometheusMetricsProvider.java | 4 ++-- 1 file changed, 2 insertions(+), 2 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 cda6256806a..8ae6178e0f0 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 @@ -242,13 +242,13 @@ private SslContextFactory.Server createSslContextFactory() { sslContextFactory.setWantClientAuth(this.wantClientAuth); if (enabledProtocols != null) { - LOG.info("Setting enabled protocols: '{}'", enabledProtocols); + LOG.debug("Setting enabled protocols: '{}'", enabledProtocols); String[] enabledProtocolsArray = enabledProtocols.split(","); sslContextFactory.setIncludeProtocols(enabledProtocolsArray); } if (cipherSuites != null) { - LOG.info("Setting enabled cipherSuites: '{}'", cipherSuites); + LOG.debug("Setting enabled cipherSuites: '{}'", cipherSuites); String[] cipherSuitesArray = cipherSuites.split(","); sslContextFactory.setIncludeCipherSuites(cipherSuitesArray); } From 8c26464681d56184045978b8b247cad115e5775d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20Paksy?= Date: Wed, 25 Mar 2026 13:17:42 +0100 Subject: [PATCH 7/8] ZOOKEEPER-5024: Added ZK version number to secure metricsProvider configs to document since when the config is used by ZK. --- .../src/main/resources/markdown/zookeeperAdmin.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md b/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md index eaf36e57af7..40d0d3f2e85 100644 --- a/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md +++ b/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md @@ -2280,6 +2280,7 @@ options are used to configure the [AdminServer](#sc_adminserver). * Note: Either HTTP or HTTPS port has to be specified or both. * *metricsProvider.httpsPort* : + **New in 3.10.0:** Prometheus.io exporter will start a Jetty server and bind to this port. Prometheus end point will be `https://hostname:httpsPort/metrics`. If omitted no HTTPS port will be opened. @@ -2306,29 +2307,35 @@ options are used to configure the [AdminServer](#sc_adminserver). Default value is 1000ms. * *metricsProvider.ssl.keyStore.location* and *metricsProvider.ssl.keyStore.password*: + **New in 3.10.0:** Specifies the file path to a Java keystore containing the local credentials to be used for PrometheusMetricsProvider TLS connections and the password to unlock the file. * *metricsProvider.ssl.keyStore.type*: + **New in 3.10.0:** Specifies the file format of the PrometheusMetricsProvider keystore. Values: JKS, PEM, PKCS12 or null (detect by filename). Default: null. * *metricsProvider.ssl.trustStore.location* and *metricsProvider.ssl.trustStore.password*: + **New in 3.10.0:** Specifies the file path to a Java truststore containing the remote credentials to be used for PrometheusMetricsProvider TLS connections and the password to unlock the file. * *metricsProvider.ssl.trustStore.type*: + **New in 3.10.0:** Specifies the file format of the PrometheusMetricsProvider truststore. Values: JKS, PEM, PKCS12 or null (detect by filename). Default: null. * *metricsProvider.ssl.need.client.auth*: + **New in 3.10.0:** Specifies options to authenticate SSL connections from clients. When set to true, PrometheusMetricsProvider will "require" client authentication. Default: true * *metricsProvider.ssl.want.client.auth*: + **New in 3.10.0:** Specifies options to authenticate SSL connections from clients. When set to true, PrometheusMetricsProvider will "request" client authentication. Default: true From 7e679bfe2e8e00521e8295c6475f0f3059b1b4c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20Paksy?= Date: Wed, 25 Mar 2026 13:21:07 +0100 Subject: [PATCH 8/8] ZOOKEEPER-5024: Added new metricsProvider.ssl.ciphersuites and metricsProvider.ssl.enabledProtocols properties to guide --- .../resources/markdown/zookeeperMonitor.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/zookeeper-docs/src/main/resources/markdown/zookeeperMonitor.md b/zookeeper-docs/src/main/resources/markdown/zookeeperMonitor.md index 180f1273c19..705b5eb049a 100644 --- a/zookeeper-docs/src/main/resources/markdown/zookeeperMonitor.md +++ b/zookeeper-docs/src/main/resources/markdown/zookeeperMonitor.md @@ -81,6 +81,24 @@ ZooKeeper also supports SSL for Prometheus metrics, which provides secure data t metricsProvider.httpPort=7000 metricsProvider.httpsPort=4443 ``` + +#### Configure TLS protocols and cipher suites for SSL/TLS negotiation in Prometheus Metrics: + +It is also possible to restrict TLS versions and cipher suites for PrometheusMetricsProvider. +Add the following configuration settings to the `zoo.cfg` config file: + +``` +metricsProvider.ssl.enabledProtocols=TLSv1.2,TLSv1.3 +metricsProvider.ssl.ciphersuites=TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 +``` + +To verify raise the log level of PrometheusMetricsProvider to DEBUG and check that the following entries can be seen in the logs: + +``` +2026-03-11 15:46:36,997 [myid:] - INFO [main:o.a.z.m.p.PrometheusMetricsProvider@245] - Setting enabled protocols: 'TLSv1.2,TLSv1.3' +2026-03-11 15:46:36,997 [myid:] - INFO [main:o.a.z.m.p.PrometheusMetricsProvider@251] - Setting enabled cipherSuites: 'TLS_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384' +``` + ### Prometheus - Running a [Prometheus](https://prometheus.io/) monitoring service is the easiest way to ingest and record ZooKeeper's metrics.