feat: 资产拦截接入执行链,新增任务级拦截/允许规则
在 agent 下发意图(add_intent)与插入资产(insert_assets)两处接入资产闸门;
并新增任务级规则(拦截 block + 允许 allow 白名单),创建任务时录入、任务详情
总览里可编辑。
- 匹配/执行(db/asset_intercept_match.go):MatchAssetInterceptRules 纯匹配
(exact/fuzzy 的 域名·IP·URL + CIDR,URL host 会拆出归类);EvaluateAssetGate
「先拦截后允许」闸门——命中拦截即禁止;未命中但存在启用的允许规则且都不命中→
不允许测试;无启用允许规则则不启用白名单。附单元测试。
- 执行点:add_intent 命中即不下发意图;insert_assets 命中即跳过 Upsert 及
AddAutoScope 副作用。均合并 全局∪任务级 规则。
- 任务级规则:表 task_intercept_rules(task_id FK CASCADE + action block/allow,
幂等 ALTER 补旧库);db/task_intercept.go 提供 List/Split/CRUD/事务插入;
创建任务随 intercept_rules 提交;CRUD 路由 /api/tasks/{id}/intercept-rules。
- 前端:新建任务抽屉多行录入(拦截/允许 + 类型,用 NativeSelect 避免 Sheet 内
下拉 portal 导致误关抽屉);任务详情总览新增规则管理卡片(增删改+启用开关)。
This commit is contained in:
1 parent
dfc9d2fa2e
commit
d5359e1f04
14 files changed
+1234
No files matched your search
@@ -4,6 +4,8 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -11,6 +13,57 @@ import (
|
||||
actool "github.com/Autumn-27/norma/tool"
|
||||
)
|
||||
|
||||
// assetInterceptCandidates 提取一条待插入资产输入项的 域名/IP/URL 候选串,用于资产拦截匹配。
|
||||
// URL 的 host 会拆出归类,使「只带 URL」的服务/端点资产也能被 域名/IP 规则命中。
|
||||
func assetInterceptCandidates(item assetInputItem) (domains, ips, urls []string) {
|
||||
add := func(dst *[]string, s string) {
|
||||
if s = strings.TrimSpace(s); s != "" {
|
||||
*dst = append(*dst, s)
|
||||
}
|
||||
}
|
||||
add(&domains, item.Domain)
|
||||
for _, d := range item.BoundDomains {
|
||||
add(&domains, d)
|
||||
}
|
||||
add(&ips, item.IP)
|
||||
add(&ips, item.ServiceIP)
|
||||
add(&urls, item.URL)
|
||||
if item.URL != "" {
|
||||
if u, err := url.Parse(item.URL); err == nil {
|
||||
if h := u.Hostname(); h != "" {
|
||||
if net.ParseIP(h) != nil {
|
||||
add(&ips, h)
|
||||
} else {
|
||||
add(&domains, h)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return domains, ips, urls
|
||||
}
|
||||
|
||||
// assetInputLabel 返回一条待插入资产的简短标识,用于拦截说明消息。
|
||||
func assetInputLabel(item assetInputItem) string {
|
||||
typ := strings.TrimSpace(item.Type)
|
||||
var target string
|
||||
switch {
|
||||
case strings.TrimSpace(item.Domain) != "":
|
||||
target = strings.TrimSpace(item.Domain)
|
||||
case strings.TrimSpace(item.URL) != "":
|
||||
target = strings.TrimSpace(item.URL)
|
||||
case strings.TrimSpace(item.IP) != "":
|
||||
target = strings.TrimSpace(item.IP)
|
||||
case strings.TrimSpace(item.ServiceIP) != "":
|
||||
target = strings.TrimSpace(item.ServiceIP)
|
||||
default:
|
||||
target = "(未知)"
|
||||
}
|
||||
if typ != "" {
|
||||
return fmt.Sprintf("[%s] %s", typ, target)
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Unified asset insertion tools
|
||||
// =====================================================================
|
||||
@@ -174,7 +227,28 @@ func (t *ToolSet) insertAssets() actool.CoreTool {
|
||||
var results []result
|
||||
var errs []errEntry
|
||||
|
||||
// 资产闸门规则一次性载入;读取失败则跳过判定(不阻断插入)。
|
||||
// 拦截规则 = 全局 ∪ 任务级 block;允许规则 = 任务级 allow。
|
||||
blockRules, _ := t.as.ListAssetInterceptRules()
|
||||
var allowRules []db.AssetInterceptRule
|
||||
if t.taskID > 0 {
|
||||
if tb, ta, err := t.as.TaskInterceptRulesSplit(t.taskID); err == nil {
|
||||
blockRules = append(blockRules, tb...)
|
||||
allowRules = ta
|
||||
}
|
||||
}
|
||||
|
||||
for i, item := range a.Assets {
|
||||
// 资产闸门:先拦截后允许,被拒的资产禁止插入(跳过 Upsert 及后续副作用)。
|
||||
domains, ips, urls := assetInterceptCandidates(item)
|
||||
if d := db.EvaluateAssetGate(blockRules, allowRules, domains, ips, urls); !d.Allowed {
|
||||
errs = append(errs, errEntry{
|
||||
Index: i,
|
||||
Error: fmt.Sprintf("资产 %s %s,已禁止插入", assetInputLabel(item), d.Reason),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
typ := strings.TrimSpace(item.Type)
|
||||
var id int64
|
||||
var err error
|
||||
|
||||
Reference in new issue
Block a user