Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Homebrew upgradeable table implementation #1847

Merged
merged 3 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading