From 7821a8f7654721b5d3401991fde51eb3b8d08e7d Mon Sep 17 00:00:00 2001 From: Sivarajan Narayanan Date: Mon, 22 Jun 2026 11:36:59 +0530 Subject: [PATCH 1/2] [fix](show frontends) show effective HTTP/HTTPS port when enable_https is true --- .../java/org/apache/doris/common/proc/FrontendsProcNode.java | 3 ++- .../main/java/org/apache/doris/common/util/HttpURLUtil.java | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/proc/FrontendsProcNode.java b/fe/fe-core/src/main/java/org/apache/doris/common/proc/FrontendsProcNode.java index 4b7adbf9638766..58fc49ae4c3460 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/common/proc/FrontendsProcNode.java +++ b/fe/fe-core/src/main/java/org/apache/doris/common/proc/FrontendsProcNode.java @@ -21,6 +21,7 @@ import org.apache.doris.common.Config; import org.apache.doris.common.Pair; import org.apache.doris.common.io.DiskUtils; +import org.apache.doris.common.util.HttpURLUtil; import org.apache.doris.common.util.TimeUtils; import org.apache.doris.qe.ConnectContext; import org.apache.doris.service.FeDiskInfo; @@ -123,7 +124,7 @@ public static void getFrontendsInfo(Env env, List> infos, String cu info.add(fe.getNodeName()); info.add(fe.getHost()); info.add(Integer.toString(fe.getEditLogPort())); - info.add(Integer.toString(Config.http_port)); + info.add(Integer.toString(HttpURLUtil.getHttpPort())); if (fe.getNodeName().equals(env.getNodeName())) { info.add(Integer.toString(Config.query_port)); diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/util/HttpURLUtil.java b/fe/fe-core/src/main/java/org/apache/doris/common/util/HttpURLUtil.java index 70b3e68450aa45..baafc104bf9f59 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/common/util/HttpURLUtil.java +++ b/fe/fe-core/src/main/java/org/apache/doris/common/util/HttpURLUtil.java @@ -19,6 +19,7 @@ import org.apache.doris.catalog.Env; import org.apache.doris.cloud.security.SecurityChecker; +import org.apache.doris.common.Config; import org.apache.doris.system.SystemInfoService.HostInfo; import com.google.common.collect.Maps; @@ -30,6 +31,10 @@ public class HttpURLUtil { + public static int getHttpPort() { + return Config.enable_https ? Config.https_port : Config.http_port; + } + public static HttpURLConnection getConnectionWithNodeIdent(String request) throws IOException { try { SecurityChecker.getInstance().startSSRFChecking(request); From 6f83514b275f58bd6adc7273703244cbf9f2a72a Mon Sep 17 00:00:00 2001 From: Sivarajan Narayanan Date: Mon, 22 Jun 2026 16:09:18 +0530 Subject: [PATCH 2/2] AuditStreamLoader to work on https enabled mode --- .../doris/common/util/InternalHttpsUtils.java | 83 +++++++++++++++++++ .../doris/plugin/audit/AuditStreamLoader.java | 16 +++- .../common/util/InternalHttpsUtilsTest.java | 64 ++++++++++++++ 3 files changed, 159 insertions(+), 4 deletions(-) create mode 100644 fe/fe-core/src/main/java/org/apache/doris/common/util/InternalHttpsUtils.java create mode 100644 fe/fe-core/src/test/java/org/apache/doris/common/util/InternalHttpsUtilsTest.java diff --git a/fe/fe-core/src/main/java/org/apache/doris/common/util/InternalHttpsUtils.java b/fe/fe-core/src/main/java/org/apache/doris/common/util/InternalHttpsUtils.java new file mode 100644 index 00000000000000..423c6d66e2d914 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/common/util/InternalHttpsUtils.java @@ -0,0 +1,83 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.doris.common.util; + +import org.apache.doris.common.Config; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.KeyStore; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; + +/** + * Utility for creating SSL-aware HTTP clients for internal FE communication. + * + *

Builds an {@link SSLContext} from the configured CA truststore once and caches it. + * Hostname verification is disabled for IP-based intra-cluster connections. + * Certificate rotation requires a FE restart. + */ +public class InternalHttpsUtils { + private static volatile SSLContext cachedSslContext = null; + + private static final Logger LOG = LogManager.getLogger(InternalHttpsUtils.class); + + /** + * Returns the cached SSLContext, building it from the configured truststore on first call. + */ + public static SSLContext getSslContext() { + if (cachedSslContext == null) { + synchronized (InternalHttpsUtils.class) { + if (cachedSslContext == null) { + cachedSslContext = buildSslContext(); + } + } + } + return cachedSslContext; + } + + private static SSLContext buildSslContext() { + try { + // The same CA signs all Doris TLS certs (FE HTTPS + MySQL SSL), so mysql_ssl_default_ca_certificate + // is the correct trust anchor for FE HTTPS. Hostname verification is skipped for IP-based comms. + KeyStore trustStore = KeyStore.getInstance(Config.ssl_trust_store_type); + try (InputStream stream = Files.newInputStream( + Paths.get(Config.mysql_ssl_default_ca_certificate))) { + trustStore.load(stream, Config.mysql_ssl_default_ca_certificate_password.toCharArray()); + } + + TrustManagerFactory tmf = TrustManagerFactory.getInstance( + TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(trustStore); + + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, tmf.getTrustManagers(), null); + return sslContext; + } catch (Exception e) { + LOG.error("Failed to build SSLContext from truststore: {}", + Config.mysql_ssl_default_ca_certificate, e); + throw new RuntimeException( + "Failed to build SSLContext from truststore: " + + Config.mysql_ssl_default_ca_certificate, e); + } + } +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/plugin/audit/AuditStreamLoader.java b/fe/fe-core/src/main/java/org/apache/doris/plugin/audit/AuditStreamLoader.java index fb9ffde02dc3c9..73a78e7356ed66 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/plugin/audit/AuditStreamLoader.java +++ b/fe/fe-core/src/main/java/org/apache/doris/plugin/audit/AuditStreamLoader.java @@ -21,8 +21,11 @@ import org.apache.doris.catalog.InternalSchema; import org.apache.doris.common.Config; import org.apache.doris.common.FeConstants; +import org.apache.doris.common.util.HttpURLUtil; +import org.apache.doris.common.util.InternalHttpsUtils; import org.apache.doris.qe.GlobalVariable; +import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -34,23 +37,23 @@ import java.net.URL; import java.util.Calendar; import java.util.stream.Collectors; +import javax.net.ssl.HttpsURLConnection; public class AuditStreamLoader { private static final Logger LOG = LogManager.getLogger(AuditStreamLoader.class); - private static String loadUrlPattern = "http://%s/api/%s/%s/_stream_load?"; // timeout for both connection and read. 10 seconds is long enough. private static final int HTTP_TIMEOUT_MS = 10000; - private String hostPort; private String db; private String auditLogTbl; private String auditLogLoadUrlStr; private String feIdentity; public AuditStreamLoader() { - this.hostPort = "127.0.0.1:" + Config.http_port; this.db = FeConstants.INTERNAL_DB_NAME; this.auditLogTbl = AuditLoader.AUDIT_LOG_TABLE; - this.auditLogLoadUrlStr = String.format(loadUrlPattern, hostPort, db, auditLogTbl); + String scheme = Config.enable_https ? "https" : "http"; + String hostPort = "127.0.0.1:" + HttpURLUtil.getHttpPort(); + this.auditLogLoadUrlStr = scheme + "://" + hostPort + "/api/" + db + "/" + auditLogTbl + "/_stream_load?"; // currently, FE identity is FE's IP:port, so we replace the "." and ":" to make it suitable for label this.feIdentity = Env.getCurrentEnv().getSelfNode().getIdent().replaceAll("\\.", "_").replaceAll(":", "_"); } @@ -58,6 +61,11 @@ public AuditStreamLoader() { private HttpURLConnection getConnection(String urlStr, String label, String clusterToken) throws IOException { URL url = new URL(urlStr); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + if (conn instanceof HttpsURLConnection && Config.enable_https) { + HttpsURLConnection httpsConn = (HttpsURLConnection) conn; + httpsConn.setSSLSocketFactory(InternalHttpsUtils.getSslContext().getSocketFactory()); + httpsConn.setHostnameVerifier(NoopHostnameVerifier.INSTANCE); + } conn.setInstanceFollowRedirects(false); conn.setRequestMethod("PUT"); conn.setRequestProperty("token", clusterToken); diff --git a/fe/fe-core/src/test/java/org/apache/doris/common/util/InternalHttpsUtilsTest.java b/fe/fe-core/src/test/java/org/apache/doris/common/util/InternalHttpsUtilsTest.java new file mode 100644 index 00000000000000..47f41c4858d555 --- /dev/null +++ b/fe/fe-core/src/test/java/org/apache/doris/common/util/InternalHttpsUtilsTest.java @@ -0,0 +1,64 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.doris.common.util; + +import org.apache.doris.common.Config; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.lang.reflect.Field; + +public class InternalHttpsUtilsTest { + + private String originalCertPath; + + @Before + public void setUp() throws Exception { + originalCertPath = Config.mysql_ssl_default_ca_certificate; + // Reset the cached SSLContext before each test so tests are independent. + resetCachedSslContext(); + } + + @After + public void tearDown() throws Exception { + Config.mysql_ssl_default_ca_certificate = originalCertPath; + resetCachedSslContext(); + } + + private void resetCachedSslContext() throws Exception { + Field field = InternalHttpsUtils.class.getDeclaredField("cachedSslContext"); + field.setAccessible(true); + field.set(null, null); + } + + @Test + public void testGetSslContextThrowsWhenCertMissing() { + Config.mysql_ssl_default_ca_certificate = "/non/existent/path/ca.p12"; + try { + InternalHttpsUtils.getSslContext(); + Assert.fail("Expected RuntimeException when cert file does not exist"); + } catch (RuntimeException e) { + // Error message must mention the cert path so operators know what to fix. + Assert.assertTrue("Error message should contain cert path", + e.getMessage() != null && e.getMessage().contains("/non/existent/path/ca.p12")); + } + } +}