From 7b379c31edab43d70d2a6ac9461fc4a2f01ea4d1 Mon Sep 17 00:00:00 2001 From: Jaime Soriano Pastor Date: Wed, 19 Oct 2022 17:18:47 +0200 Subject: [PATCH] Allow event types that are allowed by any of the categories in the document This change relaxes the validation of event types for fields whose allowed values include a set of accepted event types: - Any event type is valid if it is valid for any of the values. Before it should be valid for all values. - Empty event types are allowed. --- internal/fields/model.go | 31 --------- internal/fields/testdata/fields/fields.yml | 9 +++ internal/fields/validate.go | 80 ++++++++++++++++++---- internal/fields/validate_test.go | 35 +++++++++- 4 files changed, 107 insertions(+), 48 deletions(-) diff --git a/internal/fields/model.go b/internal/fields/model.go index 359aff9ab8..13ce51c1df 100644 --- a/internal/fields/model.go +++ b/internal/fields/model.go @@ -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 diff --git a/internal/fields/testdata/fields/fields.yml b/internal/fields/testdata/fields/fields.yml index d160c96fa4..0b90e158d7 100644 --- a/internal/fields/testdata/fields/fields.yml +++ b/internal/fields/testdata/fields/fields.yml @@ -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: diff --git a/internal/fields/validate.go b/internal/fields/validate.go index 93bf56632f..68d8533383 100644 --- a/internal/fields/validate.go +++ b/internal/fields/validate.go @@ -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) @@ -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": @@ -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. @@ -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") + } +} diff --git a/internal/fields/validate_test.go b/internal/fields/validate_test.go index 5b636ce0c3..42b60141f0 100644 --- a/internal/fields/validate_test.go +++ b/internal/fields/validate_test.go @@ -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{ @@ -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{ @@ -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") } }