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

Add querier to knapsack; expand osquery checkup #1477

Closed
Closed
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
10 changes: 8 additions & 2 deletions cmd/launcher/extension.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,18 @@ import (
// going to be pretty extensive work.
type actorQuerier struct {
actor.Actor
querier func(query string) ([]map[string]string, error)
querier func(query string) ([]map[string]string, error)
healthchecker func() error
}

func (aq actorQuerier) Query(query string) ([]map[string]string, error) {
return aq.querier(query)
}

func (aq actorQuerier) Healthy() error {
return aq.healthchecker()
}

// TODO: the extension, runtime, and client are all kind of entangled
// here. Untangle the underlying libraries and separate into units
func createExtensionRuntime(ctx context.Context, k types.Knapsack, launcherClient service.KolideService) (
Expand Down Expand Up @@ -169,7 +174,8 @@ func createExtensionRuntime(ctx context.Context, k types.Knapsack, launcherClien
}
},
},
querier: runner.Query,
querier: runner.Query,
healthchecker: runner.Healthy,
},
restartFunc,
runner.Shutdown,
Expand Down
5 changes: 2 additions & 3 deletions cmd/launcher/launcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ func runLauncher(ctx context.Context, cancel func(), slogger, systemSlogger *mul
return fmt.Errorf("create extension with runtime: %w", err)
}
runGroup.Add("osqueryExtension", extension.Execute, extension.Interrupt)
k.SetQuerier(extension)

