From fa27bbf7f51ddd699468138c8b2a78ef94ab94e5 Mon Sep 17 00:00:00 2001 From: James Pickett Date: Thu, 29 Jun 2023 18:54:34 -0700 Subject: [PATCH] exposes table desktop process info in checkpoint and in kolide_desktop_procs table (#1240) * exposes table desktop process info in checkpoint and in kolide_desktop_procs table * bump scutil timeout for determining console users for desktop process --- ee/desktop/runner/runner.go | 49 ++++++++++++++----- ee/desktop/runner/runner_test.go | 4 +- pkg/log/checkpoint/checkpoint.go | 6 +++ pkg/osquery/table/table.go | 2 + .../tables/desktopprocs/desktopprocs.go | 36 ++++++++++++++ 5 files changed, 82 insertions(+), 15 deletions(-) create mode 100644 pkg/osquery/tables/desktopprocs/desktopprocs.go diff --git a/ee/desktop/runner/runner.go b/ee/desktop/runner/runner.go index a074f2a01..6cfbbb765 100644 --- a/ee/desktop/runner/runner.go +++ b/ee/desktop/runner/runner.go @@ -75,6 +75,23 @@ func WithUsersFilesRoot(usersFilesRoot string) desktopUsersProcessesRunnerOption } } +var instance *DesktopUsersProcessesRunner +var instanceSet = &sync.Once{} + +func setInstance(r *DesktopUsersProcessesRunner) { + instanceSet.Do(func() { + instance = r + }) +} + +func InstanceDesktopProcessRecords() map[string]processRecord { + if instance == nil { + return nil + } + + return instance.uidProcs +} + // DesktopUsersProcessesRunner creates a launcher desktop process each time it detects // a new console (GUI) user. If the current console user's desktop process dies, it // will create a new one. @@ -119,9 +136,10 @@ type DesktopUsersProcessesRunner struct { // If, for example, a user logs out, the process record will remain until the // user logs in again and it is replaced. type processRecord struct { - process *os.Process - path string - socketPath string + Process *os.Process + StartTime, LastHealthCheck time.Time + path string + socketPath string } // New creates and returns a new DesktopUsersProcessesRunner runner and initializes all required fields @@ -166,6 +184,7 @@ func New(k types.Knapsack, opts ...desktopUsersProcessesRunnerOption) (*DesktopU } }() + setInstance(runner) return runner, nil } @@ -239,7 +258,7 @@ func (r *DesktopUsersProcessesRunner) killDesktopProcesses() { level.Error(r.logger).Log( "msg", "error sending shutdown command to user desktop process", "uid", uid, - "pid", proc.process.Pid, + "pid", proc.Process.Pid, "path", proc.path, "err", err, ) @@ -265,11 +284,11 @@ func (r *DesktopUsersProcessesRunner) killDesktopProcesses() { if !r.processExists(processRecord) { continue } - if err := processRecord.process.Kill(); err != nil { + if err := processRecord.Process.Kill(); err != nil { level.Error(r.logger).Log( "msg", "error killing desktop process", "uid", uid, - "pid", processRecord.process.Pid, + "pid", processRecord.Process.Pid, "path", processRecord.path, "err", err, ) @@ -368,7 +387,7 @@ func (r *DesktopUsersProcessesRunner) refreshMenu() { level.Error(r.logger).Log( "msg", "error sending refresh command to desktop process", "uid", uid, - "pid", proc.process.Pid, + "pid", proc.Process.Pid, "path", proc.path, "err", err, ) @@ -445,7 +464,7 @@ func (r *DesktopUsersProcessesRunner) runConsoleUserDesktop() error { return fmt.Errorf("determining executable path: %w", err) } - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() consoleUsers, err := consoleuser.CurrentUids(ctx) @@ -527,7 +546,8 @@ func (r *DesktopUsersProcessesRunner) addProcessTrackingRecordForUser(uid string } r.uidProcs[uid] = processRecord{ - process: osProcess, + Process: osProcess, + StartTime: time.Now().UTC(), path: path, socketPath: socketPath, } @@ -583,7 +603,7 @@ func (r *DesktopUsersProcessesRunner) userHasDesktopProcess(uid string) bool { if !r.processExists(proc) { level.Info(r.logger).Log( "msg", "found existing desktop process dead for console user", - "pid", r.uidProcs[uid].process.Pid, + "pid", r.uidProcs[uid].Process.Pid, "process_path", r.uidProcs[uid].path, "uid", uid, ) @@ -591,6 +611,9 @@ func (r *DesktopUsersProcessesRunner) userHasDesktopProcess(uid string) bool { return false } + proc.LastHealthCheck = time.Now().UTC() + r.uidProcs[uid] = proc + // have running process return true } @@ -600,11 +623,11 @@ func (r *DesktopUsersProcessesRunner) processExists(processRecord processRecord) defer cancel() // the call to process.NewProcessWithContext ensures process exists - proc, err := process.NewProcessWithContext(ctx, int32(processRecord.process.Pid)) + proc, err := process.NewProcessWithContext(ctx, int32(processRecord.Process.Pid)) if err != nil { level.Info(r.logger).Log( "msg", "looking up existing desktop process", - "pid", processRecord.process.Pid, + "pid", processRecord.Process.Pid, "process_path", processRecord.path, "err", err, ) @@ -615,7 +638,7 @@ func (r *DesktopUsersProcessesRunner) processExists(processRecord processRecord) if err != nil || path != processRecord.path { level.Info(r.logger).Log( "msg", "error or path mismatch checking existing desktop process path", - "pid", processRecord.process.Pid, + "pid", processRecord.Process.Pid, "process_record_path", processRecord.path, "err", err, "found_path", path, diff --git a/ee/desktop/runner/runner_test.go b/ee/desktop/runner/runner_test.go index 3529aafe0..d68668736 100644 --- a/ee/desktop/runner/runner_test.go +++ b/ee/desktop/runner/runner_test.go @@ -83,7 +83,7 @@ func TestDesktopUserProcessRunner_Execute(t *testing.T) { user, err := user.Current() require.NoError(t, err) r.uidProcs[user.Uid] = processRecord{ - process: &os.Process{}, + Process: &os.Process{}, path: "test", } }, @@ -176,7 +176,7 @@ func TestDesktopUserProcessRunner_Execute(t *testing.T) { // intentionally ignoring the error here // CI will intermittently fail with "wait: no child processes" due runner.go also calling process.Wait() // racing with this code to remove the child process - p.process.Wait() + p.Process.Wait() } }) }) diff --git a/pkg/log/checkpoint/checkpoint.go b/pkg/log/checkpoint/checkpoint.go index d4be60ac9..ab6d37535 100644 --- a/pkg/log/checkpoint/checkpoint.go +++ b/pkg/log/checkpoint/checkpoint.go @@ -15,6 +15,7 @@ import ( "github.com/go-kit/kit/log" "github.com/kolide/kit/version" + "github.com/kolide/launcher/ee/desktop/runner" "github.com/kolide/launcher/pkg/agent" "github.com/kolide/launcher/pkg/agent/types" ) @@ -107,6 +108,11 @@ func (c *checkPointer) logCheckPoint() { c.logger.Log("notary versions", notaryVersions) } c.logServerProvidedData() + c.logDesktopProcs() +} + +func (c *checkPointer) logDesktopProcs() { + c.logger.Log("user_desktop_processes", runner.InstanceDesktopProcessRecords()) } func (c *checkPointer) logDbSize() { diff --git a/pkg/osquery/table/table.go b/pkg/osquery/table/table.go index 08433fc9d..db54ba79a 100644 --- a/pkg/osquery/table/table.go +++ b/pkg/osquery/table/table.go @@ -4,6 +4,7 @@ import ( "github.com/kolide/launcher/pkg/agent/types" "github.com/kolide/launcher/pkg/osquery/tables/cryptoinfotable" "github.com/kolide/launcher/pkg/osquery/tables/dataflattentable" + "github.com/kolide/launcher/pkg/osquery/tables/desktopprocs" "github.com/kolide/launcher/pkg/osquery/tables/dev_table_tooling" "github.com/kolide/launcher/pkg/osquery/tables/firefox_preferences" "github.com/kolide/launcher/pkg/osquery/tables/launcher_db" @@ -29,6 +30,7 @@ func LauncherTables(k types.Knapsack) []osquery.OsqueryPlugin { osquery_instance_history.TablePlugin(), tufinfo.TufReleaseVersionTable(k), launcher_db.TablePlugin("kolide_tuf_autoupdater_errors", k.AutoupdateErrorsStore()), + desktopprocs.TablePlugin(), } } diff --git a/pkg/osquery/tables/desktopprocs/desktopprocs.go b/pkg/osquery/tables/desktopprocs/desktopprocs.go new file mode 100644 index 000000000..f86b39fb2 --- /dev/null +++ b/pkg/osquery/tables/desktopprocs/desktopprocs.go @@ -0,0 +1,36 @@ +package desktopprocs + +import ( + "context" + "fmt" + + "github.com/kolide/launcher/ee/desktop/runner" + "github.com/osquery/osquery-go/plugin/table" +) + +func TablePlugin() *table.Plugin { + columns := []table.ColumnDefinition{ + table.TextColumn("uid"), + table.TextColumn("pid"), + table.TextColumn("start_time"), + table.TextColumn("last_health_check"), + } + return table.NewPlugin("kolide_desktop_procs", columns, generate()) +} + +func generate() table.GenerateFunc { + return func(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { + results := []map[string]string{} + + for k, v := range runner.InstanceDesktopProcessRecords() { + results = append(results, map[string]string{ + "uid": k, + "pid": fmt.Sprint(v.Process.Pid), + "start_time": fmt.Sprint(v.StartTime), + "last_health_check": fmt.Sprint(v.LastHealthCheck), + }) + } + + return results, nil + } +}