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
2 changes: 1 addition & 1 deletion app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ introduction: 通过节点管理,可以对蓝鲸体系中的gse agent进行管
introduction_en: NodeMan can be used to manage the gse agent in the BlueKing
system. Its functions include agent installation, status query, version
update, plugin management, health check, process control, and so on.
version: 2.4.11
version: 2.4.12
category: 运维工具
language_support: 英语,中文
desktop:
Expand Down
5 changes: 3 additions & 2 deletions apps/backend/plugin/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,11 +245,12 @@ def list_package_infos(file_path: str) -> List[Dict[str, Any]]:
)
)
raise exceptions.PluginParseError(_("文件包含非法路径成员 -> {name},请检查").format(name=file_info.name))

# 安全解压
safe_extract(tf, path=tmp_dir)
logger.info(
"file-> {file_path} extract to path -> {tmp_dir} success.".format(file_path=file_path, tmp_dir=tmp_dir)
)
# 安全解压
safe_extract(tf, path=tmp_dir)

package_infos = []

Expand Down
12 changes: 11 additions & 1 deletion apps/backend/subscription/steps/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,9 @@ def _push_migrate_reason(_instance_id: str, **_extra_info):

action_dict = self.get_action_dict()
ap_id_obj_map = models.AccessPoint.ap_id_obj_map()
allowed_version_change_to_upgrade_biz_list = models.GlobalSettings.get_config(
key=models.GlobalSettings.KeyEnum.SUBSCRIPTION_ALLOWED_VERSION_CHANGE_TO_UPGRADE.value, default=[]
)
for group_id, host_key__proc_status_map in list(group_id__host_key__proc_status_map.items()):
_id = int(tools.parse_group_id(group_id)["id"])
instance_id = tools.create_node_id(
Expand Down Expand Up @@ -862,7 +865,14 @@ def _push_migrate_reason(_instance_id: str, **_extra_info):
)

# 如果下发版本发生变化,则重新下发
if self.subscription.category == constants.SubscriptionType.POLICY:
if any(
[
self.subscription.category == constants.SubscriptionType.POLICY,
not self.plugin_desc.is_official
and self.subscription.bk_biz_id in allowed_version_change_to_upgrade_biz_list,
]
):

