feat: 新增 Agent CLI — Web 版 AI 运维 Agent(参考 opsxcli 移植)

后端引擎 backend/pkg/agentcli/(纯引擎库,零业务依赖,剥离 CLI/TUI 外壳):
- llm: OpenAI 兼容客户端,复用 model.LLMConfig
- tools: ssh_execute(复用 pkg/remoteexec)+ analyze_output + 风险评估黑名单
- safety: 三级安全模式 + 五级风险 + WebSocket 异步确认
- prompt: 五层 System Prompt
- core: ReAct 16 轮循环 + 自适应检查点 + 循环检测
- evolver: 四层记忆 + 进化循环,data/agentcli/ 文件存储

集成层:
- controller/agent_cli.go: WebSocket 控制器(事件流推流 + 高危确认 + 会话持久化)
- model/agent_cli.go: agent_cli_sessions/messages 表 + AutoMigrate
- 路由 /ws/agent-cli + /api/v1/aiops/agent-cli/sessions
- seed 权限 aiops:agent-cli / aiops:llm-config

前端:
- AgentCLI.tsx: 对话流 + 工具块 + 高危确认 + 四模块 tips 面板
- LLMConfig.tsx: 大模型设置独立页
- api/agentCli.ts + Sidebar/App 菜单路由

文档:
- docs/agent/ 11 篇(opsxcli 同名文档适配改写 + 00-README 映射总览)

其他:
- CLAUDE.md 补充 agent-cli 模块说明
- 修正 .gitignore 根级二进制规则(前导 /,避免误伤 frontend/src/components/aiops/)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
attackerandClaude committed 2026-06-27 09:55:17 +08:00
1 parent f2069fe3f0
commit 92a2247390
37 files changed
+6449 -6

No files matched your search

