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
@@ -0,0 +1,122 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// TaskInterceptRuleInput is one task-level rule supplied at task creation.
|
||||
// Action: 'block'=拦截 'allow'=允许(白名单);空视为 'block'。
|
||||
type TaskInterceptRuleInput struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Action string `json:"action"`
|
||||
Kind string `json:"kind"`
|
||||
Pattern string `json:"pattern"`
|
||||
Note string `json:"note"`
|
||||
}
|
||||
|
||||
const taskInterceptRuleCols = `id, enabled, action, kind, pattern, note, created_at, updated_at`
|
||||
|
||||
func scanTaskInterceptRule(row interface{ Scan(...any) error }) (AssetInterceptRule, error) {
|
||||
var r AssetInterceptRule
|
||||
err := row.Scan(&r.ID, &r.Enabled, &r.Action, &r.Kind, &r.Pattern, &r.Note, &r.CreatedAt, &r.UpdatedAt)
|
||||
return r, err
|
||||
}
|
||||
|
||||
// ListTaskInterceptRules returns a task's rules (both block and allow) as
|
||||
// AssetInterceptRule (Builtin always false; Action carries block/allow). taskID
|
||||
// <= 0 returns nothing.
|
||||
func (s *AssetStore) ListTaskInterceptRules(taskID int64) ([]AssetInterceptRule, error) {
|
||||
if taskID <= 0 {
|
||||
return nil, nil
|
||||
}
|
||||
rows, err := s.db.Query(`SELECT `+taskInterceptRuleCols+` FROM task_intercept_rules WHERE task_id=$1 ORDER BY action, id`, taskID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []AssetInterceptRule
|
||||
for rows.Next() {
|
||||
r, err := scanTaskInterceptRule(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, r)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// TaskInterceptRulesSplit loads a task's rules and splits them into block and
|
||||
// allow sets, for the enforcement gate.
|
||||
func (s *AssetStore) TaskInterceptRulesSplit(taskID int64) (block, allow []AssetInterceptRule, err error) {
|
||||
rules, err := s.ListTaskInterceptRules(taskID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
for _, r := range rules {
|
||||
if r.Action == "allow" {
|
||||
allow = append(allow, r)
|
||||
} else {
|
||||
block = append(block, r)
|
||||
}
|
||||
}
|
||||
return block, allow, nil
|
||||
}
|
||||
|
||||
func normalizeRuleAction(action string) string {
|
||||
if action == "allow" {
|
||||
return "allow"
|
||||
}
|
||||
return "block"
|
||||
}
|
||||
|
||||
// CreateTaskInterceptRule inserts a rule under a task.
|
||||
func (s *AssetStore) CreateTaskInterceptRule(taskID int64, action, kind, pattern, note string, enabled bool) (AssetInterceptRule, error) {
|
||||
row := s.db.QueryRow(`
|
||||
INSERT INTO task_intercept_rules(task_id, enabled, action, kind, pattern, note)
|
||||
VALUES ($1,$2,$3,$4,$5,$6)
|
||||
RETURNING `+taskInterceptRuleCols,
|
||||
taskID, enabled, normalizeRuleAction(action), kind, pattern, note)
|
||||
return scanTaskInterceptRule(row)
|
||||
}
|
||||
|
||||
// UpdateTaskInterceptRule replaces the editable fields of a task's rule (scoped
|
||||
// by task_id so a rule can only be edited through its owning task).
|
||||
func (s *AssetStore) UpdateTaskInterceptRule(taskID, ruleID int64, action, kind, pattern, note string, enabled bool) (AssetInterceptRule, error) {
|
||||
row := s.db.QueryRow(`
|
||||
UPDATE task_intercept_rules
|
||||
SET enabled=$3, action=$4, kind=$5, pattern=$6, note=$7
|
||||
WHERE id=$1 AND task_id=$2
|
||||
RETURNING `+taskInterceptRuleCols,
|
||||
ruleID, taskID, enabled, normalizeRuleAction(action), kind, pattern, note)
|
||||
return scanTaskInterceptRule(row)
|
||||
}
|
||||
|
||||
// DeleteTaskInterceptRule removes a task's rule. Returns false if not found.
|
||||
func (s *AssetStore) DeleteTaskInterceptRule(taskID, ruleID int64) (bool, error) {
|
||||
res, err := s.db.Exec(`DELETE FROM task_intercept_rules WHERE id=$1 AND task_id=$2`, ruleID, taskID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
n, _ := res.RowsAffected()
|
||||
return n > 0, nil
|
||||
}
|
||||
|
||||
// ToggleTaskInterceptRule flips the enabled state of a task's rule.
|
||||
func (s *AssetStore) ToggleTaskInterceptRule(taskID, ruleID int64, enabled bool) error {
|
||||
_, err := s.db.Exec(`UPDATE task_intercept_rules SET enabled=$3 WHERE id=$1 AND task_id=$2`, ruleID, taskID, enabled)
|
||||
return err
|
||||
}
|
||||
|
||||
// insertTaskInterceptRules inserts task-level rules within the task-creation
|
||||
// transaction (mirrors insertTaskCompanies).
|
||||
func insertTaskInterceptRules(tx *sql.Tx, taskID int64, rules []TaskInterceptRuleInput) error {
|
||||
for _, r := range rules {
|
||||
if _, err := tx.Exec(`
|
||||
INSERT INTO task_intercept_rules(task_id, enabled, action, kind, pattern, note)
|
||||
VALUES ($1,$2,$3,$4,$5,$6)`, taskID, r.Enabled, normalizeRuleAction(r.Action), r.Kind, r.Pattern, r.Note); err != nil {
|
||||
return fmt.Errorf("insert task intercept rule %q: %w", r.Pattern, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in new issue
Block a user