if check_version_result["has_change"]:
instance_actions[instance_id] = action_dict["install_action"]
_push_migrate_reason(
Expand Down
28 changes: 25 additions & 3 deletions apps/core/ipchooser/handlers/host_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,17 +313,39 @@ def details(
:return:
"""
bk_host_id_set: typing.Set[int] = set()
cloud_inner_ip_set: typing.Set[str] = set()
bk_cloud_ip_map: typing.Dict[str, typing.Dict[str, typing.Union[int, str]]] = {}

for host_info in host_list:
# 优先取主机 ID 作为查询条件
if "host_id" in host_info:
bk_host_id_set.add(host_info["host_id"])
else:
cloud_inner_ip_set.add(f"{host_info['cloud_id']}{constants.CommonEnum.SEP.value}{host_info['ip']}")
cloud_id = host_info["cloud_id"]
ip = host_info["ip"]
key = f"{cloud_id}{constants.CommonEnum.SEP.value}{ip}"
bk_cloud_ip_map[key] = {"bk_cloud_id": cloud_id, "bk_host_innerip": ip}

if bk_cloud_ip_map:
bk_cloud_ids: typing.Set[int] = {info["bk_cloud_id"] for info in bk_cloud_ip_map.values()}
bk_host_innerips: typing.Set[str] = {info["bk_host_innerip"] for info in bk_cloud_ip_map.values()}
query_hosts_params: typing.Dict[str, typing.Any] = {
"fields": ["bk_host_id"],
"host_property_filter": {
"condition": "AND",
"rules": [
{"field": "bk_host_innerip", "operator": "in", "value": list(bk_host_innerips)},
{"field": "bk_cloud_id", "operator": "in", "value": list(bk_cloud_ids)},
],
},
}
cmdb_host_infos: typing.List[typing.Dict[str, typing.Any]] = batch_request.batch_request(
func=CCApi.list_hosts_without_biz, params=query_hosts_params
)
for host_info in cmdb_host_infos:
bk_host_id_set.add(host_info["bk_host_id"])

# 构造逻辑或查询条件
or_conditions: typing.List[types.Condition] = [
{"key": "bk_host_id", "val": bk_host_id_set},
{"key": "cloud_inner_ip", "val": cloud_inner_ip_set},
]
return cls.details_base(scope_list, or_conditions, show_agent_realtime_state=show_agent_realtime_state)
23 changes: 23 additions & 0 deletions apps/node_man/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
)
from apps.utils import basic, files, orm, translation
from apps.utils.cache import class_member_cache
from apps.utils.security import is_safe_url
from common.log import logger
from env.constants import GseVersion
from pipeline.parser import PipelineParser
Expand Down Expand Up @@ -185,6 +186,11 @@ class KeyEnum(Enum):
UPDATE_SUBSCRIPTION_RECORDS_LENGTH = "UPDATE_SUBSCRIPTION_RECORDS_LENGTH"
# 禁用的订阅
DISABLED_SUBSCRIPTIONS = "DISABLED_SUBSCRIPTIONS"
# 禁用的订阅
SUBSCRIPTION_ALLOWED_VERSION_CHANGE_TO_UPGRADE = "SUBSCRIPTION_ALLOWED_VERSION_CHANGE_TO_UPGRADE"
# 接入点url端口黑名单
AP_BLOCKED_PORTS = "AP_BLOCKED_PORTS"
AP_BLOCKED_NETWORKS = "AP_BLOCKED_NETWORKS"

key = models.CharField(_("键"), max_length=255, db_index=True, primary_key=True)
v_json = JSONField(_("值"))
Expand Down Expand Up @@ -728,6 +734,23 @@ def _check_callback_url(url: str, _logs: list):
for server in params.get("btfileserver", []) + params.get("dataserver", []) + params.get("taskserver", []):
detect_hosts.add(server.get("inner_ip") or server.get("inner_ipv6"))

blocked_ports: list = GlobalSettings.get_config(key=GlobalSettings.KeyEnum.AP_BLOCKED_PORTS.value, default=[])
blocked_networks: list = GlobalSettings.get_config(
key=GlobalSettings.KeyEnum.AP_BLOCKED_NETWORKS.value, default=[]
)
is_ok, message = is_safe_url(
[
params["package_inner_url"],
params["package_outer_url"],
params.get("outer_callback_url", ""),
params.get("callback_url", ""),
],
blocked_ports=blocked_ports,
blocked_networks=blocked_networks,
)
if not is_ok:
raise ValidationError(message)

with ThreadPoolExecutor(max_workers=settings.CONCURRENT_NUMBER) as ex:
tasks = [ex.submit(_check_ip, detect_host, test_logs) for detect_host in detect_hosts]
tasks.append(ex.submit(_check_package_url, params["package_inner_url"], test_logs))
Expand Down
21 changes: 20 additions & 1 deletion apps/node_man/serializers/ap.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@
OsType,
)
from apps.node_man.handlers.iam import IamHandler
from apps.node_man.models import AccessPoint
from apps.node_man.models import AccessPoint, GlobalSettings
from apps.utils import basic
from apps.utils.local import get_request_username
from apps.utils.security import is_safe_url
from env.constants import GseVersion


Expand Down Expand Up @@ -124,6 +125,24 @@ def validate(self, data):
if GseVersion.V1.value not in gse_version_list:
data["gse_version"] = GseVersion.V2.value
data["port_config"] = GSE_V2_PORT_DEFAULT_VALUE

blocked_ports: list = GlobalSettings.get_config(key=GlobalSettings.KeyEnum.AP_BLOCKED_PORTS.value, default=[])
blocked_networks: list = GlobalSettings.get_config(
key=GlobalSettings.KeyEnum.AP_BLOCKED_NETWORKS.value, default=[]
)
is_ok, message = is_safe_url(
[
data["package_inner_url"],
data["package_outer_url"],
data.get("outer_callback_url", ""),
data.get("callback_url", ""),
],
blocked_ports=blocked_ports,
blocked_networks=blocked_networks,
)
if not is_ok:
raise ValidationError(message)

return data

class Meta:
Expand Down
64 changes: 64 additions & 0 deletions apps/utils/security.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
"""
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-节点管理(BlueKing-BK-NODEMAN) available.
Copyright (C) 2017-2022 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at https://opensource.org/licenses/MIT
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.
"""

import ipaddress
import socket
from urllib.parse import urlparse


def is_safe_url(url_list: list, blocked_ports: list = [], blocked_networks: list = []) -> bool:

# 解析并验证网段列表
blocked_ipsets = []
for network in blocked_networks:
try:
blocked_ipsets.append(ipaddress.ip_network(network, strict=False))
except ValueError:
raise ValueError(f"Invalid network CIDR: {network}")

try:
for url in url_list:
if not url:
continue
parsed = urlparse(url)

# 限定协议
if parsed.scheme not in ["http", "https"]:
return False, f"Invalid scheme: {parsed.scheme}"

# 限定域名
hostname = parsed.hostname
if not hostname:
return False, "Missing hostname"

# 解析真实 IP
ip = socket.gethostbyname(hostname)
ip_obj = ipaddress.ip_address(ip)

# 网段限制检查
for network in blocked_ipsets:
if ip_obj in network:
return False, f"Blocked network: {ip} in {network}"

# 回环、链路本地地址
if ip_obj.is_loopback:
return False, "Loopback address detected"
if ip_obj.is_link_local:
return False, "Link local address detected"

# 端口限制
if parsed.port is not None:
if parsed.port in blocked_ports:
return False, f"Blocked port: {parsed.port}"
else:
return True, "All URLs are safe"
except Exception:
return False, "URL validation failed"
11 changes: 11 additions & 0 deletions docs/release.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Release

## 2.4.11 版本更新日志

修复

- fix: 修复jinja2模板引入方式 (closed #2603)



**Full Release Notes**: https://github.com/TencentBlueKing/bk-nodeman/compare/v2.4.10...v2.4.11


## 2.4.10 版本更新日志

修复
Expand Down
11 changes: 11 additions & 0 deletions release/V2.4.11_20250704.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

## 2.4.11 版本更新日志

修复

- fix: 修复jinja2模板引入方式 (closed #2603)



**Full Release Notes**: https://github.com/TencentBlueKing/bk-nodeman/compare/v2.4.10...v2.4.11

Loading