versionInfo := version.Version()
k.SystemSlogger().Info("started kolide launcher",
Expand Down Expand Up @@ -346,7 +347,7 @@ func runLauncher(ctx context.Context, cancel func(), slogger, systemSlogger *mul
return fmt.Errorf("failed to register auth token consumer: %w", err)
}

if exp, err := exporter.NewTraceExporter(ctx, k, extension, logger); err != nil {
if exp, err := exporter.NewTraceExporter(ctx, k, logger); err != nil {
level.Debug(logger).Log(
"msg", "could not set up trace exporter",
"err", err,
Expand Down Expand Up @@ -391,7 +392,6 @@ func runLauncher(ctx context.Context, cancel func(), slogger, systemSlogger *mul
level.Error(logger).Log("msg", "Failed to setup localserver", "error", err)
}

ls.SetQuerier(extension)
runGroup.Add("localserver", ls.Start, ls.Interrupt)
}

Expand All @@ -406,7 +406,6 @@ func runLauncher(ctx context.Context, cancel func(), slogger, systemSlogger *mul
k,
metadataClient,
mirrorClient,
extension,
tuf.WithLogger(logger),
tuf.WithOsqueryRestart(runnerRestart),
)
Expand Down
3 changes: 2 additions & 1 deletion ee/debug/checkups/checkpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/go-kit/kit/log"
storageci "github.com/kolide/launcher/pkg/agent/storage/ci"
typesmocks "github.com/kolide/launcher/pkg/agent/types/mocks"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)

Expand All @@ -28,7 +29,7 @@ func TestInterrupt_Multiple(t *testing.T) {
mockKnapsack.On("RootDirectory").Return("").Maybe()
mockKnapsack.On("Autoupdate").Return(true).Maybe()
mockKnapsack.On("NotaryServerURL").Return("localhost").Maybe()
mockKnapsack.On("LatestOsquerydPath").Return("").Maybe()
mockKnapsack.On("LatestOsquerydPath", mock.Anything).Return("").Maybe()
mockKnapsack.On("ServerProvidedDataStore").Return(nil).Maybe()
checkupLogger := NewCheckupLogger(log.NewNopLogger(), mockKnapsack)
mockKnapsack.AssertExpectations(t)
Expand Down
2 changes: 1 addition & 1 deletion ee/debug/checkups/checkups.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ func checkupsFor(k types.Knapsack, target targetBits) []checkupInt {
{&installCheckup{}, flareSupported},
{&servicesCheckup{}, doctorSupported | flareSupported},
{&powerCheckup{}, flareSupported},
{&osqueryCheckup{k: k}, doctorSupported | flareSupported},
{&osqueryCheckup{k: k}, doctorSupported | flareSupported | logSupported},
{&launcherFlags{}, doctorSupported | flareSupported},
{&gnomeExtensions{}, doctorSupported | flareSupported},
{&quarantine{}, doctorSupported | flareSupported},
Expand Down
76 changes: 44 additions & 32 deletions ee/debug/checkups/osquery.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,28 @@ import (
"fmt"
"io"
"os/exec"
"runtime"
"strings"
"time"

"github.com/kolide/launcher/pkg/agent/types"
"github.com/kolide/launcher/pkg/osquery/runtime/history"
)

type osqueryCheckup struct {
k types.Knapsack
status Status
executionTimes map[string]any // maps command to how long it took to run, in ms
summary string
k types.Knapsack
status Status
data map[string]any
summary string
}

func (o *osqueryCheckup) Name() string {
return "Osquery"
}

func (o *osqueryCheckup) Run(ctx context.Context, extraWriter io.Writer) error {
o.data = make(map[string]any)

// Determine passing status by running osqueryd --version
o.executionTimes = make(map[string]any)
if osqueryVersion, err := o.version(ctx); err != nil {
o.status = Failing
return fmt.Errorf("running osqueryd version: %w", err)
Expand All @@ -34,16 +35,18 @@ func (o *osqueryCheckup) Run(ctx context.Context, extraWriter io.Writer) error {
o.summary = osqueryVersion
}

// Run launcher interactive to capture timing details
if err := o.interactive(ctx); err != nil {
return fmt.Errorf("running launcher interactive: %w", err)
}
// Retrieve osquery instance history to see if we have an abnormal number of restarts
o.instanceHistory()

// Check to see if current extension is healthy
o.extensionHealth()

return nil
}

func (o *osqueryCheckup) version(ctx context.Context) (string, error) {
osquerydPath := o.k.LatestOsquerydPath(ctx)
o.data["osqueryd_path"] = osquerydPath

cmdCtx, cmdCancel := context.WithTimeout(ctx, 10*time.Second)
defer cmdCancel()
Expand All @@ -52,39 +55,48 @@ func (o *osqueryCheckup) version(ctx context.Context) (string, error) {
hideWindow(cmd)
startTime := time.Now().UnixMilli()
out, err := cmd.CombinedOutput()
o.executionTimes[cmd.String()] = fmt.Sprintf("%d ms", time.Now().UnixMilli()-startTime)
o.data["execution_time_osq_version"] = fmt.Sprintf("%d ms", time.Now().UnixMilli()-startTime)
if err != nil {
return "", fmt.Errorf("running %s version: err %w, output %s", osquerydPath, err, string(out))
}

return strings.TrimSpace(string(out)), nil
osqVersion := strings.TrimSpace(string(out))
o.data["osqueryd_version"] = osqVersion

return osqVersion, nil
}

func (o *osqueryCheckup) interactive(ctx context.Context) error {
var launcherPath string
switch runtime.GOOS {
case "linux", "darwin":
launcherPath = "/usr/local/kolide-k2/bin/launcher"
case "windows":
launcherPath = `C:\Program Files\Kolide\Launcher-kolide-k2\bin\launcher.exe`
func (o *osqueryCheckup) instanceHistory() {
mostRecentInstances, err := history.GetHistory()
if err != nil {
o.data["osquery_instance_history"] = fmt.Errorf("could not get instance history: %+v", err)
return
}

cmdCtx, cmdCancel := context.WithTimeout(ctx, 20*time.Second)
defer cmdCancel()
mostRecentInstancesFormatted := make([]map[string]string, len(mostRecentInstances))
for i, instance := range mostRecentInstances {
mostRecentInstancesFormatted[i] = map[string]string{
"start_time": instance.StartTime,
"connect_time": instance.ConnectTime,
"exit_time": instance.ExitTime,
"hostname": instance.Hostname,
"instance_id": instance.InstanceId,
"version": instance.Version,
"error": instance.Error,
}

// We trust the autoupdate library to find the correct path
cmd := exec.CommandContext(cmdCtx, launcherPath, "interactive") //nolint:forbidigo // We trust the autoupdate library to find the correct path
hideWindow(cmd)
cmd.Stdin = strings.NewReader(`select * from osquery_info;`)
}

startTime := time.Now().UnixMilli()
out, err := cmd.CombinedOutput()
o.executionTimes[cmd.String()] = fmt.Sprintf("%d ms", time.Now().UnixMilli()-startTime)
o.data["osquery_instance_history"] = mostRecentInstancesFormatted
}

func (o *osqueryCheckup) extensionHealth() {
err := o.k.QuerierHealthy()
if err != nil {
return fmt.Errorf("running %s interactive: err %w, output %s", launcherPath, err, string(out))
o.data["osquery_instance_healthcheck_err"] = err.Error()
} else {
o.data["osquery_instance_healthcheck_err"] = nil
}

return nil
}

func (o *osqueryCheckup) ExtraFileName() string {
Expand All @@ -100,5 +112,5 @@ func (o *osqueryCheckup) Summary() string {
}

func (o *osqueryCheckup) Data() any {
return o.executionTimes
return o.data
}
53 changes: 0 additions & 53 deletions ee/localserver/mocks/querier.go

This file was deleted.

6 changes: 1 addition & 5 deletions ee/localserver/request-id.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,7 @@ const (
)

func (ls *localServer) updateIdFields() error {
if ls.querier == nil {
return errors.New("no querier set")
}

results, err := ls.querier.Query(idSQL)
results, err := ls.knapsack.Query(idSQL)
if err != nil {
return fmt.Errorf("id query failed: %w", err)
}
Expand Down
6 changes: 3 additions & 3 deletions ee/localserver/request-query.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func (ls *localServer) requestQueryHanlderFunc(w http.ResponseWriter, r *http.Re
return
}

results, err := queryWithRetries(ls.querier, query)
results, err := queryWithRetries(ls.knapsack, query)
if err != nil {
sendClientError(w, span, fmt.Errorf("error executing query: %s", err))
return
Expand Down Expand Up @@ -92,7 +92,7 @@ func (ls *localServer) requestScheduledQueryHandlerFunc(w http.ResponseWriter, r

scheduledQueryQuery := fmt.Sprintf("select name, query from osquery_schedule where name like '%s'", name)

scheduledQueriesQueryResults, err := queryWithRetries(ls.querier, scheduledQueryQuery)
scheduledQueriesQueryResults, err := queryWithRetries(ls.knapsack, scheduledQueryQuery)
if err != nil {
sendClientError(w, span, fmt.Errorf("error executing query for scheduled queries using \"%s\": %s", scheduledQueryQuery, err))
return
Expand All @@ -105,7 +105,7 @@ func (ls *localServer) requestScheduledQueryHandlerFunc(w http.ResponseWriter, r
QueryName: scheduledQuery["name"],
}

scheduledQueryResult, err := queryWithRetries(ls.querier, scheduledQuery["query"])
scheduledQueryResult, err := queryWithRetries(ls.knapsack, scheduledQuery["query"])
if err != nil {
ls.slogger.Log(r.Context(), slog.LevelError,
"running scheduled query on demand",
Expand Down
16 changes: 3 additions & 13 deletions ee/localserver/request-query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"testing"

"github.com/go-kit/kit/log"
"github.com/kolide/launcher/ee/localserver/mocks"
"github.com/kolide/launcher/pkg/agent/storage"
storageci "github.com/kolide/launcher/pkg/agent/storage/ci"
typesMocks "github.com/kolide/launcher/pkg/agent/types/mocks"
Expand Down Expand Up @@ -60,16 +59,11 @@ func Test_localServer_requestQueryHandler(t *testing.T) {
mockKnapsack.On("KolideServerURL").Return("localhost")
mockKnapsack.On("Slogger").Return(multislogger.New().Logger)

//go:generate mockery --name Querier
// https://github.com/vektra/mockery <-- cli tool to generate mocks for usage with testify
mockQuerier := mocks.NewQuerier(t)

if tt.mockQueryResult != nil {
mockQuerier.On("Query", tt.query).Return(tt.mockQueryResult, nil).Once()
mockKnapsack.On("Query", tt.query).Return(tt.mockQueryResult, nil).Once()
}

server := testServer(t, mockKnapsack)
server.querier = mockQuerier

jsonBytes, err := json.Marshal(map[string]string{
"query": tt.query,
Expand Down Expand Up @@ -224,22 +218,18 @@ func Test_localServer_requestRunScheduledQueryHandler(t *testing.T) {
mockKnapsack.On("ConfigStore").Return(storageci.NewStore(t, log.NewNopLogger(), storage.ConfigStore.String()))
mockKnapsack.On("KolideServerURL").Return("localhost")
mockKnapsack.On("Slogger").Return(multislogger.New().Logger)

// set up mock querier
mockQuerier := mocks.NewQuerier(t)
scheduledQueryQuery := fmt.Sprintf("select name, query from osquery_schedule where name like '%s'", tt.scheduledQueriesQueryNamePattern)

// the query for the scheduled queries
mockQuerier.On("Query", scheduledQueryQuery).Return(tt.scheduledQueriesQueryResults, tt.scheduledQueriesQueryError)
mockKnapsack.On("Query", scheduledQueryQuery).Return(tt.scheduledQueriesQueryResults, tt.scheduledQueriesQueryError)

// the results of each scheduled query
for i, queryResult := range tt.queryReturns {
mockQuerier.On("Query", tt.scheduledQueriesQueryResults[i]["query"]).Return(queryResult.results, queryResult.err)
mockKnapsack.On("Query", tt.scheduledQueriesQueryResults[i]["query"]).Return(queryResult.results, queryResult.err)
}

// set up test server
server := testServer(t, mockKnapsack)
server.querier = mockQuerier

// make request body
body := make(map[string]string)
Expand Down
5 changes: 0 additions & 5 deletions ee/localserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ type localServer struct {
identifiers identifiers
limiter *rate.Limiter
tlsCerts []tls.Certificate
querier Querier
kolideServer string

myKey *rsa.PrivateKey
Expand Down Expand Up @@ -131,10 +130,6 @@ func New(k types.Knapsack) (*localServer, error) {
return ls, nil
}

func (ls *localServer) SetQuerier(querier Querier) {
ls.querier = querier
}

func (ls *localServer) LoadDefaultKeyIfNotSet() error {
if ls.serverKey != nil {
return nil
Expand Down
Loading
Loading