From 85afa4a7a0ea104695028445eae06357f6f94ec0 Mon Sep 17 00:00:00 2001 From: Dan Kortschak <90160302+efd6@users.noreply.github.com> Date: Thu, 17 Aug 2023 06:34:45 +0930 Subject: [PATCH] auditbeat/module/file_integrity: add support for selinux and posix_acl_access xattrs (#36310) (cherry picked from commit d771501b844ef5f17fb8733348551ae061c095a1) --- CHANGELOG.next.asciidoc | 1 + NOTICE.txt | 35 +++++ auditbeat/module/file_integrity/event.go | 126 +++++++++++++++--- auditbeat/module/file_integrity/event_test.go | 50 +++++++ .../module/file_integrity/fileinfo_posix.go | 29 ++++ .../module/file_integrity/flatbuffers.go | 37 +++-- auditbeat/module/file_integrity/schema.fbs | 2 + .../module/file_integrity/schema/Metadata.go | 24 +++- go.mod | 1 + go.sum | 3 + 10 files changed, 273 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.next.asciidoc b/CHANGELOG.next.asciidoc index e0df583f662d..897f4f2c56e3 100644 --- a/CHANGELOG.next.asciidoc +++ b/CHANGELOG.next.asciidoc @@ -191,6 +191,7 @@ automatic splitting at root level, if root level element is an array. {pull}3415 *Auditbeat* +- Add support for `security.selinux` and `system.posix_acl_access` extended attributes to FIM. {issue}36265[36265] {pull}36310[36310] *Filebeat* diff --git a/NOTICE.txt b/NOTICE.txt index f3af08adf814..2f7bac665b4a 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -21301,6 +21301,41 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-------------------------------------------------------------------------------- +Dependency : github.com/pkg/xattr +Version: v0.4.9 +Licence type (autodetected): BSD-2-Clause +-------------------------------------------------------------------------------- + +Contents of probable licence file $GOMODCACHE/github.com/pkg/xattr@v0.4.9/LICENSE: + +Copyright (c) 2012 Dave Cheney. All rights reserved. +Copyright (c) 2014 Kuba Podgórski. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + -------------------------------------------------------------------------------- Dependency : github.com/pmezard/go-difflib Version: v1.0.0 diff --git a/auditbeat/module/file_integrity/event.go b/auditbeat/module/file_integrity/event.go index 67615efd4a98..8d1fd54b7b87 100644 --- a/auditbeat/module/file_integrity/event.go +++ b/auditbeat/module/file_integrity/event.go @@ -23,12 +23,14 @@ import ( "crypto/sha1" "crypto/sha256" "crypto/sha512" + "encoding/binary" "encoding/hex" "fmt" "hash" "io" "math" "os" + "os/user" "path/filepath" "runtime" "strconv" @@ -127,20 +129,22 @@ type Event struct { // Metadata contains file metadata. type Metadata struct { - Inode uint64 `json:"inode"` - UID uint32 `json:"uid"` - GID uint32 `json:"gid"` - SID string `json:"sid"` - Owner string `json:"owner"` - Group string `json:"group"` - Size uint64 `json:"size"` - MTime time.Time `json:"mtime"` // Last modification time. - CTime time.Time `json:"ctime"` // Last metadata change time. - Type Type `json:"type"` // File type (dir, file, symlink). - Mode os.FileMode `json:"mode"` // Permissions - SetUID bool `json:"setuid"` // setuid bit (POSIX only) - SetGID bool `json:"setgid"` // setgid bit (POSIX only) - Origin []string `json:"origin"` // External origin info for the file (MacOS only) + Inode uint64 `json:"inode"` + UID uint32 `json:"uid"` + GID uint32 `json:"gid"` + SID string `json:"sid"` + Owner string `json:"owner"` + Group string `json:"group"` + Size uint64 `json:"size"` + MTime time.Time `json:"mtime"` // Last modification time. + CTime time.Time `json:"ctime"` // Last metadata change time. + Type Type `json:"type"` // File type (dir, file, symlink). + Mode os.FileMode `json:"mode"` // Permissions + SetUID bool `json:"setuid"` // setuid bit (POSIX only) + SetGID bool `json:"setgid"` // setgid bit (POSIX only) + Origin []string `json:"origin"` // External origin info for the file (MacOS only) + SELinux string `json:"selinux"` // security.selinux xattr value (Linux only) + POSIXACLAccess string `json:"posix_acl_access"` // system.posix_acl_access xattr value (Linux only) } // NewEventFromFileInfo creates a new Event based on data from a os.FileInfo @@ -319,6 +323,15 @@ func buildMetricbeatEvent(e *Event, existedBefore bool) mb.Event { if len(info.Origin) > 0 { file["origin"] = info.Origin } + if info.SELinux != "" { + file["selinux"] = info.SELinux + } + if info.POSIXACLAccess != "" { + a, err := aclText([]byte(info.POSIXACLAccess)) + if err == nil { + file["posix_acl_access"] = a + } + } } if len(e.Hashes) > 0 { @@ -332,14 +345,14 @@ func buildMetricbeatEvent(e *Event, existedBefore bool) mb.Event { file[k] = v } - out.MetricSetFields.Put("event.kind", "event") //nolint:errcheck // Will not error. - out.MetricSetFields.Put("event.category", []string{"file"}) //nolint:errcheck // Will not error. + out.MetricSetFields.Put("event.kind", "event") + out.MetricSetFields.Put("event.category", []string{"file"}) if e.Action > 0 { actions := e.Action.InOrder(existedBefore, e.Info != nil) - out.MetricSetFields.Put("event.type", actions.ECSTypes()) //nolint:errcheck // Will not error. - out.MetricSetFields.Put("event.action", actions.StringArray()) //nolint:errcheck // Will not error. + out.MetricSetFields.Put("event.type", actions.ECSTypes()) + out.MetricSetFields.Put("event.action", actions.StringArray()) } else { - out.MetricSetFields.Put("event.type", None.ECSTypes()) //nolint:errcheck // Will not error. + out.MetricSetFields.Put("event.type", None.ECSTypes()) } if n := len(e.errors); n > 0 { @@ -348,14 +361,82 @@ func buildMetricbeatEvent(e *Event, existedBefore bool) mb.Event { errors[idx] = err.Error() } if n == 1 { - out.MetricSetFields.Put("error.message", errors[0]) //nolint:errcheck // Will not error. + out.MetricSetFields.Put("error.message", errors[0]) } else { - out.MetricSetFields.Put("error.message", errors) //nolint:errcheck // Will not error. + out.MetricSetFields.Put("error.message", errors) } } return out } +func aclText(b []byte) ([]string, error) { + if (len(b)-4)%8 != 0 { + return nil, fmt.Errorf("unexpected ACL length: %d", len(b)) + } + b = b[4:] // The first four bytes is the version, discard it. + a := make([]string, 0, len(b)/8) + for len(b) != 0 { + tag := binary.LittleEndian.Uint16(b) + perm := binary.LittleEndian.Uint16(b[2:]) + qual := binary.LittleEndian.Uint32(b[4:]) + a = append(a, fmt.Sprintf("%s:%s:%s", tags[tag], qualString(qual, tag), modeString(perm))) + b = b[8:] + } + return a, nil +} + +var tags = map[uint16]string{ + 0x00: "undefined", + 0x01: "user", + 0x02: "user", + 0x04: "group", + 0x08: "group", + 0x10: "mask", + 0x20: "other", +} + +func qualString(qual uint32, tag uint16) string { + if qual == math.MaxUint32 { + // 0xffffffff is undefined ID, so return zero. + return "" + } + const ( + tagUser = 0x02 + tagGroup = 0x08 + ) + id := strconv.Itoa(int(qual)) + switch tag { + case tagUser: + u, err := user.LookupId(id) + if err == nil { + return u.Username + } + case tagGroup: + g, err := user.LookupGroupId(id) + if err == nil { + return g.Name + } + } + // Fallback to the numeric ID if we can't get a name + // or the tag is other than user/group. + return id +} + +func modeString(perm uint16) string { + var buf [3]byte + w := 0 + const rwx = "rwx" + for i, c := range rwx { + if perm&(1< 0 { + schema.MetadataAddSelinux(b, selinuxOffset) + } + if aclAccessOffset > 0 { + schema.MetadataAddPosixAclAccess(b, aclAccessOffset) + } return schema.MetadataEnd(b) } @@ -246,16 +257,18 @@ func fbDecodeMetadata(e *schema.Event) *Metadata { } mode := os.FileMode(info.Mode()) rtn := &Metadata{ - Inode: info.Inode(), - UID: info.Uid(), - GID: info.Gid(), - SID: string(info.Sid()), - Mode: mode & ^(os.ModeSetuid | os.ModeSetgid), - Size: info.Size(), - MTime: time.Unix(0, info.MtimeNs()).UTC(), - CTime: time.Unix(0, info.CtimeNs()).UTC(), - SetUID: mode&os.ModeSetuid != 0, - SetGID: mode&os.ModeSetgid != 0, + Inode: info.Inode(), + UID: info.Uid(), + GID: info.Gid(), + SID: string(info.Sid()), + Mode: mode & ^(os.ModeSetuid | os.ModeSetgid), + Size: info.Size(), + MTime: time.Unix(0, info.MtimeNs()).UTC(), + CTime: time.Unix(0, info.CtimeNs()).UTC(), + SetUID: mode&os.ModeSetuid != 0, + SetGID: mode&os.ModeSetgid != 0, + SELinux: string(info.Selinux()), + POSIXACLAccess: string(info.PosixAclAccess()), } switch info.Type() { diff --git a/auditbeat/module/file_integrity/schema.fbs b/auditbeat/module/file_integrity/schema.fbs index b22efd5e5ae9..a32c6bfe52f5 100644 --- a/auditbeat/module/file_integrity/schema.fbs +++ b/auditbeat/module/file_integrity/schema.fbs @@ -31,6 +31,8 @@ table Metadata { mtime_ns:long; ctime_ns:long; type:Type = 1; + selinux:string; + posix_acl_access:string; } table Hash { diff --git a/auditbeat/module/file_integrity/schema/Metadata.go b/auditbeat/module/file_integrity/schema/Metadata.go index a9f083789131..890a14210f85 100644 --- a/auditbeat/module/file_integrity/schema/Metadata.go +++ b/auditbeat/module/file_integrity/schema/Metadata.go @@ -154,8 +154,24 @@ func (rcv *Metadata) MutateType(n Type) bool { return rcv._tab.MutateByteSlot(20, byte(n)) } +func (rcv *Metadata) Selinux() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(22)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + +func (rcv *Metadata) PosixAclAccess() []byte { + o := flatbuffers.UOffsetT(rcv._tab.Offset(24)) + if o != 0 { + return rcv._tab.ByteVector(o + rcv._tab.Pos) + } + return nil +} + func MetadataStart(builder *flatbuffers.Builder) { - builder.StartObject(9) + builder.StartObject(11) } func MetadataAddInode(builder *flatbuffers.Builder, inode uint64) { builder.PrependUint64Slot(0, inode, 0) @@ -184,6 +200,12 @@ func MetadataAddCtimeNs(builder *flatbuffers.Builder, ctimeNs int64) { func MetadataAddType(builder *flatbuffers.Builder, type_ Type) { builder.PrependByteSlot(8, byte(type_), 1) } +func MetadataAddSelinux(builder *flatbuffers.Builder, selinux flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(9, flatbuffers.UOffsetT(selinux), 0) +} +func MetadataAddPosixAclAccess(builder *flatbuffers.Builder, posixAclAccess flatbuffers.UOffsetT) { + builder.PrependUOffsetTSlot(10, flatbuffers.UOffsetT(posixAclAccess), 0) +} func MetadataEnd(builder *flatbuffers.Builder) flatbuffers.UOffsetT { return builder.EndObject() } diff --git a/go.mod b/go.mod index 256c343b109f..a3ccea2f7688 100644 --- a/go.mod +++ b/go.mod @@ -216,6 +216,7 @@ require ( github.com/lestrrat-go/jwx/v2 v2.0.11 github.com/otiai10/copy v1.12.0 github.com/pierrec/lz4/v4 v4.1.15 + github.com/pkg/xattr v0.4.9 github.com/shirou/gopsutil/v3 v3.22.10 go.elastic.co/apm/module/apmelasticsearch/v2 v2.0.0 go.elastic.co/apm/module/apmhttp/v2 v2.0.0 diff --git a/go.sum b/go.sum index cb386cea46c5..f17ae791eca7 100644 --- a/go.sum +++ b/go.sum @@ -1362,6 +1362,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= +github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE= +github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= @@ -1977,6 +1979,7 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=