Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 7 additions & 11 deletions fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
Original file line number Diff line number Diff line change
Expand Up @@ -1406,8 +1406,7 @@ protected void getClusterIdAndRole() throws IOException {
getClusterIdFromStorage(storage);
token = storage.getToken();
try {
String url = "http://" + NetUtils
.getHostPortInAccessibleFormat(rightHelperNode.getHost(), Config.http_port) + "/check";
String url = HttpURLUtil.buildInternalFeUrl(rightHelperNode.getHost(), "/check", null);
HttpURLConnection conn = HttpURLUtil.getConnectionWithNodeIdent(url);
conn.setConnectTimeout(2 * 1000);
conn.setReadTimeout(2 * 1000);
Expand Down Expand Up @@ -1503,9 +1502,8 @@ protected boolean getFeNodeTypeAndNameFromHelpers() {
try {
// For upgrade compatibility, the host parameter name remains the same
// and the new hostname parameter is added
String url = "http://" + NetUtils.getHostPortInAccessibleFormat(helperNode.getHost(), Config.http_port)
+ "/role?host=" + selfNode.getHost()
+ "&port=" + selfNode.getPort();
String queryParams = "host=" + selfNode.getHost() + "&port=" + selfNode.getPort();
String url = HttpURLUtil.buildInternalFeUrl(helperNode.getHost(), "/role", queryParams);
HttpURLConnection conn = HttpURLUtil.getConnectionWithNodeIdent(url);
if (conn.getResponseCode() != 200) {
LOG.warn("failed to get fe node type from helper node: {}. response code: {}", helperNode,
Expand Down Expand Up @@ -1768,7 +1766,7 @@ private void transferToMaster() {

toMasterProgress = "log master info";
this.masterInfo = new MasterInfo(Env.getCurrentEnv().getSelfNode().getHost(),
Config.http_port,
Config.enable_https ? Config.https_port : Config.http_port,
Config.rpc_port);
editLog.logMasterInfo(masterInfo);
LOG.info("logMasterInfo:{}", masterInfo);
Expand Down Expand Up @@ -2129,8 +2127,7 @@ private void checkBeExecVersion() {

protected boolean getVersionFileFromHelper(HostInfo helperNode) throws IOException {
try {
String url = "http://" + NetUtils.getHostPortInAccessibleFormat(helperNode.getHost(), Config.http_port)
+ "/version";
String url = HttpURLUtil.buildInternalFeUrl(helperNode.getHost(), "/version", null);
File dir = new File(this.imageDir);
MetaHelper.getRemoteFile(url, HTTP_TIMEOUT_SECOND * 1000,
MetaHelper.getFile(Storage.VERSION_FILE, dir));
Expand All @@ -2149,8 +2146,7 @@ protected void getNewImage(HostInfo helperNode) throws IOException {
localImageVersion = storage.getLatestImageSeq();

try {
String hostPort = NetUtils.getHostPortInAccessibleFormat(helperNode.getHost(), Config.http_port);
String infoUrl = "http://" + hostPort + "/info";
String infoUrl = HttpURLUtil.buildInternalFeUrl(helperNode.getHost(), "/info", null);
ResponseBody<StorageInfo> responseBody = MetaHelper
.doGet(infoUrl, HTTP_TIMEOUT_SECOND * 1000, StorageInfo.class);
if (responseBody.getCode() != RestApiStatusCode.OK.code) {
Expand All @@ -2160,7 +2156,7 @@ protected void getNewImage(HostInfo helperNode) throws IOException {
StorageInfo info = responseBody.getData();
long version = info.getImageSeq();
if (version > localImageVersion) {
String url = "http://" + hostPort + "/image?version=" + version;
String url = HttpURLUtil.buildInternalFeUrl(helperNode.getHost(), "/image", "version=" + version);
String filename = Storage.IMAGE + "." + version;
File dir = new File(this.imageDir);
MetaHelper.getRemoteFile(url, Config.sync_image_timeout_second * 1000,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,17 @@

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;
import org.apache.http.conn.ssl.NoopHostnameVerifier;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;
import javax.net.ssl.HttpsURLConnection;

public class HttpURLUtil {

Expand All @@ -35,6 +38,13 @@ public static HttpURLConnection getConnectionWithNodeIdent(String request) throw
SecurityChecker.getInstance().startSSRFChecking(request);
URL url = new URL(request);
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);
}

// Must use Env.getServingEnv() instead of getCurrentEnv(),
// because here we need to obtain selfNode through the official service catalog.
HostInfo selfNode = Env.getServingEnv().getSelfNode();
Expand All @@ -58,4 +68,16 @@ public static Map<String, String> getNodeIdentHeaders() throws IOException {
return headers;
}

public static String buildInternalFeUrl(String host, String path, String queryParams) {
String protocol = Config.enable_https ? "https" : "http";
int port = Config.enable_https ? Config.https_port : Config.http_port;

String url = protocol + "://" + NetUtils.getHostPortInAccessibleFormat(host, port) + path;
if (queryParams != null && !queryParams.isEmpty()) {
url += "?" + queryParams;
}

return url;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// 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.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
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-to-FE communication.
*
* <p>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 {
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", e);
}
}

/**
* Returns an HTTP client configured with the FE CA truststore and hostname verification disabled.
*/
public static CloseableHttpClient createValidatedHttpClient() {
SSLConnectionSocketFactory sslFactory = new SSLConnectionSocketFactory(
getSslContext(),
NoopHostnameVerifier.INSTANCE);

return HttpClients.custom()
.setSSLSocketFactory(sslFactory)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.eclipse.jetty.ee10.websocket.server.config.JettyWebSocketServletContainerInitializer;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.ServerConnector;
import org.springframework.boot.web.embedded.jetty.ConfigurableJettyWebServerFactory;
import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory;
Expand Down Expand Up @@ -53,19 +54,41 @@ public void customize(ConfigurableJettyWebServerFactory factory) {
});

if (Config.enable_https) {
((JettyServletWebServerFactory) factory).setConfigurations(
if (!(factory instanceof JettyServletWebServerFactory)) {
LOG.warn("Unexpected WebServerFactory type {}, skipping HTTPS configuration",
factory.getClass().getName());
return;
}
JettyServletWebServerFactory jettyFactory = (JettyServletWebServerFactory) factory;
jettyFactory.setConfigurations(
Collections.singletonList(new HttpToHttpsJettyConfig())
);

factory.addServerCustomizers(server -> {
if (server.getConnectors() != null && server.getConnectors().length > 0) {
if (!(server.getConnectors()[0] instanceof ServerConnector)) {
LOG.warn("First connector is not a ServerConnector, cannot configure SNI");
return;
}
ServerConnector existingConnector = (ServerConnector) server.getConnectors()[0];
HttpConnectionFactory httpFactory =
existingConnector.getConnectionFactory(HttpConnectionFactory.class);
if (httpFactory != null) {
HttpConfiguration httpConfig = httpFactory.getHttpConfiguration();
httpConfig.setSecurePort(Config.https_port);
httpConfig.setSecureScheme("https");

// Disable SNI host checking to allow IP-based connections
// Safe because checkFromValidFe() validates only registered FE nodes can connect
SecureRequestCustomizer secureCustomizer =
httpConfig.getCustomizer(SecureRequestCustomizer.class);
if (secureCustomizer == null) {
secureCustomizer = new SecureRequestCustomizer();
httpConfig.addCustomizer(secureCustomizer);
}
secureCustomizer.setSniHostCheck(false);
secureCustomizer.setSniRequired(false);
LOG.info("Disabled SNI host checking for IP-based HTTPS connections");
}
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.apache.doris.catalog.Env;
import org.apache.doris.catalog.SchemaTable;
import org.apache.doris.catalog.Table;
import org.apache.doris.common.util.HttpURLUtil;
import org.apache.doris.httpv2.entity.ResponseBody;
import org.apache.doris.httpv2.entity.ResponseEntityBuilder;
import org.apache.doris.httpv2.rest.RestBaseController;
Expand Down Expand Up @@ -118,8 +119,8 @@ private List<Map<String, String>> getOtherSessionInfo(HttpServletRequest request
Frontend frontend) throws IOException {
Map<String, String> header = Maps.newHashMap();
header.put(NodeAction.AUTHORIZATION, request.getHeader(NodeAction.AUTHORIZATION));
String res = HttpUtils.doGet(String.format("http://%s:%s/rest/v1/session",
frontend.getHost(), Env.getCurrentEnv().getMasterHttpPort()), header);
String res = HttpUtils.doGet(HttpURLUtil.buildInternalFeUrl(
frontend.getHost(), "/rest/v1/session", null), header);
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> jsonMap = objectMapper.readValue(res,
new TypeReference<Map<String, Object>>() {});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import org.apache.doris.catalog.Env;
import org.apache.doris.common.Config;
import org.apache.doris.common.DdlException;
import org.apache.doris.common.util.NetUtils;
import org.apache.doris.common.util.HttpURLUtil;
import org.apache.doris.ha.FrontendNodeType;
import org.apache.doris.httpv2.entity.ResponseEntityBuilder;
import org.apache.doris.httpv2.rest.RestBaseController;
Expand Down Expand Up @@ -163,8 +163,7 @@ public Object put(HttpServletRequest request, HttpServletResponse response) thro
clientHost, clientPort, machine, portStr);

clientHost = Strings.isNullOrEmpty(clientHost) ? machine : clientHost;
String url = "http://" + NetUtils.getHostPortInAccessibleFormat(clientHost, Integer.valueOf(portStr))
+ "/image?version=" + versionStr;
String url = HttpURLUtil.buildInternalFeUrl(clientHost, "/image", "version=" + versionStr);

String filename = Storage.IMAGE + "." + versionStr;
File dir = new File(Env.getCurrentEnv().getImageDir());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ public Object clusterInfo(HttpServletRequest request, HttpServletResponse respon
result.put("mysql", frontends.stream().map(ip -> NetUtils
.getHostPortInAccessibleFormat(ip, Config.query_port)).collect(Collectors.toList()));
result.put("http", frontends.stream().map(ip -> NetUtils
.getHostPortInAccessibleFormat(ip, Config.http_port)).collect(Collectors.toList()));
.getHostPortInAccessibleFormat(ip,
Config.enable_https ? Config.https_port : Config.http_port)).collect(Collectors.toList()));
result.put("arrow flight sql server", frontends.stream().map(
ip -> NetUtils.getHostPortInAccessibleFormat(ip, Config.arrow_flight_sql_port))
.collect(Collectors.toList()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.apache.doris.catalog.Env;
import org.apache.doris.common.Config;
import org.apache.doris.common.Pair;
import org.apache.doris.common.util.InternalHttpsUtils;
import org.apache.doris.common.util.Util;
import org.apache.doris.httpv2.entity.ResponseBody;
import org.apache.doris.persist.gson.GsonUtils;
Expand Down Expand Up @@ -51,7 +52,7 @@
import java.util.stream.Collectors;

/*
* used to forward http requests from manager to be.
* Used for internal HTTP(S) communication between FE nodes and from manager to BE.
*/
public class HttpUtils {
private static final Logger LOG = LogManager.getLogger(HttpUtils.class);
Expand All @@ -60,8 +61,9 @@ public class HttpUtils {
static final int DEFAULT_TIME_OUT_MS = 2000;

static List<Pair<String, Integer>> getFeList() {
int port = Config.enable_https ? Config.https_port : Config.http_port;
return Env.getCurrentEnv().getFrontends(null)
.stream().filter(Frontend::isAlive).map(fe -> Pair.of(fe.getHost(), Config.http_port))
.stream().filter(Frontend::isAlive).map(fe -> Pair.of(fe.getHost(), port))
.collect(Collectors.toList());
}

Expand All @@ -71,7 +73,7 @@ static boolean isCurrentFe(String ip, int port) {
}

static String concatUrl(Pair<String, Integer> ipPort, String path, Map<String, String> arguments) {
StringBuilder url = new StringBuilder("http://")
StringBuilder url = new StringBuilder(Config.enable_https ? "https://" : "http://")
.append(ipPort.first).append(":").append(ipPort.second).append(path);
boolean isFirst = true;
for (Map.Entry<String, String> entry : arguments.entrySet()) {
Expand Down Expand Up @@ -130,8 +132,11 @@ public static CloseableHttpClient getHttpClient() {
}

private static String executeRequest(HttpRequestBase request) throws IOException {
CloseableHttpClient client = getHttpClient();
return client.execute(request, httpResponse -> EntityUtils.toString(httpResponse.getEntity()));
try (CloseableHttpClient client = Config.enable_https
? InternalHttpsUtils.createValidatedHttpClient()
: HttpClientBuilder.create().build()) {
return client.execute(request, httpResponse -> EntityUtils.toString(httpResponse.getEntity()));
}
}

static String parseResponse(String response) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,9 @@ public Object nodeList(HttpServletRequest request, HttpServletResponse response)
}

private static List<String> getFeList() {
int port = Config.enable_https ? Config.https_port : Config.http_port;
return Env.getCurrentEnv().getFrontends(null).stream()
.map(fe -> NetUtils.getHostPortInAccessibleFormat(fe.getHost(), Config.http_port))
.map(fe -> NetUtils.getHostPortInAccessibleFormat(fe.getHost(), port))
.collect(Collectors.toList());
}

Expand Down Expand Up @@ -496,7 +497,8 @@ public Object setConfigFe(HttpServletRequest request, HttpServletResponse respon
List<Map<String, String>> failedTotal = Lists.newArrayList();
List<NodeConfigs> nodeConfigList = parseSetConfigNodes(requestBody, failedTotal);
List<Pair<String, Integer>> aliveFe = Env.getCurrentEnv().getFrontends(null).stream().filter(Frontend::isAlive)
.map(fe -> Pair.of(fe.getHost(), Config.http_port)).collect(Collectors.toList());
.map(fe -> Pair.of(fe.getHost(),
Config.enable_https ? Config.https_port : Config.http_port)).collect(Collectors.toList());
checkNodeIsAlive(nodeConfigList, aliveFe, failedTotal);

Map<String, String> header = Maps.newHashMap();
Expand Down Expand Up @@ -568,7 +570,8 @@ private static void addFailedConfig(String configName, String value, String node
private String concatFeSetConfigUrl(NodeConfigs nodeConfigs, boolean isPersist) {
StringBuilder sb = new StringBuilder();
Pair<String, Integer> hostPort = nodeConfigs.getHostPort();
sb.append("http://").append(hostPort.first).append(":").append(hostPort.second).append("/api/_set_config");
sb.append(Config.enable_https ? "https://" : "http://")
.append(hostPort.first).append(":").append(hostPort.second).append("/api/_set_config");
Map<String, String> configs = nodeConfigs.getConfigs(isPersist);
boolean addAnd = false;
for (Map.Entry<String, String> entry : configs.entrySet()) {
Expand Down
Loading