Skip to content

Commit

Permalink
Unique filename for osquery.sock (kolide#1935)
Browse files Browse the repository at this point in the history
  • Loading branch information
RebeccaMahany authored Nov 4, 2024
1 parent 74fab89 commit 84e7e4b
Show file tree
Hide file tree
Showing 12 changed files with 115 additions and 60 deletions.
16 changes: 9 additions & 7 deletions ee/tables/osquery_instance_history/osquery_instance_history.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand All @@ -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,
})
}

Expand Down
3 changes: 2 additions & 1 deletion pkg/osquery/interactive/interactive.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand Down
54 changes: 41 additions & 13 deletions pkg/osquery/interactive/interactive_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,42 +62,53 @@ 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",
},
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 {
tt := tt
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
Expand All @@ -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)
Expand Down Expand Up @@ -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
}
3 changes: 2 additions & 1 deletion pkg/osquery/runtime/history/history.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -98,6 +98,7 @@ func NewInstance() (*Instance, error) {
}

newInstance := &Instance{
RunId: runId,
StartTime: timeNow(),
Hostname: hostname,
}
Expand Down
3 changes: 2 additions & 1 deletion pkg/osquery/runtime/history/history_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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")

Expand Down
3 changes: 2 additions & 1 deletion pkg/osquery/runtime/history/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
28 changes: 22 additions & 6 deletions pkg/osquery/runtime/osqueryinstance.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -649,20 +665,20 @@ 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")

// 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,
Expand Down
16 changes: 0 additions & 16 deletions pkg/osquery/runtime/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions pkg/osquery/runtime/runtime_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
5 changes: 2 additions & 3 deletions pkg/osquery/runtime/runtime_helpers_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"syscall"
"time"

"github.com/kolide/kit/ulid"
"github.com/kolide/launcher/ee/allowedcmd"
"github.com/pkg/errors"
)
Expand Down Expand Up @@ -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.
Expand All @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions pkg/osquery/runtime/runtime_posix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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)

Expand Down
Loading

0 comments on commit 84e7e4b

Please sign in to comment.