diff --git a/auditbeat/magefile.go b/auditbeat/magefile.go index f755fef8a0cb..fd288a28d761 100644 --- a/auditbeat/magefile.go +++ b/auditbeat/magefile.go @@ -44,6 +44,9 @@ func Build() error { // GolangCrossBuild build the Beat binary inside of the golang-builder. // Do not use directly, use crossBuild instead. func GolangCrossBuild() error { + if d, ok := deps[mage.Platform.Name]; ok { + mg.Deps(d) + } return mage.GolangCrossBuild(mage.DefaultGolangCrossBuildArgs()) } @@ -120,6 +123,83 @@ func GoTestIntegration(ctx context.Context) error { // ----------------------------------------------------------------------------- // Customizations specific to Auditbeat. // - Config files are Go templates. +var ( + deps = map[string]func() error{ + "linux/386": installLinux386, + "linux/amd64": installLinuxAMD64, + "linux/arm64": installLinuxARM64, + "linux/armv5": installLinuxARMLE, + "linux/armv6": installLinuxARMLE, + "linux/armv7": installLinuxARMHF, + "linux/mips": installLinuxMIPS, + "linux/mipsle": installLinuxMIPSLE, + "linux/mips64le": installLinuxMIPS64LE, + "linux/ppc64le": installLinuxPPC64LE, + "linux/s390x": installLinuxS390X, + + //"linux/ppc64": installLinuxPpc64, + //"linux/mips64": installLinuxMips64, + } +) + +const ( + librpmDevPkgName = "librpm-dev" +) + +func installLinuxAMD64() error { + return installDependencies(librpmDevPkgName, "") +} + +func installLinuxARM64() error { + return installDependencies(librpmDevPkgName+":arm64", "arm64") +} + +func installLinuxARMHF() error { + return installDependencies(librpmDevPkgName+":armhf", "armhf") +} + +func installLinuxARMLE() error { + return installDependencies(librpmDevPkgName+":armel", "armel") +} + +func installLinux386() error { + return installDependencies(librpmDevPkgName+":i386", "i386") +} + +func installLinuxMIPS() error { + return installDependencies(librpmDevPkgName+":mips", "mips") +} + +func installLinuxMIPS64LE() error { + return installDependencies(librpmDevPkgName+":mips64el", "mips64el") +} + +func installLinuxMIPSLE() error { + return installDependencies(librpmDevPkgName+":mipsel", "mipsel") +} + +func installLinuxPPC64LE() error { + return installDependencies(librpmDevPkgName+":ppc64el", "ppc64el") +} + +func installLinuxS390X() error { + return installDependencies(librpmDevPkgName+":s390x", "s390x") +} + +func installDependencies(pkg, arch string) error { + if arch != "" { + err := sh.Run("dpkg", "--add-architecture", arch) + if err != nil { + return errors.Wrap(err, "error while adding architecture") + } + } + + if err := sh.Run("apt-get", "update"); err != nil { + return err + } + + return sh.Run("apt-get", "install", "-y", "--no-install-recommends", pkg) +} const ( configTemplateGlob = "module/*/_meta/config*.yml.tmpl" diff --git a/x-pack/auditbeat/module/system/packages/packages.go b/x-pack/auditbeat/module/system/packages/packages.go index d73ca70ba97f..07263742afc3 100644 --- a/x-pack/auditbeat/module/system/packages/packages.go +++ b/x-pack/auditbeat/module/system/packages/packages.go @@ -204,8 +204,10 @@ func convertToCacheable(packages []*Package) []cache.Cacheable { func getPackages(osFamily string) (packages []*Package, err error) { switch osFamily { case redhat: - // TODO: Implement RPM - err = errors.New("RPM not yet supported") + packages, err = listRPMPackages() + if err != nil { + err = errors.Wrap(err, "error getting DEB packages") + } case debian: packages, err = listDebPackages() if err != nil { diff --git a/x-pack/auditbeat/module/system/packages/rpm_common_test.go b/x-pack/auditbeat/module/system/packages/rpm_common_test.go new file mode 100644 index 000000000000..af0a401cd727 --- /dev/null +++ b/x-pack/auditbeat/module/system/packages/rpm_common_test.go @@ -0,0 +1,67 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package packages + +import ( + "fmt" + "os/exec" + "strconv" + "strings" + "time" +) + +func rpmPackagesByExec() ([]*Package, error) { + format := "%{NAME}|%{VERSION}|%{RELEASE}|%{ARCH}|%{LICENSE}|%{INSTALLTIME}|%{SIZE}|%{URL}|%{SUMMARY}\\n" + out, err := exec.Command("/usr/bin/rpm", "--qf", format, "-qa").Output() + if err != nil { + return nil, fmt.Errorf("Error running rpm -qa command: %v", err) + } + + lines := strings.Split(string(out), "\n") + var packages []*Package + for _, line := range lines { + if len(strings.TrimSpace(line)) == 0 { + continue + } + words := strings.SplitN(line, "|", 9) + if len(words) < 9 { + return nil, fmt.Errorf("line '%s' doesn't have enough elements", line) + } + pkg := Package{ + Name: words[0], + Version: words[1], + Release: words[2], + Arch: words[3], + License: words[4], + // install time - 5 + // size - 6 + URL: words[7], + Summary: words[8], + } + ts, err := strconv.ParseInt(words[5], 10, 64) + if err != nil { + return nil, fmt.Errorf("error converting %s to string: %v", words[5], err) + } + pkg.InstallTime = time.Unix(ts, 0) + + pkg.Size, err = strconv.ParseUint(words[6], 10, 64) + if err != nil { + return nil, fmt.Errorf("error converting %s to string: %v", words[6], err) + } + + // Avoid "(none)" in favor of empty strings + if pkg.URL == "(none)" { + pkg.URL = "" + } + if pkg.Arch == "(none)" { + pkg.Arch = "" + } + + packages = append(packages, &pkg) + + } + + return packages, nil +} diff --git a/x-pack/auditbeat/module/system/packages/rpm_linux.go b/x-pack/auditbeat/module/system/packages/rpm_linux.go new file mode 100644 index 000000000000..0742c5d917f2 --- /dev/null +++ b/x-pack/auditbeat/module/system/packages/rpm_linux.go @@ -0,0 +1,302 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +// +build linux + +package packages + +import ( + "fmt" + "time" + "unsafe" + + "github.com/coreos/pkg/dlopen" +) + +/* +#include +#include + +#include +#include +#include +#include + +rpmts +my_rpmtsCreate(void *f) { + rpmts (*rpmtsCreate)(); + rpmtsCreate = (rpmts (*)())f; + + return rpmtsCreate(); +} + +int +my_rpmReadConfigFiles(void *f) { + int (*rpmReadConfigFiles)(const char*, const char*); + rpmReadConfigFiles = (int (*)(const char*, const char*))f; + return rpmReadConfigFiles(NULL, NULL); +} + +rpmdbMatchIterator +my_rpmtsInitIterator(void *f, rpmts ts) { + rpmdbMatchIterator (*rpmtsInitIterator)(const rpmts, rpmTag, const void*, size_t); + rpmtsInitIterator = (rpmdbMatchIterator (*)(const rpmts, rpmTag, const void*, size_t))f; + + return rpmtsInitIterator(ts, RPMDBI_PACKAGES, NULL, 0); +} + +Header +my_rpmdbNextIterator(void *f, rpmdbMatchIterator mi) { + Header (*rpmdbNextIterator)(rpmdbMatchIterator); + rpmdbNextIterator = (Header (*)(rpmdbMatchIterator))f; + + return rpmdbNextIterator(mi); +} + +Header +my_headerLink(void *f, Header h) { + Header (*headerLink)(Header); + headerLink = (Header (*)(Header))f; + + return headerLink(h); +} + +int +my_headerGetEntry(void *f, Header h, rpm_tag_t tag, char **p) { + int (*headerGetEntry)(Header, rpm_tag_t, rpm_tagtype_t*, rpm_data_t*, rpm_count_t*); + headerGetEntry = (int (*)(Header, rpm_tag_t, rpm_tagtype_t*, rpm_data_t*, rpm_count_t*))f; + + return headerGetEntry(h, tag, NULL, (void**)p, NULL); +} + +int +my_headerGetEntryInt(void *f, Header h, rpm_tag_t tag, int **p) { + int (*headerGetEntry)(Header, rpm_tag_t, rpm_tagtype_t*, rpm_data_t*, rpm_count_t*); + headerGetEntry = (int (*)(Header, rpm_tag_t, rpm_tagtype_t*, rpm_data_t*, rpm_count_t*))f; + + return headerGetEntry(h, tag, NULL, (void**)p, NULL); +} + +void +my_headerFree(void *f, Header h) { + Header (*headerFree)(Header); + headerFree = (Header (*)(Header))f; + + headerFree(h); +} + +void +my_rpmdbFreeIterator(void *f, rpmdbMatchIterator mi) { + rpmdbMatchIterator (*rpmdbFreeIterator)(rpmdbMatchIterator); + rpmdbFreeIterator = (rpmdbMatchIterator (*)(rpmdbMatchIterator))f; + + rpmdbFreeIterator(mi); +} + +void +my_rpmtsFree(void *f, rpmts ts) { + rpmts (*rpmtsFree)(rpmts); + rpmtsFree = (rpmts (*)(rpmts))f; + + rpmtsFree(ts); +}*/ +import "C" + +// Constants in sync with /usr/include/rpm/rpmtag.h +const ( + RPMTAG_NAME = 1000 + RPMTAG_VERSION = 1001 + RPMTAG_RELEASE = 1002 + RPMTAG_SUMMARY = 1004 + RPMTAG_LICENSE = 1014 + RPMTAG_URL = 1020 + RPMTAG_ARCH = 1022 + RPMTAG_SIZE = 1009 + RPMTAG_INSTALLTIME = 1008 +) + +type cFunctions struct { + rpmtsCreate unsafe.Pointer + rpmReadConfigFiles unsafe.Pointer + rpmtsInitIterator unsafe.Pointer + rpmdbNextIterator unsafe.Pointer + headerLink unsafe.Pointer + headerGetEntry unsafe.Pointer + headerFree unsafe.Pointer + rpmdbFreeIterator unsafe.Pointer + rpmtsFree unsafe.Pointer +} + +var cFun *cFunctions + +func dlopenCFunctions() (*cFunctions, error) { + var librpmNames = []string{ + "/usr/lib64/librpm.so", + } + var cFun cFunctions + + librpm, err := dlopen.GetHandle(librpmNames) + if err != nil { + return nil, err + } + + cFun.rpmtsCreate, err = librpm.GetSymbolPointer("rpmtsCreate") + if err != nil { + return nil, err + } + + cFun.rpmReadConfigFiles, err = librpm.GetSymbolPointer("rpmReadConfigFiles") + if err != nil { + return nil, err + } + + cFun.rpmtsInitIterator, err = librpm.GetSymbolPointer("rpmtsInitIterator") + if err != nil { + return nil, err + } + + cFun.rpmdbNextIterator, err = librpm.GetSymbolPointer("rpmdbNextIterator") + if err != nil { + return nil, err + } + + cFun.headerLink, err = librpm.GetSymbolPointer("headerLink") + if err != nil { + return nil, err + } + + cFun.headerGetEntry, err = librpm.GetSymbolPointer("headerGetEntry") + if err != nil { + return nil, err + } + + cFun.headerFree, err = librpm.GetSymbolPointer("headerFree") + if err != nil { + return nil, err + } + + cFun.rpmdbFreeIterator, err = librpm.GetSymbolPointer("rpmdbFreeIterator") + if err != nil { + return nil, err + } + + cFun.rpmtsFree, err = librpm.GetSymbolPointer("rpmtsFree") + if err != nil { + return nil, err + } + + return &cFun, nil +} + +func listRPMPackages() ([]*Package, error) { + if cFun == nil { + var err error + cFun, err = dlopenCFunctions() + if err != nil { + return nil, err + } + } + + rpmts := C.my_rpmtsCreate(cFun.rpmtsCreate) + if rpmts == nil { + return nil, fmt.Errorf("Failed to get rpmts") + } + defer C.my_rpmtsFree(cFun.rpmtsFree, rpmts) + res := C.my_rpmReadConfigFiles(cFun.rpmReadConfigFiles) + if int(res) != 0 { + return nil, fmt.Errorf("Error: %d", int(res)) + } + + mi := C.my_rpmtsInitIterator(cFun.rpmtsInitIterator, rpmts) + if mi == nil { + return nil, fmt.Errorf("Failed to get match iterator") + } + defer C.my_rpmdbFreeIterator(cFun.rpmdbFreeIterator, mi) + + var packages []*Package + for header := C.my_rpmdbNextIterator(cFun.rpmdbNextIterator, mi); header != nil; header = C.my_rpmdbNextIterator(cFun.rpmdbNextIterator, mi) { + + pkg, err := packageFromHeader(header, cFun) + if err != nil { + return nil, err + } + + packages = append(packages, pkg) + } + + return packages, nil +} + +func packageFromHeader(header C.Header, cFun *cFunctions) (*Package, error) { + + header = C.my_headerLink(cFun.headerLink, header) + if header == nil { + return nil, fmt.Errorf("Error calling headerLink") + } + defer C.my_headerFree(cFun.headerFree, header) + + pkg := Package{} + + var name *C.char + res := C.my_headerGetEntry(cFun.headerGetEntry, header, RPMTAG_NAME, &name) + if res != 1 { + return nil, fmt.Errorf("Failed to call headerGetEntry(name): %d", res) + } + pkg.Name = C.GoString(name) + + var version *C.char + res = C.my_headerGetEntry(cFun.headerGetEntry, header, RPMTAG_VERSION, &version) + if res != 1 { + return nil, fmt.Errorf("Failed to call headerGetEntry(version): %d", res) + } + pkg.Version = C.GoString(version) + + var release *C.char + res = C.my_headerGetEntry(cFun.headerGetEntry, header, RPMTAG_RELEASE, &release) + if res != 1 { + return nil, fmt.Errorf("Failed to call headerGetEntry(release): %d", res) + } + pkg.Release = C.GoString(release) + + var license *C.char + res = C.my_headerGetEntry(cFun.headerGetEntry, header, RPMTAG_LICENSE, &license) + if res != 1 { + return nil, fmt.Errorf("Failed to call headerGetEntry(license): %d", res) + } + pkg.License = C.GoString(license) + + var arch *C.char + res = C.my_headerGetEntry(cFun.headerGetEntry, header, RPMTAG_ARCH, &arch) + if res == 1 { // not always successful + pkg.Arch = C.GoString(arch) + } + + var url *C.char + res = C.my_headerGetEntry(cFun.headerGetEntry, header, RPMTAG_URL, &url) + if res == 1 { // not always successful + pkg.URL = C.GoString(url) + } + + var summary *C.char + res = C.my_headerGetEntry(cFun.headerGetEntry, header, RPMTAG_SUMMARY, &summary) + if res == 1 { // not always successful + pkg.Summary = C.GoString(summary) + } + + var size *C.int + res = C.my_headerGetEntryInt(cFun.headerGetEntry, header, RPMTAG_SIZE, &size) + if res != 1 { + return nil, fmt.Errorf("Failed to call headerGetEntry(size): %d", res) + } + pkg.Size = uint64(*size) + + var installTime *C.int + res = C.my_headerGetEntryInt(cFun.headerGetEntry, header, RPMTAG_INSTALLTIME, &installTime) + if res != 1 { + return nil, fmt.Errorf("Failed to call headerGetEntry(installTime): %d", res) + } + pkg.InstallTime = time.Unix(int64(*installTime), 0) + + return &pkg, nil +} diff --git a/x-pack/auditbeat/module/system/packages/rpm_linux_test.go b/x-pack/auditbeat/module/system/packages/rpm_linux_test.go new file mode 100644 index 000000000000..5e387208ddc6 --- /dev/null +++ b/x-pack/auditbeat/module/system/packages/rpm_linux_test.go @@ -0,0 +1,38 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +// +build linux + +package packages + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRPMPackages(t *testing.T) { + os, err := getOS() + if err != nil { + t.Fatal(err) + } + + if os.Family != "redhat" { + t.Skip("RPM test only on Redhat systems") + } + + // Control using the exec command + packagesExpected, err := rpmPackagesByExec() + if err != nil { + t.Fatal(err) + } + + packages, err := listRPMPackages() + if err != nil { + t.Fatal(err) + } + + assert.EqualValues(t, packagesExpected, packages) + +} diff --git a/x-pack/auditbeat/module/system/packages/rpm_others.go b/x-pack/auditbeat/module/system/packages/rpm_others.go new file mode 100644 index 000000000000..6bd43f8e093d --- /dev/null +++ b/x-pack/auditbeat/module/system/packages/rpm_others.go @@ -0,0 +1,13 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +// +build !linux + +package packages + +import "github.com/pkg/errors" + +func listRPMPackages() ([]*Package, error) { + return nil, errors.New("listing RPM packages is only supported on Linux") +}