diff --git a/sigmac/rule_or_config.go b/sigmac/rule_or_config.go new file mode 100644 index 0000000..3836431 --- /dev/null +++ b/sigmac/rule_or_config.go @@ -0,0 +1,23 @@ +package main + +import ( + "gopkg.in/yaml.v3" +) + +type ruleOrConfig string + +func (r ruleOrConfig) IsRule() bool { + return r == "rule" +} + +func (r *ruleOrConfig) UnmarshalYAML(node *yaml.Node) error { + // Check if there's a key called "detection". + // This is a required field in a Sigma rule but doesn't exist in a config + for _, node := range node.Content { + if node.Kind == yaml.ScalarNode && node.Value == "detection" { + *r = "rule" + return nil + } + } + return nil +} diff --git a/sigmac/rule_or_config_test.go b/sigmac/rule_or_config_test.go new file mode 100644 index 0000000..a83e718 --- /dev/null +++ b/sigmac/rule_or_config_test.go @@ -0,0 +1,44 @@ +package main + +import ( + "testing" + + "gopkg.in/yaml.v3" +) + +func Test_isSigmaRule(t *testing.T) { + tests := []struct { + file string + expectedIsRule bool + }{ + { + `title: foo +logsources: + foo: + category: process_creation + index: bar +`, + false, + }, + { + `title: foo +detection: + foo: + - bar + - baz + selection: foo +`, + true, + }, + } + for _, tt := range tests { + var isRule ruleOrConfig + err := yaml.Unmarshal([]byte(tt.file), &isRule) + if err != nil { + t.Fatal(err) + } + if isRule.IsRule() != tt.expectedIsRule { + t.Errorf("Expected\n%s to be detected as a rule", tt.file) + } + } +} diff --git a/sigmac/sigmac.go b/sigmac/sigmac.go index fc4237a..bc14e32 100644 --- a/sigmac/sigmac.go +++ b/sigmac/sigmac.go @@ -10,6 +10,7 @@ import ( "text/template" "github.com/bradleyjkemp/sigma-go" + "gopkg.in/yaml.v3" ) var ( @@ -33,7 +34,9 @@ func main() { } func run(root string, recursive bool) error { + directories := map[string]struct{}{} rulesByDirectory := map[string][]string{} + configByDirectory := map[string][]string{} // Collect all the rules under this root err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { @@ -53,14 +56,31 @@ func run(root string, recursive bool) error { return fmt.Errorf("error reading %s: %w", path, err) } - // Just check the rule is valid - _, err = sigma.ParseRule(contents) - if err != nil { - return fmt.Errorf("error parsing %s: %w", path, err) + var ruleOrConfig ruleOrConfig + yaml.Unmarshal(contents, &ruleOrConfig) + + if ruleOrConfig.IsRule() { + // Just check the rule is valid + _, err = sigma.ParseRule(contents) + if err != nil { + return fmt.Errorf("error parsing %s: %w", path, err) + } + + dir := filepath.Dir(path) + directories[dir] = struct{}{} + rulesByDirectory[dir] = append(rulesByDirectory[dir], strconv.Quote(string(contents))) + } else { + // Just check the config is valid + _, err = sigma.ParseConfig(contents) + if err != nil { + return fmt.Errorf("error parsing %s: %w", path, err) + } + + dir := filepath.Dir(path) + directories[dir] = struct{}{} + configByDirectory[dir] = append(configByDirectory[dir], strconv.Quote(string(contents))) } - dir := filepath.Dir(path) - rulesByDirectory[dir] = append(rulesByDirectory[dir], strconv.Quote(string(contents))) return nil }) if err != nil { @@ -68,7 +88,7 @@ func run(root string, recursive bool) error { } // For each directory containing Sigma rules, write a sigma.go file containing all the rules - for dir, rules := range rulesByDirectory { + for dir := range directories { registryFile, err := os.OpenFile(filepath.Join(dir, "sigma.go"), os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0755) if err != nil { return err @@ -76,7 +96,8 @@ func run(root string, recursive bool) error { _, packageName := filepath.Split(dir) params := map[string]interface{}{ "PackageName": packageName, - "Rules": rules, + "Rules": rulesByDirectory[dir], + "Configs": configByDirectory[dir], } err = registryTmpl.Execute(registryFile, params) if err != nil { @@ -92,10 +113,14 @@ package {{.PackageName}} import ( sigma "github.com/bradleyjkemp/sigma-go" + + "sort" ) var Rules = map[string]sigma.Rule{} +var Configs []sigma.Config + func registerRule(contents string) { // TODO: it'd be better if this were already parsed rather than being parsed at runtime rule, err := sigma.ParseRule([]byte(contents)) @@ -113,9 +138,26 @@ func registerRule(contents string) { } Rules[id] = rule } + +func registerConfig(contents string) { + // TODO: it'd be better if this were already parsed rather than being parsed at runtime + config, err := sigma.ParseConfig([]byte(contents)) + if err != nil { + panic(err) + } + + Configs = append(Configs, config) + sort.Slice(Configs, func(i, j int) bool { + return Configs[i].Order < Configs[j].Order + }) +} {{range .Rules}} func init() { registerRule({{.}}) } +{{end}}{{range .Configs}} +func init() { + registerConfig({{.}}) +} {{end}} `))