Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[libbeat]: Add support for values in lowercase processor #41530

Merged
merged 5 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ https://github.com/elastic/beats/compare/v8.8.1\...main[Check the HEAD diff]
- Update to Go 1.22.7. {pull}41018[41018]
- Replace Ubuntu 20.04 with 24.04 for Docker base images {issue}40743[40743] {pull}40942[40942]
- Reduce memory consumption of k8s autodiscovery and the add_kubernetes_metadata processor when Deployment metadata is enabled
- Add `lowercase` processor. {issue}22254[22254] {pull}41424[41424]

*Auditbeat*

Expand All @@ -245,7 +246,6 @@ https://github.com/elastic/beats/compare/v8.8.1\...main[Check the HEAD diff]
- Add process.entity_id, process.group.name and process.group.id in add_process_metadata processor. Make fim module with kprobes backend to always add an appropriately configured add_process_metadata processor to enrich file events {pull}38776[38776]

*Auditbeat*
- Add `lowercase` processor. {issue}22254[22254] {pull}41424[41424]

*Auditbeat*

Expand Down
39 changes: 37 additions & 2 deletions libbeat/processors/actions/alterFieldProcessor.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

type alterFieldProcessor struct {
Fields []string
Values []string
IgnoreMissing bool
FailOnError bool
AlterFullField bool
Expand All @@ -45,6 +46,7 @@
IgnoreMissing bool `config:"ignore_missing"`
FailOnError bool `config:"fail_on_error"`
AlterFullField bool `config:"alter_full_field"`
Values []string `config:"values"`
}{
IgnoreMissing: false,
FailOnError: true,
Expand Down Expand Up @@ -77,6 +79,7 @@
processorName: processorName,
AlterFullField: config.AlterFullField,
alterFunc: alterFunc,
Values: config.Values,
}, nil

}
Expand All @@ -92,7 +95,7 @@
}

for _, field := range a.Fields {
err := a.alter(event, field)
err := a.alterField(event, field)
if err != nil {
if a.IgnoreMissing && errors.Is(err, mapstr.ErrKeyNotFound) {
continue
Expand All @@ -105,10 +108,23 @@
}
}

for _, value := range a.Values {
err := a.alterValue(event, value)
if err != nil {
if a.IgnoreMissing && errors.Is(err, mapstr.ErrKeyNotFound) {
continue
}
if a.FailOnError {
event = backup
_, _ = event.PutValue("error.message", err.Error())
return event, err
}
}
}
return event, nil
}

