This is an automated email from the ASF dual-hosted git repository.

andor pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/zookeeper.git


The following commit(s) were added to refs/heads/master by this push:
     new 9d23039612 ZOOKEEPER-5024: Allow to set TLS version and ciphers for 
PrometheusMetricsProvider
9d23039612 is described below

commit 9d23039612ebb60ef49506898c3ed6e63bb55fdc
Author: Dávid Paksy <[email protected]>
AuthorDate: Tue Apr 7 22:23:54 2026 +0200

    ZOOKEEPER-5024: Allow to set TLS version and ciphers for 
PrometheusMetricsProvider
    
    Reviewers: meszibalu, anmolnar
    Author: PDavid
    Closes #2360 from PDavid/ZOOKEEPER-5024-prometheus-https-ciphers-protocols
---
 .../src/main/resources/markdown/zookeeperAdmin.md  |  59 ++++-
 .../main/resources/markdown/zookeeperMonitor.md    |  18 ++
 .../prometheus/PrometheusMetricsProvider.java      |  31 ++-
 .../PrometheusMetricsProviderConfigTest.java       | 285 +++++++++++++++++----
 4 files changed, 337 insertions(+), 56 deletions(-)

diff --git a/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md 
b/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md
index 786aa17c07..c87ce053e7 100644
--- a/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md
+++ b/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md
@@ -2286,8 +2286,17 @@ 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* :
+   **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.
+    * 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.
@@ -2308,7 +2317,51 @@ 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.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
+
+* *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.
+
 <a name="Communication+using+the+Netty+framework"></a>
 
 ### Communication using the Netty framework
diff --git a/zookeeper-docs/src/main/resources/markdown/zookeeperMonitor.md 
b/zookeeper-docs/src/main/resources/markdown/zookeeperMonitor.md
index 180f1273c1..705b5eb049 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.
 
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 1fbb34169c..8ae6178e0f 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.debug("Setting enabled protocols: '{}'", enabledProtocols);
+            String[] enabledProtocolsArray = enabledProtocols.split(",");
+            sslContextFactory.setIncludeProtocols(enabledProtocolsArray);
+        }
+
+        if (cipherSuites != null) {
+            LOG.debug("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 c151854b05..bb24ab75d2 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) {
+                    }
+                }
+        };
     }
 }

Reply via email to