From cb32015458a17125bfe1806477d5b8b187581653 Mon Sep 17 00:00:00 2001 From: Alex Kristiansen Date: Wed, 29 Jan 2020 07:51:39 -0800 Subject: [PATCH 01/15] fix file check bug, deal with logging --- x-pack/dockerlogbeat/config.json | 8 ++++++++ x-pack/dockerlogbeat/handlers.go | 4 ++-- x-pack/dockerlogbeat/pipelinemanager/libbeattools.go | 8 ++++---- x-pack/dockerlogbeat/pipelinemanager/pipelineManager.go | 3 +-- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/x-pack/dockerlogbeat/config.json b/x-pack/dockerlogbeat/config.json index 2ddf616b8c9..4927f800174 100644 --- a/x-pack/dockerlogbeat/config.json +++ b/x-pack/dockerlogbeat/config.json @@ -10,6 +10,14 @@ "socket": "beatSocket.sock" }, "env":[ + { + "description": "debug level", + "name": "LOG_DRIVER_LEVEL", + "value": "info", + "Settable": [ + "value" + ] + }, { "description": "libbeat env hack", "name": "BEAT_STRICT_PERMS", diff --git a/x-pack/dockerlogbeat/handlers.go b/x-pack/dockerlogbeat/handlers.go index 4348b948f52..bc9a28fa212 100644 --- a/x-pack/dockerlogbeat/handlers.go +++ b/x-pack/dockerlogbeat/handlers.go @@ -39,7 +39,7 @@ func startLoggingHandler(pm *pipelinemanager.PipelineManager) func(w http.Respon } pm.Logger.Debugf("Homepath: %v\n", filepath.Dir(os.Args[0])) - pm.Logger.Debugf("Got start request object from container %#v\n", startReq.Info.ContainerName) + pm.Logger.Infof("Got start request object from container %#v\n", startReq.Info.ContainerName) pm.Logger.Debugf("Got a container with the following labels: %#v\n", startReq.Info.ContainerLabels) pm.Logger.Debugf("Got a container with the following log opts: %#v\n", startReq.Info.Config) @@ -70,7 +70,7 @@ func stopLoggingHandler(pm *pipelinemanager.PipelineManager) func(w http.Respons go func() { err = pm.CloseClientWithFile(stopReq.File) if err != nil { - pm.Logger.Infof(" Got stop request error %#v\n", err) + pm.Logger.Errorf(" Got stop request error %#v\n", err) } }() diff --git a/x-pack/dockerlogbeat/pipelinemanager/libbeattools.go b/x-pack/dockerlogbeat/pipelinemanager/libbeattools.go index f4db79155d5..8f1e55f342e 100644 --- a/x-pack/dockerlogbeat/pipelinemanager/libbeattools.go +++ b/x-pack/dockerlogbeat/pipelinemanager/libbeattools.go @@ -245,21 +245,21 @@ func loadMeta(metaPath string) (uuid.UUID, error) { func openRegular(filename string) (*os.File, error) { f, err := os.Open(filename) if err != nil { - return f, errors.Wrapf(err, "error opening file %s", filename) + return f, err } info, err := f.Stat() if err != nil { f.Close() - return nil, errors.Wrapf(err, "error statting %s", filename) + return nil, err } if !info.Mode().IsRegular() { f.Close() if info.IsDir() { - return nil, fmt.Errorf("%s is a directory", filename) + return nil, err } - return nil, fmt.Errorf("%s is not a regular file", filename) + return nil, err } return f, nil diff --git a/x-pack/dockerlogbeat/pipelinemanager/pipelineManager.go b/x-pack/dockerlogbeat/pipelinemanager/pipelineManager.go index d0df1dba5d2..37696101e7c 100644 --- a/x-pack/dockerlogbeat/pipelinemanager/pipelineManager.go +++ b/x-pack/dockerlogbeat/pipelinemanager/pipelineManager.go @@ -43,8 +43,7 @@ type PipelineManager struct { // NewPipelineManager creates a new Pipeline map func NewPipelineManager(logCfg *common.Config) *PipelineManager { return &PipelineManager{ - Logger: logp.NewLogger("PipelineManager"), - //mu: new(sync.Mutex), + Logger: logp.NewLogger("PipelineManager"), pipelines: make(map[string]*Pipeline), clients: make(map[string]*ClientLogger), } From 961801de20922ae43fa5db37558242bc2e81937e Mon Sep 17 00:00:00 2001 From: Alex Kristiansen Date: Tue, 18 Feb 2020 10:53:30 -0800 Subject: [PATCH 02/15] init commit of users metricset --- metricbeat/docs/fields.asciidoc | 27 +++++ metricbeat/docs/modules/system.asciidoc | 4 + metricbeat/docs/modules/system/users.asciidoc | 23 ++++ metricbeat/docs/modules_list.asciidoc | 3 +- metricbeat/include/list_common.go | 1 + metricbeat/module/system/fields.go | 2 +- .../module/system/users/_meta/data.json | 19 ++++ .../module/system/users/_meta/docs.asciidoc | 11 ++ .../module/system/users/_meta/fields.yml | 16 +++ metricbeat/module/system/users/users.go | 104 ++++++++++++++++++ 10 files changed, 208 insertions(+), 2 deletions(-) create mode 100644 metricbeat/docs/modules/system/users.asciidoc create mode 100644 metricbeat/module/system/users/_meta/data.json create mode 100644 metricbeat/module/system/users/_meta/docs.asciidoc create mode 100644 metricbeat/module/system/users/_meta/fields.yml create mode 100644 metricbeat/module/system/users/users.go diff --git a/metricbeat/docs/fields.asciidoc b/metricbeat/docs/fields.asciidoc index bb92e2bf76e..8afb831a41b 100644 --- a/metricbeat/docs/fields.asciidoc +++ b/metricbeat/docs/fields.asciidoc @@ -35921,6 +35921,33 @@ format: duration -- +[float] +=== users + +Logged-in user data + + + +*`system.users.path`*:: ++ +-- +Dbus object path + + +type: keyword + +-- + +*`system.users.sessions`*:: ++ +-- +sessions associated with the user + + +type: keyword + +-- + [[exported-fields-tomcat]] == Tomcat fields diff --git a/metricbeat/docs/modules/system.asciidoc b/metricbeat/docs/modules/system.asciidoc index 74c9c06a794..1c7b49716af 100644 --- a/metricbeat/docs/modules/system.asciidoc +++ b/metricbeat/docs/modules/system.asciidoc @@ -274,6 +274,8 @@ The following metricsets are available: * <> +* <> + include::system/core.asciidoc[] include::system/cpu.asciidoc[] @@ -308,3 +310,5 @@ include::system/socket_summary.asciidoc[] include::system/uptime.asciidoc[] +include::system/users.asciidoc[] + diff --git a/metricbeat/docs/modules/system/users.asciidoc b/metricbeat/docs/modules/system/users.asciidoc new file mode 100644 index 00000000000..ab3025c02d1 --- /dev/null +++ b/metricbeat/docs/modules/system/users.asciidoc @@ -0,0 +1,23 @@ +//// +This file is generated! See scripts/mage/docs_collector.go +//// + +[[metricbeat-metricset-system-users]] +=== System users metricset + +beta[] + +include::../../../module/system/users/_meta/docs.asciidoc[] + + +==== Fields + +For a description of each field in the metricset, see the +<> section. + +Here is an example document generated by this metricset: + +[source,json] +---- +include::../../../module/system/users/_meta/data.json[] +---- diff --git a/metricbeat/docs/modules_list.asciidoc b/metricbeat/docs/modules_list.asciidoc index e35f54b7351..df684d172b1 100644 --- a/metricbeat/docs/modules_list.asciidoc +++ b/metricbeat/docs/modules_list.asciidoc @@ -202,7 +202,7 @@ This file is generated! See scripts/mage/docs_collector.go |<> beta[] |image:./images/icon-no.png[No prebuilt dashboards] | .1+| .1+| |<> beta[] |<> |image:./images/icon-yes.png[Prebuilt dashboards are available] | -.17+| .17+| |<> +.18+| .18+| |<> |<> |<> |<> @@ -219,6 +219,7 @@ This file is generated! See scripts/mage/docs_collector.go |<> |<> |<> +|<> beta[] |<> beta[] |image:./images/icon-yes.png[Prebuilt dashboards are available] | .4+| .4+| |<> beta[] |<> beta[] diff --git a/metricbeat/include/list_common.go b/metricbeat/include/list_common.go index 17318c683e8..12dc0a93d7e 100644 --- a/metricbeat/include/list_common.go +++ b/metricbeat/include/list_common.go @@ -146,6 +146,7 @@ import ( _ "github.com/elastic/beats/metricbeat/module/system/socket" _ "github.com/elastic/beats/metricbeat/module/system/socket_summary" _ "github.com/elastic/beats/metricbeat/module/system/uptime" + _ "github.com/elastic/beats/metricbeat/module/system/users" _ "github.com/elastic/beats/metricbeat/module/traefik" _ "github.com/elastic/beats/metricbeat/module/traefik/health" _ "github.com/elastic/beats/metricbeat/module/uwsgi" diff --git a/metricbeat/module/system/fields.go b/metricbeat/module/system/fields.go index f2800f92a1b..8bf08bda6a4 100644 --- a/metricbeat/module/system/fields.go +++ b/metricbeat/module/system/fields.go @@ -32,5 +32,5 @@ func init() { // AssetSystem returns asset data. // This is the base64 encoded gzipped contents of ../metricbeat/module/system. func AssetSystem() string { - return "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" + return "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" } diff --git a/metricbeat/module/system/users/_meta/data.json b/metricbeat/module/system/users/_meta/data.json new file mode 100644 index 00000000000..0815409194b --- /dev/null +++ b/metricbeat/module/system/users/_meta/data.json @@ -0,0 +1,19 @@ +{ + "@timestamp":"2016-05-23T08:05:34.853Z", + "beat":{ + "hostname":"beathost", + "name":"beathost" + }, + "metricset":{ + "host":"localhost", + "module":"system", + "name":"users", + "rtt":44269 + }, + "system":{ + "users":{ + "example": "users" + } + }, + "type":"metricsets" +} diff --git a/metricbeat/module/system/users/_meta/docs.asciidoc b/metricbeat/module/system/users/_meta/docs.asciidoc new file mode 100644 index 00000000000..c1b0720c0b8 --- /dev/null +++ b/metricbeat/module/system/users/_meta/docs.asciidoc @@ -0,0 +1,11 @@ +The system/users metricset reports logged in users and associated sessions via dbus and logind. + +This metricset is available on: + +- Linux + + +[float] +=== Configuration + +There are no configuration options for this metricset. diff --git a/metricbeat/module/system/users/_meta/fields.yml b/metricbeat/module/system/users/_meta/fields.yml new file mode 100644 index 00000000000..9785eff9942 --- /dev/null +++ b/metricbeat/module/system/users/_meta/fields.yml @@ -0,0 +1,16 @@ +- name: users + type: group + release: beta + description: > + Logged-in user data + fields: + - name: path + type: keyword + description: > + Dbus object path + - name: sessions + type: keyword + description: > + sessions associated with the user + + diff --git a/metricbeat/module/system/users/users.go b/metricbeat/module/system/users/users.go new file mode 100644 index 00000000000..33e74f73c98 --- /dev/null +++ b/metricbeat/module/system/users/users.go @@ -0,0 +1,104 @@ +package users + +import ( + "github.com/coreos/go-systemd/login1" + "github.com/elastic/beats/libbeat/common" + "github.com/elastic/beats/libbeat/common/cfgwarn" + "github.com/elastic/beats/metricbeat/mb" + "github.com/pkg/errors" +) + +type userInfo struct { + UID uint32 + User string + Path string + Sessions []login1.Session +} + +// init registers the MetricSet with the central registry as soon as the program +// starts. The New function will be called later to instantiate an instance of +// the MetricSet for each host defined in the module's configuration. After the +// MetricSet has been created then Fetch will begin to be called periodically. +func init() { + mb.Registry.MustAddMetricSet("system", "users", New) +} + +// MetricSet holds any configuration or state information. It must implement +// the mb.MetricSet interface. And this is best achieved by embedding +// mb.BaseMetricSet because it implements all of the required mb.MetricSet +// interface methods except for Fetch. +type MetricSet struct { + mb.BaseMetricSet + counter int + conn *login1.Conn +} + +// New creates a new instance of the MetricSet. New is responsible for unpacking +// any MetricSet specific configuration options if there are any. +func New(base mb.BaseMetricSet) (mb.MetricSet, error) { + cfgwarn.Beta("The system users metricset is beta.") + + config := struct{}{} + if err := base.Module().UnpackConfig(&config); err != nil { + return nil, err + } + + conn, err := login1.New() + if err != nil { + return nil, errors.Wrap(err, "error connecting to dbus") + } + + return &MetricSet{ + BaseMetricSet: base, + counter: 1, + conn: conn, + }, nil +} + +// Fetch methods implements the data gathering and data conversion to the right +// format. It publishes the event which is then forwarded to the output. In case +// of an error set the Error field of mb.Event or simply call report.Error(). +func (m *MetricSet) Fetch(report mb.ReporterV2) error { + users, err := m.conn.ListUsers() + if err != nil { + return errors.Wrap(err, "error listing users") + } + sessions, err := m.conn.ListSessions() + if err != nil { + return errors.Wrap(err, "error listing sessions") + } + + eventMapping(users, sessions, report) + + return nil +} + +// eventMapping iterates through the lists of users and sessions, combining the two +func eventMapping(users []login1.User, sessions []login1.Session, report mb.ReporterV2) error { + sessionList := []string{} + for _, user := range users { + for _, session := range sessions { + if session.UID == user.UID { + sessionList = append(sessionList, session.ID) + } + } + reported := report.Event(mb.Event{ + RootFields: common.MapStr{ + "user": common.MapStr{ + "name": user.Name, + "id": user.UID, + }, + }, + MetricSetFields: common.MapStr{ + "path": user.Path, + "sessions": sessionList, + }, + }, + ) + //if the channel is closed and metricbeat is shutting down, just return + if !reported { + break + } + } + return nil +} From a1ddc9e47e86566c047eab07d1438d89db6dc966 Mon Sep 17 00:00:00 2001 From: Alex Kristiansen Date: Tue, 18 Feb 2020 11:32:08 -0800 Subject: [PATCH 03/15] vendor new sub-systemd dep --- .../coreos/go-systemd/login1/dbus.go | 260 ++++++++++++++++++ vendor/vendor.json | 20 +- 2 files changed, 274 insertions(+), 6 deletions(-) create mode 100644 vendor/github.com/coreos/go-systemd/login1/dbus.go diff --git a/vendor/github.com/coreos/go-systemd/login1/dbus.go b/vendor/github.com/coreos/go-systemd/login1/dbus.go new file mode 100644 index 00000000000..6d2c99bcb18 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/login1/dbus.go @@ -0,0 +1,260 @@ +// Copyright 2015 CoreOS, Inc. +// +// Licensed 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 login1 provides integration with the systemd logind API. See http://www.freedesktop.org/wiki/Software/systemd/logind/ +package login1 + +import ( + "fmt" + "os" + "strconv" + + "github.com/godbus/dbus" +) + +const ( + dbusInterface = "org.freedesktop.login1.Manager" + dbusPath = "/org/freedesktop/login1" +) + +// Conn is a connection to systemds dbus endpoint. +type Conn struct { + conn *dbus.Conn + object dbus.BusObject +} + +// New establishes a connection to the system bus and authenticates. +func New() (*Conn, error) { + c := new(Conn) + + if err := c.initConnection(); err != nil { + return nil, err + } + + return c, nil +} + +// Close closes the dbus connection +func (c *Conn) Close() { + if c == nil { + return + } + + if c.conn != nil { + c.conn.Close() + } +} + +func (c *Conn) initConnection() error { + var err error + c.conn, err = dbus.SystemBusPrivate() + if err != nil { + return err + } + + // Only use EXTERNAL method, and hardcode the uid (not username) + // to avoid a username lookup (which requires a dynamically linked + // libc) + methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))} + + err = c.conn.Auth(methods) + if err != nil { + c.conn.Close() + return err + } + + err = c.conn.Hello() + if err != nil { + c.conn.Close() + return err + } + + c.object = c.conn.Object("org.freedesktop.login1", dbus.ObjectPath(dbusPath)) + + return nil +} + +// Session object definition. +type Session struct { + ID string + UID uint32 + User string + Seat string + Path dbus.ObjectPath +} + +// User object definition. +type User struct { + UID uint32 + Name string + Path dbus.ObjectPath +} + +func (s Session) toInterface() []interface{} { + return []interface{}{s.ID, s.UID, s.User, s.Seat, s.Path} +} + +func sessionFromInterfaces(session []interface{}) (*Session, error) { + if len(session) < 5 { + return nil, fmt.Errorf("invalid number of session fields: %d", len(session)) + } + id, ok := session[0].(string) + if !ok { + return nil, fmt.Errorf("failed to typecast session field 0 to string") + } + uid, ok := session[1].(uint32) + if !ok { + return nil, fmt.Errorf("failed to typecast session field 1 to uint32") + } + user, ok := session[2].(string) + if !ok { + return nil, fmt.Errorf("failed to typecast session field 2 to string") + } + seat, ok := session[3].(string) + if !ok { + return nil, fmt.Errorf("failed to typecast session field 2 to string") + } + path, ok := session[4].(dbus.ObjectPath) + if !ok { + return nil, fmt.Errorf("failed to typecast session field 4 to ObjectPath") + } + + ret := Session{ID: id, UID: uid, User: user, Seat: seat, Path: path} + return &ret, nil +} + +func userFromInterfaces(user []interface{}) (*User, error) { + if len(user) < 3 { + return nil, fmt.Errorf("invalid number of user fields: %d", len(user)) + } + uid, ok := user[0].(uint32) + if !ok { + return nil, fmt.Errorf("failed to typecast user field 0 to uint32") + } + name, ok := user[1].(string) + if !ok { + return nil, fmt.Errorf("failed to typecast session field 1 to string") + } + path, ok := user[2].(dbus.ObjectPath) + if !ok { + return nil, fmt.Errorf("failed to typecast user field 2 to ObjectPath") + } + + ret := User{UID: uid, Name: name, Path: path} + return &ret, nil +} + +// GetSession may be used to get the session object path for the session with the specified ID. +func (c *Conn) GetSession(id string) (dbus.ObjectPath, error) { + var out interface{} + if err := c.object.Call(dbusInterface+".GetSession", 0, id).Store(&out); err != nil { + return "", err + } + + ret, ok := out.(dbus.ObjectPath) + if !ok { + return "", fmt.Errorf("failed to typecast session to ObjectPath") + } + + return ret, nil +} + +// ListSessions returns an array with all current sessions. +func (c *Conn) ListSessions() ([]Session, error) { + out := [][]interface{}{} + if err := c.object.Call(dbusInterface+".ListSessions", 0).Store(&out); err != nil { + return nil, err + } + + ret := []Session{} + for _, el := range out { + session, err := sessionFromInterfaces(el) + if err != nil { + return nil, err + } + ret = append(ret, *session) + } + return ret, nil +} + +// ListUsers returns an array with all currently logged in users. +func (c *Conn) ListUsers() ([]User, error) { + out := [][]interface{}{} + if err := c.object.Call(dbusInterface+".ListUsers", 0).Store(&out); err != nil { + return nil, err + } + + ret := []User{} + for _, el := range out { + user, err := userFromInterfaces(el) + if err != nil { + return nil, err + } + ret = append(ret, *user) + } + return ret, nil +} + +// LockSession asks the session with the specified ID to activate the screen lock. +func (c *Conn) LockSession(id string) { + c.object.Call(dbusInterface+".LockSession", 0, id) +} + +// LockSessions asks all sessions to activate the screen locks. This may be used to lock any access to the machine in one action. +func (c *Conn) LockSessions() { + c.object.Call(dbusInterface+".LockSessions", 0) +} + +// TerminateSession forcibly terminate one specific session. +func (c *Conn) TerminateSession(id string) { + c.object.Call(dbusInterface+".TerminateSession", 0, id) +} + +// TerminateUser forcibly terminates all processes of a user. +func (c *Conn) TerminateUser(uid uint32) { + c.object.Call(dbusInterface+".TerminateUser", 0, uid) +} + +// Reboot asks logind for a reboot optionally asking for auth. +func (c *Conn) Reboot(askForAuth bool) { + c.object.Call(dbusInterface+".Reboot", 0, askForAuth) +} + +// Inhibit takes inhibition lock in logind. +func (c *Conn) Inhibit(what, who, why, mode string) (*os.File, error) { + var fd dbus.UnixFD + + err := c.object.Call(dbusInterface+".Inhibit", 0, what, who, why, mode).Store(&fd) + if err != nil { + return nil, err + } + + return os.NewFile(uintptr(fd), "inhibit"), nil +} + +// Subscribe to signals on the logind dbus +func (c *Conn) Subscribe(members ...string) chan *dbus.Signal { + for _, member := range members { + c.conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, + fmt.Sprintf("type='signal',interface='org.freedesktop.login1.Manager',member='%s'", member)) + } + ch := make(chan *dbus.Signal, 10) + c.conn.Signal(ch) + return ch +} + +// PowerOff asks logind for a power off optionally asking for auth. +func (c *Conn) PowerOff(askForAuth bool) { + c.object.Call(dbusInterface+".PowerOff", 0, askForAuth) +} diff --git a/vendor/vendor.json b/vendor/vendor.json index fdd29cff5ea..3d13b9b0189 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -2068,6 +2068,14 @@ "version": "v20", "versionExact": "v20" }, + { + "checksumSHA1": "H9YEySbt+BMCmgVYnOLTyihKsME=", + "path": "github.com/coreos/go-systemd/login1", + "revision": "e64a0ec8b42a61e2a9801dc1d0abe539dea79197", + "revisionTime": "2019-06-20T07:13:33Z", + "version": "v20", + "versionExact": "v20" + }, { "checksumSHA1": "kDSYVipifs9K6CgxXLIfHrGh8wA=", "path": "github.com/coreos/go-systemd/sdjournal", @@ -5808,6 +5816,12 @@ "version": "v0.7.0", "versionExact": "v0.7.0" }, + { + "path": "google.golang.org/api/internal/gensupport", + "revision": "02490b97dff7cfde1995bd77de808fd27053bc87", + "version": "v0.7.0", + "versionExact": "v0.7.0" + }, { "checksumSHA1": "I4Oe5Q+AuaxmN3duL38r2evqGKk=", "path": "google.golang.org/api/internal/gensupport", @@ -5816,12 +5830,6 @@ "version": "v0.14.0", "versionExact": "v0.14.0" }, - { - "path": "google.golang.org/api/internal/gensupport", - "revision": "02490b97dff7cfde1995bd77de808fd27053bc87", - "version": "v0.7.0", - "versionExact": "v0.7.0" - }, { "checksumSHA1": "nN+zggDyWr8HPYzwltMkzJJr1Jc=", "path": "google.golang.org/api/internal/third_party/uritemplates", From b1d0e0dd45c5108be824fc2d20d3b83e71d3ef18 Mon Sep 17 00:00:00 2001 From: Alex Kristiansen Date: Tue, 25 Feb 2020 07:53:19 -0800 Subject: [PATCH 04/15] refactor dbus calling code --- metricbeat/docs/fields.asciidoc | 88 ++++++++- metricbeat/module/system/fields.go | 2 +- .../module/system/users/_meta/data.json | 48 +++-- .../module/system/users/_meta/fields.yml | 40 +++- metricbeat/module/system/users/dbus.go | 173 ++++++++++++++++++ metricbeat/module/system/users/users.go | 75 +++++--- metricbeat/module/system/users/users_test.go | 37 ++++ 7 files changed, 414 insertions(+), 49 deletions(-) create mode 100644 metricbeat/module/system/users/dbus.go create mode 100644 metricbeat/module/system/users/users_test.go diff --git a/metricbeat/docs/fields.asciidoc b/metricbeat/docs/fields.asciidoc index 8afb831a41b..9109e2dcf5e 100644 --- a/metricbeat/docs/fields.asciidoc +++ b/metricbeat/docs/fields.asciidoc @@ -35924,24 +35924,104 @@ format: duration [float] === users -Logged-in user data +Logged-in user session data +*`system.users.id`*:: ++ +-- +The ID of the session + + +type: keyword + +-- + +*`system.users.seat`*:: ++ +-- +An associated logind seat + + +type: keyword + +-- + *`system.users.path`*:: + -- -Dbus object path +The DBus object path of the session + + +type: keyword + +-- + +*`system.users.type`*:: ++ +-- +The type of the user session + + +type: keyword + +-- + +*`system.users.service`*:: ++ +-- +A session associated with the service type: keyword -- -*`system.users.sessions`*:: +*`system.users.remote`*:: ++ +-- +A bool indicating a remote session + + +type: boolean + +-- + +*`system.users.state`*:: ++ +-- +The current state of the session + + +type: keyword + +-- + +*`system.users.scope`*:: ++ +-- +The associated systemd scope + + +type: keyword + +-- + +*`system.users.leader`*:: ++ +-- +The root PID of the session + + +type: long + +-- + +*`system.users.remote_host`*:: + -- -sessions associated with the user +A remote host address for the session type: keyword diff --git a/metricbeat/module/system/fields.go b/metricbeat/module/system/fields.go index 8bf08bda6a4..1b293c6da6e 100644 --- a/metricbeat/module/system/fields.go +++ b/metricbeat/module/system/fields.go @@ -32,5 +32,5 @@ func init() { // AssetSystem returns asset data. // This is the base64 encoded gzipped contents of ../metricbeat/module/system. func AssetSystem() string { - return "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" + return "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" } diff --git a/metricbeat/module/system/users/_meta/data.json b/metricbeat/module/system/users/_meta/data.json index 0815409194b..5b96b10ba81 100644 --- a/metricbeat/module/system/users/_meta/data.json +++ b/metricbeat/module/system/users/_meta/data.json @@ -1,19 +1,39 @@ { - "@timestamp":"2016-05-23T08:05:34.853Z", - "beat":{ - "hostname":"beathost", - "name":"beathost" + "@timestamp": "2017-10-12T08:05:34.853Z", + "event": { + "dataset": "system.users", + "duration": 115000, + "module": "system" }, - "metricset":{ - "host":"localhost", - "module":"system", - "name":"users", - "rtt":44269 + "metricset": { + "name": "users", + "period": 10000 }, - "system":{ - "users":{ - "example": "users" + "process": { + "pid": 10786 + }, + "service": { + "type": "system" + }, + "source": { + "ip": "192.168.1.86" + }, + "system": { + "users": { + "id": 6, + "leader": 10786, + "path": "/org/freedesktop/login1/session/_36", + "remote": true, + "remote_host": "192.168.1.86", + "scope": "session-6.scope", + "seat": "", + "service": "sshd", + "state": "active", + "type": "tty" } }, - "type":"metricsets" -} + "user": { + "id": 1000, + "name": "alexk" + } +} \ No newline at end of file diff --git a/metricbeat/module/system/users/_meta/fields.yml b/metricbeat/module/system/users/_meta/fields.yml index 9785eff9942..342d95629db 100644 --- a/metricbeat/module/system/users/_meta/fields.yml +++ b/metricbeat/module/system/users/_meta/fields.yml @@ -2,15 +2,47 @@ type: group release: beta description: > - Logged-in user data + Logged-in user session data fields: + - name: id + type: keyword + description: > + The ID of the session + - name: seat + type: keyword + description: > + An associated logind seat - name: path type: keyword description: > - Dbus object path - - name: sessions + The DBus object path of the session + - name: type + type: keyword + description: > + The type of the user session + - name: service + type: keyword + description: > + A session associated with the service + - name: remote + type: boolean + description: > + A bool indicating a remote session + - name: state + type: keyword + description: > + The current state of the session + - name: scope + type: keyword + description: > + The associated systemd scope + - name: leader + type: long + description: > + The root PID of the session + - name: remote_host type: keyword description: > - sessions associated with the user + A remote host address for the session diff --git a/metricbeat/module/system/users/dbus.go b/metricbeat/module/system/users/dbus.go new file mode 100644 index 00000000000..f770ea4b5ba --- /dev/null +++ b/metricbeat/module/system/users/dbus.go @@ -0,0 +1,173 @@ +package users + +import ( + "fmt" + "os" + "strconv" + + "github.com/godbus/dbus" + "github.com/pkg/errors" +) + +// sessionInfo contains useful properties for a session +type sessionInfo struct { + Remote bool + RemoteHost string + Name string + Scope string + Service string + State string + Type string + Leader uint32 +} + +// loginSession contains basic information on a login session +type loginSession struct { + ID uint64 + UID uint32 + User string + Seat string + Path dbus.ObjectPath +} + +// initDbusConnection initializes a connection to the dbus +func initDbusConnection() (*dbus.Conn, error) { + conn, err := dbus.SystemBusPrivate() + if err != nil { + return nil, errors.Wrap(err, "error getting connection to system bus") + } + + auth := dbus.AuthExternal(strconv.Itoa(os.Getuid())) + + err = conn.Auth([]dbus.Auth{auth}) + if err != nil { + return nil, errors.Wrap(err, "error authenticating") + } + + err = conn.Hello() + if err != nil { + return nil, errors.Wrap(err, "error in Hello") + } + + return conn, nil +} + +// getSessionProps returns info on a given session pointed to by path +func getSessionProps(conn *dbus.Conn, path dbus.ObjectPath) (sessionInfo, error) { + busObj := conn.Object("org.freedesktop.login1", path) + + var props map[string]dbus.Variant + + err := busObj.Call("org.freedesktop.DBus.Properties.GetAll", 0, "").Store(&props) + if err != nil { + return sessionInfo{}, errors.Wrap(err, "error calling DBus") + } + + if len(props) < 8 { + return sessionInfo{}, fmt.Errorf("wrong number of fields in info: %v", props) + } + + remote, ok := props["Remote"].Value().(bool) + if !ok { + return sessionInfo{}, fmt.Errorf("failed to cast remote to bool") + } + + remoteHost, ok := props["RemoteHost"].Value().(string) + if !ok { + return sessionInfo{}, fmt.Errorf("failed to cast remote to string") + } + + userName, ok := props["Name"].Value().(string) + if !ok { + return sessionInfo{}, fmt.Errorf("failed to cast username to string") + } + + scope, ok := props["Scope"].Value().(string) + if !ok { + return sessionInfo{}, fmt.Errorf("failed to cast scope to string") + } + + service, ok := props["Service"].Value().(string) + if !ok { + return sessionInfo{}, fmt.Errorf("failed to cast service to string") + } + + state, ok := props["State"].Value().(string) + if !ok { + return sessionInfo{}, fmt.Errorf("failed to cast state to string") + } + + sessionType, ok := props["Type"].Value().(string) + if !ok { + return sessionInfo{}, fmt.Errorf("failed to cast type to string") + } + + leader, ok := props["Leader"].Value().(uint32) + if !ok { + return sessionInfo{}, fmt.Errorf("failed to cast type to uint32") + } + + session := sessionInfo{ + Remote: remote, + RemoteHost: remoteHost, + Name: userName, + Scope: scope, + Service: service, + State: state, + Type: sessionType, + Leader: leader, + } + + return session, nil +} + +// listSessions lists all sessions known to dbus +func listSessions(conn *dbus.Conn) ([]loginSession, error) { + busObj := conn.Object("org.freedesktop.login1", dbus.ObjectPath("/org/freedesktop/login1")) + var props [][]dbus.Variant + + if err := busObj.Call("org.freedesktop.login1.Manager.ListSessions", 0).Store(&props); err != nil { + return nil, errors.Wrap(err, "error calling dbus") + } + + sessionList := make([]loginSession, len(props)) + for iter, session := range props { + if len(session) < 5 { + return nil, fmt.Errorf("wrong number of fields in session: %v", session) + } + idStr, ok := session[0].Value().(string) + if !ok { + return nil, fmt.Errorf("failed to cast user ID to string") + } + + id, err := strconv.ParseUint(idStr, 10, 32) + if err != nil { + return nil, errors.Wrap(err, "error parsing ID to int") + } + + uid, ok := session[1].Value().(uint32) + if !ok { + return nil, fmt.Errorf("failed to cast session uid to uint32") + } + user, ok := session[2].Value().(string) + if !ok { + return nil, fmt.Errorf("failed to cast session user to string") + } + seat, ok := session[3].Value().(string) + if !ok { + return nil, fmt.Errorf("failed to cast session seat to string") + } + path, ok := session[4].Value().(dbus.ObjectPath) + if !ok { + return nil, fmt.Errorf("failed to cast session path to ObjectPath") + } + newSession := loginSession{ID: id, + UID: uid, + User: user, + Seat: seat, + Path: path} + sessionList[iter] = newSession + } + + return sessionList, nil +} diff --git a/metricbeat/module/system/users/users.go b/metricbeat/module/system/users/users.go index 33e74f73c98..f63a36a55c4 100644 --- a/metricbeat/module/system/users/users.go +++ b/metricbeat/module/system/users/users.go @@ -1,10 +1,13 @@ package users import ( + "net" + "github.com/coreos/go-systemd/login1" "github.com/elastic/beats/libbeat/common" "github.com/elastic/beats/libbeat/common/cfgwarn" "github.com/elastic/beats/metricbeat/mb" + "github.com/godbus/dbus" "github.com/pkg/errors" ) @@ -30,7 +33,7 @@ func init() { type MetricSet struct { mb.BaseMetricSet counter int - conn *login1.Conn + conn *dbus.Conn } // New creates a new instance of the MetricSet. New is responsible for unpacking @@ -43,7 +46,7 @@ func New(base mb.BaseMetricSet) (mb.MetricSet, error) { return nil, err } - conn, err := login1.New() + conn, err := initDbusConnection() if err != nil { return nil, errors.Wrap(err, "error connecting to dbus") } @@ -59,42 +62,62 @@ func New(base mb.BaseMetricSet) (mb.MetricSet, error) { // format. It publishes the event which is then forwarded to the output. In case // of an error set the Error field of mb.Event or simply call report.Error(). func (m *MetricSet) Fetch(report mb.ReporterV2) error { - users, err := m.conn.ListUsers() - if err != nil { - return errors.Wrap(err, "error listing users") - } - sessions, err := m.conn.ListSessions() + + sessions, err := listSessions(m.conn) if err != nil { return errors.Wrap(err, "error listing sessions") } - eventMapping(users, sessions, report) + eventMapping(m.conn, sessions, report) return nil } // eventMapping iterates through the lists of users and sessions, combining the two -func eventMapping(users []login1.User, sessions []login1.Session, report mb.ReporterV2) error { - sessionList := []string{} - for _, user := range users { - for _, session := range sessions { - if session.UID == user.UID { - sessionList = append(sessionList, session.ID) - } +func eventMapping(conn *dbus.Conn, sessions []loginSession, report mb.ReporterV2) error { + + for _, session := range sessions { + + props, err := getSessionProps(conn, session.Path) + if err != nil { + return errors.Wrap(err, "error getting properties") } - reported := report.Event(mb.Event{ - RootFields: common.MapStr{ - "user": common.MapStr{ - "name": user.Name, - "id": user.UID, - }, + + event := common.MapStr{ + "id": session.ID, + "seat": session.Seat, + "path": session.Path, + "type": props.Type, + "service": props.Service, + "remote": props.Remote, + "state": props.State, + "scope": props.Scope, + "leader": props.Leader, + } + + rootEvents := common.MapStr{ + "process": common.MapStr{ + "pid": props.Leader, }, - MetricSetFields: common.MapStr{ - "path": user.Path, - "sessions": sessionList, + "user": common.MapStr{ + "name": session.User, + "id": session.UID, }, - }, - ) + } + + if props.Remote { + event["remote_host"] = props.RemoteHost + if ipAddr := net.ParseIP(props.RemoteHost); ipAddr != nil { + rootEvents["source"] = common.MapStr{ + "ip": ipAddr, + } + } + } + + reported := report.Event(mb.Event{ + RootFields: rootEvents, + MetricSetFields: event, + }) //if the channel is closed and metricbeat is shutting down, just return if !reported { break diff --git a/metricbeat/module/system/users/users_test.go b/metricbeat/module/system/users/users_test.go new file mode 100644 index 00000000000..cdf600bf538 --- /dev/null +++ b/metricbeat/module/system/users/users_test.go @@ -0,0 +1,37 @@ +package users + +import ( + "testing" + + mbtest "github.com/elastic/beats/metricbeat/mb/testing" + "github.com/stretchr/testify/assert" +) + +func TestFetch(t *testing.T) { + f := mbtest.NewReportingMetricSetV2Error(t, getConfig()) + events, errs := mbtest.ReportingFetchV2Error(f) + + if !assert.Empty(t, errs) { + t.FailNow() + } + if !assert.NotEmpty(t, events) { + t.FailNow() + } + t.Logf("%s/%s event: %+v", f.Module().Name(), f.Name(), + events[0].BeatEvent("system", "users").Fields.StringToPrint()) +} + +func TestData(t *testing.T) { + f := mbtest.NewReportingMetricSetV2Error(t, getConfig()) + err := mbtest.WriteEventsReporterV2Error(f, t, ".") + if err != nil { + t.Fatal("write", err) + } +} + +func getConfig() map[string]interface{} { + return map[string]interface{}{ + "module": "system", + "metricsets": []string{"users"}, + } +} From 17bd7361800396f3ae69c03ccb9d887b53cccedb Mon Sep 17 00:00:00 2001 From: Alex Kristiansen Date: Tue, 25 Feb 2020 08:30:28 -0800 Subject: [PATCH 05/15] Revert "vendor new sub-systemd dep" This reverts commit a1ddc9e47e86566c047eab07d1438d89db6dc966. --- .../coreos/go-systemd/login1/dbus.go | 260 ------------------ vendor/vendor.json | 20 +- 2 files changed, 6 insertions(+), 274 deletions(-) delete mode 100644 vendor/github.com/coreos/go-systemd/login1/dbus.go diff --git a/vendor/github.com/coreos/go-systemd/login1/dbus.go b/vendor/github.com/coreos/go-systemd/login1/dbus.go deleted file mode 100644 index 6d2c99bcb18..00000000000 --- a/vendor/github.com/coreos/go-systemd/login1/dbus.go +++ /dev/null @@ -1,260 +0,0 @@ -// Copyright 2015 CoreOS, Inc. -// -// Licensed 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 login1 provides integration with the systemd logind API. See http://www.freedesktop.org/wiki/Software/systemd/logind/ -package login1 - -import ( - "fmt" - "os" - "strconv" - - "github.com/godbus/dbus" -) - -const ( - dbusInterface = "org.freedesktop.login1.Manager" - dbusPath = "/org/freedesktop/login1" -) - -// Conn is a connection to systemds dbus endpoint. -type Conn struct { - conn *dbus.Conn - object dbus.BusObject -} - -// New establishes a connection to the system bus and authenticates. -func New() (*Conn, error) { - c := new(Conn) - - if err := c.initConnection(); err != nil { - return nil, err - } - - return c, nil -} - -// Close closes the dbus connection -func (c *Conn) Close() { - if c == nil { - return - } - - if c.conn != nil { - c.conn.Close() - } -} - -func (c *Conn) initConnection() error { - var err error - c.conn, err = dbus.SystemBusPrivate() - if err != nil { - return err - } - - // Only use EXTERNAL method, and hardcode the uid (not username) - // to avoid a username lookup (which requires a dynamically linked - // libc) - methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))} - - err = c.conn.Auth(methods) - if err != nil { - c.conn.Close() - return err - } - - err = c.conn.Hello() - if err != nil { - c.conn.Close() - return err - } - - c.object = c.conn.Object("org.freedesktop.login1", dbus.ObjectPath(dbusPath)) - - return nil -} - -// Session object definition. -type Session struct { - ID string - UID uint32 - User string - Seat string - Path dbus.ObjectPath -} - -// User object definition. -type User struct { - UID uint32 - Name string - Path dbus.ObjectPath -} - -func (s Session) toInterface() []interface{} { - return []interface{}{s.ID, s.UID, s.User, s.Seat, s.Path} -} - -func sessionFromInterfaces(session []interface{}) (*Session, error) { - if len(session) < 5 { - return nil, fmt.Errorf("invalid number of session fields: %d", len(session)) - } - id, ok := session[0].(string) - if !ok { - return nil, fmt.Errorf("failed to typecast session field 0 to string") - } - uid, ok := session[1].(uint32) - if !ok { - return nil, fmt.Errorf("failed to typecast session field 1 to uint32") - } - user, ok := session[2].(string) - if !ok { - return nil, fmt.Errorf("failed to typecast session field 2 to string") - } - seat, ok := session[3].(string) - if !ok { - return nil, fmt.Errorf("failed to typecast session field 2 to string") - } - path, ok := session[4].(dbus.ObjectPath) - if !ok { - return nil, fmt.Errorf("failed to typecast session field 4 to ObjectPath") - } - - ret := Session{ID: id, UID: uid, User: user, Seat: seat, Path: path} - return &ret, nil -} - -func userFromInterfaces(user []interface{}) (*User, error) { - if len(user) < 3 { - return nil, fmt.Errorf("invalid number of user fields: %d", len(user)) - } - uid, ok := user[0].(uint32) - if !ok { - return nil, fmt.Errorf("failed to typecast user field 0 to uint32") - } - name, ok := user[1].(string) - if !ok { - return nil, fmt.Errorf("failed to typecast session field 1 to string") - } - path, ok := user[2].(dbus.ObjectPath) - if !ok { - return nil, fmt.Errorf("failed to typecast user field 2 to ObjectPath") - } - - ret := User{UID: uid, Name: name, Path: path} - return &ret, nil -} - -// GetSession may be used to get the session object path for the session with the specified ID. -func (c *Conn) GetSession(id string) (dbus.ObjectPath, error) { - var out interface{} - if err := c.object.Call(dbusInterface+".GetSession", 0, id).Store(&out); err != nil { - return "", err - } - - ret, ok := out.(dbus.ObjectPath) - if !ok { - return "", fmt.Errorf("failed to typecast session to ObjectPath") - } - - return ret, nil -} - -// ListSessions returns an array with all current sessions. -func (c *Conn) ListSessions() ([]Session, error) { - out := [][]interface{}{} - if err := c.object.Call(dbusInterface+".ListSessions", 0).Store(&out); err != nil { - return nil, err - } - - ret := []Session{} - for _, el := range out { - session, err := sessionFromInterfaces(el) - if err != nil { - return nil, err - } - ret = append(ret, *session) - } - return ret, nil -} - -// ListUsers returns an array with all currently logged in users. -func (c *Conn) ListUsers() ([]User, error) { - out := [][]interface{}{} - if err := c.object.Call(dbusInterface+".ListUsers", 0).Store(&out); err != nil { - return nil, err - } - - ret := []User{} - for _, el := range out { - user, err := userFromInterfaces(el) - if err != nil { - return nil, err - } - ret = append(ret, *user) - } - return ret, nil -} - -// LockSession asks the session with the specified ID to activate the screen lock. -func (c *Conn) LockSession(id string) { - c.object.Call(dbusInterface+".LockSession", 0, id) -} - -// LockSessions asks all sessions to activate the screen locks. This may be used to lock any access to the machine in one action. -func (c *Conn) LockSessions() { - c.object.Call(dbusInterface+".LockSessions", 0) -} - -// TerminateSession forcibly terminate one specific session. -func (c *Conn) TerminateSession(id string) { - c.object.Call(dbusInterface+".TerminateSession", 0, id) -} - -// TerminateUser forcibly terminates all processes of a user. -func (c *Conn) TerminateUser(uid uint32) { - c.object.Call(dbusInterface+".TerminateUser", 0, uid) -} - -// Reboot asks logind for a reboot optionally asking for auth. -func (c *Conn) Reboot(askForAuth bool) { - c.object.Call(dbusInterface+".Reboot", 0, askForAuth) -} - -// Inhibit takes inhibition lock in logind. -func (c *Conn) Inhibit(what, who, why, mode string) (*os.File, error) { - var fd dbus.UnixFD - - err := c.object.Call(dbusInterface+".Inhibit", 0, what, who, why, mode).Store(&fd) - if err != nil { - return nil, err - } - - return os.NewFile(uintptr(fd), "inhibit"), nil -} - -// Subscribe to signals on the logind dbus -func (c *Conn) Subscribe(members ...string) chan *dbus.Signal { - for _, member := range members { - c.conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, - fmt.Sprintf("type='signal',interface='org.freedesktop.login1.Manager',member='%s'", member)) - } - ch := make(chan *dbus.Signal, 10) - c.conn.Signal(ch) - return ch -} - -// PowerOff asks logind for a power off optionally asking for auth. -func (c *Conn) PowerOff(askForAuth bool) { - c.object.Call(dbusInterface+".PowerOff", 0, askForAuth) -} diff --git a/vendor/vendor.json b/vendor/vendor.json index 3d13b9b0189..fdd29cff5ea 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -2068,14 +2068,6 @@ "version": "v20", "versionExact": "v20" }, - { - "checksumSHA1": "H9YEySbt+BMCmgVYnOLTyihKsME=", - "path": "github.com/coreos/go-systemd/login1", - "revision": "e64a0ec8b42a61e2a9801dc1d0abe539dea79197", - "revisionTime": "2019-06-20T07:13:33Z", - "version": "v20", - "versionExact": "v20" - }, { "checksumSHA1": "kDSYVipifs9K6CgxXLIfHrGh8wA=", "path": "github.com/coreos/go-systemd/sdjournal", @@ -5816,12 +5808,6 @@ "version": "v0.7.0", "versionExact": "v0.7.0" }, - { - "path": "google.golang.org/api/internal/gensupport", - "revision": "02490b97dff7cfde1995bd77de808fd27053bc87", - "version": "v0.7.0", - "versionExact": "v0.7.0" - }, { "checksumSHA1": "I4Oe5Q+AuaxmN3duL38r2evqGKk=", "path": "google.golang.org/api/internal/gensupport", @@ -5830,6 +5816,12 @@ "version": "v0.14.0", "versionExact": "v0.14.0" }, + { + "path": "google.golang.org/api/internal/gensupport", + "revision": "02490b97dff7cfde1995bd77de808fd27053bc87", + "version": "v0.7.0", + "versionExact": "v0.7.0" + }, { "checksumSHA1": "nN+zggDyWr8HPYzwltMkzJJr1Jc=", "path": "google.golang.org/api/internal/third_party/uritemplates", From 70f60d7a2a45fdf966a2dd20a60e9238c415d133 Mon Sep 17 00:00:00 2001 From: Alex Kristiansen Date: Tue, 25 Feb 2020 08:51:38 -0800 Subject: [PATCH 06/15] make fmt --- metricbeat/module/system/users/dbus.go | 17 +++++++++++ metricbeat/module/system/users/users.go | 30 +++++++++++++------- metricbeat/module/system/users/users_test.go | 20 ++++++++++++- 3 files changed, 56 insertions(+), 11 deletions(-) diff --git a/metricbeat/module/system/users/dbus.go b/metricbeat/module/system/users/dbus.go index f770ea4b5ba..f524e0d812d 100644 --- a/metricbeat/module/system/users/dbus.go +++ b/metricbeat/module/system/users/dbus.go @@ -1,3 +1,20 @@ +// 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 users import ( diff --git a/metricbeat/module/system/users/users.go b/metricbeat/module/system/users/users.go index f63a36a55c4..a08a9c360b3 100644 --- a/metricbeat/module/system/users/users.go +++ b/metricbeat/module/system/users/users.go @@ -1,23 +1,33 @@ +// 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 users import ( "net" - "github.com/coreos/go-systemd/login1" + "github.com/godbus/dbus" + "github.com/pkg/errors" + "github.com/elastic/beats/libbeat/common" "github.com/elastic/beats/libbeat/common/cfgwarn" "github.com/elastic/beats/metricbeat/mb" - "github.com/godbus/dbus" - "github.com/pkg/errors" ) -type userInfo struct { - UID uint32 - User string - Path string - Sessions []login1.Session -} - // init registers the MetricSet with the central registry as soon as the program // starts. The New function will be called later to instantiate an instance of // the MetricSet for each host defined in the module's configuration. After the diff --git a/metricbeat/module/system/users/users_test.go b/metricbeat/module/system/users/users_test.go index cdf600bf538..d87c3622fc7 100644 --- a/metricbeat/module/system/users/users_test.go +++ b/metricbeat/module/system/users/users_test.go @@ -1,10 +1,28 @@ +// 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 users import ( "testing" - mbtest "github.com/elastic/beats/metricbeat/mb/testing" "github.com/stretchr/testify/assert" + + mbtest "github.com/elastic/beats/metricbeat/mb/testing" ) func TestFetch(t *testing.T) { From 080e9e78eca290ea0210811e43e87a585edeece0 Mon Sep 17 00:00:00 2001 From: Alex Kristiansen Date: Tue, 25 Feb 2020 13:34:16 -0800 Subject: [PATCH 07/15] cleanup, fix tests --- metricbeat/module/system/users/dbus.go | 31 +++++++--- metricbeat/module/system/users/users_test.go | 61 +++++++++++++------- 2 files changed, 63 insertions(+), 29 deletions(-) diff --git a/metricbeat/module/system/users/dbus.go b/metricbeat/module/system/users/dbus.go index f524e0d812d..21f8a1f50d3 100644 --- a/metricbeat/module/system/users/dbus.go +++ b/metricbeat/module/system/users/dbus.go @@ -26,6 +26,12 @@ import ( "github.com/pkg/errors" ) +const ( + loginObj = "org.freedesktop.login1" + getAll = "org.freedesktop.DBus.Properties.GetAll" + sessionList = "org.freedesktop.login1.Manager.ListSessions" +) + // sessionInfo contains useful properties for a session type sessionInfo struct { Remote bool @@ -71,15 +77,19 @@ func initDbusConnection() (*dbus.Conn, error) { // getSessionProps returns info on a given session pointed to by path func getSessionProps(conn *dbus.Conn, path dbus.ObjectPath) (sessionInfo, error) { - busObj := conn.Object("org.freedesktop.login1", path) + busObj := conn.Object(loginObj, path) var props map[string]dbus.Variant - err := busObj.Call("org.freedesktop.DBus.Properties.GetAll", 0, "").Store(&props) + err := busObj.Call(getAll, 0, "").Store(&props) if err != nil { return sessionInfo{}, errors.Wrap(err, "error calling DBus") } + return formatSessonProps(props) +} + +func formatSessonProps(props map[string]dbus.Variant) (sessionInfo, error) { if len(props) < 8 { return sessionInfo{}, fmt.Errorf("wrong number of fields in info: %v", props) } @@ -91,7 +101,7 @@ func getSessionProps(conn *dbus.Conn, path dbus.ObjectPath) (sessionInfo, error) remoteHost, ok := props["RemoteHost"].Value().(string) if !ok { - return sessionInfo{}, fmt.Errorf("failed to cast remote to string") + return sessionInfo{}, fmt.Errorf("failed to cast remote host to string") } userName, ok := props["Name"].Value().(string) @@ -121,7 +131,7 @@ func getSessionProps(conn *dbus.Conn, path dbus.ObjectPath) (sessionInfo, error) leader, ok := props["Leader"].Value().(uint32) if !ok { - return sessionInfo{}, fmt.Errorf("failed to cast type to uint32") + return sessionInfo{}, fmt.Errorf("failed to cast leader to uint32") } session := sessionInfo{ @@ -140,13 +150,16 @@ func getSessionProps(conn *dbus.Conn, path dbus.ObjectPath) (sessionInfo, error) // listSessions lists all sessions known to dbus func listSessions(conn *dbus.Conn) ([]loginSession, error) { - busObj := conn.Object("org.freedesktop.login1", dbus.ObjectPath("/org/freedesktop/login1")) + busObj := conn.Object(loginObj, dbus.ObjectPath("/org/freedesktop/login1")) var props [][]dbus.Variant - if err := busObj.Call("org.freedesktop.login1.Manager.ListSessions", 0).Store(&props); err != nil { + if err := busObj.Call(sessionList, 0).Store(&props); err != nil { return nil, errors.Wrap(err, "error calling dbus") } + return formatSessionList(props) +} +func formatSessionList(props [][]dbus.Variant) ([]loginSession, error) { sessionList := make([]loginSession, len(props)) for iter, session := range props { if len(session) < 5 { @@ -178,11 +191,13 @@ func listSessions(conn *dbus.Conn) ([]loginSession, error) { if !ok { return nil, fmt.Errorf("failed to cast session path to ObjectPath") } - newSession := loginSession{ID: id, + newSession := loginSession{ + ID: id, UID: uid, User: user, Seat: seat, - Path: path} + Path: path, + } sessionList[iter] = newSession } diff --git a/metricbeat/module/system/users/users_test.go b/metricbeat/module/system/users/users_test.go index d87c3622fc7..32ab7505113 100644 --- a/metricbeat/module/system/users/users_test.go +++ b/metricbeat/module/system/users/users_test.go @@ -20,36 +20,55 @@ package users import ( "testing" + "github.com/godbus/dbus" "github.com/stretchr/testify/assert" - - mbtest "github.com/elastic/beats/metricbeat/mb/testing" ) -func TestFetch(t *testing.T) { - f := mbtest.NewReportingMetricSetV2Error(t, getConfig()) - events, errs := mbtest.ReportingFetchV2Error(f) +func TestFormatSession(t *testing.T) { - if !assert.Empty(t, errs) { - t.FailNow() + testIn := map[string]dbus.Variant{ + "Remote": dbus.MakeVariant(true), + "RemoteHost": dbus.MakeVariant("192.168.1.1"), + "Name": dbus.MakeVariant("user"), + "Scope": dbus.MakeVariant("user-6.scope"), + "Service": dbus.MakeVariant("sshd.service"), + "State": dbus.MakeVariant("active"), + "Type": dbus.MakeVariant("remote"), + "Leader": dbus.MakeVariant(uint32(17459)), } - if !assert.NotEmpty(t, events) { - t.FailNow() + + goodOut := sessionInfo{ + Remote: true, + RemoteHost: "192.168.1.1", + Name: "user", + Scope: "user-6.scope", + Service: "sshd.service", + State: "active", + Type: "remote", + Leader: 17459, } - t.Logf("%s/%s event: %+v", f.Module().Name(), f.Name(), - events[0].BeatEvent("system", "users").Fields.StringToPrint()) + + output, err := formatSessonProps(testIn) + assert.NoError(t, err) + assert.Equal(t, goodOut, output) } -func TestData(t *testing.T) { - f := mbtest.NewReportingMetricSetV2Error(t, getConfig()) - err := mbtest.WriteEventsReporterV2Error(f, t, ".") - if err != nil { - t.Fatal("write", err) +func TestFormatSessionList(t *testing.T) { + testIn := [][]dbus.Variant{ + {dbus.MakeVariant("6"), dbus.MakeVariant(uint32(1000)), dbus.MakeVariant("user"), dbus.MakeVariant(""), dbus.MakeVariant(dbus.ObjectPath("/path/to/object"))}, } -} -func getConfig() map[string]interface{} { - return map[string]interface{}{ - "module": "system", - "metricsets": []string{"users"}, + goodOut := []loginSession{{ + ID: uint64(6), + UID: uint32(1000), + User: "user", + Seat: "", + Path: dbus.ObjectPath("/path/to/object"), + }, } + + output, err := formatSessionList(testIn) + assert.NoError(t, err) + assert.Equal(t, goodOut, output) + } From 9ea220201aa0cf496cb2d906e55b9321fe996a3d Mon Sep 17 00:00:00 2001 From: Alex Kristiansen Date: Wed, 26 Feb 2020 07:45:15 -0800 Subject: [PATCH 08/15] fix cross-compile issues --- metricbeat/module/system/users/dbus.go | 2 ++ metricbeat/module/system/users/doc.go | 18 ++++++++++++++++++ metricbeat/module/system/users/users.go | 2 ++ metricbeat/module/system/users/users_test.go | 2 ++ 4 files changed, 24 insertions(+) create mode 100644 metricbeat/module/system/users/doc.go diff --git a/metricbeat/module/system/users/dbus.go b/metricbeat/module/system/users/dbus.go index 21f8a1f50d3..a452f65ef15 100644 --- a/metricbeat/module/system/users/dbus.go +++ b/metricbeat/module/system/users/dbus.go @@ -15,6 +15,8 @@ // specific language governing permissions and limitations // under the License. +//+build !netbsd + package users import ( diff --git a/metricbeat/module/system/users/doc.go b/metricbeat/module/system/users/doc.go new file mode 100644 index 00000000000..41a0d978be4 --- /dev/null +++ b/metricbeat/module/system/users/doc.go @@ -0,0 +1,18 @@ +// 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 users diff --git a/metricbeat/module/system/users/users.go b/metricbeat/module/system/users/users.go index a08a9c360b3..a742d42469a 100644 --- a/metricbeat/module/system/users/users.go +++ b/metricbeat/module/system/users/users.go @@ -15,6 +15,8 @@ // specific language governing permissions and limitations // under the License. +//+build !netbsd + package users import ( diff --git a/metricbeat/module/system/users/users_test.go b/metricbeat/module/system/users/users_test.go index 32ab7505113..3e48db3d894 100644 --- a/metricbeat/module/system/users/users_test.go +++ b/metricbeat/module/system/users/users_test.go @@ -15,6 +15,8 @@ // specific language governing permissions and limitations // under the License. +//+build !netbsd + package users import ( From 133500b4d4c5ff1e646c37f4123c6dafd6a3b30e Mon Sep 17 00:00:00 2001 From: Alex Kristiansen Date: Mon, 2 Mar 2020 13:32:14 -0800 Subject: [PATCH 09/15] update docs --- metricbeat/module/system/users/_meta/docs.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metricbeat/module/system/users/_meta/docs.asciidoc b/metricbeat/module/system/users/_meta/docs.asciidoc index c1b0720c0b8..1f149614ed2 100644 --- a/metricbeat/module/system/users/_meta/docs.asciidoc +++ b/metricbeat/module/system/users/_meta/docs.asciidoc @@ -1,4 +1,4 @@ -The system/users metricset reports logged in users and associated sessions via dbus and logind. +The system/users metricset reports logged in users and associated sessions via dbus and logind, which is a system component. By default, the metricset will look in `/var/run/dbus/` for a system socket, although a new path can be selected with `DBUS_SYSTEM_BUS_ADDRESS`. This metricset is available on: From 8d9d42e75f37db0d1bcb3718ded25c4877e7b4fd Mon Sep 17 00:00:00 2001 From: Alex Kristiansen Date: Tue, 3 Mar 2020 10:09:36 -0800 Subject: [PATCH 10/15] code quality cleanup --- metricbeat/module/system/users/_meta/docs.asciidoc | 2 +- metricbeat/module/system/users/dbus.go | 6 +++--- metricbeat/module/system/users/users.go | 8 +------- metricbeat/module/system/users/users_test.go | 4 ++-- 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/metricbeat/module/system/users/_meta/docs.asciidoc b/metricbeat/module/system/users/_meta/docs.asciidoc index 1f149614ed2..596fae667a7 100644 --- a/metricbeat/module/system/users/_meta/docs.asciidoc +++ b/metricbeat/module/system/users/_meta/docs.asciidoc @@ -1,4 +1,4 @@ -The system/users metricset reports logged in users and associated sessions via dbus and logind, which is a system component. By default, the metricset will look in `/var/run/dbus/` for a system socket, although a new path can be selected with `DBUS_SYSTEM_BUS_ADDRESS`. +The system/users metricset reports logged in users and associated sessions via dbus and logind, which is a systemd component. By default, the metricset will look in `/var/run/dbus/` for a system socket, although a new path can be selected with `DBUS_SYSTEM_BUS_ADDRESS`. This metricset is available on: diff --git a/metricbeat/module/system/users/dbus.go b/metricbeat/module/system/users/dbus.go index a452f65ef15..03dbc9fc3a7 100644 --- a/metricbeat/module/system/users/dbus.go +++ b/metricbeat/module/system/users/dbus.go @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -//+build !netbsd +//+build linux package users @@ -88,10 +88,10 @@ func getSessionProps(conn *dbus.Conn, path dbus.ObjectPath) (sessionInfo, error) return sessionInfo{}, errors.Wrap(err, "error calling DBus") } - return formatSessonProps(props) + return formatSessionProps(props) } -func formatSessonProps(props map[string]dbus.Variant) (sessionInfo, error) { +func formatSessionProps(props map[string]dbus.Variant) (sessionInfo, error) { if len(props) < 8 { return sessionInfo{}, fmt.Errorf("wrong number of fields in info: %v", props) } diff --git a/metricbeat/module/system/users/users.go b/metricbeat/module/system/users/users.go index a742d42469a..ca7f2b1d8a8 100644 --- a/metricbeat/module/system/users/users.go +++ b/metricbeat/module/system/users/users.go @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -//+build !netbsd +//+build linux package users @@ -53,11 +53,6 @@ type MetricSet struct { func New(base mb.BaseMetricSet) (mb.MetricSet, error) { cfgwarn.Beta("The system users metricset is beta.") - config := struct{}{} - if err := base.Module().UnpackConfig(&config); err != nil { - return nil, err - } - conn, err := initDbusConnection() if err != nil { return nil, errors.Wrap(err, "error connecting to dbus") @@ -74,7 +69,6 @@ func New(base mb.BaseMetricSet) (mb.MetricSet, error) { // format. It publishes the event which is then forwarded to the output. In case // of an error set the Error field of mb.Event or simply call report.Error(). func (m *MetricSet) Fetch(report mb.ReporterV2) error { - sessions, err := listSessions(m.conn) if err != nil { return errors.Wrap(err, "error listing sessions") diff --git a/metricbeat/module/system/users/users_test.go b/metricbeat/module/system/users/users_test.go index 3e48db3d894..50de07a6eee 100644 --- a/metricbeat/module/system/users/users_test.go +++ b/metricbeat/module/system/users/users_test.go @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -//+build !netbsd +//+build linux package users @@ -50,7 +50,7 @@ func TestFormatSession(t *testing.T) { Leader: 17459, } - output, err := formatSessonProps(testIn) + output, err := formatSessionProps(testIn) assert.NoError(t, err) assert.Equal(t, goodOut, output) } From ed1aa4c3a103c3074ba6b17c941d4c993be66298 Mon Sep 17 00:00:00 2001 From: Alex Kristiansen Date: Tue, 3 Mar 2020 10:21:43 -0800 Subject: [PATCH 11/15] make update --- metricbeat/include/list_common.go | 1 + 1 file changed, 1 insertion(+) diff --git a/metricbeat/include/list_common.go b/metricbeat/include/list_common.go index a44f3bdb7c9..424d36f66a4 100644 --- a/metricbeat/include/list_common.go +++ b/metricbeat/include/list_common.go @@ -154,6 +154,7 @@ import ( _ "github.com/elastic/beats/v7/metricbeat/module/system/socket" _ "github.com/elastic/beats/v7/metricbeat/module/system/socket_summary" _ "github.com/elastic/beats/v7/metricbeat/module/system/uptime" + _ "github.com/elastic/beats/v7/metricbeat/module/system/users" _ "github.com/elastic/beats/v7/metricbeat/module/traefik" _ "github.com/elastic/beats/v7/metricbeat/module/traefik/health" _ "github.com/elastic/beats/v7/metricbeat/module/uwsgi" From c2720e60c504811552d32a369885c72b7b653d8b Mon Sep 17 00:00:00 2001 From: Alex Kristiansen Date: Wed, 4 Mar 2020 06:44:39 -0800 Subject: [PATCH 12/15] fix deps, update vendor --- go.mod | 1 + metricbeat/module/system/users/users.go | 6 +- vendor/github.com/godbus/dbus/.travis.yml | 46 + vendor/github.com/godbus/dbus/CONTRIBUTING.md | 50 ++ vendor/github.com/godbus/dbus/LICENSE | 25 + vendor/github.com/godbus/dbus/MAINTAINERS | 3 + vendor/github.com/godbus/dbus/README.markdown | 44 + vendor/github.com/godbus/dbus/auth.go | 252 ++++++ .../github.com/godbus/dbus/auth_anonymous.go | 16 + .../github.com/godbus/dbus/auth_external.go | 26 + vendor/github.com/godbus/dbus/auth_sha1.go | 102 +++ vendor/github.com/godbus/dbus/call.go | 60 ++ vendor/github.com/godbus/dbus/conn.go | 847 ++++++++++++++++++ vendor/github.com/godbus/dbus/conn_darwin.go | 37 + vendor/github.com/godbus/dbus/conn_other.go | 93 ++ vendor/github.com/godbus/dbus/conn_unix.go | 18 + vendor/github.com/godbus/dbus/conn_windows.go | 15 + vendor/github.com/godbus/dbus/dbus.go | 427 +++++++++ vendor/github.com/godbus/dbus/decoder.go | 286 ++++++ .../github.com/godbus/dbus/default_handler.go | 321 +++++++ vendor/github.com/godbus/dbus/doc.go | 69 ++ vendor/github.com/godbus/dbus/encoder.go | 210 +++++ vendor/github.com/godbus/dbus/export.go | 412 +++++++++ vendor/github.com/godbus/dbus/go.mod | 3 + vendor/github.com/godbus/dbus/homedir.go | 28 + .../github.com/godbus/dbus/homedir_dynamic.go | 15 + .../github.com/godbus/dbus/homedir_static.go | 45 + vendor/github.com/godbus/dbus/message.go | 353 ++++++++ vendor/github.com/godbus/dbus/object.go | 234 +++++ .../godbus/dbus/server_interfaces.go | 99 ++ vendor/github.com/godbus/dbus/sig.go | 259 ++++++ .../godbus/dbus/transport_darwin.go | 6 + .../godbus/dbus/transport_generic.go | 50 ++ .../godbus/dbus/transport_nonce_tcp.go | 39 + .../github.com/godbus/dbus/transport_tcp.go | 41 + .../github.com/godbus/dbus/transport_unix.go | 214 +++++ .../dbus/transport_unixcred_dragonfly.go | 95 ++ .../godbus/dbus/transport_unixcred_freebsd.go | 91 ++ .../godbus/dbus/transport_unixcred_linux.go | 25 + .../godbus/dbus/transport_unixcred_openbsd.go | 14 + vendor/github.com/godbus/dbus/variant.go | 144 +++ .../github.com/godbus/dbus/variant_lexer.go | 284 ++++++ .../github.com/godbus/dbus/variant_parser.go | 817 +++++++++++++++++ vendor/modules.txt | 2 + 44 files changed, 6221 insertions(+), 3 deletions(-) create mode 100644 vendor/github.com/godbus/dbus/.travis.yml create mode 100644 vendor/github.com/godbus/dbus/CONTRIBUTING.md create mode 100644 vendor/github.com/godbus/dbus/LICENSE create mode 100644 vendor/github.com/godbus/dbus/MAINTAINERS create mode 100644 vendor/github.com/godbus/dbus/README.markdown create mode 100644 vendor/github.com/godbus/dbus/auth.go create mode 100644 vendor/github.com/godbus/dbus/auth_anonymous.go create mode 100644 vendor/github.com/godbus/dbus/auth_external.go create mode 100644 vendor/github.com/godbus/dbus/auth_sha1.go create mode 100644 vendor/github.com/godbus/dbus/call.go create mode 100644 vendor/github.com/godbus/dbus/conn.go create mode 100644 vendor/github.com/godbus/dbus/conn_darwin.go create mode 100644 vendor/github.com/godbus/dbus/conn_other.go create mode 100644 vendor/github.com/godbus/dbus/conn_unix.go create mode 100644 vendor/github.com/godbus/dbus/conn_windows.go create mode 100644 vendor/github.com/godbus/dbus/dbus.go create mode 100644 vendor/github.com/godbus/dbus/decoder.go create mode 100644 vendor/github.com/godbus/dbus/default_handler.go create mode 100644 vendor/github.com/godbus/dbus/doc.go create mode 100644 vendor/github.com/godbus/dbus/encoder.go create mode 100644 vendor/github.com/godbus/dbus/export.go create mode 100644 vendor/github.com/godbus/dbus/go.mod create mode 100644 vendor/github.com/godbus/dbus/homedir.go create mode 100644 vendor/github.com/godbus/dbus/homedir_dynamic.go create mode 100644 vendor/github.com/godbus/dbus/homedir_static.go create mode 100644 vendor/github.com/godbus/dbus/message.go create mode 100644 vendor/github.com/godbus/dbus/object.go create mode 100644 vendor/github.com/godbus/dbus/server_interfaces.go create mode 100644 vendor/github.com/godbus/dbus/sig.go create mode 100644 vendor/github.com/godbus/dbus/transport_darwin.go create mode 100644 vendor/github.com/godbus/dbus/transport_generic.go create mode 100644 vendor/github.com/godbus/dbus/transport_nonce_tcp.go create mode 100644 vendor/github.com/godbus/dbus/transport_tcp.go create mode 100644 vendor/github.com/godbus/dbus/transport_unix.go create mode 100644 vendor/github.com/godbus/dbus/transport_unixcred_dragonfly.go create mode 100644 vendor/github.com/godbus/dbus/transport_unixcred_freebsd.go create mode 100644 vendor/github.com/godbus/dbus/transport_unixcred_linux.go create mode 100644 vendor/github.com/godbus/dbus/transport_unixcred_openbsd.go create mode 100644 vendor/github.com/godbus/dbus/variant.go create mode 100644 vendor/github.com/godbus/dbus/variant_lexer.go create mode 100644 vendor/github.com/godbus/dbus/variant_parser.go diff --git a/go.mod b/go.mod index de1bb8b07f9..8f09a03cf39 100644 --- a/go.mod +++ b/go.mod @@ -74,6 +74,7 @@ require ( github.com/go-sourcemap/sourcemap v2.1.2+incompatible // indirect github.com/go-sql-driver/mysql v1.4.1 github.com/gocarina/gocsv v0.0.0-20170324095351-ffef3ffc77be + github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e github.com/godror/godror v0.10.4 github.com/gofrs/flock v0.7.2-0.20190320160742-5135e617513b github.com/gofrs/uuid v3.2.0+incompatible diff --git a/metricbeat/module/system/users/users.go b/metricbeat/module/system/users/users.go index ca7f2b1d8a8..ff6ad38fa70 100644 --- a/metricbeat/module/system/users/users.go +++ b/metricbeat/module/system/users/users.go @@ -25,9 +25,9 @@ import ( "github.com/godbus/dbus" "github.com/pkg/errors" - "github.com/elastic/beats/libbeat/common" - "github.com/elastic/beats/libbeat/common/cfgwarn" - "github.com/elastic/beats/metricbeat/mb" + "github.com/elastic/beats/v7/libbeat/common" + "github.com/elastic/beats/v7/libbeat/common/cfgwarn" + "github.com/elastic/beats/v7/metricbeat/mb" ) // init registers the MetricSet with the central registry as soon as the program diff --git a/vendor/github.com/godbus/dbus/.travis.yml b/vendor/github.com/godbus/dbus/.travis.yml new file mode 100644 index 00000000000..9cd57f432b0 --- /dev/null +++ b/vendor/github.com/godbus/dbus/.travis.yml @@ -0,0 +1,46 @@ +dist: precise +language: go +go_import_path: github.com/godbus/dbus +sudo: true + +go: + - 1.7.3 + - 1.8.7 + - 1.9.5 + - 1.10.1 + - tip + +env: + global: + matrix: + - TARGET=amd64 + - TARGET=arm64 + - TARGET=arm + - TARGET=386 + - TARGET=ppc64le + +matrix: + fast_finish: true + allow_failures: + - go: tip + exclude: + - go: tip + env: TARGET=arm + - go: tip + env: TARGET=arm64 + - go: tip + env: TARGET=386 + - go: tip + env: TARGET=ppc64le + +addons: + apt: + packages: + - dbus + - dbus-x11 + +before_install: + +script: + - go test -v -race ./... # Run all the tests with the race detector enabled + - go vet ./... # go vet is the official Go static analyzer diff --git a/vendor/github.com/godbus/dbus/CONTRIBUTING.md b/vendor/github.com/godbus/dbus/CONTRIBUTING.md new file mode 100644 index 00000000000..c88f9b2bdd0 --- /dev/null +++ b/vendor/github.com/godbus/dbus/CONTRIBUTING.md @@ -0,0 +1,50 @@ +# How to Contribute + +## Getting Started + +- Fork the repository on GitHub +- Read the [README](README.markdown) for build and test instructions +- Play with the project, submit bugs, submit patches! + +## Contribution Flow + +This is a rough outline of what a contributor's workflow looks like: + +- Create a topic branch from where you want to base your work (usually master). +- Make commits of logical units. +- Make sure your commit messages are in the proper format (see below). +- Push your changes to a topic branch in your fork of the repository. +- Make sure the tests pass, and add any new tests as appropriate. +- Submit a pull request to the original repository. + +Thanks for your contributions! + +### Format of the Commit Message + +We follow a rough convention for commit messages that is designed to answer two +questions: what changed and why. The subject line should feature the what and +the body of the commit should describe the why. + +``` +scripts: add the test-cluster command + +this uses tmux to setup a test cluster that you can easily kill and +start for debugging. + +Fixes #38 +``` + +The format can be described more formally as follows: + +``` +: + + + +