diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 7e31f71e69e..5bb00dbce58 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -299,6 +299,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d - Fix incorrect field name appending to `related.hash` in `threatintel.abusechmalware` ingest pipeline. {issue}25151[25151] {pull}25674[25674] - Add improvements to the azure activitylogs and platformlogs ingest pipelines. {pull}26148[26148] - Fix `kibana.log` pipeline when `event.duration` calculation becomes a Long. {issue}24556[24556] {pull}25675[25675] +- Clone value when copy fields in processors to avoid crash. {issue}19206[19206] {pull}20500[20500] *Heartbeat* diff --git a/libbeat/processors/actions/copy_fields.go b/libbeat/processors/actions/copy_fields.go index 44c13f41c8a..43a797e0fd5 100644 --- a/libbeat/processors/actions/copy_fields.go +++ b/libbeat/processors/actions/copy_fields.go @@ -104,7 +104,7 @@ func (f *copyFields) copyField(from string, to string, fields common.MapStr) err return fmt.Errorf("could not fetch value for key: %s, Error: %s", from, err) } - _, err = fields.Put(to, value) + _, err = fields.Put(to, cloneValue(value)) if err != nil { return fmt.Errorf("could not copy value to %s: %v, %+v", to, value, err) } @@ -114,3 +114,24 @@ func (f *copyFields) copyField(from string, to string, fields common.MapStr) err func (f *copyFields) String() string { return "copy_fields=" + fmt.Sprintf("%+v", f.config.Fields) } + +// cloneValue returns a shallow copy of a map. All other types are passed +// through in the return. This should be used when making straight copies of +// maps without doing any type conversions. +func cloneValue(value interface{}) interface{} { + switch v := value.(type) { + case common.MapStr: + return v.Clone() + case map[string]interface{}: + return common.MapStr(v).Clone() + case []interface{}: + len := len(v) + newArr := make([]interface{}, len) + for idx, val := range v { + newArr[idx] = cloneValue(val) + } + return newArr + default: + return value + } +} diff --git a/libbeat/processors/actions/copy_fields_test.go b/libbeat/processors/actions/copy_fields_test.go index 5dacf79dddd..8652ea87e78 100644 --- a/libbeat/processors/actions/copy_fields_test.go +++ b/libbeat/processors/actions/copy_fields_test.go @@ -121,6 +121,29 @@ func TestCopyFields(t *testing.T) { "message": 42, }, }, + "copy map from nested key message.original to top level field message_copied": { + FromTo: fromTo{ + From: "message.original", + To: "message_copied", + }, + Input: common.MapStr{ + "message": common.MapStr{ + "original": common.MapStr{ + "original": "original", + }, + }, + }, + Expected: common.MapStr{ + "message": common.MapStr{ + "original": common.MapStr{ + "original": "original", + }, + }, + "message_copied": common.MapStr{ + "original": "original", + }, + }, + }, } for name, test := range tests {