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
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,25 @@ public static void validateUrlNotInternal(String url) throws Exception {
validateHostNotInternal(host);
}

public static void validateEndpointListNotInternal(String endpoints) throws Exception {
if (endpoints == null || endpoints.trim().isEmpty()) {
throw new Exception("endpoint list cannot be null or empty");
}
String[] parts = endpoints.split("[,;]");
boolean hasEntry = false;
for (String part : parts) {
String entry = part == null ? null : part.trim();
if (entry == null || entry.isEmpty()) {
continue;
}
hasEntry = true;
validateUrlNotInternal(entry);
}
if (!hasEntry) {
throw new Exception("endpoint list cannot be blank");
}
}

/**
* Validates that a hostname does not resolve to an internal/private IP address.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.apache.inlong.manager.common.enums.ErrorCodeEnum;
import org.apache.inlong.manager.common.exceptions.BusinessException;
import org.apache.inlong.manager.common.util.CommonBeanUtils;
import org.apache.inlong.manager.common.util.UrlVerificationUtils;
import org.apache.inlong.manager.dao.entity.DataNodeEntity;
import org.apache.inlong.manager.pojo.node.DataNodeInfo;
import org.apache.inlong.manager.pojo.node.DataNodeRequest;
Expand Down Expand Up @@ -91,9 +92,19 @@ public DataNodeInfo getFromEntity(DataNodeEntity entity) {
@Override
public Boolean testConnection(DataNodeRequest request) {
ClsDataNodeRequest dataNodeRequest = (ClsDataNodeRequest) request;
// SSRF protection: the endpoint used to contact Tencent Cloud must be an external address
String endpoint = dataNodeRequest.getEndpoint();
if (StringUtils.isNotBlank(endpoint)) {
try {
UrlVerificationUtils.validateUrlNotInternal(endpoint);
} catch (Exception e) {
throw new BusinessException(ErrorCodeEnum.INVALID_PARAMETER,
"SSRF protection: " + e.getMessage());
}
}
Credential cred = new Credential(dataNodeRequest.getManageSecretId(), dataNodeRequest.getManageSecretId());
HttpProfile httpProfile = new HttpProfile();
httpProfile.setEndpoint(dataNodeRequest.getEndpoint());
httpProfile.setEndpoint(endpoint);
ClientProfile clientProfile = new ClientProfile();
clientProfile.setHttpProfile(httpProfile);
ClsClient client = new ClsClient(cred, dataNodeRequest.getRegion(), clientProfile);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.apache.inlong.manager.common.exceptions.BusinessException;
import org.apache.inlong.manager.common.util.CommonBeanUtils;
import org.apache.inlong.manager.common.util.Preconditions;
import org.apache.inlong.manager.common.util.UrlVerificationUtils;
import org.apache.inlong.manager.dao.entity.DataNodeEntity;
import org.apache.inlong.manager.pojo.node.DataNodeInfo;
import org.apache.inlong.manager.pojo.node.DataNodeRequest;
Expand Down Expand Up @@ -90,6 +91,13 @@ public Boolean testConnection(DataNodeRequest request) {
String metastoreUri = hudiRequest.getUrl();
String warehouse = hudiRequest.getWarehouse();
Preconditions.expectNotBlank(metastoreUri, ErrorCodeEnum.INVALID_PARAMETER, "connection url cannot be empty");
// SSRF protection: block requests to internal/loopback/cloud-metadata addresses.
try {
UrlVerificationUtils.validateUrlNotInternal(metastoreUri);
} catch (Exception e) {
throw new BusinessException(ErrorCodeEnum.INVALID_PARAMETER,
"SSRF protection: " + e.getMessage());
}
try (HudiCatalogClient client = new HudiCatalogClient(metastoreUri, warehouse)) {
client.open();
client.listAllDatabases();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.apache.inlong.manager.common.exceptions.BusinessException;
import org.apache.inlong.manager.common.util.CommonBeanUtils;
import org.apache.inlong.manager.common.util.Preconditions;
import org.apache.inlong.manager.common.util.UrlVerificationUtils;
import org.apache.inlong.manager.dao.entity.DataNodeEntity;
import org.apache.inlong.manager.pojo.node.DataNodeInfo;
import org.apache.inlong.manager.pojo.node.DataNodeRequest;
Expand Down Expand Up @@ -90,6 +91,14 @@ public Boolean testConnection(DataNodeRequest request) {
String metastoreUri = icebergDataNodeRequest.getUrl();
String warehouse = icebergDataNodeRequest.getWarehouse();
Preconditions.expectNotBlank(metastoreUri, ErrorCodeEnum.INVALID_PARAMETER, "connection url cannot be empty");
// SSRF protection: even though the service layer validates request.getUrl(), keep an
// operator-side check so the operator remains safe if invoked from other call sites.
try {
UrlVerificationUtils.validateUrlNotInternal(metastoreUri);
} catch (Exception e) {
throw new BusinessException(ErrorCodeEnum.INVALID_PARAMETER,
"SSRF protection: " + e.getMessage());
}
try {
HiveCatalog catalog = IcebergCatalogUtils.getCatalog(metastoreUri, warehouse);
catalog.listNamespaces();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.apache.inlong.manager.common.exceptions.BusinessException;
import org.apache.inlong.manager.common.util.CommonBeanUtils;
import org.apache.inlong.manager.common.util.Preconditions;
import org.apache.inlong.manager.common.util.UrlVerificationUtils;
import org.apache.inlong.manager.dao.entity.DataNodeEntity;
import org.apache.inlong.manager.pojo.cluster.kafka.KafkaClusterInfo;
import org.apache.inlong.manager.pojo.node.DataNodeInfo;
Expand Down Expand Up @@ -114,6 +115,13 @@ public Boolean testConnection(DataNodeRequest request) {
String bootstrapServers = kafkaDataNodeRequest.getBootstrapServers();
Preconditions.expectNotBlank(bootstrapServers, ErrorCodeEnum.INVALID_PARAMETER,
"connection bootstrapServers cannot be empty");
// SSRF protection: every bootstrap server must resolve to a public/external address
try {
UrlVerificationUtils.validateEndpointListNotInternal(bootstrapServers);
} catch (Exception e) {
throw new BusinessException(ErrorCodeEnum.INVALID_PARAMETER,
"SSRF protection: " + e.getMessage());
}
if (getKafkaConnection(bootstrapServers)) {
LOGGER.info("kafka connection success for bootstrapServers={}",
bootstrapServers);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.apache.inlong.manager.common.exceptions.BusinessException;
import org.apache.inlong.manager.common.util.CommonBeanUtils;
import org.apache.inlong.manager.common.util.Preconditions;
import org.apache.inlong.manager.common.util.UrlVerificationUtils;
import org.apache.inlong.manager.dao.entity.DataNodeEntity;
import org.apache.inlong.manager.pojo.node.DataNodeInfo;
import org.apache.inlong.manager.pojo.node.DataNodeRequest;
Expand Down Expand Up @@ -96,6 +97,13 @@ public Boolean testConnection(DataNodeRequest request) {
KuduDataNodeRequest kuduRequest = (KuduDataNodeRequest) request;
String masters = kuduRequest.getMasters();
Preconditions.expectNotBlank(masters, ErrorCodeEnum.INVALID_PARAMETER, "masters cannot be empty");
// SSRF protection: every master endpoint must resolve to a public/external address
try {
UrlVerificationUtils.validateEndpointListNotInternal(masters);
} catch (Exception e) {
throw new BusinessException(ErrorCodeEnum.INVALID_PARAMETER,
"SSRF protection: " + e.getMessage());
}

try (KuduResourceClient kuduClient = new KuduResourceClient(masters)) {
kuduClient.getTablesList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.apache.inlong.manager.common.exceptions.BusinessException;
import org.apache.inlong.manager.common.util.CommonBeanUtils;
import org.apache.inlong.manager.common.util.Preconditions;
import org.apache.inlong.manager.common.util.UrlVerificationUtils;
import org.apache.inlong.manager.dao.entity.DataNodeEntity;
import org.apache.inlong.manager.pojo.cluster.pulsar.PulsarClusterInfo;
import org.apache.inlong.manager.pojo.node.DataNodeInfo;
Expand Down Expand Up @@ -100,6 +101,13 @@ public Boolean testConnection(DataNodeRequest request) {
String adminUrl = pulsarDataNodeRequest.getAdminUrl();
String token = pulsarDataNodeRequest.getToken();
Preconditions.expectNotBlank(adminUrl, ErrorCodeEnum.INVALID_PARAMETER, "connection admin urlcannot be empty");
// SSRF protection: block requests to internal/loopback/link-local/cloud-metadata addresses
try {
UrlVerificationUtils.validateUrlNotInternal(adminUrl);
} catch (Exception e) {
throw new BusinessException(ErrorCodeEnum.INVALID_PARAMETER,
"SSRF protection: " + e.getMessage());
}
if (getPulsarConnection(adminUrl, token)) {
LOGGER.info("pulsar connection success for adminUrl={}, token={}",
adminUrl, token);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.apache.inlong.manager.common.exceptions.BusinessException;
import org.apache.inlong.manager.common.util.CommonBeanUtils;
import org.apache.inlong.manager.common.util.Preconditions;
import org.apache.inlong.manager.common.util.UrlVerificationUtils;
import org.apache.inlong.manager.dao.entity.DataNodeEntity;
import org.apache.inlong.manager.pojo.node.DataNodeInfo;
import org.apache.inlong.manager.pojo.node.DataNodeRequest;
Expand Down Expand Up @@ -117,6 +118,35 @@ protected void setTargetEntity(DataNodeRequest request, DataNodeEntity targetEnt
@Override
public Boolean testConnection(DataNodeRequest request) {
RedisDataNodeRequest redisDataNodeRequest = (RedisDataNodeRequest) request;
// SSRF protection: reject requests targeting internal/loopback/cloud-metadata addresses.
// Redis operator uses different fields per cluster mode; validate all that apply.
try {
RedisClusterMode clusterMode = RedisClusterMode.of(redisDataNodeRequest.getClusterMode());
switch (clusterMode) {
case STANDALONE:
if (StringUtils.isNotBlank(redisDataNodeRequest.getHost())) {
UrlVerificationUtils.validateHostNotInternal(redisDataNodeRequest.getHost());
}
break;
case CLUSTER:
if (StringUtils.isNotBlank(redisDataNodeRequest.getClusterNodes())) {
UrlVerificationUtils
.validateEndpointListNotInternal(redisDataNodeRequest.getClusterNodes());
}
break;
case SENTINEL:
if (StringUtils.isNotBlank(redisDataNodeRequest.getSentinelsInfo())) {
UrlVerificationUtils
.validateEndpointListNotInternal(redisDataNodeRequest.getSentinelsInfo());
}
break;
default:
break;
}
} catch (Exception e) {
throw new BusinessException(ErrorCodeEnum.INVALID_PARAMETER,
"SSRF protection: " + e.getMessage());
}
try {
return RedisResourceClient.testConnection(redisDataNodeRequest);
} catch (Exception e) {
Expand Down
Loading