// SPDX-License-Identifier: GPL-3.0-or-later package events import ( "sort" "strings" ) // RuleCatalog returns the stable operator-facing metadata currently exposed by // Python ruleops.list_rules. Conditions remain data, so auditors can inspect // what the binary will match without reading Go source or loading eBPF. func RuleCatalog(execRules ExecRuleEngine) []map[string]any { builtinExec := map[int]bool{100001: true, 100002: true, 100003: true, 100004: true} records := make([]map[string]any, 0, len(execRules.Rules)+len(DefaultSyscallRuleEngine().Rules)+len(DefaultHostRuleEngine().Rules)) for _, rule := range execRules.Rules { origin := "configured" if builtinExec[rule.SID] { origin = "builtin" } conditions := map[string]any{} if len(rule.PathPrefixes) > 0 { conditions["path_prefixes"] = append([]string(nil), rule.PathPrefixes...) } if len(rule.ExecComms) > 0 { conditions["exec_comm"] = sortedStringKeys(rule.ExecComms) } if len(rule.ParentComms) > 0 { conditions["parent_comm"] = sortedStringKeys(rule.ParentComms) } if rule.ArgvRegex != "" { conditions["argv_regex"] = rule.ArgvRegex } if len(rule.ParentExclude) > 0 { conditions["parent_exclude"] = sortedStringKeys(rule.ParentExclude) } records = append(records, map[string]any{ "sid": rule.SID, "event": "exec", "origin": origin, "severity": rule.Severity, "classtype": rule.Classtype, "signature": "exec_rule." + rule.Classtype, "msg": rule.Message, "conditions": conditions, }) } for _, rule := range DefaultSyscallRuleEngine().Rules { records = append(records, map[string]any{ "sid": rule.SID, "event": "syscall", "origin": "builtin", "severity": rule.Severity, "classtype": rule.Classtype, "signature": "syscall_rule." + rule.Classtype, "msg": rule.Message, "conditions": map[string]any{ "syscalls": sortedStringKeys(rule.Syscalls), "predicate": "built-in predicate", }, }) } for _, rule := range DefaultHostRuleEngine().Rules { conditions := map[string]any{"events": sortedStringKeys(rule.Events)} if len(rule.Comms) > 0 { conditions["comm"] = sortedStringKeys(rule.Comms) } if len(rule.ParentComms) > 0 { conditions["parent_comm"] = sortedStringKeys(rule.ParentComms) } if rule.PeerPublic != nil { conditions["peer_public"] = *rule.PeerPublic } if len(rule.PeerPorts) > 0 { conditions["peer_ports"] = sortedIntKeys(rule.PeerPorts) } if len(rule.PeerPortExclude) > 0 { conditions["peer_port_exclude"] = sortedIntKeys(rule.PeerPortExclude) } if len(rule.LocalPorts) > 0 { conditions["local_ports"] = sortedIntKeys(rule.LocalPorts) } if len(rule.LocalPortExclude) > 0 { conditions["local_port_exclude"] = sortedIntKeys(rule.LocalPortExclude) } if len(rule.PathPrefixes) > 0 { conditions["path_prefixes"] = append([]string(nil), rule.PathPrefixes...) } if len(rule.TargetUIDs) > 0 { conditions["target_uids"] = sortedIntKeys(rule.TargetUIDs) } if len(rule.TargetGIDs) > 0 { conditions["target_gids"] = sortedIntKeys(rule.TargetGIDs) } if len(rule.CapabilitiesAny) > 0 { conditions["capabilities_any"] = sortedStringKeys(rule.CapabilitiesAny) } if len(rule.ModuleNames) > 0 { conditions["module_names"] = sortedStringKeys(rule.ModuleNames) } if rule.ArgvRegex != "" { conditions["argv_regex"] = rule.ArgvRegex } eventTypes := sortedStringKeys(rule.Events) records = append(records, map[string]any{ "sid": rule.SID, "event": strings.Join(eventTypes, ","), "origin": "builtin", "severity": rule.Severity, "classtype": rule.Classtype, "signature": "host_rule." + rule.Classtype, "msg": rule.Message, "conditions": conditions, }) } sort.SliceStable(records, func(i, j int) bool { leftSID, rightSID := records[i]["sid"].(int), records[j]["sid"].(int) if leftSID != rightSID { return leftSID < rightSID } return records[i]["event"].(string) < records[j]["event"].(string) }) return records } func FindRule(records []map[string]any, sid int) map[string]any { for _, record := range records { if record["sid"].(int) == sid { return record } } return nil } func sortedStringKeys(values map[string]bool) []string { result := make([]string, 0, len(values)) for value := range values { result = append(result, value) } sort.Strings(result) return result } func sortedIntKeys(values map[int]bool) []int { result := make([]int, 0, len(values)) for value := range values { result = append(result, value) } sort.Ints(result) return result }