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

Make the osquery extension and runner separate rungroups #1596

Merged
merged 7 commits into from
Feb 14, 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
229 changes: 3 additions & 226 deletions cmd/launcher/extension.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,45 +6,16 @@ import (
"fmt"
"log/slog"
"os"
"path/filepath"

"github.com/go-kit/kit/log"
"github.com/kolide/kit/actor"
"github.com/kolide/launcher/cmd/launcher/internal"
"github.com/kolide/launcher/ee/agent/types"
kolidelog "github.com/kolide/launcher/ee/log/osquerylogs"
"github.com/kolide/launcher/pkg/augeas"
"github.com/kolide/launcher/pkg/contexts/ctxlog"
"github.com/kolide/launcher/pkg/osquery"
"github.com/kolide/launcher/pkg/osquery/runtime"
ktable "github.com/kolide/launcher/pkg/osquery/table"
"github.com/kolide/launcher/pkg/service"
"github.com/kolide/launcher/pkg/traces"
"github.com/osquery/osquery-go/plugin/config"
"github.com/osquery/osquery-go/plugin/distributed"
osquerylogger "github.com/osquery/osquery-go/plugin/logger"
)

// actorQuerier is a type wrapper over kolide/kit/actor. This should
// probably all be refactored into reasonable interfaces. But that's
// going to be pretty extensive work.
type actorQuerier struct {
actor.Actor
querier func(query string) ([]map[string]string, error)
}

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

