From 136c75aa4bdf5bc96c57a8abc53417cb92c98824 Mon Sep 17 00:00:00 2001 From: Micah-Kolide <109157253+Micah-Kolide@users.noreply.github.com> Date: Fri, 1 Mar 2024 13:35:46 -0800 Subject: [PATCH] Add `kolide_brew_upgradeable` table (#1634) --- ee/allowedcmd/cmd_darwin.go | 4 + ee/allowedcmd/cmd_linux.go | 4 + ee/tables/homebrew/upgradeable.go | 146 ++++++++++++++++++++ pkg/osquery/table/platform_tables_darwin.go | 2 + pkg/osquery/table/platform_tables_linux.go | 2 + 5 files changed, 158 insertions(+) create mode 100644 ee/tables/homebrew/upgradeable.go diff --git a/ee/allowedcmd/cmd_darwin.go b/ee/allowedcmd/cmd_darwin.go index b1a65c734..b00cfd161 100644 --- a/ee/allowedcmd/cmd_darwin.go +++ b/ee/allowedcmd/cmd_darwin.go @@ -20,6 +20,10 @@ func Bputil(ctx context.Context, arg ...string) (*exec.Cmd, error) { return validatedCommand(ctx, "/usr/bin/bputil", arg...) } +func Brew(ctx context.Context, arg ...string) (*exec.Cmd, error) { + return validatedCommand(ctx, "/opt/homebrew/bin/brew", arg...) +} + func Diskutil(ctx context.Context, arg ...string) (*exec.Cmd, error) { return validatedCommand(ctx, "/usr/sbin/diskutil", arg...) } diff --git a/ee/allowedcmd/cmd_linux.go b/ee/allowedcmd/cmd_linux.go index f569d8a33..5519121b7 100644 --- a/ee/allowedcmd/cmd_linux.go +++ b/ee/allowedcmd/cmd_linux.go @@ -13,6 +13,10 @@ func Apt(ctx context.Context, arg ...string) (*exec.Cmd, error) { return validatedCommand(ctx, "/usr/bin/apt", arg...) } +func Brew(ctx context.Context, arg ...string) (*exec.Cmd, error) { + return validatedCommand(ctx, "/home/linuxbrew/.linuxbrew/bin/brew", arg...) +} + func Cryptsetup(ctx context.Context, arg ...string) (*exec.Cmd, error) { for _, p := range []string{"/usr/sbin/cryptsetup", "/sbin/cryptsetup"} { validatedCmd, err := validatedCommand(ctx, p, arg...) diff --git a/ee/tables/homebrew/upgradeable.go b/ee/tables/homebrew/upgradeable.go new file mode 100644 index 000000000..45689dbe9 --- /dev/null +++ b/ee/tables/homebrew/upgradeable.go @@ -0,0 +1,146 @@ +//go:build !windows +// +build !windows + +package brew_upgradeable + +import ( + "context" + "fmt" + "io" + "log/slog" + "os/exec" + "os/user" + "strconv" + "strings" + "syscall" + + "github.com/kolide/launcher/ee/allowedcmd" + "github.com/kolide/launcher/ee/dataflatten" + "github.com/kolide/launcher/ee/tables/dataflattentable" + "github.com/kolide/launcher/ee/tables/tablehelpers" + "github.com/osquery/osquery-go/plugin/table" +) + +const allowedCharacters = "0123456789" + +type Table struct { + slogger *slog.Logger + execCC allowedcmd.AllowedCommand +} + +func TablePlugin(slogger *slog.Logger) *table.Plugin { + columns := dataflattentable.Columns( + table.TextColumn("uid"), + ) + + t := &Table{ + slogger: slogger.With("table", "kolide_brew_upgradeable"), + execCC: allowedcmd.Brew, + } + + return table.NewPlugin("kolide_brew_upgradeable", columns, t.generate) +} + +func (t *Table) generate(ctx context.Context, queryContext table.QueryContext) ([]map[string]string, error) { + var results []map[string]string + + uids := tablehelpers.GetConstraints(queryContext, "uid", tablehelpers.WithAllowedCharacters(allowedCharacters)) + if len(uids) < 1 { + return results, fmt.Errorf("kolide_brew_upgradeable requires at least one user id to be specified") + } + + for _, uid := range uids { + for _, dataQuery := range tablehelpers.GetConstraints(queryContext, "query", tablehelpers.WithDefaults("*")) { + output, err := t.getBrewOutdated(ctx, uid) + if err != nil { + t.slogger.Log(ctx, slog.LevelInfo, "failure querying user brew installed packages", "err", err, "target_uid", uid) + continue + } + + flattenOpts := []dataflatten.FlattenOpts{ + dataflatten.WithSlogger(t.slogger), + dataflatten.WithQuery(strings.Split(dataQuery, "/")), + } + + flattened, err := dataflatten.Json(output, flattenOpts...) + if err != nil { + t.slogger.Log(ctx, slog.LevelInfo, "failure flattening output", "err", err) + continue + } + + rowData := map[string]string{ + "uid": uid, + } + + results = append(results, dataflattentable.ToMap(flattened, dataQuery, rowData)...) + } + } + + return results, nil +} + +func (t *Table) getBrewOutdated(ctx context.Context, uid string) ([]byte, error) { + cmd, err := t.execCC(ctx, "outdated", "--json") + if err != nil { + return nil, fmt.Errorf("creating brew outdated command: %w", err) + } + + stdout, err := cmd.StdoutPipe() + if err != nil { + return nil, fmt.Errorf("assigning StdoutPipe for brew outdated command: %w", err) + } + + if err := runAsUser(ctx, uid, cmd); err != nil { + return nil, fmt.Errorf("runAsUser brew outdated command as user %s: %w", uid, err) + } + + data, err := io.ReadAll(stdout) + if err != nil { + return nil, fmt.Errorf("ReadAll brew outdated stdout: %w", err) + } + + if err := cmd.Wait(); err != nil { + return nil, fmt.Errorf("deallocation of brew outdated command as user %s: %w", uid, err) + } + + return data, nil +} + +func runAsUser(ctx context.Context, uid string, cmd *exec.Cmd) error { + currentUser, err := user.Current() + if err != nil { + return fmt.Errorf("getting current user: %w", err) + } + + runningUser, err := user.LookupId(uid) + if err != nil { + return fmt.Errorf("looking up user with uid %s: %w", uid, err) + } + + if currentUser.Uid != "0" { + if currentUser.Uid != runningUser.Uid { + return fmt.Errorf("current user %s is not root and can't start process for other user %s", currentUser.Uid, uid) + } + + return cmd.Start() + } + + runningUserUid, err := strconv.ParseUint(runningUser.Uid, 10, 32) + if err != nil { + return fmt.Errorf("converting uid %s to int: %w", runningUser.Uid, err) + } + + runningUserGid, err := strconv.ParseUint(runningUser.Gid, 10, 32) + if err != nil { + return fmt.Errorf("converting gid %s to int: %w", runningUser.Gid, err) + } + + cmd.SysProcAttr = &syscall.SysProcAttr{ + Credential: &syscall.Credential{ + Uid: uint32(runningUserUid), + Gid: uint32(runningUserGid), + }, + } + + return cmd.Start() +} diff --git a/pkg/osquery/table/platform_tables_darwin.go b/pkg/osquery/table/platform_tables_darwin.go index 110187bfb..74eb84ba2 100644 --- a/pkg/osquery/table/platform_tables_darwin.go +++ b/pkg/osquery/table/platform_tables_darwin.go @@ -18,6 +18,7 @@ import ( "github.com/kolide/launcher/ee/tables/execparsers/softwareupdate" "github.com/kolide/launcher/ee/tables/filevault" "github.com/kolide/launcher/ee/tables/firmwarepasswd" + "github.com/kolide/launcher/ee/tables/homebrew" "github.com/kolide/launcher/ee/tables/ioreg" "github.com/kolide/launcher/ee/tables/macos_software_update" "github.com/kolide/launcher/ee/tables/mdmclient" @@ -81,6 +82,7 @@ func platformSpecificTables(slogger *slog.Logger, currentOsquerydBinaryPath stri keychainAclsTable, keychainItemsTable, appicons.AppIcons(), + brew_upgradeable.TablePlugin(slogger), ChromeLoginKeychainInfo(slogger), firmwarepasswd.TablePlugin(slogger), GDriveSyncConfig(slogger), diff --git a/pkg/osquery/table/platform_tables_linux.go b/pkg/osquery/table/platform_tables_linux.go index 569766adb..16b50180a 100644 --- a/pkg/osquery/table/platform_tables_linux.go +++ b/pkg/osquery/table/platform_tables_linux.go @@ -22,6 +22,7 @@ import ( "github.com/kolide/launcher/ee/tables/execparsers/simple_array" "github.com/kolide/launcher/ee/tables/fscrypt_info" "github.com/kolide/launcher/ee/tables/gsettings" + "github.com/kolide/launcher/ee/tables/homebrew" nix_env_upgradeable "github.com/kolide/launcher/ee/tables/nix_env/upgradeable" "github.com/kolide/launcher/ee/tables/secureboot" "github.com/kolide/launcher/ee/tables/xfconf" @@ -32,6 +33,7 @@ import ( func platformSpecificTables(slogger *slog.Logger, currentOsquerydBinaryPath string) []osquery.OsqueryPlugin { return []osquery.OsqueryPlugin{ + brew_upgradeable.TablePlugin(slogger), cryptsetup.TablePlugin(slogger), gsettings.Settings(slogger), gsettings.Metadata(slogger),