@@ -0,0 +1,468 @@
# 09 - 安全系统与工具注册
> 本文由 opsxcli 同名设计文档适配改写,结合 aiops Agent CLI 的 Web 实现(`backend/pkg/agentcli/safety/safety.go` + `tools/registry.go` + `tools/risk.go`)。
> **保留**:三级安全模式 / 五级风险 / 危险命令黑名单 / AssessBashRisk 动态评估 / Tool 接口 + Registry。
> **适配**:确认机制从 CLI 终端 `bufio.Reader` 改为 WebSocket `confirm` 事件 + 前端 `useConfirm()` 弹框;审计日志从文件改为 DB 落库。
## 设计理念
安全系统是运维 Agent 的生命线。aiops Agent CLI 安全系统遵循两个核心原则:
1. **默认安全**:balanced 模式下,high+ 操作需要确认,覆盖 95% 日常运维
2. **可审计**:每次工具调用都通过 EventCallback 落库,事后可追溯
工具注册系统遵循**工具化思维**:万物皆 Tool,实现接口即可扩展。
---
## Part 1: 安全系统
### 架构总览
```
┌────────────────────────────────────────────────────┐
│ safety.Controller │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ risk.go │ │ controller.go │ │
│ │ (在 tools 包)│ │ (在 safety 包)│ │
│ │ 黑名单 + 分级 │ │ 阈值 + ConfirmFn│ │
│ └──────────────┘ └──────────────┘ │
│ │
│ Safety Modes: │
│ strict → medium+ 确认 │
│ balanced → high+ 确认(默认) │
│ permissive → critical 仅处理 │
└────────────────────────────────────────────────────┘
```
> 与 opsxcli 的差异:opsxcli 把风险评估和审计都放在 `safety/` 包;aiops 为避免 `tools` ↔ `safety` 循环依赖,把 `risk.go`(黑名单 + AssessBashRisk)置于 `tools` 包,由 `ssh_execute` 的 `DynamicRiskTool` 直接调用;`safety` 包只做「是否需确认」的策略。
### Controller 结构
```go
// backend/pkg/agentcli/safety/safety.go
type Controller struct {
mode Mode // 当前安全模式
confirmFn ConfirmFn // 确认回调(controller 注入:推 WS confirm + 阻塞等回传)
autoApprove bool // 自动批准(仅测试用,生产禁用)
}
```
> 与 opsxcli 的差异:剥离 `reader *bufio.Reader`(CLI 终端交互)和 `history []ExecutionRecord`(内存审计历史),改为通过 EventCallback 落库 DB;剥离 `auditLog *AuditLogWriter`(文件审计)。
### ConfirmFn —— Web 版的确认机制
```go
type ConfirmFn func(toolName string, args map[string]interface{}, risk tools.RiskLevel) (bool, error)
```
controller 实现的 `confirmFn`:
```go
// backend/controller/agent_cli.go
func (ac *agentConn) confirmFn(toolName string, args map[string]interface{}, risk tools.RiskLevel) (bool, error) {
confirmID := fmt.Sprintf("cf-%d", time.Now().UnixNano())
cmd, _ := args["command"].(string)
// 1. 推 WS confirm 事件(前端 useConfirm 弹框)
ac.write(wsEvent{
Type: "confirm", ConfirmID: confirmID,
ToolName: toolName, Command: cmd, Risk: risk.Display(), ToolArgs: args,
})
// 2. 注册 channel 等待前端回传
ch := make(chan bool, 1)
ac.cfMu.Lock()
ac.confirms[confirmID] = ch
ac.cfMu.Unlock()
// 3. 阻塞等待(5 分钟超时)
select {
case approved := <-ch:
return approved, nil
case <-time.After(5 * time.Minute):
ac.delConfirm(confirmID)
return false, fmt.Errorf("确认超时")
}
}
```
前端处理(`AgentCLI.tsx`):
```tsx
case 'confirm': {
const ok = await confirm({
title: '确认执行高危命令',
description: `即将执行:${ev.command}`,
confirmText: '确认执行',
});
wsRef.current?.send(JSON.stringify({
type: ok ? 'confirm' : 'deny',
confirm_id: ev.confirm_id,
}));
break;
}
```
这是「Web 版 CLI 终端确认」的完整替代 —— 用户在浏览器看到弹框,点确认/拒绝,结果通过 WebSocket 回传解锁引擎。
### 三级安全模式
```go
const (
ModeStrict Mode = "strict" // medium+ 需确认
ModeBalanced Mode = "balanced" // high+ 需确认(默认)
ModePermissive Mode = "permissive" // 仅 critical 处理
)
```
| 模式 | 需要确认的等级 | 适用场景 |
|------|---------------|----------|
| `strict` | medium 及以上 | 生产环境、关键主机、新手操作 |
| `balanced`(默认) | high 及以上 | 日常运维 |
| `permissive` | 仅 critical | 测试环境、自动化脚本 |
阈值映射:
```go
func (c *Controller) riskThreshold() tools.RiskLevel {
switch c.mode {
case ModeStrict: return tools.RiskMedium
case ModePermissive: return tools.RiskCritical
default: return tools.RiskHigh
}
}
```
### 五级风险评估
```go
// backend/pkg/agentcli/tools/registry.go
const (
RiskSafe RiskLevel = iota // 0 - 只读(cat, grep, df, ps)
RiskLow // 1 - 低风险(mkdir, touch)
RiskMedium // 2 - 中风险(chmod, 文件修改)
RiskHigh // 3 - 高风险(ssh_execute, 服务控制)
RiskCritical // 4 - 危险(rm -rf /, mkfs, dd, 数据删除)
)
```
每级都有中文显示(`Display()`):
```go
case RiskSafe: return "安全"
case RiskLow: return "低风险"
case RiskMedium: return "中风险"
case RiskHigh: return "高风险"
case RiskCritical: return "危险"
```
前端在 `tool_call` 事件中拿到 `risk` 字段,渲染为风险标签。
### 危险命令黑名单(硬红线)
```go
// backend/pkg/agentcli/tools/risk.go
var dangerousCommands = []string{
// 文件系统毁灭性操作
"rm -rf /", "rm -rf /*", "rm -rf /bin", "rm -rf /boot", "rm -rf /etc",
"rm -rf /home", "rm -rf /lib", "rm -rf /lib64", "rm -rf /proc", "rm -rf /root",
"rm -rf /sbin", "rm -rf /sys", "rm -rf /usr", "rm -rf /var",
// 磁盘格式化
"mkfs", "mkfs.ext", "mkfs.xfs", "mkfs.btrfs", "mkfs.vfat",
// 磁盘覆写
"dd if=/dev/zero", "dd if=/dev/random", "dd if=/dev/urandom",
// Fork bomb
":(){ :|:& };:", ":() { : | : & }; :",
// 设备重定向
"> /dev/sda", "> /dev/sdb", "> /dev/sdc", "> /dev/hda",
// 数据销毁
"mv / /dev/null", "mv /* /dev/null",
// 权限破坏
"chmod -R 000 /", "chmod -R 777 /",
// 服务自毁
"systemctl stop ssh", "systemctl disable ssh", "kill -9 1",
// 防火墙清空
"iptables -F", "iptables --flush",
}
```
**特殊检测**:`rm -rf` + 根目录路径(即使不在黑名单字面中):
```go
if strings.HasPrefix(lower, "rm") && strings.Contains(lower, "-rf") && containsRootPath(lower) {
return true, "rm -rf 包含根目录路径"
}
```
`containsRootPath` 检测 `/boot`、`/etc`、`/usr`、`/var` 等系统目录。
### Check 策略
```go
func (c *Controller) Check(tool tools.Tool, args map[string]interface{}) (bool, error) {
risk := assessToolRisk(tool, args)
// 1. 黑名单硬红线:绝不允许(不可确认放行)
if risk >= tools.RiskCritical {
return false, nil
}
// 2. 未达阈值或自动批准:直接放行
if c.autoApprove || risk < c.riskThreshold() {
return true, nil
}
// 3. 达阈值:请求用户确认
if c.confirmFn == nil {
return false, nil // 无确认回调时保守拒绝(Web 场景应始终注入 confirmFn)
}
return c.confirmFn(tool.Name(), args, risk)
}
```
**三层策略**:
1. **硬红线**(Critical):黑名单命令直接拒绝,**不可被确认放行**
2. **阈值放行**:低于模式阈值的操作直接执行
3. **用户确认**(达阈值):调 `confirmFn` 推 WS 弹框等用户决定
### AssessBashRisk 动态评估
```go
func AssessBashRisk(command string) RiskLevel {
// 1. 黑名单精确匹配 → Critical
if isDangerous, _ := IsDangerousCommand(command); isDangerous {
return RiskCritical
}
// 2. 纯只读命令 → Safe
if isReadOnlyCommand(lower) {
return RiskSafe
}
// 3. 组合命令(&& || | ;)→ 取子命令最高风险
if isCompound(lower) {
return assessCompoundCommand(lower)
}
// 4. 单命令分类
isSudo := strings.HasPrefix(lower, "sudo ")
return assessSingleCommand(strings.TrimPrefix(lower, "sudo "), isSudo)
}
```
### 命令分类表
| 类别 | 命令示例 | 风险等级 |
|------|---------|---------|
| 只读(Safe) | cat, grep, awk, sed, head, tail, ps, top, df, du, free, uptime, uname, hostname, ifconfig, ip addr, netstat, ss, ls, pwd, who, w, last, vmstat, iostat, lsof, find, which, file, stat, date, echo, id, whoami, dmesg, systemctl status, journalctl, lsblk, mount | Safe |
| 修改(Low/Medium) | cp, mv, mkdir, rmdir, touch, chmod, chown, chgrp, ln, tar, gzip, gunzip, zip, unzip, rsync, tee | Low(sudo 时 Medium) |
| 高危(Medium/High) | rm, dd, shutdown, reboot, halt, poweroff, init, systemctl restart/stop/start, kill, killall, pkill, userdel, groupdel, fdisk, parted, mkfs | Medium(sudo 时 High) |
| 危险(Critical) | rm -rf /, mkfs, dd if=/dev/zero, fork bomb, > /dev/sda, chmod -R 000 / | Critical(硬拒) |
### sudo 提升
`sudo` 会让风险等级 +1(最高到 Critical 前):
```go
// assessSingleCommand
if isSudo {
return RiskHigh // 高危命令 + sudo → High
}
return RiskMedium // 高危命令无 sudo → Medium
```
### 组合命令取最高
```go
// assessCompoundCommand: 拆分 && || | ; 后取子命令最高风险
// "df -h && rm /tmp/file" → max(Safe, Medium) = Medium
// "cat /etc/passwd; rm -rf /tmp" → max(Safe, Low) = Low
```
### 审计落库(替代文件审计)
> opsxcli 把审计写到 `~/.opsxcli/audit/audit.log`;aiops 直接走 DB(`agent_cli_messages` 表),多用户场景下可按 user_id / session_id 查询。
controller 的 `onEvent` 回调把每个 `tool_call` / `tool_result` 落库:
```go
case core.EventToolCall:
argsJSON, _ := json.Marshal(e.ToolArgs)
ac.persistMsg(sid, e.Round, "tool_call", "tool", "",
e.ToolName, string(argsJSON), "", e.Risk, false, "", 0)
case core.EventToolResult:
ac.persistMsg(sid, e.Round, "tool_result", "tool", e.Output,
e.ToolName, "", "", "", e.Success, e.Duration, 0)
```
每条记录包含:工具名、参数 JSON、风险等级、成败、耗时、轮次、会话 ID、用户 ID(通过 session 关联)。前端「会话历史」页面可完整回放。
---
## Part 2: 工具注册系统
### Registry 注册中心
```go
// backend/pkg/agentcli/tools/registry.go
type Registry struct {
tools map[string]Tool
mu sync.RWMutex
}
```
核心方法:
| 方法 | 说明 |
|------|------|
| `NewRegistry()` | 创建空注册中心 |
| `Register(tool)` | 注册工具(线程安全) |
| `Get(name)` | 按名获取工具(LLM 调用时按名查找) |
| `List()` | 列出所有工具 |
| `ToLLMTools()` | 转换为 OpenAI function calling 格式 |
### Tool 接口(5 方法)
```go
type Tool interface {
Name() string
Description() string
Parameters() map[string]interface{} // JSON Schema
RiskLevel() RiskLevel
Execute(ctx context.Context, args map[string]interface{}) (*Result, error)
}
```
### DynamicRiskTool 扩展接口
```go
type DynamicRiskTool interface {
Tool
RiskLevelForArgs(args map[string]interface{}) RiskLevel
}
```
安全控制器 `Check` 时优先调用动态评估:
```go
func assessToolRisk(tool tools.Tool, args map[string]interface{}) tools.RiskLevel {
if dt, ok := tool.(tools.DynamicRiskTool); ok {
return dt.RiskLevelForArgs(args) // 动态优先
}
return tool.RiskLevel()
}
```
### Result 结构
```go
type Result struct {
Success bool `json:"success"`
Output string `json:"output"`
Error string `json:"error,omitempty"`
}
```
### 参数解析辅助
```go
func StringParam(args map[string]interface{}, key string) string
func IntParam(args map[string]interface{}, key string) int // 兼容 int/float64/int64
```
LLM 返回的 JSON 参数反序列化后,数字可能是 `float64`(JSON 原生),`IntParam` 统一处理。
### 已注册工具一览
| 工具 | 静态风险 | 动态风险 | 功能 |
|------|---------|---------|------|
| `ssh_execute` | High | Safe~Critical | 远程命令执行(DynamicRiskTool) |
| `analyze_output` | Safe | — | 输出分析(纯文本,只读) |
详细设计见 [05 - Builtin 内置工具](05-Builtin内置命令.md)。
> 与 opsxcli 的差异:剥离了 `local_bash` / `scp_transfer` / `file_read` / `file_search` 四个工具;剥离 SSH 连接池(aiops 直接用 `pkg/remoteexec`,无连接池需求);剥离 `Closer` 接口(Web 工具无资源需释放)。
## 扩展指南
### 添加新工具
```go
// 1. 实现 Tool 接口(必要时实现 DynamicRiskTool)
type MyTool struct{}
func (t *MyTool) Name() string { return "my_tool" }
func (t *MyTool) Description() string { return "我的工具" }
func (t *MyTool) Parameters() map[string]interface{} {
return map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"target": map[string]interface{}{"type": "string", "description": "目标"},
},
"required": []string{"target"},
}
}
func (t *MyTool) RiskLevel() tools.RiskLevel { return tools.RiskMedium }
func (t *MyTool) Execute(ctx context.Context, args map[string]interface{}) (*tools.Result, error) {
// 实现
return &tools.Result{Success: true, Output: "done"}, nil
}
// 2. 在 agentcli.NewEngine 中注册
registry.Register(&MyTool{})
```
### 扩展危险命令黑名单
修改 `tools/risk.go` 的 `dangerousCommands` 切片:
```go
var dangerousCommands = []string{
// ... 现有
"your-critical-command",
}
```
### 自定义确认逻辑
controller 的 `confirmFn` 可对接企业审批系统(如先走工单再确认):
```go
engine.SetConfirmFn(func(toolName string, args map[string]interface{}, risk tools.RiskLevel) (bool, error) {
// 1. 推 WS 弹框让用户初步确认
// 2. 高危场景额外对接审批工单系统
// 3. 返回最终批准结果
})
```
## 常见陷阱
| 陷阱 | 解决方案 |
|------|----------|
| 忘记设置 RiskLevel 导致误判 | 默认按接口返回值,高危工具必须显式声明 |
| 高危工具未实现 DynamicRiskTool | 静态等级无法反映参数差异,必须实现动态评估 |
| 黑名单只看前缀匹配 | `IsDangerousCommand` 同时做精确匹配 + `rm -rf` 根路径特殊检测 |
| 工具 Execute 无超时 | `Config.ToolTimeout=120s` 兜底(`context.WithTimeout`) |
| 确认回调无超时 | controller 的 `confirmFn` 内置 5 分钟超时 |
| 黑名单可被确认放行 | Check 策略第一层 Critical 直接 `return false, nil`,不走 confirmFn |
| LLM 编造工具名 | `registry.Get` 找不到时返回 observation「工具未找到」,LLM 自动换法 |
| 参数 JSON 解析失败 | `json.Unmarshal` 失败时返回 observation「参数解析失败」,不 panic |
## 与 opsxcli 的差异总结
| 维度 | opsxcli | aiops |
|------|---------|-------|
| 确认机制 | `bufio.Reader` CLI 终端交互 | WebSocket `confirm` 事件 + 前端 `useConfirm()` 弹框 |
| 审计日志 | 文件 `~/.opsxcli/audit/audit.log` | DB 表 `agent_cli_messages` |
| 风险评估位置 | `safety/risk.go` | `tools/risk.go`(避免循环依赖) |
| 内存审计历史 | `history []ExecutionRecord`(上限 1000 FIFO) | 剥离(直接走 DB) |
| 工具集 | 6 个 | 2 个 |
| SSH 连接池 | 有(`ssh_pool.go`) | 剥离(用 `pkg/remoteexec`) |
| Closer 接口 | 有 | 剥离 |
安全核心(三级模式 / 五级风险 / 黑名单 / 动态评估)完全保留,差异仅在确认机制和审计载体。
> 作者:Logan.Li