From 804a5d4631a1be41a68741bd31dd068f9130f793 Mon Sep 17 00:00:00 2001 From: Dan Kortschak Date: Thu, 31 Aug 2023 06:06:03 +0930 Subject: [PATCH] filebeat/input/internal/journald/pkg/journaldfield: add support for capablility rendering --- CHANGELOG.next.asciidoc | 1 + .../input/journald/pkg/journalfield/conv.go | 83 +++++ .../pkg/journalfield/conv_expand_test.go | 298 ++++++++++++++++++ 3 files changed, 382 insertions(+) create mode 100644 filebeat/input/journald/pkg/journalfield/conv_expand_test.go diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index 2f9967569083..2a545dbd4817 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -208,6 +208,7 @@ https://github.com/elastic/beats/compare/v8.8.1\...main[Check the HEAD diff] - [Azure] Add input metrics to the azure-eventhub input. {pull}35739[35739] - Reduce HTTPJSON metrics allocations. {pull}36282[36282] - Add support for a simplified input configuraton when running under Elastic-Agent {pull}36390[36390] +- Add support for expanding `journald.process.capabilities` into the human-readable effective capabilities in the ECS `process.thread.capabilities.effective` field. {issue}36454[36454] {pull}[] *Auditbeat* diff --git a/filebeat/input/journald/pkg/journalfield/conv.go b/filebeat/input/journald/pkg/journalfield/conv.go index 59d435e8eedf..a6a9b78bcbf2 100644 --- a/filebeat/input/journald/pkg/journalfield/conv.go +++ b/filebeat/input/journald/pkg/journalfield/conv.go @@ -19,6 +19,7 @@ package journalfield import ( "fmt" + "math/bits" "regexp" "strconv" "strings" @@ -116,6 +117,7 @@ func withECSEnrichment(fields mapstr.M) mapstr.M { setGidUidFields("journald.object", fields) setProcessFields("journald", fields) setProcessFields("journald.object", fields) + expandCapabilities(fields) return fields } @@ -173,6 +175,87 @@ func setProcessFields(prefix string, fields mapstr.M) { } } +// expandCapabilites expands the hex string of capabilities bits in the +// journald.process.capabilities field in-place into an array of conventional +// capabilities names in process.thread.capabilities.effective. If a +// capability is unknown it is rendered as the numeric value of the cap. +// The original capabilities string is not altered. If any error is +// encountered no modification is made to the fields. +func expandCapabilities(fields mapstr.M) { + cs, err := fields.GetValue("journald.process.capabilities") + if err != nil { + return + } + c, ok := cs.(string) + if !ok { + return + } + w, err := strconv.ParseUint(c, 16, 64) + if err != nil { + return + } + if w == 0 { + return + } + caps := make([]string, 0, bits.OnesCount64(w)) + for i := 0; w != 0; i++ { + if w&1 != 0 { + if i < len(capTable) { + caps = append(caps, capTable[i]) + } else { + caps = append(caps, strconv.Itoa(i)) + } + } + w >>= 1 + } + fields.Put("process.thread.capabilities.effective", caps) +} + +// include/uapi/linux/capability.h +var capTable = [...]string{ + 0: "cap_chown", + 1: "cap_dac_override", + 2: "cap_dac_read_search", + 3: "cap_fowner", + 4: "cap_fsetid", + 5: "cap_kill", + 6: "cap_setgid", + 7: "cap_setuid", + 8: "cap_setpcap", + 9: "cap_linux_immutable", + 10: "cap_net_bind_service", + 11: "cap_net_broadcast", + 12: "cap_net_admin", + 13: "cap_net_raw", + 14: "cap_ipc_lock", + 15: "cap_ipc_owner", + 16: "cap_sys_module", + 17: "cap_sys_rawio", + 18: "cap_sys_chroot", + 19: "cap_sys_ptrace", + 20: "cap_sys_pacct", + 21: "cap_sys_admin", + 22: "cap_sys_boot", + 23: "cap_sys_nice", + 24: "cap_sys_resource", + 25: "cap_sys_time", + 26: "cap_sys_tty_config", + 27: "cap_mknod", + 28: "cap_lease", + 29: "cap_audit_write", + 30: "cap_audit_control", + 31: "cap_setfcap", + 32: "cap_mac_override", + 33: "cap_mac_admin", + 34: "cap_syslog", + 35: "cap_wake_alarm", + 36: "cap_block_suspend", + 37: "cap_audit_read", + 38: "cap_perfmon", + 39: "cap_bpf", + 40: "cap_checkpoint_restore", +} + func getStringFromFields(key string, fields mapstr.M) string { value, _ := fields.GetValue(key) str, _ := value.(string) diff --git a/filebeat/input/journald/pkg/journalfield/conv_expand_test.go b/filebeat/input/journald/pkg/journalfield/conv_expand_test.go new file mode 100644 index 000000000000..8d851e1a490b --- /dev/null +++ b/filebeat/input/journald/pkg/journalfield/conv_expand_test.go @@ -0,0 +1,298 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package journalfield + +import ( + "testing" + + "github.com/elastic/elastic-agent-libs/mapstr" + "github.com/google/go-cmp/cmp" +) + +var expandCapabilitiesTests = []struct { + name string + src mapstr.M + want mapstr.M +}{ + // All test cases were constructed based on behavour of capsh --decode . + { + name: "none", + src: mapstr.M{ + "journald": mapstr.M{ + "process": mapstr.M{ + "capabilities": "0", + }, + }, + }, + want: mapstr.M{ + "journald": mapstr.M{ + "process": mapstr.M{ + "capabilities": "0", + }, + }, + }, + }, + { + name: "cap_chown_short", + src: mapstr.M{ + "journald": mapstr.M{ + "process": mapstr.M{ + "capabilities": "1", + }, + }, + }, + want: mapstr.M{ + "journald": mapstr.M{ + "process": mapstr.M{ + "capabilities": "1", + }, + }, + "process": mapstr.M{ + "thread": mapstr.M{ + "capabilities": mapstr.M{ + "effective": []string{ + "cap_chown", + }, + }, + }, + }, + }, + }, + { + name: "cap_chown_long", + src: mapstr.M{ + "journald": mapstr.M{ + "process": mapstr.M{ + "capabilities": "0000000000000001", + }, + }, + }, + want: mapstr.M{ + "journald": mapstr.M{ + "process": mapstr.M{ + "capabilities": "0000000000000001", + }, + }, + "process": mapstr.M{ + "thread": mapstr.M{ + "capabilities": mapstr.M{ + "effective": []string{ + "cap_chown", + }, + }, + }, + }, + }, + }, + { + name: "all", + src: mapstr.M{ + "journald": mapstr.M{ + "process": mapstr.M{ + "capabilities": "1ffffffffff", + }, + }, + }, + want: mapstr.M{ + "journald": mapstr.M{ + "process": mapstr.M{ + "capabilities": "1ffffffffff", + }, + }, + "process": mapstr.M{ + "thread": mapstr.M{ + "capabilities": mapstr.M{ + "effective": []string{ + "cap_chown", + "cap_dac_override", + "cap_dac_read_search", + "cap_fowner", + "cap_fsetid", + "cap_kill", + "cap_setgid", + "cap_setuid", + "cap_setpcap", + "cap_linux_immutable", + "cap_net_bind_service", + "cap_net_broadcast", + "cap_net_admin", + "cap_net_raw", + "cap_ipc_lock", + "cap_ipc_owner", + "cap_sys_module", + "cap_sys_rawio", + "cap_sys_chroot", + "cap_sys_ptrace", + "cap_sys_pacct", + "cap_sys_admin", + "cap_sys_boot", + "cap_sys_nice", + "cap_sys_resource", + "cap_sys_time", + "cap_sys_tty_config", + "cap_mknod", + "cap_lease", + "cap_audit_write", + "cap_audit_control", + "cap_setfcap", + "cap_mac_override", + "cap_mac_admin", + "cap_syslog", + "cap_wake_alarm", + "cap_block_suspend", + "cap_audit_read", + "cap_perfmon", + "cap_bpf", + "cap_checkpoint_restore", + }, + }, + }, + }, + }, + }, + { + name: "all_and_new", + src: mapstr.M{ + "journald": mapstr.M{ + "process": mapstr.M{ + "capabilities": "7ffffffffff", + }, + }, + }, + want: mapstr.M{ + "journald": mapstr.M{ + "process": mapstr.M{ + "capabilities": "7ffffffffff", + }, + }, + "process": mapstr.M{ + "thread": mapstr.M{ + "capabilities": mapstr.M{ + "effective": []string{ + "cap_chown", + "cap_dac_override", + "cap_dac_read_search", + "cap_fowner", + "cap_fsetid", + "cap_kill", + "cap_setgid", + "cap_setuid", + "cap_setpcap", + "cap_linux_immutable", + "cap_net_bind_service", + "cap_net_broadcast", + "cap_net_admin", + "cap_net_raw", + "cap_ipc_lock", + "cap_ipc_owner", + "cap_sys_module", + "cap_sys_rawio", + "cap_sys_chroot", + "cap_sys_ptrace", + "cap_sys_pacct", + "cap_sys_admin", + "cap_sys_boot", + "cap_sys_nice", + "cap_sys_resource", + "cap_sys_time", + "cap_sys_tty_config", + "cap_mknod", + "cap_lease", + "cap_audit_write", + "cap_audit_control", + "cap_setfcap", + "cap_mac_override", + "cap_mac_admin", + "cap_syslog", + "cap_wake_alarm", + "cap_block_suspend", + "cap_audit_read", + "cap_perfmon", + "cap_bpf", + "cap_checkpoint_restore", + "41", + "42", + }, + }, + }, + }, + }, + }, + { + name: "sparse", + src: mapstr.M{ + "journald": mapstr.M{ + "process": mapstr.M{ + "capabilities": "deadbeef", + }, + }, + }, + want: mapstr.M{ + "journald": mapstr.M{ + "process": mapstr.M{ + "capabilities": "deadbeef", + }, + }, + "process": mapstr.M{ + "thread": mapstr.M{ + "capabilities": mapstr.M{ + "effective": []string{ + "cap_chown", + "cap_dac_override", + "cap_dac_read_search", + "cap_fowner", + "cap_kill", + "cap_setgid", + "cap_setuid", + "cap_linux_immutable", + "cap_net_bind_service", + "cap_net_broadcast", + "cap_net_admin", + "cap_net_raw", + "cap_ipc_owner", + "cap_sys_module", + "cap_sys_chroot", + "cap_sys_ptrace", + "cap_sys_admin", + "cap_sys_nice", + "cap_sys_time", + "cap_sys_tty_config", + "cap_mknod", + "cap_lease", + "cap_audit_control", + "cap_setfcap", + }, + }, + }, + }, + }, + }, +} + +func TestExpandCapabilities(t *testing.T) { + for _, test := range expandCapabilitiesTests { + t.Run(test.name, func(t *testing.T) { + dst := test.src.Clone() + expandCapabilities(dst) + got := dst + if !cmp.Equal(test.want, got) { + t.Errorf("unexpected result\n--- want\n+++ got\n%s", cmp.Diff(test.want, got)) + } + }) + } +}