|
29 | 29 | from env.constants import GseVersion |
30 | 30 |
|
31 | 31 |
|
| 32 | +def validate_agent_config(agent_config): |
| 33 | + """ |
| 34 | + 校验 agent_config 字段的合法性和安全性 |
| 35 | + |
| 36 | + Args: |
| 37 | + agent_config: Agent配置字典,格式为 {os_type: {config_key: config_value}} |
| 38 | + |
| 39 | + Returns: |
| 40 | + 校验后的 agent_config |
| 41 | + |
| 42 | + Raises: |
| 43 | + ValidationError: 当配置不合法时抛出异常 |
| 44 | + """ |
| 45 | + # 定义允许的操作系统类型 |
| 46 | + allowed_os_types = {"linux", "windows", "aix", "solaris", "darwin"} |
| 47 | + |
| 48 | + # 定义每个操作系统下允许的字段及其类型 |
| 49 | + allowed_fields = { |
| 50 | + "setup_path": str, # Agent 安装路径 |
| 51 | + "temp_path": str, # 临时文件目录 |
| 52 | + "log_path": str, # 日志路径 |
| 53 | + "data_path": str, # 数据路径 |
| 54 | + "pluginipc": (str, int), # 插件 IPC 配置 |
| 55 | + "dataipc": (str, int), # 数据 IPC 配置 |
| 56 | + "alarm_event_data_id": int, # 告警事件数据 ID |
| 57 | + } |
| 58 | + |
| 59 | + # 危险路径关键字,禁止出现在路径中 |
| 60 | + dangerous_patterns = [ |
| 61 | + "..", # 路径遍历 |
| 62 | + "~", # 家目录 |
| 63 | + "$(", # 命令替换 |
| 64 | + "`", # 命令替换 |
| 65 | + "|", # 管道 |
| 66 | + ";", # 命令分隔符 |
| 67 | + "&", # 后台执行(必须放在 && 之前) |
| 68 | + "&&", # 命令连接 |
| 69 | + "||", # 命令连接 |
| 70 | + ">", # 重定向 |
| 71 | + "<", # 重定向 |
| 72 | + "\n", # 换行符 |
| 73 | + "\r", # 回车符 |
| 74 | + "$", # 变量引用 |
| 75 | + "(", # 子shell |
| 76 | + ")", # 子shell |
| 77 | + "{", # 代码块 |
| 78 | + "}", # 代码块 |
| 79 | + "[", # 通配符 |
| 80 | + "]", # 通配符 |
| 81 | + "!", # 历史扩展 |
| 82 | + "\\", # 转义符 |
| 83 | + ] |
| 84 | + |
| 85 | + if not isinstance(agent_config, dict): |
| 86 | + raise ValidationError(_("agent_config 必须是字典类型")) |
| 87 | + |
| 88 | + for os_type, config in agent_config.items(): |
| 89 | + # 校验操作系统类型 |
| 90 | + if os_type.lower() not in allowed_os_types: |
| 91 | + raise ValidationError(_("不支持的操作系统类型: {os_type}").format(os_type=os_type)) |
| 92 | + |
| 93 | + if not isinstance(config, dict): |
| 94 | + raise ValidationError(_("操作系统 {os_type} 的配置必须是字典类型").format(os_type=os_type)) |
| 95 | + |
| 96 | + for key, value in config.items(): |
| 97 | + # 校验字段名是否合法 |
| 98 | + if key not in allowed_fields: |
| 99 | + raise ValidationError( |
| 100 | + _("操作系统 {os_type} 包含不支持的配置项: {key}").format(os_type=os_type, key=key) |
| 101 | + ) |
| 102 | + |
| 103 | + # 校验字段类型 |
| 104 | + expected_type = allowed_fields[key] |
| 105 | + if not isinstance(value, expected_type): |
| 106 | + raise ValidationError( |
| 107 | + _("操作系统 {os_type} 的配置项 {key} 类型错误,期望 {expected_type},实际为 {actual_type}").format( |
| 108 | + os_type=os_type, |
| 109 | + key=key, |
| 110 | + expected_type=expected_type.__name__, |
| 111 | + actual_type=type(value).__name__, |
| 112 | + ) |
| 113 | + ) |
| 114 | + |
| 115 | + # 如果是路径类型的字段,进行安全校验 |
| 116 | + if key in ["setup_path", "temp_path", "log_path", "data_path", "pluginipc", "dataipc"]: |
| 117 | + if isinstance(value, str): |
| 118 | + # 检查危险字符 |
| 119 | + for pattern in dangerous_patterns: |
| 120 | + if pattern in value: |
| 121 | + raise ValidationError( |
| 122 | + _("操作系统 {os_type} 的配置项 {key} 包含不安全的字符: {pattern}").format( |
| 123 | + os_type=os_type, key=key, pattern=pattern |
| 124 | + ) |
| 125 | + ) |
| 126 | + |
| 127 | + # 检查是否以 / 或盘符开头(绝对路径) |
| 128 | + if os_type.lower() == "windows": |
| 129 | + # Windows 路径应该以盘符开头,如 C:\ 或 C:/ |
| 130 | + if not (value[0].isalpha() and value[1:3] in [":\\", ":/"]): |
| 131 | + # 相对路径也不允许 |
| 132 | + if not (value.startswith(".\\") or value.startswith("./")): |
| 133 | + raise ValidationError( |
| 134 | + _("操作系统 {os_type} 的配置项 {key} 必须是绝对路径").format( |
| 135 | + os_type=os_type, key=key |
| 136 | + ) |
| 137 | + ) |
| 138 | + else: |
| 139 | + # Linux/Unix 路径应该以 / 开头 |
| 140 | + if not value.startswith("/"): |
| 141 | + raise ValidationError( |
| 142 | + _("操作系统 {os_type} 的配置项 {key} 必须是绝对路径").format( |
| 143 | + os_type=os_type, key=key |
| 144 | + ) |
| 145 | + ) |
| 146 | + |
| 147 | + return agent_config |
| 148 | + |
| 149 | + |
| 150 | +def validate_port_config(port_config): |
| 151 | + """ |
| 152 | + 校验 port_config 字段的合法性和安全性 |
| 153 | + |
| 154 | + Args: |
| 155 | + port_config: 端口配置字典 |
| 156 | + |
| 157 | + Returns: |
| 158 | + 校验后的 port_config |
| 159 | + |
| 160 | + Raises: |
| 161 | + ValidationError: 当配置不合法时抛出异常 |
| 162 | + """ |
| 163 | + # 定义允许的端口配置字段 |
| 164 | + allowed_fields = { |
| 165 | + "agent_port", # Agent 端口 |
| 166 | + "bt_port", # BT 端口 |
| 167 | + "data_port", # 数据端口 |
| 168 | + "file_port", # 文件端口 |
| 169 | + "info_port", # 信息端口 |
| 170 | + "listen_port", # 监听端口 |
| 171 | + "mesh_port", # Mesh 端口 |
| 172 | + } |
| 173 | + |
| 174 | + # 危险字符黑名单 |
| 175 | + dangerous_patterns = [ |
| 176 | + "$((", "$(`", "$([", "$(<", "$(>", # 命令替换变种 |
| 177 | + "$(", "`", "|", ";", "&", "&&", "||", ">", "<", # 原黑名单 |
| 178 | + "\n", "\r", "$", "(", ")", "{", "}", "[", "]", "!", "\\", |
| 179 | + ] |
| 180 | + |
| 181 | + if not isinstance(port_config, dict): |
| 182 | + raise ValidationError(_("port_config 必须是字典类型")) |
| 183 | + |
| 184 | + for key, value in port_config.items(): |
| 185 | + # 校验字段名是否合法 |
| 186 | + if key not in allowed_fields: |
| 187 | + raise ValidationError( |
| 188 | + _("不支持的端口配置项: {key}").format(key=key) |
| 189 | + ) |
| 190 | + |
| 191 | + # 校验端口号必须是正整数 |
| 192 | + if not isinstance(value, int): |
| 193 | + raise ValidationError( |
| 194 | + _("端口配置项 {key} 必须是正整数").format(key=key) |
| 195 | + ) |
| 196 | + |
| 197 | + # 校验端口号范围(1-65535) |
| 198 | + if not (1 <= value <= 65535): |
| 199 | + raise ValidationError( |
| 200 | + _("端口配置项 {key} 必须在 1-65535 范围内").format(key=key) |
| 201 | + ) |
| 202 | + |
| 203 | + # 如果值是字符串(不应该出现,但以防万一),检查危险字符 |
| 204 | + if isinstance(value, str): |
| 205 | + for pattern in dangerous_patterns: |
| 206 | + if pattern in value: |
| 207 | + raise ValidationError( |
| 208 | + _("端口配置项 {key} 包含不安全的字符: {pattern}").format( |
| 209 | + key=key, pattern=pattern |
| 210 | + ) |
| 211 | + ) |
| 212 | + |
| 213 | + return port_config |
| 214 | + |
| 215 | + |
| 216 | +def validate_bscp_config(bscp_config): |
| 217 | + """ |
| 218 | + 校验 bscp_config 字段的合法性和安全性 |
| 219 | + |
| 220 | + Args: |
| 221 | + bscp_config: BSCP 配置字典 |
| 222 | + |
| 223 | + Returns: |
| 224 | + 校验后的 bscp_config |
| 225 | + |
| 226 | + Raises: |
| 227 | + ValidationError: 当配置不合法时抛出异常 |
| 228 | + """ |
| 229 | + # 定义允许的 BSCP 配置字段 |
| 230 | + allowed_fields = { |
| 231 | + "server_url": str, # BSCP 服务器地址 |
| 232 | + "config_path": str, # 配置文件路径 |
| 233 | + "log_path": str, # 日志路径 |
| 234 | + "data_path": str, # 数据路径 |
| 235 | + } |
| 236 | + |
| 237 | + # 危险字符黑名单(复用 agent_config 的黑名单) |
| 238 | + dangerous_patterns = [ |
| 239 | + "..", "~", "$(", "`", "|", ";", "&", "&&", "||", |
| 240 | + ">", "<", "\n", "\r", "$", "(", ")", "{", "}", |
| 241 | + "[", "]", "!", "\\", |
| 242 | + ] |
| 243 | + |
| 244 | + if not isinstance(bscp_config, dict): |
| 245 | + raise ValidationError(_("bscp_config 必须是字典类型")) |
| 246 | + |
| 247 | + for key, value in bscp_config.items(): |
| 248 | + # 校验字段名是否合法 |
| 249 | + if key not in allowed_fields: |
| 250 | + raise ValidationError( |
| 251 | + _("不支持的 BSCP 配置项: {key}").format(key=key) |
| 252 | + ) |
| 253 | + |
| 254 | + # 校验字段类型 |
| 255 | + expected_type = allowed_fields[key] |
| 256 | + if not isinstance(value, expected_type): |
| 257 | + raise ValidationError( |
| 258 | + _("BSCP 配置项 {key} 类型错误,期望 {expected_type},实际为 {actual_type}").format( |
| 259 | + key=key, |
| 260 | + expected_type=expected_type.__name__, |
| 261 | + actual_type=type(value).__name__, |
| 262 | + ) |
| 263 | + ) |
| 264 | + |
| 265 | + # 如果是路径类型的字段,进行安全校验 |
| 266 | + if key in ["config_path", "log_path", "data_path"]: |
| 267 | + if isinstance(value, str): |
| 268 | + # 检查危险字符 |
| 269 | + for pattern in dangerous_patterns: |
| 270 | + if pattern in value: |
| 271 | + raise ValidationError( |
| 272 | + _("BSCP 配置项 {key} 包含不安全的字符: {pattern}").format( |
| 273 | + key=key, pattern=pattern |
| 274 | + ) |
| 275 | + ) |
| 276 | + |
| 277 | + # 检查绝对路径 |
| 278 | + if not value.startswith("/"): |
| 279 | + raise ValidationError( |
| 280 | + _("BSCP 配置项 {key} 必须是绝对路径").format(key=key) |
| 281 | + ) |
| 282 | + |
| 283 | + return bscp_config |
| 284 | + |
| 285 | + |
32 | 286 | class ListSerializer(serializers.ModelSerializer): |
33 | 287 | """ |
34 | 288 | AP返回数据 |
@@ -120,6 +374,18 @@ class ZKSerializer(serializers.Serializer): |
120 | 374 | callback_url = serializers.CharField(label=_("节点管理内网回调地址"), required=False, allow_blank=True) |
121 | 375 |
|
122 | 376 | def validate(self, data): |
| 377 | + # 校验 agent_config 的安全性 |
| 378 | + if "agent_config" in data: |
| 379 | + data["agent_config"] = validate_agent_config(data["agent_config"]) |
| 380 | + |
| 381 | + # 校验 port_config 的安全性 |
| 382 | + if "port_config" in data: |
| 383 | + data["port_config"] = validate_port_config(data["port_config"]) |
| 384 | + |
| 385 | + # 校验 bscp_config 的安全性 |
| 386 | + if "bscp_config" in data and data["bscp_config"]: |
| 387 | + data["bscp_config"] = validate_bscp_config(data["bscp_config"]) |
| 388 | + |
123 | 389 | gse_version_list: List[str] = list(set(AccessPoint.objects.values_list("gse_version", flat=True))) |
124 | 390 | # 存量接入点版本全部为V2新建/更新版本也为V2版本 |
125 | 391 | if GseVersion.V1.value not in gse_version_list: |
|
0 commit comments