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

[7.16](backport #28705) [libbeat] Allow fingerprint processor to read @timestamp #28767

Merged
merged 1 commit into from
Nov 2, 2021
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
1 change: 1 addition & 0 deletions CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d
- Allow `clone3` syscall in seccomp filters. {pull}28117[28117]
- Remove unnecessary escaping step in dashboard loading, so they can be displayed in Kibana. {pull}28395[28395]
- Fix handling of float data types within processors. {issue}28279[28279] {pull}28280[28280]
- Fix `fingerprint` processor to give it access to the `@timestamp` field. {issue}28683[28683]

*Auditbeat*

Expand Down
6 changes: 5 additions & 1 deletion libbeat/processors/fingerprint/docs/fingerprint.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
The `fingerprint` processor generates a fingerprint of an event based on a
specified subset of its fields.

The value that is hashed is constructed as a concatenation of the field name and
field value separated by `|`. For example `|field1|value1|field2|value2|`.

[source,yaml]
-----------------------------------------------------
processors:
Expand All @@ -17,7 +20,8 @@ processors:

The following settings are supported:

`fields`:: List of fields to use as the source for the fingerprint.
`fields`:: List of fields to use as the source for the fingerprint. The list
will be alphabetically sorted by the processor.
`ignore_missing`:: (Optional) Whether to ignore missing fields. Default is `false`.
`target_field`:: (Optional) Field in which the generated fingerprint should be stored. Default is `fingerprint`.
`method`:: (Optional) Algorithm to use for computing the fingerprint. Must be one of: `md5`, `sha1`, `sha256`, `sha384`, `sha512`, `xxhash`. Default is `sha256`.
Expand Down
2 changes: 1 addition & 1 deletion libbeat/processors/fingerprint/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func makeErrConfigUnpack(cause error) errConfigUnpack {
return errConfigUnpack{cause}
}
func (e errConfigUnpack) Error() string {
return fmt.Sprintf("failed to unpack %v processor configuration: %v", processorName, e.cause)
return fmt.Sprintf("failed to unpack %v processor configuration: %v", procName, e.cause)
}

func makeErrComputeFingerprint(cause error) errComputeFingerprint {
Expand Down
34 changes: 18 additions & 16 deletions libbeat/processors/fingerprint/fingerprint.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
package fingerprint

import (
"encoding/json"
"fmt"
"io"
"strings"
"time"

"github.com/elastic/beats/v7/libbeat/beat"
Expand All @@ -28,13 +30,15 @@ import (
jsprocessor "github.com/elastic/beats/v7/libbeat/processors/script/javascript/module/processor"
)

const (
procName = "fingerprint"
)

func init() {
processors.RegisterPlugin("fingerprint", New)
jsprocessor.RegisterPlugin("Fingerprint", New)
processors.RegisterPlugin(procName, New)
jsprocessor.RegisterPlugin(strings.Title(procName), New)
}

const processorName = "fingerprint"

type fingerprint struct {
config Config
fields []string
Expand Down Expand Up @@ -62,49 +66,47 @@ func New(cfg *common.Config) (processors.Processor, error) {
return p, nil
}

// Run enriches the given event with fingerprint information
// Run enriches the given event with a fingerprint.
func (p *fingerprint) Run(event *beat.Event) (*beat.Event, error) {
hashFn := p.hash()

err := p.writeFields(hashFn, event.Fields)
if err != nil {
if err := p.writeFields(hashFn, event); err != nil {
return nil, makeErrComputeFingerprint(err)
}

hash := hashFn.Sum(nil)
encodedHash := p.config.Encoding(hash)
encodedHash := p.config.Encoding(hashFn.Sum(nil))

if _, err = event.PutValue(p.config.TargetField, encodedHash); err != nil {
if _, err := event.PutValue(p.config.TargetField, encodedHash); err != nil {
return nil, makeErrComputeFingerprint(err)
}

return event, nil
}

func (p *fingerprint) String() string {
return fmt.Sprintf("%v=[method=[%v]]", processorName, p.config.Method)
json, _ := json.Marshal(p.config)
return procName + "=" + string(json)
}

func (p *fingerprint) writeFields(to io.Writer, eventFields common.MapStr) error {
func (p *fingerprint) writeFields(to io.Writer, event *beat.Event) error {
for _, k := range p.fields {
v, err := eventFields.GetValue(k)
v, err := event.GetValue(k)
if err != nil {
if p.config.IgnoreMissing {
continue
}
return makeErrMissingField(k, err)
}

i := v
switch vv := v.(type) {
case map[string]interface{}, []interface{}, common.MapStr:
return makeErrNonScalarField(k)
case time.Time:
// Ensure we consistently hash times in UTC.
i = vv.UTC()
v = vv.UTC()
}

fmt.Fprintf(to, "|%v|%v", k, i)
fmt.Fprintf(to, "|%v|%v", k, v)
}

io.WriteString(to, "|")
Expand Down
11 changes: 10 additions & 1 deletion libbeat/processors/fingerprint/fingerprint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ func TestWithConfig(t *testing.T) {
},
want: "14a0364b79acbe4c78dd5e77db2c93ae8c750518b32581927d50b3eef407184e",
},
"with @timestamp": {
config: common.MapStr{
"fields": []string{"@timestamp", "message"},
},
input: common.MapStr{
"message": `test message "hello world"`,
},
want: "081da76e049554943843b83948ac83ab7aa79fd2849331813e02042586021c26",
},
}

for name, test := range cases {
Expand All @@ -63,7 +72,7 @@ func TestWithConfig(t *testing.T) {
require.NoError(t, err)

testEvent := &beat.Event{
Timestamp: time.Now(),
Timestamp: time.Unix(1635443183, 0),
Fields: test.input.Clone(),
}
newEvent, err := p.Run(testEvent)
Expand Down