diff --git a/ee/tables/osquery_instance_history/osquery_instance_history.go b/ee/tables/osquery_instance_history/osquery_instance_history.go index a7cce448d..5cce87e2f 100644 --- a/ee/tables/osquery_instance_history/osquery_instance_history.go +++ b/ee/tables/osquery_instance_history/osquery_instance_history.go @@ -9,6 +9,7 @@ import ( func TablePlugin() *table.Plugin { columns := []table.ColumnDefinition{ + table.TextColumn("instance_run_id"), table.TextColumn("start_time"), table.TextColumn("connect_time"), table.TextColumn("exit_time"), @@ -32,13 +33,14 @@ func generate() table.GenerateFunc { for _, instance := range history { results = append(results, map[string]string{ - "start_time": instance.StartTime, - "connect_time": instance.ConnectTime, - "exit_time": instance.ExitTime, - "instance_id": instance.InstanceId, - "version": instance.Version, - "hostname": instance.Hostname, - "errors": instance.Error, + "instance_run_id": instance.RunId, + "start_time": instance.StartTime, + "connect_time": instance.ConnectTime, + "exit_time": instance.ExitTime, + "instance_id": instance.InstanceId, + "version": instance.Version, + "hostname": instance.Hostname, + "errors": instance.Error, }) } diff --git a/pkg/osquery/interactive/interactive.go b/pkg/osquery/interactive/interactive.go index dd9ba8942..cef69dd71 100644 --- a/pkg/osquery/interactive/interactive.go +++ b/pkg/osquery/interactive/interactive.go @@ -11,6 +11,7 @@ import ( "time" "github.com/kolide/kit/fsutil" + "github.com/kolide/kit/ulid" "github.com/kolide/launcher/ee/agent/startupsettings" "github.com/kolide/launcher/ee/agent/types" "github.com/kolide/launcher/pkg/augeas" @@ -30,7 +31,7 @@ func StartProcess(knapsack types.Knapsack, interactiveRootDir string) (*os.Proce return nil, nil, fmt.Errorf("creating root dir for interactive mode: %w", err) } - socketPath := osqueryRuntime.SocketPath(interactiveRootDir) + socketPath := osqueryRuntime.SocketPath(interactiveRootDir, ulid.New()) augeasLensesPath := filepath.Join(interactiveRootDir, "augeas-lenses") // only install augeas lenses on non-windows platforms diff --git a/pkg/osquery/interactive/interactive_test.go b/pkg/osquery/interactive/interactive_test.go index d44c9de03..ff9298425 100644 --- a/pkg/osquery/interactive/interactive_test.go +++ b/pkg/osquery/interactive/interactive_test.go @@ -62,17 +62,20 @@ func TestProc(t *testing.T) { t.Parallel() tests := []struct { - name string - osqueryFlags []string - wantProc bool - errContainsStr string + name string + useShortRootDir bool + osqueryFlags []string + wantProc bool + errContainsStr string }{ { - name: "no flags", - wantProc: true, + name: "no flags", + useShortRootDir: true, + wantProc: true, }, { - name: "flags", + name: "flags", + useShortRootDir: true, osqueryFlags: []string{ "verbose", "force=false", @@ -80,16 +83,18 @@ func TestProc(t *testing.T) { wantProc: true, }, { - name: "config path", + name: "config path", + useShortRootDir: true, osqueryFlags: []string{ fmt.Sprintf("config_path=%s", ulid.New()), }, wantProc: true, }, { - name: "socket path too long, the name of the test causes the socket path to be to long to be created, resulting in timeout waiting for the socket", - wantProc: false, - errContainsStr: "error waiting for osquery to create socket", + name: "socket path too long, the name of the test causes the socket path to be to long to be created, resulting in timeout waiting for the socket", + useShortRootDir: false, + wantProc: false, + errContainsStr: "error waiting for osquery to create socket", }, } for _, tt := range tests { @@ -97,7 +102,13 @@ func TestProc(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - rootDir := t.TempDir() + var rootDir string + if tt.useShortRootDir { + rootDir = testRootDirectory(t) + } else { + rootDir = t.TempDir() + } + require.NoError(t, downloadOsquery(rootDir)) var logBytes threadsafebuffer.ThreadSafeBuffer @@ -110,7 +121,7 @@ func TestProc(t *testing.T) { mockSack.On("OsquerydPath").Return(filepath.Join(rootDir, "osqueryd")) mockSack.On("OsqueryFlags").Return(tt.osqueryFlags) mockSack.On("Slogger").Return(slogger) - mockSack.On("RootDirectory").Maybe().Return("whatever_the_root_launcher_dir_is") + mockSack.On("RootDirectory").Maybe().Return(rootDir) store, err := storageci.NewStore(t, slogger, storage.KatcConfigStore.String()) require.NoError(t, err) mockSack.On("KatcConfigStore").Return(store) @@ -176,3 +187,20 @@ func downloadOsquery(dir string) error { return nil } + +// testRootDirectory returns a temporary directory suitable for use in these tests. +// The default t.TempDir is too long of a path, creating too long of an osquery +// extension socket, on posix systems. +func testRootDirectory(t *testing.T) string { + ulid := ulid.New() + rootDir := filepath.Join(os.TempDir(), ulid[len(ulid)-4:]) + require.NoError(t, os.Mkdir(rootDir, 0700)) + + t.Cleanup(func() { + if err := os.RemoveAll(rootDir); err != nil { + t.Errorf("testRootDirectory RemoveAll cleanup: %v", err) + } + }) + + return rootDir +} diff --git a/pkg/osquery/runtime/history/history.go b/pkg/osquery/runtime/history/history.go index 7decfc0df..57ae0a6c0 100644 --- a/pkg/osquery/runtime/history/history.go +++ b/pkg/osquery/runtime/history/history.go @@ -88,7 +88,7 @@ func LatestInstanceUptimeMinutes() (int64, error) { } // NewInstance adds a new instance to the osquery instance history and returns it -func NewInstance() (*Instance, error) { +func NewInstance(runId string) (*Instance, error) { currentHistory.Lock() defer currentHistory.Unlock() @@ -98,6 +98,7 @@ func NewInstance() (*Instance, error) { } newInstance := &Instance{ + RunId: runId, StartTime: timeNow(), Hostname: hostname, } diff --git a/pkg/osquery/runtime/history/history_test.go b/pkg/osquery/runtime/history/history_test.go index 5cda4757c..ac91b3c61 100644 --- a/pkg/osquery/runtime/history/history_test.go +++ b/pkg/osquery/runtime/history/history_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "github.com/kolide/kit/ulid" "github.com/kolide/launcher/ee/agent/storage" storageci "github.com/kolide/launcher/ee/agent/storage/ci" "github.com/kolide/launcher/ee/agent/types" @@ -58,7 +59,7 @@ func TestNewInstance(t *testing.T) { // nolint:paralleltest require.NoError(t, InitHistory(setupStorage(t, tt.initialInstances...))) - _, err := NewInstance() + _, err := NewInstance(ulid.New()) assert.Equal(t, tt.wantNumInstances, len(currentHistory.instances), "expect history length to reflect new instance") diff --git a/pkg/osquery/runtime/history/instance.go b/pkg/osquery/runtime/history/instance.go index 251773b9a..aaf65f22a 100644 --- a/pkg/osquery/runtime/history/instance.go +++ b/pkg/osquery/runtime/history/instance.go @@ -6,11 +6,12 @@ import ( ) type Instance struct { + RunId string // ID for instance, assigned by launcher StartTime string ConnectTime string ExitTime string Hostname string - InstanceId string + InstanceId string // ID from osquery Version string Error string } diff --git a/pkg/osquery/runtime/osqueryinstance.go b/pkg/osquery/runtime/osqueryinstance.go index 2663591c8..6e9173dd6 100644 --- a/pkg/osquery/runtime/osqueryinstance.go +++ b/pkg/osquery/runtime/osqueryinstance.go @@ -41,6 +41,19 @@ const ( // is required for osquery startup. It is called kolide_grpc for mostly historic reasons; // communication with Kolide SaaS happens over JSONRPC. KolideSaasExtensionName = "kolide_grpc" + + // How long to wait before erroring because we cannot open the osquery + // extension socket. + socketOpenTimeout = 10 * time.Second + + // How often to try to open the osquery extension socket + socketOpenInterval = 200 * time.Millisecond + + // How frequently we should healthcheck the client/server + healthCheckInterval = 60 * time.Second + + // The maximum amount of time to wait for the osquery socket to be available -- overrides context deadline + maxSocketWaitTime = 30 * time.Second ) // OsqueryInstanceOption is a functional option pattern for defining how an @@ -93,6 +106,7 @@ type OsqueryInstance struct { serviceClient service.KolideService // the following are instance artifacts that are created and held as a result // of launching an osqueryd process + runId string // string identifier for this instance errgroup *errgroup.Group saasExtension *launcherosq.Extension doneCtx context.Context // nolint:containedctx @@ -175,10 +189,12 @@ type osqueryOptions struct { } func newInstance(knapsack types.Knapsack, serviceClient service.KolideService, opts ...OsqueryInstanceOption) *OsqueryInstance { + runId := ulid.New() i := &OsqueryInstance{ knapsack: knapsack, - slogger: knapsack.Slogger().With("component", "osquery_instance"), + slogger: knapsack.Slogger().With("component", "osquery_instance", "instance_run_id", runId), serviceClient: serviceClient, + runId: runId, } for _, opt := range opts { @@ -244,7 +260,7 @@ func (i *OsqueryInstance) Launch() error { // Based on the root directory, calculate the file names of all of the // required osquery artifact files. - paths, err := calculateOsqueryPaths(i.knapsack.RootDirectory(), i.opts) + paths, err := calculateOsqueryPaths(i.knapsack.RootDirectory(), i.runId, i.opts) if err != nil { traces.SetError(span, fmt.Errorf("could not calculate osquery file paths: %w", err)) return fmt.Errorf("could not calculate osquery file paths: %w", err) @@ -344,7 +360,7 @@ func (i *OsqueryInstance) Launch() error { "osquery socket created", ) - stats, err := history.NewInstance() + stats, err := history.NewInstance(i.runId) if err != nil { i.slogger.Log(ctx, slog.LevelWarn, "could not create new osquery instance history", @@ -649,12 +665,12 @@ type osqueryFilePaths struct { // In return, a structure of paths is returned that can be used to launch an // osqueryd instance. An error may be returned if the supplied parameters are // unacceptable. -func calculateOsqueryPaths(rootDirectory string, opts osqueryOptions) (*osqueryFilePaths, error) { +func calculateOsqueryPaths(rootDirectory string, runId string, opts osqueryOptions) (*osqueryFilePaths, error) { // Determine the path to the extension socket extensionSocketPath := opts.extensionSocketPath if extensionSocketPath == "" { - extensionSocketPath = SocketPath(rootDirectory) + extensionSocketPath = SocketPath(rootDirectory, runId) } extensionAutoloadPath := filepath.Join(rootDirectory, "osquery.autoload") @@ -662,7 +678,7 @@ func calculateOsqueryPaths(rootDirectory string, opts osqueryOptions) (*osqueryF // We want to use a unique pidfile per launcher run to avoid file locking issues. // See: https://github.com/kolide/launcher/issues/1599 osqueryFilePaths := &osqueryFilePaths{ - pidfilePath: filepath.Join(rootDirectory, fmt.Sprintf("osquery-%s.pid", ulid.New())), + pidfilePath: filepath.Join(rootDirectory, fmt.Sprintf("osquery-%s.pid", runId)), databasePath: filepath.Join(rootDirectory, "osquery.db"), augeasPath: filepath.Join(rootDirectory, "augeas-lenses"), extensionSocketPath: extensionSocketPath, diff --git a/pkg/osquery/runtime/runner.go b/pkg/osquery/runtime/runner.go index 10fe1eaec..7200d5c03 100644 --- a/pkg/osquery/runtime/runner.go +++ b/pkg/osquery/runtime/runner.go @@ -5,28 +5,12 @@ import ( "fmt" "log/slog" "sync" - "time" "github.com/kolide/launcher/ee/agent/flags/keys" "github.com/kolide/launcher/ee/agent/types" "github.com/kolide/launcher/pkg/service" ) -const ( - // How long to wait before erroring because we cannot open the osquery - // extension socket. - socketOpenTimeout = 10 * time.Second - - // How often to try to open the osquery extension socket - socketOpenInterval = 200 * time.Millisecond - - // How frequently we should healthcheck the client/server - healthCheckInterval = 60 * time.Second - - // The maximum amount of time to wait for the osquery socket to be available -- overrides context deadline - maxSocketWaitTime = 30 * time.Second -) - type Runner struct { instance *OsqueryInstance instanceLock sync.Mutex diff --git a/pkg/osquery/runtime/runtime_helpers.go b/pkg/osquery/runtime/runtime_helpers.go index cd55bd254..67eec82a4 100644 --- a/pkg/osquery/runtime/runtime_helpers.go +++ b/pkg/osquery/runtime/runtime_helpers.go @@ -22,8 +22,8 @@ func killProcessGroup(cmd *exec.Cmd) error { return nil } -func SocketPath(rootDir string) string { - return filepath.Join(rootDir, "osquery.sock") +func SocketPath(rootDir string, id string) string { + return filepath.Join(rootDir, fmt.Sprintf("osquery-%s.sock", id)) } func platformArgs() []string { diff --git a/pkg/osquery/runtime/runtime_helpers_windows.go b/pkg/osquery/runtime/runtime_helpers_windows.go index 5c5dda948..7c5ed26e3 100644 --- a/pkg/osquery/runtime/runtime_helpers_windows.go +++ b/pkg/osquery/runtime/runtime_helpers_windows.go @@ -10,7 +10,6 @@ import ( "syscall" "time" - "github.com/kolide/kit/ulid" "github.com/kolide/launcher/ee/allowedcmd" "github.com/pkg/errors" ) @@ -47,7 +46,7 @@ func killProcessGroup(origCmd *exec.Cmd) error { return nil } -func SocketPath(rootDir string) string { +func SocketPath(rootDir string, id string) string { // On windows, local names pipes paths are all rooted in \\.\pipe\ // their names are limited to 256 characters, and can include any // character other than backslash. They are case insensitive. @@ -62,7 +61,7 @@ func SocketPath(rootDir string) string { // // We could use something based on the launcher root, but given the // context this runs in a ulid seems simpler. - return fmt.Sprintf(`\\.\pipe\kolide-osquery-%s`, ulid.New()) + return fmt.Sprintf(`\\.\pipe\kolide-osquery-%s`, id) } func platformArgs() []string { diff --git a/pkg/osquery/runtime/runtime_posix_test.go b/pkg/osquery/runtime/runtime_posix_test.go index 8d0dbdaa8..aa1ccd91d 100644 --- a/pkg/osquery/runtime/runtime_posix_test.go +++ b/pkg/osquery/runtime/runtime_posix_test.go @@ -36,7 +36,7 @@ func hasPermissionsToRunTest() bool { func TestOsquerySlowStart(t *testing.T) { t.Parallel() - rootDirectory := t.TempDir() + rootDirectory := testRootDirectory(t) logBytes, slogger, opts := setUpTestSlogger(rootDirectory) @@ -84,7 +84,7 @@ func TestOsquerySlowStart(t *testing.T) { func TestExtensionSocketPath(t *testing.T) { t.Parallel() - rootDirectory := t.TempDir() + rootDirectory := testRootDirectory(t) logBytes, slogger, opts := setUpTestSlogger(rootDirectory) diff --git a/pkg/osquery/runtime/runtime_test.go b/pkg/osquery/runtime/runtime_test.go index c6b195ad5..32629349c 100644 --- a/pkg/osquery/runtime/runtime_test.go +++ b/pkg/osquery/runtime/runtime_test.go @@ -17,6 +17,7 @@ import ( "github.com/apache/thrift/lib/go/thrift" "github.com/kolide/kit/fsutil" + "github.com/kolide/kit/ulid" "github.com/kolide/launcher/ee/agent" "github.com/kolide/launcher/ee/agent/flags/keys" "github.com/kolide/launcher/ee/agent/storage" @@ -95,7 +96,7 @@ func TestCalculateOsqueryPaths(t *testing.T) { binDir, err := getBinDir() require.NoError(t, err) - paths, err := calculateOsqueryPaths(binDir, osqueryOptions{}) + paths, err := calculateOsqueryPaths(binDir, ulid.New(), osqueryOptions{}) require.NoError(t, err) @@ -346,7 +347,7 @@ func TestBadBinaryPath(t *testing.T) { func TestWithOsqueryFlags(t *testing.T) { t.Parallel() - rootDirectory := t.TempDir() + rootDirectory := testRootDirectory(t) logBytes, slogger, opts := setUpTestSlogger(rootDirectory) @@ -374,7 +375,7 @@ func TestWithOsqueryFlags(t *testing.T) { func TestFlagsChanged(t *testing.T) { t.Parallel() - rootDirectory := t.TempDir() + rootDirectory := testRootDirectory(t) logBytes, slogger, opts := setUpTestSlogger(rootDirectory) @@ -509,7 +510,7 @@ func waitHealthy(t *testing.T, runner *Runner, logBytes *threadsafebuffer.Thread func TestSimplePath(t *testing.T) { t.Parallel() - rootDirectory := t.TempDir() + rootDirectory := testRootDirectory(t) logBytes, slogger, opts := setUpTestSlogger(rootDirectory) @@ -541,7 +542,7 @@ func TestSimplePath(t *testing.T) { func TestMultipleShutdowns(t *testing.T) { t.Parallel() - rootDirectory := t.TempDir() + rootDirectory := testRootDirectory(t) logBytes, slogger, opts := setUpTestSlogger(rootDirectory) @@ -572,7 +573,7 @@ func TestMultipleShutdowns(t *testing.T) { func TestOsqueryDies(t *testing.T) { t.Parallel() - rootDirectory := t.TempDir() + rootDirectory := testRootDirectory(t) logBytes, slogger, opts := setUpTestSlogger(rootDirectory) @@ -667,7 +668,7 @@ func TestExtensionIsCleanedUp(t *testing.T) { // sets up an osquery instance with a running extension to be used in tests. func setupOsqueryInstanceForTests(t *testing.T) (runner *Runner, logBytes *threadsafebuffer.ThreadSafeBuffer, teardown func()) { - rootDirectory := t.TempDir() + rootDirectory := testRootDirectory(t) logBytes, slogger, opts := setUpTestSlogger(rootDirectory) @@ -772,3 +773,24 @@ func setUpTestSlogger(rootDirectory string) (*threadsafebuffer.ThreadSafeBuffer, return logBytes, slogger, opts } + +// testRootDirectory returns a temporary directory suitable for use in these tests. +// The default t.TempDir is too long of a path, creating too long of an osquery +// extension socket, on posix systems. +func testRootDirectory(t *testing.T) string { + if runtime.GOOS == "windows" { + return t.TempDir() + } + + ulid := ulid.New() + rootDir := filepath.Join(os.TempDir(), ulid[len(ulid)-4:]) + require.NoError(t, os.Mkdir(rootDir, 0700)) + + t.Cleanup(func() { + if err := os.RemoveAll(rootDir); err != nil { + t.Errorf("testRootDirectory RemoveAll cleanup: %v", err) + } + }) + + return rootDir +}