diff --git a/hertzbeat-collector/hertzbeat-collector-basic/src/main/java/org/apache/hertzbeat/collector/collect/http/SslCertificateCollectImpl.java b/hertzbeat-collector/hertzbeat-collector-basic/src/main/java/org/apache/hertzbeat/collector/collect/http/SslCertificateCollectImpl.java index 131cd52bbb..8bd9aa8d46 100644 --- a/hertzbeat-collector/hertzbeat-collector-basic/src/main/java/org/apache/hertzbeat/collector/collect/http/SslCertificateCollectImpl.java +++ b/hertzbeat-collector/hertzbeat-collector-basic/src/main/java/org/apache/hertzbeat/collector/collect/http/SslCertificateCollectImpl.java @@ -92,6 +92,7 @@ public class SslCertificateCollectImpl extends AbstractCollect { if (!verifySsl){ SSLContext ignoreSslContext = createIgnoreVerifySslContext(); urlConnection.setSSLSocketFactory(ignoreSslContext.getSocketFactory()); + urlConnection.setHostnameVerifier((hostname, session) -> true); } urlConnection.connect(); diff --git a/hertzbeat-collector/hertzbeat-collector-basic/src/test/java/org/apache/hertzbeat/collector/collect/http/SslCertificateCollectImplTest.java b/hertzbeat-collector/hertzbeat-collector-basic/src/test/java/org/apache/hertzbeat/collector/collect/http/SslCertificateCollectImplTest.java index 263a7dbe55..780ec5023c 100644 --- a/hertzbeat-collector/hertzbeat-collector-basic/src/test/java/org/apache/hertzbeat/collector/collect/http/SslCertificateCollectImplTest.java +++ b/hertzbeat-collector/hertzbeat-collector-basic/src/test/java/org/apache/hertzbeat/collector/collect/http/SslCertificateCollectImplTest.java @@ -17,23 +17,101 @@ package org.apache.hertzbeat.collector.collect.http; -import org.junit.jupiter.api.BeforeEach; +import com.sun.net.httpserver.HttpsConfigurator; +import com.sun.net.httpserver.HttpsServer; +import java.io.FileInputStream; +import java.net.InetSocketAddress; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.KeyStore; +import java.util.List; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import org.apache.hertzbeat.common.entity.job.Metrics; +import org.apache.hertzbeat.common.entity.job.protocol.HttpProtocol; +import org.apache.hertzbeat.common.entity.message.CollectRep; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; /** - * Test case for {@link SslCertificateCollectImpl} + * Test case for {@link SslCertificateCollectImpl}: real TLS handshake against a local + * HTTPS server using a self-signed cert whose CN/SAN does not match the target address. */ class SslCertificateCollectImplTest { - @BeforeEach - void setUp() { + private static HttpsServer server; + private static Path keystore; + + @BeforeAll + static void startServer() throws Exception { + keystore = genSelfSignedKeystore(); + KeyStore ks = KeyStore.getInstance("PKCS12"); + try (FileInputStream in = new FileInputStream(keystore.toFile())) { + ks.load(in, "changeit".toCharArray()); + } + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(ks, "changeit".toCharArray()); + SSLContext ctx = SSLContext.getInstance("TLS"); + ctx.init(kmf.getKeyManagers(), null, null); + server = HttpsServer.create(new InetSocketAddress("127.0.0.1", 0), 0); + server.setHttpsConfigurator(new HttpsConfigurator(ctx)); + server.createContext("/", exchange -> { + exchange.sendResponseHeaders(200, -1); + exchange.close(); + }); + server.start(); + } + + @AfterAll + static void stopServer() throws Exception { + if (server != null) { + server.stop(0); + } + if (keystore != null) { + Files.deleteIfExists(keystore); + } + } + + private static Path genSelfSignedKeystore() throws Exception { + Path ks = Files.createTempFile("ssl-collect-test", ".p12"); + Files.delete(ks); + String keytool = Path.of(System.getProperty("java.home"), "bin", "keytool").toString(); + int exit = new ProcessBuilder(keytool, "-genkeypair", "-alias", "test", + "-keyalg", "RSA", "-keysize", "2048", "-storetype", "PKCS12", + "-keystore", ks.toString(), "-storepass", "changeit", + "-dname", "CN=test", "-ext", "SAN=dns:not-this-host", "-validity", "1") + .inheritIO().start().waitFor(); + Assertions.assertEquals(0, exit, "keytool failed to generate test keystore"); + return ks; + } + + private CollectRep.MetricsData.Builder collect(boolean verify) { + HttpProtocol http = new HttpProtocol(); + http.setHost("127.0.0.1"); + http.setPort(String.valueOf(server.getAddress().getPort())); + http.setSsl(String.valueOf(verify)); + Metrics metrics = Metrics.builder() + .http(http) + .aliasFields(List.of("subject", "expired", "end_timestamp")) + .build(); + CollectRep.MetricsData.Builder builder = CollectRep.MetricsData.newBuilder(); + new SslCertificateCollectImpl().collect(builder, metrics); + return builder; } @Test - void getInstance() { + void verifyOnFailsForUntrustedCert() { + CollectRep.MetricsData.Builder builder = collect(true); + Assertions.assertEquals(CollectRep.Code.UN_CONNECTABLE, builder.getCode(), builder.getMsg()); + Assertions.assertEquals(0, builder.getValuesCount()); } @Test - void collect() { + void verifyOffCollectsUntrustedMismatchedCert() { + CollectRep.MetricsData.Builder builder = collect(false); + Assertions.assertTrue(builder.getValuesCount() > 0, "expected cert rows, got: " + builder.getMsg()); + Assertions.assertEquals("CN=test", builder.getValues(0).getColumns(0)); } -} \ No newline at end of file +}