Skip to content

Commit

Permalink
Update Homebrew upgradeable table implementation (#1847)
Browse files Browse the repository at this point in the history
  • Loading branch information
Micah-Kolide authored Aug 29, 2024
1 parent fb784dc commit 4a2c595
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 44 deletions.
17 changes: 11 additions & 6 deletions ee/allowedcmd/cmd_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package allowedcmd

import (
"context"
"errors"
"os/exec"
)

Expand All @@ -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) {
Expand Down
6 changes: 3 additions & 3 deletions ee/allowedcmd/cmd_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
84 changes: 49 additions & 35 deletions ee/tables/homebrew/upgradeable.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -17,8 +20,6 @@ import (
"github.com/osquery/osquery-go/plugin/table"
)

const allowedCharacters = "0123456789"

type Table struct {
slogger *slog.Logger
}
Expand All @@ -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
Expand Down

0 comments on commit 4a2c595

Please sign in to comment.