// 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) (
run *actorQuerier,
restart func() error, // restart osqueryd runner
shutdown func() error, // shutdown osqueryd runner
err error,
) {
func createExtensionRuntime(ctx context.Context, k types.Knapsack, launcherClient service.KolideService) (*osquery.Extension, error) {
ctx, span := traces.StartSpan(ctx)
defer span.End()

Expand All @@ -58,7 +29,7 @@ func createExtensionRuntime(ctx context.Context, k types.Knapsack, launcherClien
} else if k.EnrollSecretPath() != "" {
content, err := os.ReadFile(k.EnrollSecretPath())
if err != nil {
return nil, nil, nil, fmt.Errorf("could not read enroll_secret_path: %s: %w", k.EnrollSecretPath(), err)
return nil, fmt.Errorf("could not read enroll_secret_path: %s: %w", k.EnrollSecretPath(), err)
}
enrollSecret = string(bytes.TrimSpace(content))
}
Expand Down Expand Up @@ -95,199 +66,5 @@ func createExtensionRuntime(ctx context.Context, k types.Knapsack, launcherClien
}

// create the extension
ext, err := osquery.NewExtension(ctx, launcherClient, k, extOpts)
if err != nil {
return nil, nil, nil, fmt.Errorf("starting grpc extension: %w", err)
}

var runnerOptions []runtime.OsqueryInstanceOption

if k.Transport() == "osquery" {
var err error
runnerOptions, err = osqueryRunnerOptions(logger, k)
if err != nil {
return nil, nil, nil, fmt.Errorf("creating osquery runner options: %w", err)
}
} else {
runnerOptions = grpcRunnerOptions(logger, k, ext)
}

runner := runtime.LaunchUnstartedInstance(runnerOptions...)

restartFunc := func() error {
slogger.Log(context.TODO(), slog.LevelDebug,
"osquery instance runner restart called",
)

return runner.Restart()
}

osqCtx, osqCancel := context.WithCancel(ctx)

return &actorQuerier{
Actor: actor.Actor{
// and the methods for starting and stopping the extension
Execute: func() error {
// Attempt to enroll before starting up osquery. If we can't enroll now, don't error out --
// we'll attempt again the first time osquery calls launcher plugins.
_, invalid, err := ext.Enroll(osqCtx)
if err != nil {
slogger.Log(osqCtx, slog.LevelDebug,
"error performing enrollment",
"err", err,
)
} else if invalid {
slogger.Log(osqCtx, slog.LevelDebug,
"invalid enroll secret",
"err", err,
)
}

// Start the osqueryd instance -- pass in cancel so the osquery runner can let
// this function know to stop waiting when the runner shuts down
if err := runner.Start(osqCancel); err != nil {
return fmt.Errorf("launching osquery instance: %w", err)
}

// If we're using osquery transport, we don't need the extension
if k.Transport() == "osquery" {
slogger.Log(osqCtx, slog.LevelDebug,
"using osquery transport, skipping extension startup",
)

// TODO: remove when underlying libs are refactored
// everything exits right now, so block this actor on the context finishing
<-osqCtx.Done()
return nil
}

// The runner allows querying the osqueryd instance from the extension.
ext.SetQuerier(runner)

// start the extension
ext.Start()

slogger.Log(osqCtx, slog.LevelInfo,
"extension started",
)

// TODO: remove when underlying libs are refactored
// everything exits right now, so block this actor on the context finishing
<-osqCtx.Done()
return nil
},
Interrupt: func(_ error) {
ext.Shutdown()
if runner != nil {
if err := runner.Shutdown(); err != nil {
slogger.Log(osqCtx, slog.LevelInfo,
"error shutting down runtime",
"err", err,
)
slogger.Log(osqCtx, slog.LevelDebug,
"error shutting down runtime",
"err", err,
"stack", fmt.Sprintf("%+v", err),
)
}
}
osqCancel()
},
},
querier: runner.Query,
},
restartFunc,
runner.Shutdown,
nil
}

// commonRunnerOptions returns osquery runtime options common to all transports
func commonRunnerOptions(logger log.Logger, k types.Knapsack) []runtime.OsqueryInstanceOption {
// create the logging adapters for osquery
osqueryStderrLogger := kolidelog.NewOsqueryLogAdapter(
k.Slogger().With(
"component", "osquery",
"osqlevel", "stderr",
),
k.RootDirectory(),
kolidelog.WithLevel(slog.LevelInfo),
)
osqueryStdoutLogger := kolidelog.NewOsqueryLogAdapter(
k.Slogger().With(
"component", "osquery",
"osqlevel", "stdout",
),
k.RootDirectory(),
kolidelog.WithLevel(slog.LevelDebug),
)

return []runtime.OsqueryInstanceOption{
runtime.WithKnapsack(k),
runtime.WithOsquerydBinary(k.OsquerydPath()),
runtime.WithRootDirectory(k.RootDirectory()),
runtime.WithOsqueryExtensionPlugins(ktable.LauncherTables(k)...),
runtime.WithStdout(osqueryStdoutLogger),
runtime.WithStderr(osqueryStderrLogger),
runtime.WithLogger(logger),
runtime.WithOsqueryVerbose(k.OsqueryVerbose()),
runtime.WithOsqueryFlags(k.OsqueryFlags()),
runtime.WithAugeasLensFunction(augeas.InstallLenses),
runtime.WithAutoloadedExtensions(k.AutoloadedExtensions()...),
runtime.WithUpdateDirectory(k.UpdateDirectory()),
runtime.WithUpdateChannel(k.UpdateChannel()),
}
}

// osqueryRunnerOptions returns the osquery runtime options when using native osquery transport
func osqueryRunnerOptions(logger log.Logger, k types.Knapsack) ([]runtime.OsqueryInstanceOption, error) {
// As osquery requires TLS server certs, we'll use our embedded defaults if not specified
caCertFile := k.RootPEM()
if caCertFile == "" {
var err error
caCertFile, err = internal.InstallCaCerts(k.RootDirectory())
if err != nil {
return nil, fmt.Errorf("writing CA certs: %w", err)
}
}

runtimeOptions := append(
commonRunnerOptions(logger, k),
runtime.WithConfigPluginFlag("tls"),
runtime.WithDistributedPluginFlag("tls"),
runtime.WithLoggerPluginFlag("tls"),
runtime.WithTlsConfigEndpoint(k.OsqueryTlsConfigEndpoint()),
runtime.WithTlsDistributedReadEndpoint(k.OsqueryTlsDistributedReadEndpoint()),
runtime.WithTlsDistributedWriteEndpoint(k.OsqueryTlsDistributedWriteEndpoint()),
runtime.WithTlsEnrollEndpoint(k.OsqueryTlsEnrollEndpoint()),
runtime.WithTlsHostname(k.KolideServerURL()),
runtime.WithTlsLoggerEndpoint(k.OsqueryTlsLoggerEndpoint()),
runtime.WithTlsServerCerts(caCertFile),
)

// Enroll secrets... Either we pass a file, or we write a
// secret, and pass _that_ file
if k.EnrollSecretPath() != "" {
runtimeOptions = append(runtimeOptions, runtime.WithEnrollSecretPath(k.EnrollSecretPath()))
} else if k.EnrollSecret() != "" {
filename := filepath.Join(k.RootDirectory(), "secret")
os.WriteFile(filename, []byte(k.EnrollSecret()), 0400)
runtimeOptions = append(runtimeOptions, runtime.WithEnrollSecretPath(filename))
}

return runtimeOptions, nil
}

// grpcRunnerOptions returns the osquery runtime options when using launcher transports. (Eg: grpc or jsonrpc)
func grpcRunnerOptions(logger log.Logger, k types.Knapsack, ext *osquery.Extension) []runtime.OsqueryInstanceOption {
return append(
commonRunnerOptions(logger, k),
runtime.WithConfigPluginFlag("kolide_grpc"),
runtime.WithLoggerPluginFlag("kolide_grpc"),
runtime.WithDistributedPluginFlag("kolide_grpc"),
runtime.WithOsqueryExtensionPlugins(
config.NewPlugin("kolide_grpc", ext.GenerateConfigs),
distributed.NewPlugin("kolide_grpc", ext.GetQueries, ext.WriteResults),
osquerylogger.NewPlugin("kolide_grpc", ext.LogString),
),
)
return osquery.NewExtension(ctx, launcherClient, k, extOpts)
}
65 changes: 56 additions & 9 deletions cmd/launcher/launcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@ import (
"github.com/kolide/launcher/ee/debug/checkups"
desktopRunner "github.com/kolide/launcher/ee/desktop/runner"
"github.com/kolide/launcher/ee/localserver"
kolidelog "github.com/kolide/launcher/ee/log/osquerylogs"
"github.com/kolide/launcher/ee/powereventwatcher"
"github.com/kolide/launcher/ee/tuf"
"github.com/kolide/launcher/pkg/augeas"
"github.com/kolide/launcher/pkg/autoupdate"
"github.com/kolide/launcher/pkg/backoff"
"github.com/kolide/launcher/pkg/contexts/ctxlog"
Expand All @@ -53,11 +55,16 @@ import (
"github.com/kolide/launcher/pkg/log/teelogger"
"github.com/kolide/launcher/pkg/osquery"
"github.com/kolide/launcher/pkg/osquery/runsimple"
osqueryruntime "github.com/kolide/launcher/pkg/osquery/runtime"
osqueryInstanceHistory "github.com/kolide/launcher/pkg/osquery/runtime/history"
"github.com/kolide/launcher/pkg/osquery/table"
"github.com/kolide/launcher/pkg/rungroup"
"github.com/kolide/launcher/pkg/service"
"github.com/kolide/launcher/pkg/traces"
"github.com/kolide/launcher/pkg/traces/exporter"
"github.com/osquery/osquery-go/plugin/config"
"github.com/osquery/osquery-go/plugin/distributed"
osquerylogger "github.com/osquery/osquery-go/plugin/logger"

"go.etcd.io/bbolt"
)
Expand Down Expand Up @@ -324,12 +331,52 @@ func runLauncher(ctx context.Context, cancel func(), multiSlogger, systemMultiSl
return fmt.Errorf("error initializing osquery instance history: %w", err)
}

// create the osquery extension for launcher. This is where osquery itself is launched.
extension, runnerRestart, runnerShutdown, err := createExtensionRuntime(ctx, k, client)
// create the osquery extension
extension, err := createExtensionRuntime(ctx, k, client)
if err != nil {
return fmt.Errorf("create extension with runtime: %w", err)
}
runGroup.Add("osqueryExtension", extension.Execute, extension.Interrupt)
runGroup.Add("osqueryExtension", extension.Execute, extension.Shutdown)
// create the runner that will launch osquery
osqueryRunner := osqueryruntime.New(
k,
osqueryruntime.WithKnapsack(k),
osqueryruntime.WithOsquerydBinary(k.OsquerydPath()),
osqueryruntime.WithRootDirectory(k.RootDirectory()),
osqueryruntime.WithOsqueryExtensionPlugins(table.LauncherTables(k)...),
osqueryruntime.WithLogger(logger),
osqueryruntime.WithOsqueryVerbose(k.OsqueryVerbose()),
osqueryruntime.WithOsqueryFlags(k.OsqueryFlags()),
osqueryruntime.WithStdout(kolidelog.NewOsqueryLogAdapter(
k.Slogger().With(
"component", "osquery",
"osqlevel", "stdout",
),
k.RootDirectory(),
kolidelog.WithLevel(slog.LevelDebug),
)),
osqueryruntime.WithStderr(kolidelog.NewOsqueryLogAdapter(
k.Slogger().With(
"component", "osquery",
"osqlevel", "stderr",
),
k.RootDirectory(),
kolidelog.WithLevel(slog.LevelInfo),
)),
osqueryruntime.WithAugeasLensFunction(augeas.InstallLenses),
osqueryruntime.WithAutoloadedExtensions(k.AutoloadedExtensions()...),
osqueryruntime.WithUpdateDirectory(k.UpdateDirectory()),
osqueryruntime.WithUpdateChannel(k.UpdateChannel()),
osqueryruntime.WithConfigPluginFlag("kolide_grpc"),
osqueryruntime.WithLoggerPluginFlag("kolide_grpc"),
osqueryruntime.WithDistributedPluginFlag("kolide_grpc"),
osqueryruntime.WithOsqueryExtensionPlugins(
config.NewPlugin("kolide_grpc", extension.GenerateConfigs),
distributed.NewPlugin("kolide_grpc", extension.GetQueries, extension.WriteResults),
osquerylogger.NewPlugin("kolide_grpc", extension.LogString),
),
)
runGroup.Add("osqueryRunner", osqueryRunner.Run, osqueryRunner.Interrupt)

versionInfo := version.Version()
k.SystemSlogger().Log(ctx, slog.LevelInfo,
Expand All @@ -339,7 +386,7 @@ func runLauncher(ctx context.Context, cancel func(), multiSlogger, systemMultiSl
)

if traceExporter != nil {
traceExporter.SetOsqueryClient(extension)
traceExporter.SetOsqueryClient(osqueryRunner)
}

// Create the control service and services that depend on it
Expand Down Expand Up @@ -452,7 +499,7 @@ func runLauncher(ctx context.Context, cancel func(), multiSlogger, systemMultiSl
)
}

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

Expand All @@ -468,8 +515,8 @@ func runLauncher(ctx context.Context, cancel func(), multiSlogger, systemMultiSl
k,
metadataClient,
mirrorClient,
extension,
tuf.WithOsqueryRestart(runnerRestart),
osqueryRunner,
tuf.WithOsqueryRestart(osqueryRunner.Restart),
)
if err != nil {
return fmt.Errorf("creating TUF autoupdater updater: %w", err)
Expand Down Expand Up @@ -498,7 +545,7 @@ func runLauncher(ctx context.Context, cancel func(), multiSlogger, systemMultiSl
}

// create an updater for osquery
osqueryLegacyUpdater, err := updater.NewUpdater(ctx, opts.OsquerydPath, runnerRestart, osqueryUpdaterconfig)
osqueryLegacyUpdater, err := updater.NewUpdater(ctx, opts.OsquerydPath, osqueryRunner.Restart, osqueryUpdaterconfig)
if err != nil {
return fmt.Errorf("create osquery updater: %w", err)
}
Expand Down Expand Up @@ -530,7 +577,7 @@ func runLauncher(ctx context.Context, cancel func(), multiSlogger, systemMultiSl
if runner != nil {
runner.Interrupt(nil)
}
return runnerShutdown()
return osqueryRunner.Shutdown()
}),
launcherUpdaterconfig,
)
Expand Down
Loading
Loading