From e2c810f7c52cfd5a2a783bb8801d5cd6384f1b4a Mon Sep 17 00:00:00 2001 From: zhanghan Date: Wed, 10 Sep 2025 15:46:48 +0800 Subject: [PATCH] When saving, detect whether high-risk commands can exist --- automation/server/cmd/commands/server.go | 12 ++ .../dangerous_rule/service/dangerous_rule.go | 28 ++- .../module/dangerous_rule/service/detect.go | 174 ++++++++++++++++++ 3 files changed, 210 insertions(+), 4 deletions(-) create mode 100644 automation/server/internal/module/dangerous_rule/service/detect.go diff --git a/automation/server/cmd/commands/server.go b/automation/server/cmd/commands/server.go index 50bb6d33..3f5ab622 100644 --- a/automation/server/cmd/commands/server.go +++ b/automation/server/cmd/commands/server.go @@ -7,6 +7,7 @@ import ( "github.com/spf13/cobra" "openeuler.org/PilotGo/PilotGo-plugin-automation/cmd/config/options" "openeuler.org/PilotGo/PilotGo-plugin-automation/internal/global" + dangerousRuleService "openeuler.org/PilotGo/PilotGo-plugin-automation/internal/module/dangerous_rule/service" "openeuler.org/PilotGo/PilotGo-plugin-automation/internal/router" "openeuler.org/PilotGo/PilotGo-plugin-automation/internal/service" "openeuler.org/PilotGo/PilotGo-plugin-automation/pkg/utils" @@ -53,8 +54,19 @@ func Run() error { } defer manager.CloseAll() + if err := startService(); err != nil { + return err + } + if err := router.HttpServerInit().Run(global.App.HttpAddr); err != nil { return err } return nil } + +func startService() error { + if err := dangerousRuleService.LoadFromDB(); err != nil { + return err + } + return nil +} diff --git a/automation/server/internal/module/dangerous_rule/service/dangerous_rule.go b/automation/server/internal/module/dangerous_rule/service/dangerous_rule.go index afa29187..c4a6fd49 100644 --- a/automation/server/internal/module/dangerous_rule/service/dangerous_rule.go +++ b/automation/server/internal/module/dangerous_rule/service/dangerous_rule.go @@ -6,6 +6,7 @@ import ( "gitee.com/openeuler/PilotGo/sdk/response" "github.com/go-sql-driver/mysql" + "openeuler.org/PilotGo/PilotGo-plugin-automation/internal/global" "openeuler.org/PilotGo/PilotGo-plugin-automation/internal/module/dangerous_rule/dao" "openeuler.org/PilotGo/PilotGo-plugin-automation/internal/module/dangerous_rule/model" ) @@ -28,7 +29,7 @@ func AddDangerousRule(rule *model.DangerousRule) error { } return err } - return nil + return LoadFromDB() } func GetDangerousRules(query *response.PaginationQ) ([]model.DangerousRule, int, error) { @@ -52,19 +53,38 @@ func UpdateDangerousRule(rule *model.DangerousRule) error { } return err } - return nil + return LoadFromDB() } func ChangeDangerousRuleStatus(id int, status bool) error { if id == 0 { return fmt.Errorf("ID is required") } - return dao.ChangeDangerousRuleStatus(id, map[string]interface{}{ + err := dao.ChangeDangerousRuleStatus(id, map[string]interface{}{ "updated_at": time.Now().Format("2006-01-02 15:04:05"), "status": status, }) + if err != nil { + return err + } + return LoadFromDB() } func DeleteDangerousRule(id []int) error { - return dao.DeleteDangerousRule(id) + if err := dao.DeleteDangerousRule(id); err != nil { + return err + } + return LoadFromDB() +} + +/******************************更新redis**************************************/ +const DangerousRuleKey = "dangerous_rules" + +func LoadFromDB() error { + var rules []model.DangerousRule + if err := global.App.MySQL.Where("status = ?", 1).Find(&rules).Error; err != nil { + return err + } + + return global.App.Redis.Set(DangerousRuleKey, rules, 0) } diff --git a/automation/server/internal/module/dangerous_rule/service/detect.go b/automation/server/internal/module/dangerous_rule/service/detect.go new file mode 100644 index 00000000..7552fc60 --- /dev/null +++ b/automation/server/internal/module/dangerous_rule/service/detect.go @@ -0,0 +1,174 @@ +package service + +import ( + "fmt" + "regexp" + "sort" + "strings" + + "openeuler.org/PilotGo/PilotGo-plugin-automation/internal/global" + "openeuler.org/PilotGo/PilotGo-plugin-automation/internal/module/common/enum/rule" + "openeuler.org/PilotGo/PilotGo-plugin-automation/internal/module/common/enum/script" + "openeuler.org/PilotGo/PilotGo-plugin-automation/internal/module/dangerous_rule/model" +) + +func getetRulesFromRedis() ([]model.DangerousRule, error) { + var rules []model.DangerousRule + err := global.App.Redis.Get(DangerousRuleKey, &rules) + if err != nil { + return nil, err + } + return rules, nil +} + +type Finding struct { + RuleID int `json:"rule_id"` + Description string `json:"description"` + Action rule.ActionType `json:"action"` + Line int `json:"line"` // 1-based + Snippet string `json:"snippet"` // 匹配文本或行片段 + Match string `json:"match"` // 匹配到的文本 +} + +type detectRule struct { + ID int + Description string + Action rule.ActionType + Regex *regexp.Regexp + Keywords []string +} + +// Detect 脚本检测主方法 +func Detect(script string, scriptType script.ScriptType) ([]Finding, error) { + return detectInternal(script, scriptType) +} + +// DetectWithVars 支持变量替换 +func DetectWithVars(script string, scriptType script.ScriptType, params map[string]string) ([]Finding, error) { + expanded := expandSimpleVars(script, params) + return detectInternal(expanded, scriptType) +} + +func detectInternal(script string, scriptType script.ScriptType) ([]Finding, error) { + // 1. 从 Redis 获取高危规则 + dangerousRules, err := getetRulesFromRedis() + if err != nil { + return nil, fmt.Errorf("获取高危命令失败: %w", err) + } + + // 2. 转换为可检测规则(Regex 或 Keywords) + var rules []detectRule + for _, r := range dangerousRules { + // 如果脚本类型不在规则的 ScriptTypes 中,跳过 + if !containsScriptType(r.ScriptTypes, scriptType) { + continue + } + rules = append(rules, toDetectRule(r)) + } + + lines := splitLines(script) + var findings []Finding + + for i, line := range lines { + trim := strings.TrimSpace(line) + if trim == "" || isCommentLine(trim) { + continue + } + lower := strings.ToLower(trim) + + for _, r := range rules { + matched := false + var matchText string + + // 正则匹配 + if r.Regex != nil { + if loc := r.Regex.FindStringIndex(trim); loc != nil { + matched = true + matchText = trim[loc[0]:loc[1]] + } + } + + // 关键字匹配 + if !matched && len(r.Keywords) > 0 { + for _, k := range r.Keywords { + if strings.Contains(lower, strings.ToLower(k)) { + matched = true + matchText = k + break + } + } + } + + if matched { + findings = append(findings, Finding{ + RuleID: r.ID, + Description: r.Description, + Action: r.Action, + Line: i + 1, + Snippet: trim, + Match: matchText, + }) + } + } + } + + // 排序:Block 优先,Warning 次之;同类型按行号升序 + sort.Slice(findings, func(i, j int) bool { + if findings[i].Action == findings[j].Action { + return findings[i].Line < findings[j].Line + } + return findings[i].Action == rule.Block + }) + + return findings, nil +} + +// 判断 ScriptTypes 是否包含某个类型 +func containsScriptType(arr script.ScriptTypeArr, t script.ScriptType) bool { + for _, v := range arr { + if v == t { + return true + } + } + return false +} + +// 将 DangerousRule 转换为 detectRule +func toDetectRule(r model.DangerousRule) detectRule { + dr := detectRule{ + ID: r.ID, + Description: r.Description, + Action: r.Action, + } + + if r.Expression != "" { + if re, err := regexp.Compile(r.Expression); err == nil { + dr.Regex = re + } else { + dr.Keywords = []string{r.Expression} + } + } + + return dr +} + +// 简单变量替换 ${VAR} 或 $VAR +func expandSimpleVars(script string, params map[string]string) string { + out := script + for k, v := range params { + out = strings.ReplaceAll(out, fmt.Sprintf("${%s}", k), v) + out = strings.ReplaceAll(out, fmt.Sprintf("$%s", k), v) + } + return out +} + +// 判断是否为注释行 +func isCommentLine(line string) bool { + trim := strings.TrimSpace(line) + return strings.HasPrefix(trim, "#") || strings.HasPrefix(trim, "//") +} + +// 按行切分脚本 +func splitLines(s string) []string { + return strings.Split(strings.ReplaceAll(s, "\r\n", "\n"), "\n") +} -- Gitee