func (a *alterFieldProcessor) alter(event *beat.Event, field string) error {
func (a *alterFieldProcessor) alterField(event *beat.Event, field string) error {

// modify all segments of the key
if a.AlterFullField {
Expand All @@ -133,3 +149,22 @@

return nil
}

func (a *alterFieldProcessor) alterValue(event *beat.Event, value string) error {
khushijain21 marked this conversation as resolved.
Show resolved Hide resolved
segmentCount := strings.Count(value, ".")
err := event.Fields.Traverse(value, mapstr.CaseSensitiveMode, func(level mapstr.M, key string) error {
if segmentCount == 0 {
matchedValue := level[key]
if v, ok := matchedValue.(string); ok {

Check failure on line 158 in libbeat/processors/actions/alterFieldProcessor.go

View workflow job for this annotation

GitHub Actions / lint (darwin)

v declared and not used (typecheck)
lowerValue, _ := a.alterFunc(v)
level[key] = lowerValue
return nil
}
return fmt.Errorf("value of key %q is not a string", value)
}
segmentCount--
return nil
})

return err
}
19 changes: 12 additions & 7 deletions libbeat/processors/actions/docs/lowercase.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<titleabbrev>lowercase</titleabbrev>
++++

The `lowercase` processor specifies a list of fields that should be converted to lowercase. This transformation applies to keys that match the specified fields. Matching is performed case-insensitively.
The `lowercase` processor specifies a list of `fields` and `values` to be converted to lowercase. Keys listed in `fields` will be matched case-insensitively and converted to lowercase. For `values`, only exact, case-sensitive matches are transformed to lowercase. This way, keys and values can be selectively converted based on the specified criteria.


==== Examples:
Expand All @@ -18,28 +18,32 @@ processors:
- rename:
fields:
- "ab.cd"
values:
- "testKey"
ignore_missing: false
fail_on_error: true
full_path: true
alter_full_field: true
----
[source,json]
----
// Input
{
"AB": {"CD":"data"},
"CD": {"ef":"data"}
"CD": {"ef":"data"},
"testKey": {"TESTVALUE"}
}


// output
{
"ab": {"cd":"data"}, // `AB.CD` -> `ab.cd`
"CD": {"ef":"data"}
"CD": {"ef":"data"},
"testKey": {"testvalue"} // `TESTVALUE` -> `testvalue` is lowercased
}
----

[start=2]
2. When `full_path` is false
2. When `alter_full_field` is false (applicable only for fields)

[source,yaml]
----
Expand All @@ -57,14 +61,14 @@ processors:
// Input
{
"AB": {"CD":"data"},
"CD": {"ef":"data"}
"CD": {"ef":"data"},
}


// output
{
"AB": {"cd":"data"}, // `AB.CD` -> `AB.cd` (only `cd` is lowercased)
"CD": {"ef":"data"}
"CD": {"ef":"data"},
}
----

Expand Down Expand Up @@ -103,6 +107,7 @@ processors:
The `lowercase` processor has the following configuration settings:

`fields`:: The field names to lowercase. The match is case-insensitive, e.g. `a.b.c.d` would match `A.b.C.d` or `A.B.C.D`.
`values`:: (Optional) Specifies the exact values to be converted to lowercase. Each entry should include the full path to the value. Key matching is case-sensitive.
khushijain21 marked this conversation as resolved.
Show resolved Hide resolved
`ignore_missing`:: (Optional) Indicates whether to ignore events that lack the source field.
The default is `false`, which will fail processing of an event if a field is missing.
`fail_on_error`:: (Optional) If set to `true` and an error occurs, the changes are reverted and the original event is returned.
Expand Down
2 changes: 1 addition & 1 deletion libbeat/processors/actions/lowercase.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func init() {
checks.ConfigChecked(
NewLowerCaseProcessor,
checks.RequireFields("fields"),
checks.AllowedFields("fields", "when", "ignore_missing", "fail_on_error", "alter_full_field"),
checks.AllowedFields("fields", "ignore_missing", "fail_on_error", "alter_full_field", "values"),
),
)
}
Expand Down
109 changes: 109 additions & 0 deletions libbeat/processors/actions/lowercase_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,115 @@ func TestLowerCaseProcessorRun(t *testing.T) {
})
}

func TestLowerCaseProcessorValues(t *testing.T) {
tests := []struct {
Name string
Values []string
IgnoreMissing bool
FailOnError bool
FullPath bool
Input mapstr.M
Output mapstr.M
Error bool
}{
{
Name: "Lowercase Values",
Values: []string{"a.b.c"},
IgnoreMissing: false,
FailOnError: true,
FullPath: true,
Input: mapstr.M{
"a": mapstr.M{
"b": mapstr.M{
"c": "D",
},
},
},
Output: mapstr.M{
"a": mapstr.M{
"b": mapstr.M{
"c": "d", // d is lowercased
},
},
},
Error: false,
},
{
Name: "Fail if given path to value is not a string",
Values: []string{"a.B"},
IgnoreMissing: false,
FailOnError: true,
FullPath: true,
Input: mapstr.M{
"Field3": "Value",
"a": mapstr.M{
"B": mapstr.M{
"C": "D",
},
},
},
Output: mapstr.M{
"Field3": "Value",
"a": mapstr.M{
"B": mapstr.M{
"C": "D",
},
},
"error": mapstr.M{"message": "error visiting key \"B\" of the path \"a.B\": value of key \"a.B\" is not a string"},
},

Error: true,
},
{
Name: "Fail On Missing Key Error",
Values: []string{"a.B.c"},
IgnoreMissing: false,
FailOnError: true,
FullPath: true,
Input: mapstr.M{
"Field3": "Value",
"a": mapstr.M{
"B": mapstr.M{
"C": "D",
},
},
},
Output: mapstr.M{
"Field3": "Value",
"a": mapstr.M{
"B": mapstr.M{
"C": "D",
},
},
"error": mapstr.M{"message": "could not fetch value for key: a.B.c, Error: key not found"},
},

Error: true,
},
}

for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
p := &alterFieldProcessor{
Values: test.Values,
IgnoreMissing: test.IgnoreMissing,
FailOnError: test.FailOnError,
AlterFullField: test.FullPath,
alterFunc: lowerCase,
}

event, err := p.Run(&beat.Event{Fields: test.Input})

if !test.Error {
require.NoError(t, err)
} else {
require.Error(t, err)
}

assert.Equal(t, test.Output, event.Fields)
})
}
}
func BenchmarkLowerCaseProcessorRun(b *testing.B) {
tests := []struct {
Name string
Expand Down
Loading