diff --git a/ee/allowedcmd/cmd_darwin.go b/ee/allowedcmd/cmd_darwin.go index a4ec3538a..459c43884 100644 --- a/ee/allowedcmd/cmd_darwin.go +++ b/ee/allowedcmd/cmd_darwin.go @@ -5,6 +5,7 @@ package allowedcmd import ( "context" + "errors" "os/exec" ) @@ -21,14 +22,18 @@ func Bputil(ctx context.Context, arg ...string) (*exec.Cmd, error) { } func Brew(ctx context.Context, arg ...string) (*exec.Cmd, error) { - cmd, err := validatedCommand(ctx, "/opt/homebrew/bin/brew", arg...) - if err != nil { - return nil, err - } + for _, p := range []string{"/opt/homebrew/bin/brew", "/usr/local/bin/brew"} { + validatedCmd, err := validatedCommand(ctx, p, arg...) + if err != nil { + continue + } + + validatedCmd.Env = append(validatedCmd.Environ(), "HOMEBREW_NO_AUTO_UPDATE=1") - cmd.Env = append(cmd.Environ(), "HOMEBREW_NO_AUTO_UPDATE=1") + return validatedCmd, nil + } - return cmd, nil + return nil, errors.New("homebrew not found") } func Diskutil(ctx context.Context, arg ...string) (*exec.Cmd, error) { diff --git a/ee/allowedcmd/cmd_linux.go b/ee/allowedcmd/cmd_linux.go index 21c02b1fb..ed511e812 100644 --- a/ee/allowedcmd/cmd_linux.go +++ b/ee/allowedcmd/cmd_linux.go @@ -14,14 +14,14 @@ func Apt(ctx context.Context, arg ...string) (*exec.Cmd, error) { } func Brew(ctx context.Context, arg ...string) (*exec.Cmd, error) { - cmd, err := validatedCommand(ctx, "/home/linuxbrew/.linuxbrew/bin/brew", arg...) + validatedCmd, err := validatedCommand(ctx, "/home/linuxbrew/.linuxbrew/bin/brew", arg...) if err != nil { return nil, err } - cmd.Env = append(cmd.Environ(), "HOMEBREW_NO_AUTO_UPDATE=1") + validatedCmd.Env = append(validatedCmd.Environ(), "HOMEBREW_NO_AUTO_UPDATE=1") - return cmd, nil + return validatedCmd, nil } func Cryptsetup(ctx context.Context, arg ...string) (*exec.Cmd, error) { diff --git a/ee/tables/homebrew/upgradeable.go b/ee/tables/homebrew/upgradeable.go index 06b6dc5e0..3b823d019 100644 --- a/ee/tables/homebrew/upgradeable.go +++ b/ee/tables/homebrew/upgradeable.go @@ -8,7 +8,10 @@ import ( "context" "fmt" "log/slog" + "os" + "strconv" "strings" + "syscall" "github.com/kolide/launcher/ee/allowedcmd" "github.com/kolide/launcher/ee/dataflatten" @@ -17,8 +20,6 @@ import ( "github.com/osquery/osquery-go/plugin/table" ) -const allowedCharacters = "0123456789" - type Table struct { slogger *slog.Logger } @@ -38,42 +39,55 @@ func TablePlugin(slogger *slog.Logger) *table.Plugin { 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") + // Brew is owned by a single user on a system. Brew is only intended to run with the context of + // that user. To reduce duplicating the WithUid table helper, we can find the owner of the binary, + // and pass the said owner to the WIthUid method to handle setting the appropriate env vars. + cmd, err := allowedcmd.Brew(ctx) + if err != nil { + return nil, fmt.Errorf("failure allocating allowedcmd.Brew: %w", err) + } + + info, err := os.Stat(cmd.Path) + if err != nil { + return nil, fmt.Errorf("failure getting FileInfo: %s. err: %w", cmd.Path, err) } - for _, uid := range uids { - for _, dataQuery := range tablehelpers.GetConstraints(queryContext, "query", tablehelpers.WithDefaults("*")) { - // Brew can take a while to load the first time the command is ran, so leaving 60 seconds for the timeout here. - var output bytes.Buffer - if err := tablehelpers.Run(ctx, t.slogger, 60, allowedcmd.Brew, []string{"outdated", "--json"}, &output, &output, tablehelpers.WithUid(uid)); err != nil { - t.slogger.Log(ctx, slog.LevelInfo, - "failure querying user brew installed packages", - "err", err, - "target_uid", uid, - "output", output.String(), - ) - continue - } - - flattenOpts := []dataflatten.FlattenOpts{ - dataflatten.WithSlogger(t.slogger), - dataflatten.WithQuery(strings.Split(dataQuery, "/")), - } - - flattened, err := dataflatten.Json(output.Bytes(), 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)...) + stat, ok := info.Sys().(*syscall.Stat_t) + if !ok { + return nil, fmt.Errorf("failure getting Sys data source: %s", cmd.Path) + } + + uid := strconv.FormatUint(uint64(stat.Uid), 10) + + for _, dataQuery := range tablehelpers.GetConstraints(queryContext, "query", tablehelpers.WithDefaults("*")) { + // Brew can take a while to load the first time the command is ran, so leaving 60 seconds for the timeout here. + var output bytes.Buffer + if err := tablehelpers.Run(ctx, t.slogger, 60, allowedcmd.Brew, []string{"outdated", "--json"}, &output, &output, tablehelpers.WithUid(uid)); err != nil { + t.slogger.Log(ctx, slog.LevelInfo, + "failure querying user brew installed packages", + "err", err, + "target_uid", uid, + "output", output.String(), + ) + continue + } + + flattenOpts := []dataflatten.FlattenOpts{ + dataflatten.WithSlogger(t.slogger), + dataflatten.WithQuery(strings.Split(dataQuery, "/")), + } + + flattened, err := dataflatten.Json(output.Bytes(), 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