From 01ac2cf5729162bb13ecdff34fafbce549187c27 Mon Sep 17 00:00:00 2001 From: duguwanglong Date: Fri, 15 May 2026 14:45:15 +0800 Subject: [PATCH] feat(skills): add sangfor-edr-use and sangfor-xdr-use skills MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move sangfor-edr-use and sangfor-xdr-use from docs/ into the standard skills directory (.flocks/plugins/skills/). Changes made during review: - edr: remove incorrect API mode section; EDR has no open API, all operations must go through browser/CDP - xdr: fix wrong script name in pitfalls table (cdp_fetch.py -> fetch_xdr_system_state.py) - xdr: fix API vs CDP comparison table — system state row had CDP script listed under API column, now shows ❌ - xdr: fix ambiguous "request confirmation" wording; now clearly states API is default and fallback to browser only on failure - both: replace hardcoded Windows absolute paths in execution examples with cross-platform placeholders ( / ), and add macOS/Linux parallel examples - both: use tempfile.gettempdir() for daemon port file path instead of hardcoded C:/Users/Administrator/AppData/Local/Temp - both: add macOS/Linux Chrome/Edge remote debugging start commands - xdr: rename dict key data接入 -> data_ingestion to avoid mixed Chinese/English key names - both: move inline `import re` to module top level Co-authored-by: Cursor --- .../plugins/skills/sangfor-edr-use/SKILL.md | 86 +++++++ .../references/cdp-workflow.md | 208 +++++++++++++++ .../references/fetch_edr_system_state.py | 173 +++++++++++++ .../plugins/skills/sangfor-xdr-use/SKILL.md | 191 ++++++++++++++ .../references/cdp-workflow.md | 208 +++++++++++++++ .../references/fetch_xdr_system_state.py | 238 ++++++++++++++++++ .../references/xdr-inspection-template.md | 106 ++++++++ 7 files changed, 1210 insertions(+) create mode 100644 .flocks/plugins/skills/sangfor-edr-use/SKILL.md create mode 100644 .flocks/plugins/skills/sangfor-edr-use/references/cdp-workflow.md create mode 100644 .flocks/plugins/skills/sangfor-edr-use/references/fetch_edr_system_state.py create mode 100644 .flocks/plugins/skills/sangfor-xdr-use/SKILL.md create mode 100644 .flocks/plugins/skills/sangfor-xdr-use/references/cdp-workflow.md create mode 100644 .flocks/plugins/skills/sangfor-xdr-use/references/fetch_xdr_system_state.py create mode 100644 .flocks/plugins/skills/sangfor-xdr-use/references/xdr-inspection-template.md diff --git a/.flocks/plugins/skills/sangfor-edr-use/SKILL.md b/.flocks/plugins/skills/sangfor-edr-use/SKILL.md new file mode 100644 index 00000000..e2d7f41f --- /dev/null +++ b/.flocks/plugins/skills/sangfor-edr-use/SKILL.md @@ -0,0 +1,86 @@ +--- +name: sangfor-edr-use +description: 用于处理深信服 EDR(终端检测与响应)相关任务,通过浏览器(CDP 直连)进行以下任务:终端状态查询、终端概况统计、失陷设备排查、设备运行状态查看等。只要用户提到 深信服 EDR、EDR、sangfor EDR 等需求时,必须先加载本 skill。本 skill 是 EDR 平台操作的唯一决策入口:在未阅读本 skill 前,不要直接使用 browser-use skill。 +--- + +# 深信服 EDR Use + +## First + +> ⚠️ **EDR 没有开放 API**,所有操作必须通过浏览器(CDP 直连)完成。 + +进入浏览器模式前,**必须询问用户 EDR URL**(如 `https://edr.example.com/`),然后阅读浏览器模式使用指南。 + +## 浏览器模式使用指南 + +请阅读以下文档获取完整流程: +- [references/cdp-workflow.md](references/cdp-workflow.md) + +### CDP 模式适用场景 + +- **首页仪表盘**(`/ui/#/index`):设备 CPU/内存/硬盘使用率、终端概况(在线/离线/服务器/PC)、失陷设备统计 +- **威胁资产分析**:已失陷终端列表(需点击"已失陷终端"标签页,不是默认的"全部") +- 页面详情、交互式筛选 + +### 可用工具脚本 + +| 脚本路径 | 功能 | 必需参数 | +|---------|------|---------| +| `references/fetch_edr_system_state.py` | 设备状态抓取 | `--url {EDR_URL}` | + +脚本位于 skill 目录的 `references/` 下,无硬编码 URL 或敏感信息。 + +### 执行示例 + +脚本位于 `/skills/sangfor-edr-use/references/fetch_edr_system_state.py`,请按当前平台选择对应命令。 + +**Windows(PowerShell)** + +```powershell +powershell -Command "& '\Scripts\python.exe' '\skills\sangfor-edr-use\references\fetch_edr_system_state.py' --url '{EDR_URL}'" +``` + +**macOS / Linux(bash / zsh)** + +```bash +"/bin/python" "/skills/sangfor-edr-use/references/fetch_edr_system_state.py" --url "{EDR_URL}" +``` + +**占位符说明** + +| 占位符 | Windows 典型值 | macOS/Linux 典型值 | +|--------|---------------|-------------------| +| `` | `D:\Flocks Project\flocks\.venv` | `~/Flocks/flocks/.venv`(取决于实际安装位置) | +| `` | `%USERPROFILE%\.flocks\plugins` | `~/.flocks/plugins` | + +> 必须使用 Flocks 虚拟环境(`.venv`)执行;系统 Python 可能缺少依赖。如不确定 venv 位置,先执行 `flocks --version` 或检查 Flocks 安装目录。 + +## 关键坑点(必须避免) + +| 坑 | 原因 | 解法 | +|---|---|---| +| `flocks browser -c js(...)` 返回空文本 | daemon session 指向错误的 tab | 用 Python socket 直连 daemon,通过 `Runtime.evaluate` 在正确 context 执行 | +| `flocks browser -c new_tab()` 后后续命令无响应 | tab 切换导致 session 错位 | 用 `switch_tab(targetId)` 明确切到 EDR tab | +| 多行代码转义失败 | PowerShell 引号嵌套 | 使用 `fetch_edr_system_state.py` 脚本,无需手动转义 | +| EDR 页面数据为空 | EDR 内容在跨域 iframe 中 | 用 CDP direct 方式 attach 到 EDR tab,在正确 frame context 执行 JS | +| 失陷设备数量不匹配 | 读取的是"全部"筛选而非"已失陷"筛选 | 需点击"已失陷终端"标签页获取准确数量 | + +## 失陷设备查询 SOP + +**问题**:首页仪表盘显示"已失陷 N 台",但威胁资产分析页面默认只列出部分。 + +**成功路径**: +1. 在 EDR 首页仪表盘确认"已失陷 N 台"的数量 +2. 如需具体清单,进入 `威胁资产分析` → 点击 `已失陷终端` 标签页(不是默认的"全部") +3. 只有点击"已失陷终端"标签页,列表数量才会与仪表盘一致 + +**⚠️ 必须避免**:直接读取威胁资产分析的默认"全部"筛选结果作为失陷设备清单,这是错误的。 + +## 执行规范 + +**必须使用 Flocks 虚拟环境(`.venv`)执行 Python 脚本,禁止使用系统 Python。** + +- ✅ 正确:`/bin/python`(Unix)或 `\Scripts\python.exe`(Windows) +- ❌ 禁止:`python script.py` / `python3 script.py`(直接调用 PATH 中的 Python) + +**原因**:Flocks 虚拟环境包含了所有项目依赖,系统 Python 可能缺少必要的包。完整跨平台示例见上一节"执行示例"。 \ No newline at end of file diff --git a/.flocks/plugins/skills/sangfor-edr-use/references/cdp-workflow.md b/.flocks/plugins/skills/sangfor-edr-use/references/cdp-workflow.md new file mode 100644 index 00000000..e8ab1650 --- /dev/null +++ b/.flocks/plugins/skills/sangfor-edr-use/references/cdp-workflow.md @@ -0,0 +1,208 @@ +# 深信服 EDR CDP 直连浏览器流程 + +## 环境信息 + +| 项目 | 值 | +|------|-----| +| **Browser daemon port 文件** | `{tempfile.gettempdir()}/bu-default.port`(由 Python `tempfile.gettempdir()` 解析,跨平台) | +| **EDR 地址** | **需用户提供**(无默认值) | +| **目标页面 URL** | `{EDR_URL}/ui/#/index`(首页仪表盘) | + +## 零、前置条件 + +### 1. 确保浏览器 daemon 可用 +```bash +flocks browser --doctor +``` + +如果 `active browser connections` 为 0,需用户开启浏览器 remote debugging。 + +### 2. 开启 Chrome Remote Debugging + +**Windows** +```powershell +# Chrome +chrome.exe --remote-debugging-port=9222 +# Edge +msedge.exe --remote-debugging-port=9222 +``` + +**macOS** +```bash +# Chrome +"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --remote-debugging-port=9222 +# Edge +"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge" --remote-debugging-port=9222 +``` + +**Linux** +```bash +google-chrome --remote-debugging-port=9222 +# 或 +chromium --remote-debugging-port=9222 +``` + +### 3. 登录 EDR +确保用户在 Chrome 中已登录 EDR(如需 MFA,完成认证)。 + +--- + +## 一、执行流程 + +### Step 1:确认 EDR URL +**必须询问用户 EDR 地址**,例如: +- `https://edr.example.com/` +- `https://edr.company.com/` + +### Step 2:检查 daemon +```bash +flocks browser --doctor +``` + +如果 daemon 未运行,执行: +```bash +flocks browser --setup +``` + +### Step 3:用户打开目标页面 +用户在 Chrome 中打开: +``` +{EDR_URL}/ui/#/index +``` + +### Step 4:执行抓取脚本 + +**工具脚本路径**(位于 skill references 目录): +``` +references/fetch_edr_system_state.py +``` + +**执行命令(按平台选择):** + +```powershell +# Windows PowerShell +powershell -Command "& '\Scripts\python.exe' '\skills\sangfor-edr-use\references\fetch_edr_system_state.py' --url '{EDR_URL}'" +``` + +```bash +# macOS / Linux +"/bin/python" "/skills/sangfor-edr-use/references/fetch_edr_system_state.py" --url "{EDR_URL}" +``` + +占位符 `` / `` 含义见 SKILL.md "执行示例"。 + +**参数说明:** +| 参数 | 必需 | 默认值 | 说明 | +|------|------|--------|------| +| `--url` | 是 | - | EDR URL,如 `https://edr.example.com/` | +| `--wait` | 否 | 3 | 等待页面渲染秒数 | +| `--raw` | 否 | False | 输出原始页面文本 | + +--- + +## 二、手动 CDP Socket 方式 + +> 当脚本不可用时,使用此方式。 + +```python +import json +import socket +import tempfile +import time +from pathlib import Path + +port_file = Path(tempfile.gettempdir()) / "bu-default.port" +port = int(port_file.read_text().strip()) + +def send_cmd(sock, cmd): + sock.sendall((json.dumps(cmd) + "\n").encode()) + data = b"" + while not data.endswith(b"\n"): + chunk = sock.recv(8192) + if not chunk: + break + data += chunk + return json.loads(data) + +sock = socket.socket() +sock.settimeout(15) +sock.connect(("127.0.0.1", port)) + +targets = send_cmd(sock, {"method": "Target.getTargets"})["result"]["targetInfos"] + +edr_url = "{EDR_URL}" +host = edr_url.replace("https://", "").replace("http://", "").rstrip("/") + +edr_tab = next((t for t in targets if host in t.get("url", "") and "#/index" in t.get("url", "")), None) + +if not edr_tab: + edr_tab = next((t for t in targets if host in t.get("url", "") and t.get("type") == "page"), None) + +if not edr_tab: + print(f"EDR tab not found. Please open: {edr_url}/ui/#/index") + exit(1) + +attach = send_cmd(sock, {"method": "Target.attachToTarget", "params": {"targetId": edr_tab["targetId"], "flatten": True}}) +session_id = attach["result"]["sessionId"] + +time.sleep(3) + +text_result = send_cmd(sock, {"method": "Runtime.evaluate", "params": {"expression": "document.body.innerText"}, "session_id": session_id}) +print(text_result["result"]["result"]["value"]) +``` + +--- + +## 三、页面数据提取 + +从 `page_text` 中按关键词提取: + +| 数据 | 关键词 | +|------|--------| +| CPU使用率 | `CPU:` 或 `CPU:` | +| 内存使用率 | `内存:` 或 `内存:` | +| 硬盘使用率 | `硬盘:` 或 `硬盘:` | +| 终端总数 | `受管控终端` | +| 在线/离线/其它 | `在线:` / `离线:` / `其它:` | +| 服务器/PC | `服务器:` / `PC:` | +| 已失陷/高可疑/低可疑 | `已失陷 N 台` / `高可疑 N 台` / `低可疑 N 台` | + +--- + +## 四、关键坑点 + +| 坑 | 原因 | 解法 | +|---|---|---| +| `Target.getTargets` 返回空 | 浏览器未开启 remote debugging | 用户执行 `chrome.exe --remote-debugging-port=9222` | +| EDR tab 未找到 | 页面未打开或 URL 不匹配 | 确保 Chrome 中打开了 EDR 首页 | +| 页面数据为空 | EDR 内容在跨域 iframe 中 | 用 CDP direct 方式 attach 到 EDR tab,在正确 frame context 执行 JS | +| 页面显示登录框 | 会话已失效 | 告知用户重新登录 EDR | + +--- + +## 五、执行规范 + +**必须使用 Flocks 虚拟环境(`.venv`)执行 Python 脚本,禁止使用系统 Python。** + +- ✅ 正确:`/bin/python`(Unix)或 `\Scripts\python.exe`(Windows) +- ❌ 禁止:`python script.py` / `python3 script.py` + +--- + +## 六、可用工具脚本 + +| 脚本路径 | 功能 | 必需参数 | +|---------|------|---------| +| `references/fetch_edr_system_state.py` | 设备状态抓取 | `--url {EDR_URL}` | + +### 执行示例 + +```powershell +# Windows +powershell -Command "& '\Scripts\python.exe' '\skills\sangfor-edr-use\references\fetch_edr_system_state.py' --url 'https://edr.example.com/'" +``` + +```bash +# macOS / Linux +"/bin/python" "/skills/sangfor-edr-use/references/fetch_edr_system_state.py" --url "https://edr.example.com/" +``` \ No newline at end of file diff --git a/.flocks/plugins/skills/sangfor-edr-use/references/fetch_edr_system_state.py b/.flocks/plugins/skills/sangfor-edr-use/references/fetch_edr_system_state.py new file mode 100644 index 00000000..c554aefd --- /dev/null +++ b/.flocks/plugins/skills/sangfor-edr-use/references/fetch_edr_system_state.py @@ -0,0 +1,173 @@ +import argparse +import json +import re +import socket +import tempfile +import time +from pathlib import Path + +def send_cmd(sock, cmd): + sock.sendall((json.dumps(cmd) + "\n").encode()) + data = b"" + while not data.endswith(b"\n"): + chunk = sock.recv(8192) + if not chunk: + break + data += chunk + return json.loads(data) + +def send_cmd_new_conn(port, cmd): + sock = socket.socket() + sock.settimeout(15) + sock.connect(("127.0.0.1", port)) + result = send_cmd(sock, cmd) + sock.close() + return result + +def find_edr_tab(targets, edr_url): + host = edr_url.replace("https://", "").replace("http://", "").rstrip("/") + for t in targets: + url = t.get("url", "") + if host in url and "#/index" in url: + return t + for t in targets: + url = t.get("url", "") + if host in url and t.get("type") == "page": + return t + return None + +def parse_edr_status(text): + result = { + "device_status": {"cpu": None, "memory": None, "disk": None}, + "terminal_overview": {"total": None, "online": None, "offline": None, "other": None, "server": None, "pc": None}, + "compromised": {"count": None, "high_suspicious": None, "low_suspicious": None} + } + + cpu_match = re.search(r'CPU[::]\s*([\d.]+)%', text) + if cpu_match: + result["device_status"]["cpu"] = cpu_match.group(1) + "%" + + mem_match = re.search(r'内存[::]\s*([\d.]+)%', text) + if mem_match: + result["device_status"]["memory"] = mem_match.group(1) + "%" + + disk_match = re.search(r'硬盘[::]\s*([\d.]+)%', text) + if disk_match: + result["device_status"]["disk"] = disk_match.group(1) + "%" + + compromised_match = re.search(r'已失陷\s*(\d+)', text) + if compromised_match: + result["compromised"]["count"] = compromised_match.group(1) + + high_match = re.search(r'高可疑\s*(\d+)', text) + if high_match: + result["compromised"]["high_suspicious"] = high_match.group(1) + + low_match = re.search(r'低可疑\s*(\d+)', text) + if low_match: + result["compromised"]["low_suspicious"] = low_match.group(1) + + total_match = re.search(r'受管控终端\s*\n\s*(\d+)', text) + if total_match: + result["terminal_overview"]["total"] = total_match.group(1) + else: + total_match2 = re.search(r'(\d+)\s*\n\s*受管控终端', text) + if total_match2: + result["terminal_overview"]["total"] = total_match2.group(1) + + online_match = re.search(r'在线[::]\s*(\d+)', text) + if online_match: + result["terminal_overview"]["online"] = online_match.group(1) + + offline_match = re.search(r'离线[::]\s*(\d+)', text) + if offline_match: + result["terminal_overview"]["offline"] = offline_match.group(1) + + other_match = re.search(r'其它[::]\s*(\d+)', text) + if other_match: + result["terminal_overview"]["other"] = other_match.group(1) + + server_match = re.search(r'服务器[::]\s*(\d+)', text) + if server_match: + result["terminal_overview"]["server"] = server_match.group(1) + + pc_match = re.search(r'PC[::]\s*(\d+)', text) + if pc_match: + result["terminal_overview"]["pc"] = pc_match.group(1) + + return result + +def main(): + parser = argparse.ArgumentParser(description="Fetch EDR system status via CDP") + parser.add_argument("--url", required=True, help="EDR URL (e.g. https://edr.example.com/)") + parser.add_argument("--wait", type=int, default=3, help="Wait seconds for page render (default: 3)") + parser.add_argument("--raw", action="store_true", help="Print raw page text") + args = parser.parse_args() + + port_file = Path(tempfile.gettempdir()) / "bu-default.port" + if not port_file.exists(): + print("ERROR: Browser daemon port file not found") + print("Please run: flocks browser --setup") + exit(1) + + port = int(port_file.read_text().strip()) + + targets_result = send_cmd_new_conn(port, {"method": "Target.getTargets"}) + targets = targets_result.get("result", {}).get("targetInfos", []) + + edr_tab = find_edr_tab(targets, args.url) + if not edr_tab: + target_url = args.url.rstrip("/") + "/ui/#/index" + print(f"EDR tab not found. Please open: {target_url}") + exit(1) + + print(f"Found EDR tab: {edr_tab['targetId']}") + + attach_result = send_cmd_new_conn(port, { + "method": "Target.attachToTarget", + "params": {"targetId": edr_tab["targetId"], "flatten": True} + }) + session_id = attach_result["result"]["sessionId"] + + time.sleep(args.wait) + + text_result = send_cmd_new_conn(port, { + "method": "Runtime.evaluate", + "params": {"expression": "document.body.innerText"}, + "session_id": session_id + }) + page_text = text_result["result"]["result"]["value"] + + if args.raw: + print(page_text) + return + + result = parse_edr_status(page_text) + + print("\n### EDR Device Status\n") + print("#### Device Status") + print(f"| Metric | Value |") + print(f"|--------|-------|") + print(f"| CPU | {result['device_status']['cpu'] or '-'} |") + print(f"| Memory | {result['device_status']['memory'] or '-'} |") + print(f"| Disk | {result['device_status']['disk'] or '-'} |") + + print("\n#### Terminal Overview") + print(f"| Metric | Value |") + print(f"|--------|-------|") + print(f"| Total | {result['terminal_overview']['total'] or '-'} |") + print(f"| Online | {result['terminal_overview']['online'] or '-'} |") + print(f"| Offline | {result['terminal_overview']['offline'] or '-'} |") + print(f"| Other | {result['terminal_overview']['other'] or '-'} |") + print(f"| Server | {result['terminal_overview']['server'] or '-'} |") + print(f"| PC | {result['terminal_overview']['pc'] or '-'} |") + + print("\n#### Compromised Status") + print(f"| Metric | Value |") + print(f"|--------|-------|") + print(f"| Compromised | {result['compromised']['count'] or '-'} |") + print(f"| High Suspicious | {result['compromised']['high_suspicious'] or '-'} |") + print(f"| Low Suspicious | {result['compromised']['low_suspicious'] or '-'} |") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/.flocks/plugins/skills/sangfor-xdr-use/SKILL.md b/.flocks/plugins/skills/sangfor-xdr-use/SKILL.md new file mode 100644 index 00000000..710cd9af --- /dev/null +++ b/.flocks/plugins/skills/sangfor-xdr-use/SKILL.md @@ -0,0 +1,191 @@ +--- +name: sangfor-xdr-use +description: 用于处理深信服 XDR(扩展检测与响应)相关任务,适合通过 API 或者结合浏览器进行以下任务:告警查询与处置、事件调查与响应、脆弱性管理、资产盘点、主机隔离、白名单管理、系统运维状态查看、节点健康监控等。只要用户提到 深信服 XDR、XDR、sangfor XDR 等需求时,必须先加载本 skill。本 skill 是 XDR 平台操作的唯一决策入口:在未阅读本 skill 并完成模式判断前,不要直接调用任何 `sangfor_xdr_*` tool 或使用 browser-use skill。 +--- + +# 深信服 XDR Use + +## First + +操作模式 API V.S Browser + +### 何时使用 API(默认) +- 默认模式,默认使用 API +- API 覆盖大部分运营场景(告警、事件、资产、漏洞、响应、白名单) + +### 何时使用浏览器(CDP 直连) +- API 没有覆盖目标能力(如**系统运维页面/节点状态/CPU内存磁盘趋势**没有 API) +- 需要查看页面详情、交互式筛选、图表数据 +- 页面需要人工登录、验证码、多因子认证 +- 用户要求使用浏览器,或已在浏览器操作过程中 + +### 请求确认 + +- 默认走 API 模式;仅当 API 调用失败(如 401/403/连接失败)或目标能力不在 API 覆盖范围内时,再提示用户:"XDR API 不可用,请检查配置或切换到浏览器模式"。 +- 用户明确要求使用浏览器时,直接进入浏览器模式。 + +确定模式后: +- API 模式 → 阅读 API 模式使用指南 +- 浏览器模式 → 阅读浏览器模式使用指南 + +## API 模式使用指南 + +### 工具速查 + +| 工具 | 能力 | 适用场景 | +|------|------|---------| +| `sangfor_xdr_alerts` | 告警列表查询 / 处置状态修改 / 举证信息 | 安全事件分诊、告警运营 | +| `sangfor_xdr_incidents` | 事件列表查询 / 处置状态修改 / 举证信息 / 关联实体 | 事件响应、攻击故事线分析 | +| `sangfor_xdr_vulns` | 基线合规 / 漏洞列表 / 弱密码列表 / 修复状态 | 漏洞管理、合规检查 | +| `sangfor_xdr_assets` | 资产列表 / IP段树 / 资产分类 / 接入设备 / 部门树 | 资产盘点、攻击面管理 | +| `sangfor_xdr_responses` | 查询已隔离主机 / 解除隔离 | 事件遏制、恢复 | +| `sangfor_xdr_whitelists` | 白名单 CRUD + 启用/禁用切换 | 抑制已知合规告警 | + +### 路由规则 + +| 用户意图 | 推荐工具 | 常用 action | +|---------|---------|------------| +| 查告警、告警列表、分诊 | `sangfor_xdr_alerts` | `list` | +| 修改告警处置状态 | `sangfor_xdr_alerts` | `update_status` | +| 查告警当前处置状态 | `sangfor_xdr_alerts` | `status_list` | +| 查告警举证信息 | `sangfor_xdr_alerts` | `get_proof` | +| 查事件、安全事件 | `sangfor_xdr_incidents` | `list` | +| 修改事件处置状态 | `sangfor_xdr_incidents` | `update_status` | +| 查事件关联实体(主机/IP/文件/进程/DNS) | `sangfor_xdr_incidents` | `get_entities` | +| 查事件举证信息 | `sangfor_xdr_incidents` | `get_proof` | +| 查漏洞/基线合规 | `sangfor_xdr_vulns` | `baseline` / `vuln_list` | +| 查弱密码 | `sangfor_xdr_vulns` | `vuln_list(data_type="weakpwd")` | +| 修改漏洞修复状态 | `sangfor_xdr_vulns` | `update_status` | +| 查数据源设备 | `sangfor_xdr_vulns` | `source_device` | +| 查资产列表 | `sangfor_xdr_assets` | `list` | +| 查 IP 段树结构 | `sangfor_xdr_assets` | `ip_segment_tree` | +| 查资产分类/类型 | `sangfor_xdr_assets` | `asset_class` | +| 查接入设备 | `sangfor_xdr_assets` | `device_list` | +| 查部门组织树 | `sangfor_xdr_assets` | `department_tree` | +| 删除资产 | `sangfor_xdr_assets` | `delete` | +| 查已隔离主机 | `sangfor_xdr_responses` | `isolate_list` | +| 解除主机隔离 | `sangfor_xdr_responses` | `unisolate` | +| 查/增/改/删白名单 | `sangfor_xdr_whitelists` | `list` / `create` / `update` / `delete` | +| 切换白名单启用状态 | `sangfor_xdr_whitelists` | `toggle_status` | +| 系统运维、节点状态、CPU/内存/磁盘/IO趋势 | **CDP 脚本** | 见浏览器模式 | + +### 时间参数说明 + +- 时间字段支持 Unix 秒级时间戳或 ISO8601 字符串(如 `"2024-01-01T00:00:00"`) +- 默认时间范围:最近 24 小时 +- 需精确时间范围时,先用 `uv run python` 计算时间戳再传参 + +### 关键返回字段 + +**告警(list)** +- `uuId`:告警唯一ID +- `name`:告警名称 +- `riskLevel`:风险等级(0=严重 1=高危 2=中危 3=低危 4=信息) +- `severity`:严重性分值(0-100) +- `srcIp / dstIp`:源/目的IP +- `dealStatus`:处置状态 +- `attackState`:攻击状态(0=尝试 1=失败 2=成功 3=失陷) +- `hostIp`:受影响主机IP +- `firstTimestamp / lastTimestamp`:首/末发现时间 +- `riskTag`:风险标签数组 + +**事件(list)** +- `uuId`:事件唯一ID +- `name`:事件名称 +- `riskLevel`:风险等级 +- `alertIds`:关联告警ID列表 +- `dataSource`:数据来源(EDR/NDR) +- `hostIp`:受影响主机 +- `dealStatus`:处置状态 +- `detectionStatus`:检测状态(0=事中,1=事后) +- `attackStory`:攻击故事线(JSON) +- `rootCauseAnalysis`:根因分析(JSON) + +### 高风险操作 + +| 工具 | action | 风险 | 说明 | +|------|--------|------|------| +| `sangfor_xdr_responses` | `unisolate` | 需确认 | 解除主机隔离,恢复网络通信 | +| `sangfor_xdr_whitelists` | `create/update/delete` | 需确认 | 修改白名单规则,可能导致告警漏报 | +| `sangfor_xdr_assets` | `delete` | 需确认 | 删除资产记录 | +| `sangfor_xdr_vulns` | `update_status` | 需确认 | 修改漏洞修复状态 | + +### 常见错误与回退 + +- API 返回 401/403 → **会话已失效**,告知用户"XDR 会话已过期,请在浏览器中重新登录 XDR 后再试" +- 查询结果为空 → 确认时间范围和筛选条件 +- 写操作(处置/删除)→ 必须先获用户明确授权 + +## 浏览器模式使用指南 + +> ⚠️ 进入浏览器模式前,**必须询问用户 XDR URL**(如 `https://xdr.example.com/`)。 + +请阅读以下文档获取完整流程: +- [references/cdp-workflow.md](references/cdp-workflow.md) + +### CDP 模式适用场景 + +- **系统运维页面**(`#/apex-business/settings/run/state`):节点状态、CPU/内存/磁盘/IO 趋势、数据接入指标 +- 页面详情、交互式筛选 +- API 不可用的场景 + +### 可用工具脚本 + +| 脚本路径 | 功能 | 必需参数 | +|---------|------|---------| +| `references/fetch_xdr_system_state.py` | 系统运行状态抓取 | `--url {XDR_URL}` | + +脚本位于 skill 目录的 `references/` 下,无硬编码 URL 或敏感信息。 + +### 执行示例 + +脚本位于 `/skills/sangfor-xdr-use/references/fetch_xdr_system_state.py`,请按当前平台选择对应命令。 + +**Windows(PowerShell)** + +```powershell +powershell -Command "& '\Scripts\python.exe' '\skills\sangfor-xdr-use\references\fetch_xdr_system_state.py' --url '{XDR_URL}'" +``` + +**macOS / Linux(bash / zsh)** + +```bash +"/bin/python" "/skills/sangfor-xdr-use/references/fetch_xdr_system_state.py" --url "{XDR_URL}" +``` + +**占位符说明** + +| 占位符 | Windows 典型值 | macOS/Linux 典型值 | +|--------|---------------|-------------------| +| `` | `D:\Flocks Project\flocks\.venv` | `~/Flocks/flocks/.venv`(取决于实际安装位置) | +| `` | `%USERPROFILE%\.flocks\plugins` | `~/.flocks/plugins` | + +> 必须使用 Flocks 虚拟环境(`.venv`)执行;系统 Python 可能缺少依赖。 + +## API 与 CDP 功能对照 + +| 能力 | API 工具 | CDP 浏览器 | 说明 | +|------|---------|----------|------| +| 告警列表查询 | ✅ `sangfor_xdr_alerts` | ❌ | | +| 告警处置状态修改 | ✅ `sangfor_xdr_alerts` | ❌ | | +| 事件列表查询 | ✅ `sangfor_xdr_incidents` | ❌ | | +| 事件关联实体查询 | ✅ `sangfor_xdr_incidents` | ❌ | | +| 脆弱性/漏洞管理 | ✅ `sangfor_xdr_vulns` | ❌ | | +| 资产盘点 | ✅ `sangfor_xdr_assets` | ❌ | | +| 主机隔离/解除 | ✅ `sangfor_xdr_responses` | ❌ | | +| 白名单管理 | ✅ `sangfor_xdr_whitelists` | ❌ | | +| 系统运维状态 | ❌ | ✅ `fetch_xdr_system_state.py` | 需提供 `--url {XDR_URL}` | + +## 关键坑点(必须避免) + +| 坑 | 原因 | 解法 | +|---|---|---| +| `flocks browser -c js(...)` 返回空文本 | daemon session 指向错误的 tab | 用 Python socket 直连 daemon,通过 `Runtime.evaluate` 在正确 context 执行 | +| `flocks browser -c new_tab()` 后后续命令无响应 | tab 切换导致 session 错位 | 用 `switch_tab(targetId)` 明确切到 XDR tab | +| 多行代码转义失败 | PowerShell 引号嵌套 | 使用 `fetch_xdr_system_state.py` 脚本,无需手动转义 | +| `scroll()` 导致 TimeoutError | CDP mouse event 阻塞 | 避免在 XDR 页面使用 scroll,改用 `captureBeyondViewport: true` | +| XDR 页面数据为空或加载中 | 页面需等待渲染 | 导航后等待 3-5 秒再抓取 | + +## 巡检数据输出格式 + +详见 [references/xdr-inspection-template.md](references/xdr-inspection-template.md)。 \ No newline at end of file diff --git a/.flocks/plugins/skills/sangfor-xdr-use/references/cdp-workflow.md b/.flocks/plugins/skills/sangfor-xdr-use/references/cdp-workflow.md new file mode 100644 index 00000000..4194dede --- /dev/null +++ b/.flocks/plugins/skills/sangfor-xdr-use/references/cdp-workflow.md @@ -0,0 +1,208 @@ +# 深信服 XDR CDP 直连浏览器流程 + +## 环境信息 + +| 项目 | 值 | +|------|-----| +| **Browser daemon port 文件** | `{tempfile.gettempdir()}/bu-default.port`(由 Python `tempfile.gettempdir()` 解析,跨平台) | +| **XDR 地址** | **需用户提供**(无默认值) | +| **目标页面 URL** | `{XDR_URL}/#/apex-business/settings/run/state` | + +## 零、前置条件 + +### 1. 确保浏览器 daemon 可用 +```bash +flocks browser --doctor +``` + +如果 `active browser connections` 为 0,需用户开启浏览器 remote debugging。 + +### 2. 开启 Chrome Remote Debugging + +**Windows** +```powershell +# Chrome +chrome.exe --remote-debugging-port=9222 +# Edge +msedge.exe --remote-debugging-port=9222 +``` + +**macOS** +```bash +# Chrome +"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --remote-debugging-port=9222 +# Edge +"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge" --remote-debugging-port=9222 +``` + +**Linux** +```bash +google-chrome --remote-debugging-port=9222 +# 或 +chromium --remote-debugging-port=9222 +``` + +### 3. 登录 XDR +确保用户在 Chrome 中已登录 XDR(如需 MFA,完成认证)。 + +--- + +## 一、执行流程 + +### Step 1:确认 XDR URL +**必须询问用户 XDR 地址**,例如: +- `https://xdr.example.com/` +- `https://xdr.company.com/` + +### Step 2:检查 daemon +```bash +flocks browser --doctor +``` + +如果 daemon 未运行,执行: +```bash +flocks browser --setup +``` + +### Step 3:用户打开目标页面 +用户在 Chrome 中打开: +``` +{XDR_URL}/#/apex-business/settings/run/state +``` + +### Step 4:执行抓取脚本 + +**工具脚本路径**(位于 skill references 目录): +``` +references/fetch_xdr_system_state.py +``` + +**执行命令(按平台选择):** + +```powershell +# Windows PowerShell +powershell -Command "& '\Scripts\python.exe' '\skills\sangfor-xdr-use\references\fetch_xdr_system_state.py' --url '{XDR_URL}'" +``` + +```bash +# macOS / Linux +"/bin/python" "/skills/sangfor-xdr-use/references/fetch_xdr_system_state.py" --url "{XDR_URL}" +``` + +占位符 `` / `` 含义见 SKILL.md "执行示例"。 + +**参数说明:** +| 参数 | 必需 | 默认值 | 说明 | +|------|------|--------|------| +| `--url` | 是 | - | XDR URL,如 `https://xdr.example.com/` | +| `--wait` | 否 | 5 | 等待页面渲染秒数 | +| `--raw` | 否 | False | 输出原始页面文本 | + +--- + +## 二、手动 CDP Socket 方式 + +> 当脚本不可用时,使用此方式。 + +```python +import json +import socket +import tempfile +import time +from pathlib import Path + +port_file = Path(tempfile.gettempdir()) / "bu-default.port" +port = int(port_file.read_text().strip()) + +def send_cmd(sock, cmd): + sock.sendall((json.dumps(cmd) + "\n").encode()) + data = b"" + while not data.endswith(b"\n"): + chunk = sock.recv(8192) + if not chunk: + break + data += chunk + return json.loads(data) + +sock = socket.socket() +sock.settimeout(15) +sock.connect(("127.0.0.1", port)) + +targets = send_cmd(sock, {"method": "Target.getTargets"})["result"]["targetInfos"] + +xdr_url = "{XDR_URL}" +host = xdr_url.replace("https://", "").replace("http://", "").rstrip("/") + +xdr_tab = next((t for t in targets if host in t.get("url", "") and "/apex-business/settings/run/state" in t.get("url", "")), None) + +if not xdr_tab: + xdr_tab = next((t for t in targets if host in t.get("url", "") and t.get("type") == "page"), None) + +if not xdr_tab: + print(f"XDR tab not found. Please open: {xdr_url}/#/apex-business/settings/run/state") + exit(1) + +attach = send_cmd(sock, {"method": "Target.attachToTarget", "params": {"targetId": xdr_tab["targetId"], "flatten": True}}) +session_id = attach["result"]["sessionId"] + +time.sleep(5) + +text_result = send_cmd(sock, {"method": "Runtime.evaluate", "params": {"expression": "document.body.innerText"}, "session_id": session_id}) +print(text_result["result"]["result"]["value"]) +``` + +--- + +## 三、页面数据提取 + +从 `page_text` 中按关键词提取: + +| 数据 | 关键词 | +|------|--------| +| 节点健康/异常/不可用 | `状态总览` 区块 | +| CPU/内存/磁盘使用率 | `CPU使用趋势` / `内存使用趋势` / `磁盘使用趋势` | +| 系统盘/数据盘/CPU温度 | `系统盘监控状况` / `数据盘监控状况` / `CPU最高温度` | +| 磁盘IO | `读取` / `写入` + `MiB/s` | +| 网口流量 | `接收` / `发送` + `MiB/s` | +| IO延迟 | `IO读取延迟` / `IO写入延迟` + `ms` | +| 数据接入指标 | `数据采集吞吐率` / `数据解析速率` / `授权日志上限` / `日志接入总量` | + +--- + +## 四、关键坑点 + +| 坑 | 原因 | 解法 | +|---|---|---| +| `Target.getTargets` 返回空 | 浏览器未开启 remote debugging | 用户执行 `chrome.exe --remote-debugging-port=9222` | +| XDR tab 未找到 | 页面未打开或 URL 不匹配 | 确保 Chrome 中打开了系统运维页面 | +| 页面数据为空 | XDR 图表需时间渲染 | 增加 `--wait` 参数(默认 5 秒) | +| 页面显示登录框 | 会话已失效 | 告知用户重新登录 XDR | + +--- + +## 五、执行规范 + +**必须使用 Flocks 虚拟环境(`.venv`)执行 Python 脚本,禁止使用系统 Python。** + +- ✅ 正确:`/bin/python`(Unix)或 `\Scripts\python.exe`(Windows) +- ❌ 禁止:`python script.py` / `python3 script.py` + +--- + +## 六、可用工具脚本 + +| 脚本路径 | 功能 | 必需参数 | +|---------|------|---------| +| `references/fetch_xdr_system_state.py` | 系统运行状态抓取 | `--url {XDR_URL}` | + +### 执行示例 + +```powershell +# Windows +powershell -Command "& '\Scripts\python.exe' '\skills\sangfor-xdr-use\references\fetch_xdr_system_state.py' --url 'https://xdr.example.com/'" +``` + +```bash +# macOS / Linux +"/bin/python" "/skills/sangfor-xdr-use/references/fetch_xdr_system_state.py" --url "https://xdr.example.com/" +``` \ No newline at end of file diff --git a/.flocks/plugins/skills/sangfor-xdr-use/references/fetch_xdr_system_state.py b/.flocks/plugins/skills/sangfor-xdr-use/references/fetch_xdr_system_state.py new file mode 100644 index 00000000..5a6d1cd4 --- /dev/null +++ b/.flocks/plugins/skills/sangfor-xdr-use/references/fetch_xdr_system_state.py @@ -0,0 +1,238 @@ +import argparse +import json +import re +import socket +import tempfile +import time +from pathlib import Path + +def send_cmd(sock, cmd): + sock.sendall((json.dumps(cmd) + "\n").encode()) + data = b"" + while not data.endswith(b"\n"): + chunk = sock.recv(8192) + if not chunk: + break + data += chunk + return json.loads(data) + +def send_cmd_new_conn(port, cmd): + sock = socket.socket() + sock.settimeout(15) + sock.connect(("127.0.0.1", port)) + result = send_cmd(sock, cmd) + sock.close() + return result + +def find_xdr_tab(targets, xdr_url): + host = xdr_url.replace("https://", "").replace("http://", "").rstrip("/") + for t in targets: + url = t.get("url", "") + if host in url and "/apex-business/settings/run/state" in url: + return t + for t in targets: + url = t.get("url", "") + if host in url and t.get("type") == "page": + return t + return None + +def parse_system_run_state(text): + lines = text.split("\n") + result = { + "nodes": {"total": None, "healthy": None, "abnormal": None, "unavailable": None}, + "resources": {"cpu": None, "memory": None, "disk": None}, + "disk_monitor": {"system": None, "data": None, "cpu_temp": None}, + "io_network": {"disk_read": None, "disk_write": None, "io_read_latency": None, "io_write_latency": None, "net_recv": None, "net_send": None}, + "data_ingestion": {"throughput": None, "parse_rate": None, "log_limit": None, "log_total": None} + } + + node_count = 0 + for line in text.split("\n"): + line = line.strip() + if re.match(r'^node\d+$', line): + node_count += 1 + + for i, line in enumerate(lines): + line = line.strip() + if "节点状态" in line: + if i + 2 < len(lines): + next_line = lines[i + 2].strip() + if next_line.isdigit(): + result["nodes"]["total"] = next_line + if "状态总览" in line: + for j in range(i + 1, min(i + 15, len(lines))): + l = lines[j].strip() + if l == "健康" and result["nodes"]["healthy"] is None: + if j + 1 < len(lines): + val = lines[j + 1].strip() + if val.isdigit(): + result["nodes"]["healthy"] = val + elif l == "异常" and result["nodes"]["abnormal"] is None: + if j + 1 < len(lines): + val = lines[j + 1].strip() + if val.isdigit(): + result["nodes"]["abnormal"] = val + elif l == "不可用" and result["nodes"]["unavailable"] is None: + if j + 1 < len(lines): + val = lines[j + 1].strip() + if val.isdigit(): + result["nodes"]["unavailable"] = val + if "系统盘监控状况" in line: + val = lines[i + 1].strip() if i + 1 < len(lines) else None + result["disk_monitor"]["system"] = val + elif "数据盘监控状况" in line: + val = lines[i + 1].strip() if i + 1 < len(lines) else None + result["disk_monitor"]["data"] = val + elif "CPU最高温度" in line: + val = lines[i + 1].strip() if i + 1 < len(lines) else None + result["disk_monitor"]["cpu_temp"] = val + + cpu_match = re.search(r"CPU使用趋势[^\d]*([\d.]+)\s*%", text) + if cpu_match: + result["resources"]["cpu"] = cpu_match.group(1) + "%" + + mem_match = re.search(r"内存使用趋势[^\d]*([\d.]+)\s*%", text) + if mem_match: + result["resources"]["memory"] = mem_match.group(1) + "%" + + disk_match = re.search(r"磁盘使用趋势[^\d]*([\d.]+)\s*%", text) + if disk_match: + result["resources"]["disk"] = disk_match.group(1) + "%" + + read_match = re.search(r"读取\s+([\d.]+)\s+MiB/s", text) + if read_match: + result["io_network"]["disk_read"] = read_match.group(1) + " MiB/s" + write_match = re.search(r"写入\s+([\d.]+)\s+MiB/s", text) + if write_match: + result["io_network"]["disk_write"] = write_match.group(1) + " MiB/s" + + recv_match = re.search(r"接收\s+([\d.]+)\s+MiB/s", text) + if recv_match: + result["io_network"]["net_recv"] = recv_match.group(1) + " MiB/s" + send_match = re.search(r"发送\s+([\d.]+)\s+MiB/s", text) + if send_match: + result["io_network"]["net_send"] = send_match.group(1) + " MiB/s" + + io_read_match = re.search(r"IO读取延迟[^\d]*([\d.]+)\s*ms", text) + if io_read_match: + result["io_network"]["io_read_latency"] = io_read_match.group(1) + " ms" + io_write_match = re.search(r"IO写入延迟[^\d]*([\d.]+)\s*ms", text) + if io_write_match: + result["io_network"]["io_write_latency"] = io_write_match.group(1) + " ms" + + throughput_match = re.search(r"数据采集吞吐率[^\d]*([\d.]+)\s+MiB/s", text) + if throughput_match: + result["data_ingestion"]["throughput"] = throughput_match.group(1) + " MiB/s" + + parse_rate_match = re.search(r"数据解析速率[^\d]*([\d.]+)\s+条/s", text) + if parse_rate_match: + result["data_ingestion"]["parse_rate"] = parse_rate_match.group(1) + " 条/s" + + log_limit_match = re.search(r"授权日志上限[^\d]*([\d.]+)\s+亿条", text) + if log_limit_match: + result["data_ingestion"]["log_limit"] = log_limit_match.group(1) + " 亿条" + + log_total_match = re.search(r"日志接入总量[^\d]*([\d.]+)\s+亿条", text) + if log_total_match: + result["data_ingestion"]["log_total"] = log_total_match.group(1) + " 亿条" + + if node_count > 0 and result["nodes"]["healthy"] and result["nodes"]["healthy"].isdigit(): + if int(result["nodes"]["healthy"]) > node_count: + result["nodes"]["total"] = result["nodes"]["healthy"] + else: + result["nodes"]["total"] = str(node_count) + elif node_count > 0: + result["nodes"]["total"] = str(node_count) + + return result + +def main(): + parser = argparse.ArgumentParser(description="Fetch XDR system run state via CDP") + parser.add_argument("--url", required=True, help="XDR URL (e.g. https://xdr.example.com/)") + parser.add_argument("--wait", type=int, default=5, help="Wait seconds for page render (default: 5)") + parser.add_argument("--raw", action="store_true", help="Print raw page text") + args = parser.parse_args() + + port_file = Path(tempfile.gettempdir()) / "bu-default.port" + if not port_file.exists(): + print("ERROR: Browser daemon port file not found") + print("Please run: flocks browser --setup") + exit(1) + + port = int(port_file.read_text().strip()) + + targets_result = send_cmd_new_conn(port, {"method": "Target.getTargets"}) + targets = targets_result.get("result", {}).get("targetInfos", []) + + xdr_tab = find_xdr_tab(targets, args.url) + if not xdr_tab: + target_url = args.url.rstrip("/") + "/#/apex-business/settings/run/state" + print(f"XDR tab not found. Please open: {target_url}") + exit(1) + + print(f"Found XDR tab: {xdr_tab['targetId']}") + + attach_result = send_cmd_new_conn(port, { + "method": "Target.attachToTarget", + "params": {"targetId": xdr_tab["targetId"], "flatten": True} + }) + session_id = attach_result["result"]["sessionId"] + + time.sleep(args.wait) + + text_result = send_cmd_new_conn(port, { + "method": "Runtime.evaluate", + "params": {"expression": "document.body.innerText"}, + "session_id": session_id + }) + page_text = text_result["result"]["result"]["value"] + + if args.raw: + print(page_text) + return + + result = parse_system_run_state(page_text) + + print("\n### XDR System Run State\n") + print("#### Nodes Overview") + print(f"| Metric | Value |") + print(f"|--------|-------|") + print(f"| Total Nodes | {result['nodes']['total'] or '-'} |") + print(f"| Healthy | {result['nodes']['healthy'] or '-'} |") + print(f"| Abnormal | {result['nodes']['abnormal'] or '-'} |") + print(f"| Unavailable | {result['nodes']['unavailable'] or '-'} |") + + print("\n#### Resource Usage Trend") + print(f"| Metric | Current |") + print(f"|--------|---------|") + print(f"| CPU Usage | {result['resources']['cpu'] or '-'} |") + print(f"| Memory Usage | {result['resources']['memory'] or '-'} |") + print(f"| Disk Usage | {result['resources']['disk'] or '-'} |") + + print("\n#### Disk Monitor") + print(f"| Item | Status |") + print(f"|------|--------|") + print(f"| System Disk | {result['disk_monitor']['system'] or '-'} |") + print(f"| Data Disk | {result['disk_monitor']['data'] or '-'} |") + print(f"| CPU Max Temp | {result['disk_monitor']['cpu_temp'] or '-'} |") + + print("\n#### IO & Network") + print(f"| Metric | Current |") + print(f"|--------|---------|") + print(f"| Disk Read | {result['io_network']['disk_read'] or '-'} |") + print(f"| Disk Write | {result['io_network']['disk_write'] or '-'} |") + print(f"| IO Read Latency | {result['io_network']['io_read_latency'] or '-'} |") + print(f"| IO Write Latency | {result['io_network']['io_write_latency'] or '-'} |") + print(f"| Net Receive | {result['io_network']['net_recv'] or '-'} |") + print(f"| Net Send | {result['io_network']['net_send'] or '-'} |") + + print("\n#### Data Ingestion") + print(f"| Metric | Value |") + print(f"|--------|-------|") + print(f"| Throughput | {result['data_ingestion']['throughput'] or '-'} |") + print(f"| Parse Rate | {result['data_ingestion']['parse_rate'] or '-'} |") + print(f"| Log Limit | {result['data_ingestion']['log_limit'] or '-'} |") + print(f"| Log Total | {result['data_ingestion']['log_total'] or '-'} |") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/.flocks/plugins/skills/sangfor-xdr-use/references/xdr-inspection-template.md b/.flocks/plugins/skills/sangfor-xdr-use/references/xdr-inspection-template.md new file mode 100644 index 00000000..8a260cc0 --- /dev/null +++ b/.flocks/plugins/skills/sangfor-xdr-use/references/xdr-inspection-template.md @@ -0,0 +1,106 @@ +# 深信服 XDR 巡检数据输出模板 + +## 一、节点总览 + +| 指标 | 数值 | +|------|------| +| 节点总数 | N | +| 健康 | N | +| 异常 | N | +| 不可用 | N | + +## 二、资源使用趋势(近 1 小时) + +| 指标 | 当前值 | 总容量 | +|------|--------|--------| +| CPU 使用率 | X.X% | - | +| 内存使用率 | X.X% | X.XX TiB | +| 磁盘使用率 | X.X% | X.XX TiB | + +## 三、磁盘监控 + +| 盘类型 | 状态 | +|--------|------| +| 系统盘 | 健康/异常 | +| 数据盘 | 健康/异常 | +| CPU 最高温度 | 健康/异常 | + +## 四、IO 与网络 + +| 指标 | 当前值 | +|------|--------| +| 磁盘读取 | X.XX MiB/s | +| 磁盘写入 | X.XX MiB/s | +| IO 读取延迟 | X.XX ms | +| IO 写入延迟 | X.XX ms | +| 网口接收 | X.XX MiB/s | +| 网口发送 | X.XX MiB/s | + +## 五、数据接入 + +| 指标 | 数值 | +|------|------| +| 数据采集吞吐率 | X.XX MiB/s | +| 数据解析速率 | XXXXX.X 条/s | +| 授权日志上限 | X.XX 亿条 | +| 日志接入总量 | X.XX 亿条 | + +## 六、XDR 失陷节点说明 + +> **⚠️ 注意**:XDR 的"失陷"与 EDR 的"失陷"是两个独立的概念: +> - **EDR 失陷**:终端已被远控/感染(如宏病毒) +> - **XDR 失陷**:XDR 平台自身节点(如采集器/分析器)不可用 +> - 如需综合失陷评估,需分别从 EDR 和 XDR 各自查询 + +XDR 系统运维页面中的"节点状态"卡片显示各节点健康状态。节点异常/不可用数量即为 XDR 层面的失陷节点数。 + +--- + +## 七、巡检结果汇总格式 + +``` +### 深信服 XDR 系统运维状态 + +#### 节点总览 +| 指标 | 数值 | +|------|------| +| 节点总数 | N | +| 健康 | N | +| 异常 | N | +| 不可用 | N | + +#### 资源使用趋势(近1小时) +| 指标 | 当前值 | 总容量 | +|------|--------|--------| +| CPU使用率 | X.X% | - | +| 内存使用率 | X.X% | X.XX TiB | +| 磁盘使用率 | X.X% | X.XX TiB | + +#### 磁盘监控 +| 盘类型 | 状态 | +|--------|------| +| 系统盘 | 健康/异常 | +| 数据盘 | 健康/异常 | +| CPU最高温度 | 健康/异常 | + +#### IO与网络 +| 指标 | 当前值 | +|------|--------| +| 磁盘读取 | X.XX MiB/s | +| 磁盘写入 | X.XX MiB/s | +| IO读取延迟 | X.XX ms | +| IO写入延迟 | X.XX ms | +| 网口接收 | X.XX MiB/s | +| 网口发送 | X.XX MiB/s | + +#### 数据接入 +| 指标 | 数值 | +|------|------| +| 数据采集吞吐率 | X.XX MiB/s | +| 数据解析速率 | XXXXX.X 条/s | +| 授权日志上限 | X.XX 亿条 | +| 日志接入总量 | X.XX 亿条 | + +#### 总体健康评估 +(列出需要关注的异常项,如节点异常数 > 0、CPU > 80% 等) +``` \ No newline at end of file