Skip to content

Commit

Permalink
Expose position information for key rule parts (#34)
Browse files Browse the repository at this point in the history
* Expose raw yaml.Node for certain rule fields

* Only expose line+column

* Implement custom inlining

* Include Search nodes even in config files
  • Loading branch information
bradleyjkemp authored May 24, 2023
1 parent 65353ca commit 99be942
Show file tree
Hide file tree
Showing 7 changed files with 938 additions and 2 deletions.
7 changes: 7 additions & 0 deletions ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package sigma

import (
"fmt"
"gopkg.in/yaml.v3"
"strings"

"github.com/bradleyjkemp/sigma-go/internal/grammar"
)

type Condition struct {
node *yaml.Node
Search SearchExpr
Aggregation AggregationExpr
}
Expand All @@ -21,6 +23,11 @@ func (c Condition) MarshalYAML() (interface{}, error) {
}
}

// Position returns the line and column of this Condition in the original input
func (c Condition) Position() (int, int) {
return c.node.Line - 1, c.node.Column - 1
}

type SearchExpr interface {
searchExpr()
toString() string
Expand Down
52 changes: 51 additions & 1 deletion rule_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,40 @@ type Detection struct {
Timeframe time.Duration `yaml:",omitempty"`
}

func (d *Detection) UnmarshalYAML(node *yaml.Node) error {
// we need a custom unmarshaller here to handle the position information for searches
if node.Kind != yaml.MappingNode || len(node.Content)%2 != 0 {
return fmt.Errorf("cannot unmarshal %d into Detection", node.Kind)
}

for i := 0; i < len(node.Content); i += 2 {
key, value := node.Content[i], node.Content[i+1]

switch key.Value {
case "condition":
if err := d.Conditions.UnmarshalYAML(value); err != nil {
return err
}
case "timeframe":
if err := node.Decode(&d.Timeframe); err != nil {
return err
}
default:
search := Search{}
if err := search.UnmarshalYAML(value); err != nil {
return err
}
search.node = key
if d.Searches == nil {
d.Searches = map[string]Search{}
}
d.Searches[key.Value] = search
}

}
return nil
}

type Conditions []Condition

func (c *Conditions) UnmarshalYAML(node *yaml.Node) error {
Expand All @@ -62,18 +96,20 @@ func (c *Conditions) UnmarshalYAML(node *yaml.Node) error {
if err != nil {
return err
}
parsed.node = node
*c = []Condition{parsed}

case yaml.SequenceNode:
var conditions []string
if err := node.Decode(&conditions); err != nil {
return err
}
for _, condition := range conditions {
for i, condition := range conditions {
parsed, err := ParseCondition(condition)
if err != nil {
return fmt.Errorf("error parsing condition \"%s\": %w", condition, err)
}
parsed.node = node.Content[i]
*c = append(*c, parsed)
}

Expand All @@ -94,11 +130,13 @@ func (c Conditions) MarshalYAML() (interface{}, error) {
}

type Search struct {
node *yaml.Node
Keywords []string
EventMatchers []EventMatcher
}

func (s *Search) UnmarshalYAML(node *yaml.Node) error {
s.node = node
switch node.Kind {
// In the common case, SearchIdentifiers are a single EventMatcher (map of field names to values)
case yaml.MappingNode:
Expand Down Expand Up @@ -126,6 +164,11 @@ func (s *Search) UnmarshalYAML(node *yaml.Node) error {
}
}

// Position returns the line and column of this Search in the original input
func (s Search) Position() (int, int) {
return s.node.Line - 1, s.node.Column - 1
}

func (s Search) MarshalYAML() (interface{}, error) {

var err error
Expand Down Expand Up @@ -184,12 +227,19 @@ func (f EventMatcher) MarshalYAML() (interface{}, error) {
}

type FieldMatcher struct {
node *yaml.Node
Field string
Modifiers []string
Values []interface{}
}

// Position returns the line and column of this FieldMatcher in the original input
func (f FieldMatcher) Position() (int, int) {
return f.node.Line - 1, f.node.Column - 1
}

func (f *FieldMatcher) unmarshal(field *yaml.Node, values *yaml.Node) error {
f.node = field
fieldParts := strings.Split(field.Value, "|")
f.Field, f.Modifiers = fieldParts[0], fieldParts[1:]

Expand Down
3 changes: 2 additions & 1 deletion rule_parser_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package sigma

import (
"github.com/google/go-cmp/cmp/cmpopts"
"io/ioutil"
"os"
"path/filepath"
Expand Down Expand Up @@ -79,7 +80,7 @@ func TestMarshalRule(t *testing.T) {
t.Fatalf("error decoding rule copy: %v", err)
}

if !cmp.Equal(rule, rule_copy) {
if !cmp.Equal(rule, rule_copy, cmpopts.IgnoreUnexported(Condition{}, FieldMatcher{}, Search{})) {
t.Fatalf("rule and marshalled copy are not equal")
}
})
Expand Down
Loading

0 comments on commit 99be942

Please sign in to comment.