Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 0 additions & 31 deletions internal/fields/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,37 +214,6 @@ func (avs AllowedValues) IsAllowed(value string) bool {
return common.StringSliceContains(avs.Values(), value)
}

// IsExpectedEventType returns true if the event type is allowed for the given value.
// This method can be used to check single values of event type or arrays of them.
func (avs AllowedValues) IsExpectedEventType(value string, eventType interface{}) bool {
expected := avs.ExpectedEventTypes(value)
if len(expected) == 0 {
// No restrictions defined, all good to go.
return true
}
switch eventType := eventType.(type) {
case string:
return common.StringSliceContains(expected, eventType)
case []interface{}:
if len(eventType) == 0 {
return false
}
for _, elem := range eventType {
elem, ok := elem.(string)
if !ok {
return false
}
if !common.StringSliceContains(expected, elem) {
return false
}
}
return true
default:
// It must be a string, or an array of strings.
return false
}
}

// Values returns the list of allowed values.
func (avs AllowedValues) Values() []string {
var values []string
Expand Down
9 changes: 9 additions & 0 deletions internal/fields/testdata/fields/fields.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@
- info
- protocol
- start
- name: iam
expected_event_types:
- admin
- change
- creation
- deletion
- group
- info
- user
- name: event.type
type: keyword
normalize:
Expand Down
80 changes: 65 additions & 15 deletions internal/fields/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -468,9 +468,33 @@ func validSubField(def FieldDefinition, extraPart string) bool {
// parseElementValue checks that the value stored in a field matches the field definition. For
// arrays it checks it for each Element.
func (v *Validator) parseElementValue(key string, definition FieldDefinition, val interface{}, doc common.MapStr) error {
err := v.parseAllElementValues(key, definition, val, doc)
if err != nil {
return err
}

return forEachElementValue(key, definition, val, doc, v.parseSingleElementValue)
}

// parseAllElementValues performs validations that must be done for all elements at once in
// case that there are multiple values.
func (v *Validator) parseAllElementValues(key string, definition FieldDefinition, val interface{}, doc common.MapStr) error {
switch definition.Type {
case "constant_keyword", "keyword", "text":
if !v.specVersion.LessThan(semver2_0_0) {
strings, err := valueToStringsSlice(val)
if err != nil {
return fmt.Errorf("field %q value \"%v\" (%T): %w", key, val, val, err)
}
if err := ensureExpectedEventType(key, strings, definition, doc); err != nil {
return err
}
}
}
return nil
}

// parseSingeElementValue performs validations on individual values of each element.
func (v *Validator) parseSingleElementValue(key string, definition FieldDefinition, val interface{}, doc common.MapStr) error {
invalidTypeError := func() error {
return fmt.Errorf("field %q's Go type, %T, does not match the expected field type: %s (field value: %v)", key, val, definition.Type, val)
Expand All @@ -495,11 +519,6 @@ func (v *Validator) parseSingleElementValue(key string, definition FieldDefiniti
if err := ensureAllowedValues(key, valStr, definition); err != nil {
return err
}
if !v.specVersion.LessThan(semver2_0_0) {
if err := ensureExpectedEventType(key, valStr, definition, doc); err != nil {
return err
}
}
// Normal text fields should be of type string.
// If a pattern is provided, it checks if the value matches.
case "keyword", "text":
Expand All @@ -514,11 +533,6 @@ func (v *Validator) parseSingleElementValue(key string, definition FieldDefiniti
if err := ensureAllowedValues(key, valStr, definition); err != nil {
return err
}
if !v.specVersion.LessThan(semver2_0_0) {
if err := ensureExpectedEventType(key, valStr, definition, doc); err != nil {
return err
}
}
// Dates are expected to be formatted as strings or as seconds or milliseconds
// since epoch.
// If it is a string and a pattern is provided, it checks if the value matches.
Expand Down Expand Up @@ -663,11 +677,47 @@ func ensureAllowedValues(key, value string, definition FieldDefinition) error {

// ensureExpectedEventType validates that the document's `event.type` field is one of the expected
// one for the given value.
func ensureExpectedEventType(key, value string, definition FieldDefinition, doc common.MapStr) error {
eventType, _ := doc.GetValue("event.type")
if !definition.AllowedValues.IsExpectedEventType(value, eventType) {
expected := definition.AllowedValues.ExpectedEventTypes(value)
return fmt.Errorf("field \"event.type\" value \"%v\" (%T) is not one of the expected values (%s) for %s=%q", eventType, eventType, strings.Join(expected, ", "), key, value)
func ensureExpectedEventType(key string, values []string, definition FieldDefinition, doc common.MapStr) error {
eventTypeVal, _ := doc.GetValue("event.type")
eventTypes, err := valueToStringsSlice(eventTypeVal)
if err != nil {
return fmt.Errorf("field \"event.type\" value \"%v\" (%T): %w", eventTypeVal, eventTypeVal, err)
}
var expected []string
for _, value := range values {
expectedForValue := definition.AllowedValues.ExpectedEventTypes(value)
expected = common.StringSlicesUnion(expected, expectedForValue)
}
if len(expected) == 0 {
// No restrictions defined for this value, all good to go.
return nil
}
for _, eventType := range eventTypes {
if !common.StringSliceContains(expected, eventType) {
return fmt.Errorf("field \"event.type\" value %q is not one of the expected values (%s) for any of the values of %q (%s)", eventType, strings.Join(expected, ", "), key, strings.Join(values, ", "))
}
}

return nil
}

func valueToStringsSlice(value interface{}) ([]string, error) {
switch v := value.(type) {
case nil:
return nil, nil
case string:
return []string{v}, nil
case []interface{}:
var values []string
for _, e := range v {
s, ok := e.(string)
if !ok {
return nil, fmt.Errorf("expected string or array of strings")
}
values = append(values, s)
}
return values, nil
default:
return nil, fmt.Errorf("expected string or array of strings")
}
}
35 changes: 33 additions & 2 deletions internal/fields/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,13 @@ func TestValidate_ExpectedEventType(t *testing.T) {
},
valid: true,
},
{
title: "no event type",
doc: common.MapStr{
"event.category": "authentication",
},
valid: true,
},
{
title: "multiple valid event type",
doc: common.MapStr{
Expand All @@ -141,6 +148,14 @@ func TestValidate_ExpectedEventType(t *testing.T) {
},
valid: true,
},
{
title: "multiple categories",
doc: common.MapStr{
"event.category": []interface{}{"iam", "configuration"},
"event.type": []interface{}{"group", "change"},
},
valid: true,
},
{
title: "unexpected event type",
doc: common.MapStr{
Expand All @@ -149,15 +164,31 @@ func TestValidate_ExpectedEventType(t *testing.T) {
},
valid: false,
},
{
title: "multiple categories, no match",
doc: common.MapStr{
"event.category": []interface{}{"iam", "configuration"},
"event.type": []interface{}{"denied", "end"},
},
valid: false,
},
{
title: "multiple categories, some types don't match",
doc: common.MapStr{
"event.category": []interface{}{"iam", "configuration"},
"event.type": []interface{}{"denied", "end", "group", "change"},
},
valid: false,
},
}

for _, c := range cases {
t.Run(c.title, func(t *testing.T) {
errs := validator.ValidateDocumentMap(c.doc)
if c.valid {
assert.Empty(t, errs)
assert.Empty(t, errs, "should not have errors")
} else {
if assert.Len(t, errs, 1) {
if assert.Len(t, errs, 1, "should have one error") {
assert.Contains(t, errs[0].Error(), "is not one of the expected values")
}
}
Expand Down