Files
aiops/docs/agent/09-安全系统与工具注册.md
T
attackerandClaude 92a2247390 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>
2026-06-27 09:55:17 +08:00

16 KiB
Raw Blame History

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 结构

// 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 版的确认机制

type ConfirmFn func(toolName string, args map[string]interface{}, risk tools.RiskLevel) (bool, error)

controller 实现的 confirmFn:

// 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):

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 回传解锁引擎。

三级安全模式

const (
    ModeStrict     Mode = "strict"     // medium+ 需确认
    ModeBalanced   Mode = "balanced"   // high+ 需确认(默认)
    ModePermissive Mode = "permissive" // 仅 critical 处理
)
模式 需要确认的等级 适用场景
strict medium 及以上 生产环境、关键主机、新手操作
balanced(默认) high 及以上 日常运维
permissive 仅 critical 测试环境、自动化脚本

阈值映射:

func (c *Controller) riskThreshold() tools.RiskLevel {
    switch c.mode {
    case ModeStrict:     return tools.RiskMedium
    case ModePermissive: return tools.RiskCritical
    default:             return tools.RiskHigh
    }
}

五级风险评估

// 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()):

case RiskSafe:     return "安全"
case RiskLow:      return "低风险"
case RiskMedium:   return "中风险"
case RiskHigh:     return "高风险"
case RiskCritical: return "危险"

前端在 tool_call 事件中拿到 risk 字段,渲染为风险标签。

危险命令黑名单(硬红线)

// 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 + 根目录路径(即使不在黑名单字面中):

if strings.HasPrefix(lower, "rm") && strings.Contains(lower, "-rf") && containsRootPath(lower) {
    return true, "rm -rf 包含根目录路径"
}

containsRootPath 检测 /boot、/etc、/usr、/var 等系统目录。

Check 策略

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 动态评估

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 前):

// assessSingleCommand
if isSudo {
    return RiskHigh  // 高危命令 + sudo → High
}
return RiskMedium  // 高危命令无 sudo → Medium

组合命令取最高

// 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 落库:

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 注册中心

// 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 方法)

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 扩展接口

type DynamicRiskTool interface {
    Tool
    RiskLevelForArgs(args map[string]interface{}) RiskLevel
}

安全控制器 Check 时优先调用动态评估:

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 结构

type Result struct {
    Success bool   `json:"success"`
    Output  string `json:"output"`
    Error   string `json:"error,omitempty"`
}

参数解析辅助

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 内置工具。

与 opsxcli 的差异:剥离了 local_bash / scp_transfer / file_read / file_search 四个工具;剥离 SSH 连接池(aiops 直接用 pkg/remoteexec,无连接池需求);剥离 Closer 接口(Web 工具无资源需释放)。

扩展指南

添加新工具

// 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 切片:

var dangerousCommands = []string{
    // ... 现有
    "your-critical-command",
}

自定义确认逻辑

controller 的 confirmFn 可对接企业审批系统(如先走工单再确认):

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