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

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


The following commit(s) were added to refs/heads/master by this push:
     new a0f8ae14c7 [INLONG-12153][Manager] Add parameter validation for node 
in clusterController (#12153)
a0f8ae14c7 is described below

commit a0f8ae14c7aafaeeceb81798d6bd144ef8af03b4
Author: fuweng11 <[email protected]>
AuthorDate: Thu Jul 9 09:35:28 2026 +0800

    [INLONG-12153][Manager] Add parameter validation for node in 
clusterController (#12153)
    
    Co-authored-by: wakefu <[email protected]>
---
 .../manager/pojo/cluster/ClusterNodeRequest.java   | 19 ++++++++++-
 .../service/cluster/InlongClusterServiceImpl.java  | 38 ++++++++++++++++++++++
 .../web/controller/InlongClusterController.java    |  2 ++
 3 files changed, 58 insertions(+), 1 deletion(-)

diff --git 
a/inlong-manager/manager-pojo/src/main/java/org/apache/inlong/manager/pojo/cluster/ClusterNodeRequest.java
 
b/inlong-manager/manager-pojo/src/main/java/org/apache/inlong/manager/pojo/cluster/ClusterNodeRequest.java
index 14eaa7d984..c2ff1511c1 100644
--- 
a/inlong-manager/manager-pojo/src/main/java/org/apache/inlong/manager/pojo/cluster/ClusterNodeRequest.java
+++ 
b/inlong-manager/manager-pojo/src/main/java/org/apache/inlong/manager/pojo/cluster/ClusterNodeRequest.java
@@ -25,8 +25,11 @@ import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 import org.hibernate.validator.constraints.Length;
 
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
 import javax.validation.constraints.NotBlank;
 import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
 
 /**
  * Inlong cluster node request
@@ -51,19 +54,33 @@ public class ClusterNodeRequest {
 
     @ApiModelProperty(value = "Cluster IP")
     @NotBlank(message = "ip cannot be blank")
-    @Length(max = 512, message = "length must be less than or equal to 512")
+    @Length(max = 255, message = "ip length must be less than or equal to 255")
+    @Pattern(regexp = "^(?!-)"
+            + "(?:"
+            + "(?:[0-9]{1,3}\\.){3}[0-9]{1,3}"
+            + "|\\[[0-9a-fA-F:]+\\]"
+            + "|[0-9a-fA-F:]+"
+            + "|(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)"
+            + "(?:\\.(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?))*"
+            + ")$", message = "ip must be a valid IPv4/IPv6/hostname and must 
not start with '-'")
     private String ip;
 
     @ApiModelProperty(value = "Cluster port")
+    @Min(value = 1, message = "port must be between 1 and 65535")
+    @Max(value = 65535, message = "port must be between 1 and 65535")
     private Integer port;
 
     @ApiModelProperty(value = "Username")
+    @Length(max = 64, message = "username length must be less than or equal to 
64")
+    @Pattern(regexp = "^$|^[A-Za-z0-9_][A-Za-z0-9_.@-]{0,63}$", message = 
"username must not start with '-' and can only contain letters, digits, '_', 
'.', '@' or '-'")
     private String username;
 
     @ApiModelProperty(value = "password")
     private String password;
 
     @ApiModelProperty(value = "SSH port")
+    @Min(value = 1, message = "sshPort must be between 1 and 65535")
+    @Max(value = 65535, message = "sshPort must be between 1 and 65535")
     private Integer sshPort;
 
     @ApiModelProperty(value = "Cluster protocol type")
diff --git 
a/inlong-manager/manager-service/src/main/java/org/apache/inlong/manager/service/cluster/InlongClusterServiceImpl.java
 
b/inlong-manager/manager-service/src/main/java/org/apache/inlong/manager/service/cluster/InlongClusterServiceImpl.java
index 5985724f2b..6161b7e05f 100644
--- 
a/inlong-manager/manager-service/src/main/java/org/apache/inlong/manager/service/cluster/InlongClusterServiceImpl.java
+++ 
b/inlong-manager/manager-service/src/main/java/org/apache/inlong/manager/service/cluster/InlongClusterServiceImpl.java
@@ -857,6 +857,11 @@ public class InlongClusterServiceImpl implements 
InlongClusterService {
     public Boolean updateNode(ClusterNodeRequest request, String operator) {
         LOGGER.debug("begin to update inlong cluster node={}", request);
         Preconditions.expectNotNull(request, "inlong cluster node cannot be 
empty");
+
+        // Same defense as saveNode: reject unsafe/internal ip before the
+        // install lane can reach the CommandExecutor sink.
+        validateClusterNodeIp(request);
+
         Integer id = request.getId();
         InlongClusterNodeEntity entity = clusterNodeMapper.selectById(id);
         if (entity == null) {
@@ -961,6 +966,39 @@ public class InlongClusterServiceImpl implements 
InlongClusterService {
         }
     }
 
+    /**
+     * Validate that the cluster node ip/host does not target internal/private
+     * networks before an install request is queued for the CommandExecutor.
+     *
+     * <p>Format-level checks (non-blank, length, no leading '-', character
+     * whitelist) are enforced declaratively on {@link ClusterNodeRequest#ip}
+     * via {@code @NotBlank}, {@code @Length} and {@code @Pattern}, so this
+     * method only carries the semantic SSRF check that cannot be expressed
+     * as an annotation.
+     *
+     * <p>Only enforced for AGENT-type nodes that actually go through the
+     * install lane, and only when {@code isInstall} is true, so existing
+     * flows for other cluster node types remain unaffected.
+     */
+    private void validateClusterNodeIp(ClusterNodeRequest request) {
+        if (request == null || !Boolean.TRUE.equals(request.getIsInstall())) {
+            return;
+        }
+        if (!ClusterType.AGENT.equalsIgnoreCase(request.getType())) {
+            return;
+        }
+        String ip = request.getIp();
+        if (StringUtils.isBlank(ip)) {
+            return;
+        }
+        try {
+            UrlVerificationUtils.validateHostNotInternal(ip);
+        } catch (Exception e) {
+            throw new BusinessException(ErrorCodeEnum.INVALID_PARAMETER,
+                    "SSRF protection: " + e.getMessage());
+        }
+    }
+
     @Override
     public DataProxyNodeResponse getDataProxyNodes(String groupId, String 
protocolType) {
         LOGGER.debug("begin to get data proxy nodes for groupId={}, 
protocol={}", groupId, protocolType);
diff --git 
a/inlong-manager/manager-web/src/main/java/org/apache/inlong/manager/web/controller/InlongClusterController.java
 
b/inlong-manager/manager-web/src/main/java/org/apache/inlong/manager/web/controller/InlongClusterController.java
index 23f2f0d7fd..9ebe381b4c 100644
--- 
a/inlong-manager/manager-web/src/main/java/org/apache/inlong/manager/web/controller/InlongClusterController.java
+++ 
b/inlong-manager/manager-web/src/main/java/org/apache/inlong/manager/web/controller/InlongClusterController.java
@@ -238,6 +238,7 @@ public class InlongClusterController {
     @PostMapping(value = "/cluster/node/save")
     @ApiOperation(value = "Save cluster node")
     @OperationLog(operation = OperationType.CREATE, operationTarget = 
OperationTarget.CLUSTER_NODE)
+    @RequiresRoles(UserRoleCode.INLONG_ADMIN)
     public Response<Integer> saveNode(@Validated @RequestBody 
ClusterNodeRequest request) {
         String currentUser = LoginUserUtils.getLoginUser().getName();
         request.setCurrentUser(currentUser);
@@ -275,6 +276,7 @@ public class InlongClusterController {
     @RequestMapping(value = "/cluster/node/update", method = 
RequestMethod.POST)
     @OperationLog(operation = OperationType.UPDATE, operationTarget = 
OperationTarget.CLUSTER_NODE)
     @ApiOperation(value = "Update cluster node")
+    @RequiresRoles(UserRoleCode.INLONG_ADMIN)
     public Response<Boolean> updateNode(@Validated(UpdateValidation.class) 
@RequestBody ClusterNodeRequest request) {
         String username = LoginUserUtils.getLoginUser().getName();
         request.setCurrentUser(username);

Reply via email to