diff --git a/cmd/launcher/launcher.go b/cmd/launcher/launcher.go index 9c81fde78..fe6911cf7 100644 --- a/cmd/launcher/launcher.go +++ b/cmd/launcher/launcher.go @@ -195,6 +195,13 @@ func runLauncher(ctx context.Context, cancel func(), multiSlogger, systemMultiSl flagController := flags.NewFlagController(slogger, stores[storage.AgentFlagsStore], fcOpts...) k := knapsack.New(stores, flagController, db, multiSlogger, systemMultiSlogger) + if err := osquery.CollectAndSetEnrollmentDetails(ctx, k, 5*time.Second, 5*time.Second); err != nil { + slogger.Log(ctx, slog.LevelError, + "setting enrollment details", + "err", err, + ) + } + // Generate a new run ID newRunID := k.GetRunID() diff --git a/ee/agent/knapsack/knapsack.go b/ee/agent/knapsack/knapsack.go index f52c63ce1..b7d4a10bb 100644 --- a/ee/agent/knapsack/knapsack.go +++ b/ee/agent/knapsack/knapsack.go @@ -14,12 +14,16 @@ import ( "github.com/kolide/launcher/ee/agent/types" "github.com/kolide/launcher/ee/tuf" "github.com/kolide/launcher/pkg/log/multislogger" + "go.etcd.io/bbolt" ) // Package-level runID variable var runID string +// Package-level enrollmentDetails variable +var enrollmentDetails *types.EnrollmentDetails + // type alias Flags, so that we can embed it inside knapsack, as `flags` and not `Flags` type flags types.Flags @@ -239,3 +243,33 @@ func (k *knapsack) CurrentEnrollmentStatus() (types.EnrollmentStatus, error) { return types.Enrolled, nil } + +func (k *knapsack) SetEnrollmentDetails(details types.EnrollmentDetails) error { + if enrollmentDetails == nil { + newDetails := details + enrollmentDetails = &newDetails + k.Slogger().Log(context.Background(), slog.LevelDebug, + "initializing enrollment details", + "details", fmt.Sprintf("%+v", enrollmentDetails), + ) + return nil + } + + current := *enrollmentDetails + + k.Slogger().Log(context.Background(), slog.LevelDebug, + "updating enrollment details", + "old_details", fmt.Sprintf("%+v", enrollmentDetails), + "new_details", fmt.Sprintf("%+v", current), + ) + enrollmentDetails = ¤t + + return nil +} + +func (k *knapsack) GetEnrollmentDetails() (types.EnrollmentDetails, error) { + if enrollmentDetails == nil { + return types.EnrollmentDetails{}, nil + } + return *enrollmentDetails, nil +} diff --git a/ee/agent/types/enrollment.go b/ee/agent/types/enrollment.go new file mode 100644 index 000000000..32b32ab82 --- /dev/null +++ b/ee/agent/types/enrollment.go @@ -0,0 +1,32 @@ +package types + +type EnrollmentStatus string + +const ( + NoEnrollmentKey EnrollmentStatus = "no_enrollment_key" + Unenrolled EnrollmentStatus = "unenrolled" + Enrolled EnrollmentStatus = "enrolled" + Unknown EnrollmentStatus = "unknown" +) + +// EnrollmentDetails is the set of details that are collected from osqueryd +// that are sent to the Kolide server during enrollment. +type EnrollmentDetails struct { + OSVersion string `json:"os_version"` + OSBuildID string `json:"os_build_id"` + OSPlatform string `json:"os_platform"` + Hostname string `json:"hostname"` + HardwareVendor string `json:"hardware_vendor"` + HardwareModel string `json:"hardware_model"` + HardwareSerial string `json:"hardware_serial"` + OsqueryVersion string `json:"osquery_version"` + LauncherHardwareKey string `json:"launcher_hardware_key"` + LauncherHardwareKeySource string `json:"launcher_hardware_key_source"` + LauncherLocalKey string `json:"launcher_local_key"` + LauncherVersion string `json:"launcher_version"` + OSName string `json:"os_name"` + OSPlatformLike string `json:"os_platform_like"` + GOOS string `json:"goos"` + GOARCH string `json:"goarch"` + HardwareUUID string `json:"hardware_uuid"` +} diff --git a/ee/agent/types/knapsack.go b/ee/agent/types/knapsack.go index 45a441cb1..1920669a7 100644 --- a/ee/agent/types/knapsack.go +++ b/ee/agent/types/knapsack.go @@ -20,4 +20,8 @@ type Knapsack interface { CurrentEnrollmentStatus() (EnrollmentStatus, error) // GetRunID returns the current launcher run ID GetRunID() string + // GetEnrollmentDetails returns the enrollment details for the launcher installation + GetEnrollmentDetails() (EnrollmentDetails, error) + // SetEnrollmentDetails sets the enrollment details for the launcher installation + SetEnrollmentDetails(details EnrollmentDetails) error } diff --git a/ee/agent/types/mocks/flags.go b/ee/agent/types/mocks/flags.go index c202a37b3..efd077ac3 100644 --- a/ee/agent/types/mocks/flags.go +++ b/ee/agent/types/mocks/flags.go @@ -686,96 +686,6 @@ func (_m *Flags) OsqueryHealthcheckStartupDelay() time.Duration { return r0 } -// OsqueryTlsConfigEndpoint provides a mock function with given fields: -func (_m *Flags) OsqueryTlsConfigEndpoint() string { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for OsqueryTlsConfigEndpoint") - } - - var r0 string - if rf, ok := ret.Get(0).(func() string); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - -// OsqueryTlsDistributedReadEndpoint provides a mock function with given fields: -func (_m *Flags) OsqueryTlsDistributedReadEndpoint() string { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for OsqueryTlsDistributedReadEndpoint") - } - - var r0 string - if rf, ok := ret.Get(0).(func() string); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - -// OsqueryTlsDistributedWriteEndpoint provides a mock function with given fields: -func (_m *Flags) OsqueryTlsDistributedWriteEndpoint() string { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for OsqueryTlsDistributedWriteEndpoint") - } - - var r0 string - if rf, ok := ret.Get(0).(func() string); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - -// OsqueryTlsEnrollEndpoint provides a mock function with given fields: -func (_m *Flags) OsqueryTlsEnrollEndpoint() string { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for OsqueryTlsEnrollEndpoint") - } - - var r0 string - if rf, ok := ret.Get(0).(func() string); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - -// OsqueryTlsLoggerEndpoint provides a mock function with given fields: -func (_m *Flags) OsqueryTlsLoggerEndpoint() string { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for OsqueryTlsLoggerEndpoint") - } - - var r0 string - if rf, ok := ret.Get(0).(func() string); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - // OsqueryVerbose provides a mock function with given fields: func (_m *Flags) OsqueryVerbose() bool { ret := _m.Called() diff --git a/ee/agent/types/mocks/knapsack.go b/ee/agent/types/mocks/knapsack.go index 58bea406d..eaa2f0b37 100644 --- a/ee/agent/types/mocks/knapsack.go +++ b/ee/agent/types/mocks/knapsack.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.45.0. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package mocks @@ -37,19 +37,19 @@ func (_m *Knapsack) AddSlogHandler(handler ...slog.Handler) { } // AgentFlagsStore provides a mock function with given fields: -func (_m *Knapsack) AgentFlagsStore() types.GetterSetterDeleterIteratorUpdaterCounterAppender { +func (_m *Knapsack) AgentFlagsStore() types.KVStore { ret := _m.Called() if len(ret) == 0 { panic("no return value specified for AgentFlagsStore") } - var r0 types.GetterSetterDeleterIteratorUpdaterCounterAppender - if rf, ok := ret.Get(0).(func() types.GetterSetterDeleterIteratorUpdaterCounterAppender); ok { + var r0 types.KVStore + if rf, ok := ret.Get(0).(func() types.KVStore); ok { r0 = rf() } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(types.GetterSetterDeleterIteratorUpdaterCounterAppender) + r0 = ret.Get(0).(types.KVStore) } } @@ -75,19 +75,19 @@ func (_m *Knapsack) Autoupdate() bool { } // AutoupdateErrorsStore provides a mock function with given fields: -func (_m *Knapsack) AutoupdateErrorsStore() types.GetterSetterDeleterIteratorUpdaterCounterAppender { +func (_m *Knapsack) AutoupdateErrorsStore() types.KVStore { ret := _m.Called() if len(ret) == 0 { panic("no return value specified for AutoupdateErrorsStore") } - var r0 types.GetterSetterDeleterIteratorUpdaterCounterAppender - if rf, ok := ret.Get(0).(func() types.GetterSetterDeleterIteratorUpdaterCounterAppender); ok { + var r0 types.KVStore + if rf, ok := ret.Get(0).(func() types.KVStore); ok { r0 = rf() } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(types.GetterSetterDeleterIteratorUpdaterCounterAppender) + r0 = ret.Get(0).(types.KVStore) } } @@ -171,19 +171,19 @@ func (_m *Knapsack) CertPins() [][]byte { } // ConfigStore provides a mock function with given fields: -func (_m *Knapsack) ConfigStore() types.GetterSetterDeleterIteratorUpdaterCounterAppender { +func (_m *Knapsack) ConfigStore() types.KVStore { ret := _m.Called() if len(ret) == 0 { panic("no return value specified for ConfigStore") } - var r0 types.GetterSetterDeleterIteratorUpdaterCounterAppender - if rf, ok := ret.Get(0).(func() types.GetterSetterDeleterIteratorUpdaterCounterAppender); ok { + var r0 types.KVStore + if rf, ok := ret.Get(0).(func() types.KVStore); ok { r0 = rf() } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(types.GetterSetterDeleterIteratorUpdaterCounterAppender) + r0 = ret.Get(0).(types.KVStore) } } @@ -227,19 +227,19 @@ func (_m *Knapsack) ControlServerURL() string { } // ControlStore provides a mock function with given fields: -func (_m *Knapsack) ControlStore() types.GetterSetterDeleterIteratorUpdaterCounterAppender { +func (_m *Knapsack) ControlStore() types.KVStore { ret := _m.Called() if len(ret) == 0 { panic("no return value specified for ControlStore") } - var r0 types.GetterSetterDeleterIteratorUpdaterCounterAppender - if rf, ok := ret.Get(0).(func() types.GetterSetterDeleterIteratorUpdaterCounterAppender); ok { + var r0 types.KVStore + if rf, ok := ret.Get(0).(func() types.KVStore); ok { r0 = rf() } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(types.GetterSetterDeleterIteratorUpdaterCounterAppender) + r0 = ret.Get(0).(types.KVStore) } } @@ -526,6 +526,34 @@ func (_m *Knapsack) ForceControlSubsystems() bool { return r0 } +// GetEnrollmentDetails provides a mock function with given fields: +func (_m *Knapsack) GetEnrollmentDetails() (types.EnrollmentDetails, error) { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetEnrollmentDetails") + } + + var r0 types.EnrollmentDetails + var r1 error + if rf, ok := ret.Get(0).(func() (types.EnrollmentDetails, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() types.EnrollmentDetails); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(types.EnrollmentDetails) + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetRunID provides a mock function with given fields: func (_m *Knapsack) GetRunID() string { ret := _m.Called() @@ -599,19 +627,19 @@ func (_m *Knapsack) InModernStandby() bool { } // InitialResultsStore provides a mock function with given fields: -func (_m *Knapsack) InitialResultsStore() types.GetterSetterDeleterIteratorUpdaterCounterAppender { +func (_m *Knapsack) InitialResultsStore() types.KVStore { ret := _m.Called() if len(ret) == 0 { panic("no return value specified for InitialResultsStore") } - var r0 types.GetterSetterDeleterIteratorUpdaterCounterAppender - if rf, ok := ret.Get(0).(func() types.GetterSetterDeleterIteratorUpdaterCounterAppender); ok { + var r0 types.KVStore + if rf, ok := ret.Get(0).(func() types.KVStore); ok { r0 = rf() } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(types.GetterSetterDeleterIteratorUpdaterCounterAppender) + r0 = ret.Get(0).(types.KVStore) } } @@ -693,19 +721,19 @@ func (_m *Knapsack) InstanceStatuses() map[string]types.InstanceStatus { } // KatcConfigStore provides a mock function with given fields: -func (_m *Knapsack) KatcConfigStore() types.GetterSetterDeleterIteratorUpdaterCounterAppender { +func (_m *Knapsack) KatcConfigStore() types.KVStore { ret := _m.Called() if len(ret) == 0 { panic("no return value specified for KatcConfigStore") } - var r0 types.GetterSetterDeleterIteratorUpdaterCounterAppender - if rf, ok := ret.Get(0).(func() types.GetterSetterDeleterIteratorUpdaterCounterAppender); ok { + var r0 types.KVStore + if rf, ok := ret.Get(0).(func() types.KVStore); ok { r0 = rf() } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(types.GetterSetterDeleterIteratorUpdaterCounterAppender) + r0 = ret.Get(0).(types.KVStore) } } @@ -767,19 +795,19 @@ func (_m *Knapsack) LatestOsquerydPath(ctx context.Context) string { } // LauncherHistoryStore provides a mock function with given fields: -func (_m *Knapsack) LauncherHistoryStore() types.GetterSetterDeleterIteratorUpdaterCounterAppender { +func (_m *Knapsack) LauncherHistoryStore() types.KVStore { ret := _m.Called() if len(ret) == 0 { panic("no return value specified for LauncherHistoryStore") } - var r0 types.GetterSetterDeleterIteratorUpdaterCounterAppender - if rf, ok := ret.Get(0).(func() types.GetterSetterDeleterIteratorUpdaterCounterAppender); ok { + var r0 types.KVStore + if rf, ok := ret.Get(0).(func() types.KVStore); ok { r0 = rf() } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(types.GetterSetterDeleterIteratorUpdaterCounterAppender) + r0 = ret.Get(0).(types.KVStore) } } @@ -951,19 +979,19 @@ func (_m *Knapsack) OsqueryHealthcheckStartupDelay() time.Duration { } // OsqueryHistoryInstanceStore provides a mock function with given fields: -func (_m *Knapsack) OsqueryHistoryInstanceStore() types.GetterSetterDeleterIteratorUpdaterCounterAppender { +func (_m *Knapsack) OsqueryHistoryInstanceStore() types.KVStore { ret := _m.Called() if len(ret) == 0 { panic("no return value specified for OsqueryHistoryInstanceStore") } - var r0 types.GetterSetterDeleterIteratorUpdaterCounterAppender - if rf, ok := ret.Get(0).(func() types.GetterSetterDeleterIteratorUpdaterCounterAppender); ok { + var r0 types.KVStore + if rf, ok := ret.Get(0).(func() types.KVStore); ok { r0 = rf() } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(types.GetterSetterDeleterIteratorUpdaterCounterAppender) + r0 = ret.Get(0).(types.KVStore) } } @@ -1007,19 +1035,19 @@ func (_m *Knapsack) OsquerydPath() string { } // PersistentHostDataStore provides a mock function with given fields: -func (_m *Knapsack) PersistentHostDataStore() types.GetterSetterDeleterIteratorUpdaterCounterAppender { +func (_m *Knapsack) PersistentHostDataStore() types.KVStore { ret := _m.Called() if len(ret) == 0 { panic("no return value specified for PersistentHostDataStore") } - var r0 types.GetterSetterDeleterIteratorUpdaterCounterAppender - if rf, ok := ret.Get(0).(func() types.GetterSetterDeleterIteratorUpdaterCounterAppender); ok { + var r0 types.KVStore + if rf, ok := ret.Get(0).(func() types.KVStore); ok { r0 = rf() } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(types.GetterSetterDeleterIteratorUpdaterCounterAppender) + r0 = ret.Get(0).(types.KVStore) } } @@ -1123,19 +1151,19 @@ func (_m *Knapsack) RegistrationIDs() []string { } // ResultLogsStore provides a mock function with given fields: -func (_m *Knapsack) ResultLogsStore() types.GetterSetterDeleterIteratorUpdaterCounterAppender { +func (_m *Knapsack) ResultLogsStore() types.KVStore { ret := _m.Called() if len(ret) == 0 { panic("no return value specified for ResultLogsStore") } - var r0 types.GetterSetterDeleterIteratorUpdaterCounterAppender - if rf, ok := ret.Get(0).(func() types.GetterSetterDeleterIteratorUpdaterCounterAppender); ok { + var r0 types.KVStore + if rf, ok := ret.Get(0).(func() types.KVStore); ok { r0 = rf() } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(types.GetterSetterDeleterIteratorUpdaterCounterAppender) + r0 = ret.Get(0).(types.KVStore) } } @@ -1179,19 +1207,19 @@ func (_m *Knapsack) RootPEM() string { } // SentNotificationsStore provides a mock function with given fields: -func (_m *Knapsack) SentNotificationsStore() types.GetterSetterDeleterIteratorUpdaterCounterAppender { +func (_m *Knapsack) SentNotificationsStore() types.KVStore { ret := _m.Called() if len(ret) == 0 { panic("no return value specified for SentNotificationsStore") } - var r0 types.GetterSetterDeleterIteratorUpdaterCounterAppender - if rf, ok := ret.Get(0).(func() types.GetterSetterDeleterIteratorUpdaterCounterAppender); ok { + var r0 types.KVStore + if rf, ok := ret.Get(0).(func() types.KVStore); ok { r0 = rf() } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(types.GetterSetterDeleterIteratorUpdaterCounterAppender) + r0 = ret.Get(0).(types.KVStore) } } @@ -1199,19 +1227,19 @@ func (_m *Knapsack) SentNotificationsStore() types.GetterSetterDeleterIteratorUp } // ServerProvidedDataStore provides a mock function with given fields: -func (_m *Knapsack) ServerProvidedDataStore() types.GetterSetterDeleterIteratorUpdaterCounterAppender { +func (_m *Knapsack) ServerProvidedDataStore() types.KVStore { ret := _m.Called() if len(ret) == 0 { panic("no return value specified for ServerProvidedDataStore") } - var r0 types.GetterSetterDeleterIteratorUpdaterCounterAppender - if rf, ok := ret.Get(0).(func() types.GetterSetterDeleterIteratorUpdaterCounterAppender); ok { + var r0 types.KVStore + if rf, ok := ret.Get(0).(func() types.KVStore); ok { r0 = rf() } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(types.GetterSetterDeleterIteratorUpdaterCounterAppender) + r0 = ret.Get(0).(types.KVStore) } } @@ -1457,6 +1485,24 @@ func (_m *Knapsack) SetDisableTraceIngestTLS(enabled bool) error { return r0 } +// SetEnrollmentDetails provides a mock function with given fields: details +func (_m *Knapsack) SetEnrollmentDetails(details types.EnrollmentDetails) error { + ret := _m.Called(details) + + if len(ret) == 0 { + panic("no return value specified for SetEnrollmentDetails") + } + + var r0 error + if rf, ok := ret.Get(0).(func(types.EnrollmentDetails) error); ok { + r0 = rf(details) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // SetExportTraces provides a mock function with given fields: enabled func (_m *Knapsack) SetExportTraces(enabled bool) error { ret := _m.Called(enabled) @@ -1984,19 +2030,19 @@ func (_m *Knapsack) Slogger() *slog.Logger { } // StatusLogsStore provides a mock function with given fields: -func (_m *Knapsack) StatusLogsStore() types.GetterSetterDeleterIteratorUpdaterCounterAppender { +func (_m *Knapsack) StatusLogsStore() types.KVStore { ret := _m.Called() if len(ret) == 0 { panic("no return value specified for StatusLogsStore") } - var r0 types.GetterSetterDeleterIteratorUpdaterCounterAppender - if rf, ok := ret.Get(0).(func() types.GetterSetterDeleterIteratorUpdaterCounterAppender); ok { + var r0 types.KVStore + if rf, ok := ret.Get(0).(func() types.KVStore); ok { r0 = rf() } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(types.GetterSetterDeleterIteratorUpdaterCounterAppender) + r0 = ret.Get(0).(types.KVStore) } } @@ -2004,19 +2050,19 @@ func (_m *Knapsack) StatusLogsStore() types.GetterSetterDeleterIteratorUpdaterCo } // Stores provides a mock function with given fields: -func (_m *Knapsack) Stores() map[storage.Store]types.GetterSetterDeleterIteratorUpdaterCounterAppender { +func (_m *Knapsack) Stores() map[storage.Store]types.KVStore { ret := _m.Called() if len(ret) == 0 { panic("no return value specified for Stores") } - var r0 map[storage.Store]types.GetterSetterDeleterIteratorUpdaterCounterAppender - if rf, ok := ret.Get(0).(func() map[storage.Store]types.GetterSetterDeleterIteratorUpdaterCounterAppender); ok { + var r0 map[storage.Store]types.KVStore + if rf, ok := ret.Get(0).(func() map[storage.Store]types.KVStore); ok { r0 = rf() } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(map[storage.Store]types.GetterSetterDeleterIteratorUpdaterCounterAppender) + r0 = ret.Get(0).(map[storage.Store]types.KVStore) } } @@ -2062,19 +2108,19 @@ func (_m *Knapsack) SystrayRestartEnabled() bool { } // TokenStore provides a mock function with given fields: -func (_m *Knapsack) TokenStore() types.GetterSetterDeleterIteratorUpdaterCounterAppender { +func (_m *Knapsack) TokenStore() types.KVStore { ret := _m.Called() if len(ret) == 0 { panic("no return value specified for TokenStore") } - var r0 types.GetterSetterDeleterIteratorUpdaterCounterAppender - if rf, ok := ret.Get(0).(func() types.GetterSetterDeleterIteratorUpdaterCounterAppender); ok { + var r0 types.KVStore + if rf, ok := ret.Get(0).(func() types.KVStore); ok { r0 = rf() } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(types.GetterSetterDeleterIteratorUpdaterCounterAppender) + r0 = ret.Get(0).(types.KVStore) } } diff --git a/ee/agent/types/status.go b/ee/agent/types/status.go deleted file mode 100644 index 426442168..000000000 --- a/ee/agent/types/status.go +++ /dev/null @@ -1,10 +0,0 @@ -package types - -type EnrollmentStatus string - -const ( - NoEnrollmentKey EnrollmentStatus = "no_enrollment_key" - Unenrolled EnrollmentStatus = "unenrolled" - Enrolled EnrollmentStatus = "enrolled" - Unknown EnrollmentStatus = "unknown" -) diff --git a/ee/localserver/request-id.go b/ee/localserver/request-id.go index 0ecf3eafa..9bbd3c589 100644 --- a/ee/localserver/request-id.go +++ b/ee/localserver/request-id.go @@ -23,10 +23,11 @@ type ( requestIdsResponse struct { RequestId string identifiers - Nonce string - Timestamp time.Time - Status status - Origin string + Nonce string + Timestamp time.Time + Status status + Origin string + EnrollmentDetails types.EnrollmentDetails } status struct { @@ -77,6 +78,7 @@ func (ls *localServer) requestIdHandlerFunc(w http.ResponseWriter, r *http.Reque defer span.End() enrollmentStatus, _ := ls.knapsack.CurrentEnrollmentStatus() + enrollmentDetails, _ := ls.knapsack.GetEnrollmentDetails() response := requestIdsResponse{ Nonce: ulid.New(), @@ -85,6 +87,7 @@ func (ls *localServer) requestIdHandlerFunc(w http.ResponseWriter, r *http.Reque Status: status{ EnrollmentStatus: string(enrollmentStatus), }, + EnrollmentDetails: enrollmentDetails, } response.identifiers = ls.identifiers diff --git a/ee/localserver/request-id_test.go b/ee/localserver/request-id_test.go index 8873bb34c..03c53d221 100644 --- a/ee/localserver/request-id_test.go +++ b/ee/localserver/request-id_test.go @@ -27,6 +27,7 @@ func Test_localServer_requestIdHandler(t *testing.T) { mockKnapsack.On("ConfigStore").Return(storageci.NewStore(t, multislogger.NewNopLogger(), storage.ConfigStore.String())) mockKnapsack.On("KolideServerURL").Return("localhost") mockKnapsack.On("CurrentEnrollmentStatus").Return(types.Enrolled, nil) + mockKnapsack.On("GetEnrollmentDetails").Return(types.EnrollmentDetails{}, nil) var logBytes bytes.Buffer slogger := slog.New(slog.NewJSONHandler(&logBytes, &slog.HandlerOptions{ diff --git a/ee/tuf/autoupdate.go b/ee/tuf/autoupdate.go index c10569427..2a35f6af8 100644 --- a/ee/tuf/autoupdate.go +++ b/ee/tuf/autoupdate.go @@ -25,6 +25,7 @@ import ( "github.com/kolide/kit/version" "github.com/kolide/launcher/ee/agent/flags/keys" "github.com/kolide/launcher/ee/agent/types" + "github.com/kolide/launcher/pkg/osquery" "github.com/kolide/launcher/pkg/traces" client "github.com/theupdateframework/go-tuf/client" filejsonstore "github.com/theupdateframework/go-tuf/client/filejsonstore" @@ -84,22 +85,24 @@ type querier interface { } type TufAutoupdater struct { - metadataClient *client.Client - libraryManager librarian - osquerier querier // used to query for current running osquery version - osquerierRetryInterval time.Duration - knapsack types.Knapsack - store types.KVStore // stores autoupdater errors for kolide_tuf_autoupdater_errors table - updateChannel string - pinnedVersions map[autoupdatableBinary]string // maps the binaries to their pinned versions - pinnedVersionGetters map[autoupdatableBinary]func() string // maps the binaries to the knapsack function to retrieve updated pinned versions - initialDelayEnd time.Time - updateLock *sync.Mutex - interrupt chan struct{} - interrupted atomic.Bool - signalRestart chan error - slogger *slog.Logger - restartFuncs map[autoupdatableBinary]func() error + metadataClient *client.Client + libraryManager librarian + osquerier querier // used to query for current running osquery version + osquerierRetryInterval time.Duration + knapsack types.Knapsack + store types.KVStore // stores autoupdater errors for kolide_tuf_autoupdater_errors table + updateChannel string + pinnedVersions map[autoupdatableBinary]string // maps the binaries to their pinned versions + pinnedVersionGetters map[autoupdatableBinary]func() string // maps the binaries to the knapsack function to retrieve updated pinned versions + initialDelayEnd time.Time + updateLock *sync.Mutex + interrupt chan struct{} + interrupted atomic.Bool + signalRestart chan error + slogger *slog.Logger + restartFuncs map[autoupdatableBinary]func() error + osquerierBackoffTimeout time.Duration + osquerierBackoffInterval time.Duration } type TufAutoupdaterOption func(*TufAutoupdater) @@ -113,6 +116,13 @@ func WithOsqueryRestart(restart func() error) TufAutoupdaterOption { } } +func WithOsquerierBackoff(timeout, interval time.Duration) TufAutoupdaterOption { + return func(ta *TufAutoupdater) { + ta.osquerierBackoffTimeout = timeout + ta.osquerierBackoffInterval = interval + } +} + func NewTufAutoupdater(ctx context.Context, k types.Knapsack, metadataHttpClient *http.Client, mirrorHttpClient *http.Client, osquerier querier, opts ...TufAutoupdaterOption) (*TufAutoupdater, error) { ctx, span := traces.StartSpan(ctx) @@ -132,12 +142,14 @@ func NewTufAutoupdater(ctx context.Context, k types.Knapsack, metadataHttpClient binaryLauncher: func() string { return k.PinnedLauncherVersion() }, binaryOsqueryd: func() string { return k.PinnedOsquerydVersion() }, }, - initialDelayEnd: time.Now().Add(k.AutoupdateInitialDelay()), - updateLock: &sync.Mutex{}, - osquerier: osquerier, - osquerierRetryInterval: 30 * time.Second, - slogger: k.Slogger().With("component", "tuf_autoupdater"), - restartFuncs: make(map[autoupdatableBinary]func() error), + initialDelayEnd: time.Now().Add(k.AutoupdateInitialDelay()), + updateLock: &sync.Mutex{}, + osquerier: osquerier, + osquerierRetryInterval: 30 * time.Second, + slogger: k.Slogger().With("component", "tuf_autoupdater"), + restartFuncs: make(map[autoupdatableBinary]func() error), + osquerierBackoffTimeout: 30 * time.Second, // Default production value + osquerierBackoffInterval: 5 * time.Second, // Default production value } for _, opt := range opts { @@ -528,6 +540,15 @@ func (ta *TufAutoupdater) checkForUpdate(binariesToCheck []autoupdatableBinary) if updatedVersion, ok := updatesDownloaded[binaryLauncher]; ok { // Only reload if we're not using a localdev path if ta.knapsack.LocalDevelopmentPath() == "" { + ctx := context.Background() + // Collect enrollment details before restart + if err := osquery.CollectAndSetEnrollmentDetails(ctx, ta.knapsack, ta.osquerierBackoffTimeout, ta.osquerierBackoffInterval); err != nil { + ta.slogger.Log(ctx, slog.LevelError, + "collecting enrollment details before restart", + "err", err, + ) + } + ta.slogger.Log(context.TODO(), slog.LevelInfo, "launcher updated -- exiting to load new version", "new_binary_version", updatedVersion, diff --git a/ee/tuf/autoupdate_test.go b/ee/tuf/autoupdate_test.go index 7e8479950..7aac14c2e 100644 --- a/ee/tuf/autoupdate_test.go +++ b/ee/tuf/autoupdate_test.go @@ -48,7 +48,7 @@ func TestNewTufAutoupdater(t *testing.T) { mockKnapsack.On("PinnedOsquerydVersion").Return("") mockKnapsack.On("RegisterChangeObserver", mock.Anything, keys.UpdateChannel, keys.PinnedLauncherVersion, keys.PinnedOsquerydVersion).Return() - _, err := NewTufAutoupdater(context.TODO(), mockKnapsack, http.DefaultClient, http.DefaultClient, newMockQuerier(t)) + _, err := NewTufAutoupdater(context.TODO(), mockKnapsack, http.DefaultClient, http.DefaultClient, newMockQuerier(t), WithOsquerierBackoff(100*time.Millisecond, 20*time.Millisecond)) require.NoError(t, err, "could not initialize new TUF autoupdater") // Confirm we pulled all config items as expected @@ -103,7 +103,7 @@ func TestExecute_launcherUpdate(t *testing.T) { mockKnapsack.On("Slogger").Return(slogger.Logger) // Set up autoupdater - autoupdater, err := NewTufAutoupdater(context.TODO(), mockKnapsack, http.DefaultClient, http.DefaultClient, mockQuerier) + autoupdater, err := NewTufAutoupdater(context.TODO(), mockKnapsack, http.DefaultClient, http.DefaultClient, mockQuerier, WithOsquerierBackoff(100*time.Millisecond, 20*time.Millisecond)) require.NoError(t, err, "could not initialize new TUF autoupdater") // Update the metadata client with our test root JSON @@ -203,7 +203,7 @@ func TestExecute_osquerydUpdate(t *testing.T) { mockKnapsack.On("Slogger").Return(slogger.Logger) // Set up autoupdater - autoupdater, err := NewTufAutoupdater(context.TODO(), mockKnapsack, http.DefaultClient, http.DefaultClient, mockQuerier, WithOsqueryRestart(func() error { return nil })) + autoupdater, err := NewTufAutoupdater(context.TODO(), mockKnapsack, http.DefaultClient, http.DefaultClient, mockQuerier, WithOsqueryRestart(func() error { return nil }), WithOsquerierBackoff(100*time.Millisecond, 20*time.Millisecond)) require.NoError(t, err, "could not initialize new TUF autoupdater") // Update the metadata client with our test root JSON @@ -286,7 +286,7 @@ func TestExecute_downgrade(t *testing.T) { mockKnapsack.On("Slogger").Return(slogger.Logger) // Set up autoupdater - autoupdater, err := NewTufAutoupdater(context.TODO(), mockKnapsack, http.DefaultClient, http.DefaultClient, mockQuerier, WithOsqueryRestart(func() error { return nil })) + autoupdater, err := NewTufAutoupdater(context.TODO(), mockKnapsack, http.DefaultClient, http.DefaultClient, mockQuerier, WithOsqueryRestart(func() error { return nil }), WithOsquerierBackoff(100*time.Millisecond, 20*time.Millisecond)) require.NoError(t, err, "could not initialize new TUF autoupdater") // Update the metadata client with our test root JSON @@ -376,7 +376,7 @@ func TestExecute_withInitialDelay(t *testing.T) { // Set up autoupdater autoupdater, err := NewTufAutoupdater(context.TODO(), mockKnapsack, http.DefaultClient, http.DefaultClient, - mockQuerier, WithOsqueryRestart(func() error { return nil })) + mockQuerier, WithOsqueryRestart(func() error { return nil }), WithOsquerierBackoff(100*time.Millisecond, 20*time.Millisecond)) require.NoError(t, err, "could not initialize new TUF autoupdater") // Expect that we interrupt @@ -439,7 +439,7 @@ func TestExecute_inModernStandby(t *testing.T) { // Set up autoupdater autoupdater, err := NewTufAutoupdater(context.TODO(), mockKnapsack, http.DefaultClient, http.DefaultClient, - mockQuerier, WithOsqueryRestart(func() error { return nil })) + mockQuerier, WithOsqueryRestart(func() error { return nil }), WithOsquerierBackoff(100*time.Millisecond, 20*time.Millisecond)) require.NoError(t, err, "could not initialize new TUF autoupdater") // Set up library manager: we should expect to tidy the library on startup, but NOT add anything to it @@ -500,7 +500,7 @@ func TestInterrupt_Multiple(t *testing.T) { // Set up autoupdater autoupdater, err := NewTufAutoupdater(context.TODO(), mockKnapsack, http.DefaultClient, http.DefaultClient, - mockQuerier, WithOsqueryRestart(func() error { return nil })) + mockQuerier, WithOsqueryRestart(func() error { return nil }), WithOsquerierBackoff(100*time.Millisecond, 20*time.Millisecond)) require.NoError(t, err, "could not initialize new TUF autoupdater") // Set up normal library and querier interactions @@ -635,7 +635,7 @@ func TestDo(t *testing.T) { mockKnapsack.On("LatestOsquerydPath", mock.Anything).Return(fakeOsqBinaryPath).Maybe() // Set up autoupdater - autoupdater, err := NewTufAutoupdater(context.TODO(), mockKnapsack, http.DefaultClient, http.DefaultClient, mockQuerier, WithOsqueryRestart(func() error { return nil })) + autoupdater, err := NewTufAutoupdater(context.TODO(), mockKnapsack, http.DefaultClient, http.DefaultClient, mockQuerier, WithOsqueryRestart(func() error { return nil }), WithOsquerierBackoff(100*time.Millisecond, 20*time.Millisecond)) require.NoError(t, err, "could not initialize new TUF autoupdater") // Update the metadata client with our test root JSON @@ -710,7 +710,7 @@ func TestDo_HandlesSimultaneousUpdates(t *testing.T) { mockKnapsack.On("LatestOsquerydPath", mock.Anything).Return(fakeOsqBinaryPath) // Set up autoupdater - autoupdater, err := NewTufAutoupdater(context.TODO(), mockKnapsack, http.DefaultClient, http.DefaultClient, mockQuerier, WithOsqueryRestart(func() error { return nil })) + autoupdater, err := NewTufAutoupdater(context.TODO(), mockKnapsack, http.DefaultClient, http.DefaultClient, mockQuerier, WithOsqueryRestart(func() error { return nil }), WithOsquerierBackoff(100*time.Millisecond, 20*time.Millisecond)) require.NoError(t, err, "could not initialize new TUF autoupdater") // Update the metadata client with our test root JSON @@ -795,7 +795,7 @@ func TestDo_WillNotExecuteDuringInitialDelay(t *testing.T) { mockKnapsack.On("LatestOsquerydPath", mock.Anything).Return(fakeOsqBinaryPath) // Set up autoupdater - autoupdater, err := NewTufAutoupdater(context.TODO(), mockKnapsack, http.DefaultClient, http.DefaultClient, mockQuerier, WithOsqueryRestart(func() error { return nil })) + autoupdater, err := NewTufAutoupdater(context.TODO(), mockKnapsack, http.DefaultClient, http.DefaultClient, mockQuerier, WithOsqueryRestart(func() error { return nil }), WithOsquerierBackoff(100*time.Millisecond, 20*time.Millisecond)) require.NoError(t, err, "could not initialize new TUF autoupdater") // Update the metadata client with our test root JSON @@ -878,7 +878,7 @@ func TestFlagsChanged_UpdateChannelChanged(t *testing.T) { mockKnapsack.On("UpdateChannel").Return("nightly") // Set up autoupdater - autoupdater, err := NewTufAutoupdater(context.TODO(), mockKnapsack, http.DefaultClient, http.DefaultClient, mockQuerier, WithOsqueryRestart(func() error { return nil })) + autoupdater, err := NewTufAutoupdater(context.TODO(), mockKnapsack, http.DefaultClient, http.DefaultClient, mockQuerier, WithOsqueryRestart(func() error { return nil }), WithOsquerierBackoff(100*time.Millisecond, 20*time.Millisecond)) require.NoError(t, err, "could not initialize new TUF autoupdater") require.Equal(t, "beta", autoupdater.updateChannel) @@ -945,7 +945,7 @@ func TestFlagsChanged_PinnedVersionChanged(t *testing.T) { mockKnapsack.On("PinnedOsquerydVersion").Return(pinnedOsquerydVersion) // Set up autoupdater - autoupdater, err := NewTufAutoupdater(context.TODO(), mockKnapsack, http.DefaultClient, http.DefaultClient, mockQuerier, WithOsqueryRestart(func() error { return nil })) + autoupdater, err := NewTufAutoupdater(context.TODO(), mockKnapsack, http.DefaultClient, http.DefaultClient, mockQuerier, WithOsqueryRestart(func() error { return nil }), WithOsquerierBackoff(100*time.Millisecond, 20*time.Millisecond)) require.NoError(t, err, "could not initialize new TUF autoupdater") require.Equal(t, "", autoupdater.pinnedVersions[binaryOsqueryd]) @@ -1004,7 +1004,7 @@ func TestFlagsChanged_DuringInitialDelay(t *testing.T) { mockKnapsack.On("PinnedLauncherVersion").Return("") // Set up autoupdater - autoupdater, err := NewTufAutoupdater(context.TODO(), mockKnapsack, http.DefaultClient, http.DefaultClient, mockQuerier, WithOsqueryRestart(func() error { return nil })) + autoupdater, err := NewTufAutoupdater(context.TODO(), mockKnapsack, http.DefaultClient, http.DefaultClient, mockQuerier, WithOsqueryRestart(func() error { return nil }), WithOsquerierBackoff(100*time.Millisecond, 20*time.Millisecond)) require.NoError(t, err, "could not initialize new TUF autoupdater") require.Equal(t, pinnedLauncherVersion, autoupdater.pinnedVersions[binaryLauncher]) @@ -1135,7 +1135,7 @@ func Test_storeError(t *testing.T) { mockKnapsack.On("LatestOsquerydPath", mock.Anything).Return(fakeOsqBinaryPath) mockQuerier := newMockQuerier(t) - autoupdater, err := NewTufAutoupdater(context.TODO(), mockKnapsack, http.DefaultClient, http.DefaultClient, mockQuerier) + autoupdater, err := NewTufAutoupdater(context.TODO(), mockKnapsack, http.DefaultClient, http.DefaultClient, mockQuerier, WithOsquerierBackoff(100*time.Millisecond, 20*time.Millisecond)) require.NoError(t, err, "could not initialize new TUF autoupdater") mockLibraryManager := NewMocklibrarian(t) diff --git a/pkg/osquery/enrollment_details.go b/pkg/osquery/enrollment_details.go index a8382bfef..2ee18beb1 100644 --- a/pkg/osquery/enrollment_details.go +++ b/pkg/osquery/enrollment_details.go @@ -13,6 +13,8 @@ import ( "github.com/kolide/kit/version" "github.com/kolide/launcher/ee/agent" + "github.com/kolide/launcher/ee/agent/types" + "github.com/kolide/launcher/pkg/backoff" "github.com/kolide/launcher/pkg/osquery/runsimple" "github.com/kolide/launcher/pkg/service" "github.com/kolide/launcher/pkg/traces" @@ -21,7 +23,7 @@ import ( // getEnrollDetails returns an EnrollmentDetails struct with populated with details it can fetch without osquery. // To get the rest of the details, pass the struct to getOsqEnrollDetails. -func getRuntimeEnrollDetails() service.EnrollmentDetails { +func GetRuntimeEnrollDetails() service.EnrollmentDetails { details := service.EnrollmentDetails{ OSPlatform: runtime.GOOS, OSPlatformLike: runtime.GOOS, @@ -51,7 +53,7 @@ func getRuntimeEnrollDetails() service.EnrollmentDetails { // getOsqEnrollDetails queries osquery for enrollment details and populates the EnrollmentDetails struct. // It's expected that the caller has initially populated the struct with runtimeEnrollDetails by calling getRuntimeEnrollDetails. -func getOsqEnrollDetails(ctx context.Context, osquerydPath string, details *service.EnrollmentDetails) error { +func GetOsqEnrollDetails(ctx context.Context, osquerydPath string, details *service.EnrollmentDetails) error { ctx, span := traces.StartSpan(ctx) defer span.End() @@ -145,3 +147,32 @@ func getOsqEnrollDetails(ctx context.Context, osquerydPath string, details *serv return nil } + +// CollectAndSetEnrollmentDetails collects enrollment details from osquery and sets them in the knapsack. +func CollectAndSetEnrollmentDetails(ctx context.Context, k types.Knapsack, collectTimeout time.Duration, collectRetryInterval time.Duration) error { + ctx, span := traces.StartSpan(ctx) + defer span.End() + + // Get the runtime details + details := GetRuntimeEnrollDetails() + + latestOsquerydPath := k.LatestOsquerydPath(ctx) + + if latestOsquerydPath == "" { + span.AddEvent("no osqueryd path, skipping enrollment osquery details") + return errors.New("no osqueryd path, skipping enrollment osquery details, no osqueryd path, this is probably CI") + } + + // Get the osquery details + if err := backoff.WaitFor(func() error { + err := GetOsqEnrollDetails(ctx, latestOsquerydPath, &details) + if err != nil { + span.AddEvent("failed to get enrollment details") + } + return err + }, collectTimeout, collectRetryInterval); err != nil { + return errors.Wrap(err, "collecting enrollment details") + } + + return k.SetEnrollmentDetails(details) +} diff --git a/pkg/osquery/enrollment_details_test.go b/pkg/osquery/enrollment_details_test.go index a872f44a0..880543e6b 100644 --- a/pkg/osquery/enrollment_details_test.go +++ b/pkg/osquery/enrollment_details_test.go @@ -4,9 +4,15 @@ import ( "context" "os" "path/filepath" + "runtime" "testing" + "time" + "github.com/kolide/kit/fsutil" + typesmocks "github.com/kolide/launcher/ee/agent/types/mocks" + "github.com/kolide/launcher/pkg/packaging" "github.com/kolide/launcher/pkg/service" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -15,10 +21,10 @@ func Test_getEnrollDetails_binaryNotExist(t *testing.T) { var details service.EnrollmentDetails - err1 := getOsqEnrollDetails(context.TODO(), filepath.Join("some", "fake", "path", "to", "osqueryd"), &details) + err1 := GetOsqEnrollDetails(context.TODO(), filepath.Join("some", "fake", "path", "to", "osqueryd"), &details) require.Error(t, err1, "expected error when path does not exist") - err2 := getOsqEnrollDetails(context.TODO(), t.TempDir(), &details) + err2 := GetOsqEnrollDetails(context.TODO(), t.TempDir(), &details) require.Error(t, err2, "expected error when path is directory") } @@ -31,6 +37,68 @@ func Test_getEnrollDetails_executionError(t *testing.T) { require.NoError(t, err, "could not get current executable for test") // We expect getEnrollDetails to fail when called against an executable that is not osquery - err = getOsqEnrollDetails(context.TODO(), currentExecutable, &details) + err = GetOsqEnrollDetails(context.TODO(), currentExecutable, &details) require.Error(t, err, "should not have been able to get enroll details with non-osqueryd executable") } + +func TestCollectAndSetEnrollmentDetails(t *testing.T) { + t.Parallel() + + testRootDir := t.TempDir() + + // Download real osqueryd binary + target := packaging.Target{} + require.NoError(t, target.PlatformFromString(runtime.GOOS)) + target.Arch = packaging.ArchFlavor(runtime.GOARCH) + if runtime.GOOS == "darwin" { + target.Arch = packaging.Universal + } + + osquerydPath := filepath.Join(testRootDir, target.PlatformBinaryName("osqueryd")) + + // Fetch and copy osqueryd + downloadPath, err := packaging.FetchBinary(context.TODO(), testRootDir, "osqueryd", + target.PlatformBinaryName("osqueryd"), "stable", target) + require.NoError(t, err) + require.NoError(t, fsutil.CopyFile(downloadPath, osquerydPath)) + + // Make binary executable + require.NoError(t, os.Chmod(osquerydPath, 0755)) + + t.Run("empty osqueryd path", func(t *testing.T) { + t.Parallel() + mockKnapsack := typesmocks.NewKnapsack(t) + ctx := context.Background() + + mockKnapsack.On("LatestOsquerydPath", mock.Anything).Return("") + + err := CollectAndSetEnrollmentDetails(ctx, mockKnapsack, 1*time.Second, 100*time.Millisecond) + require.Error(t, err) + require.Contains(t, err.Error(), "no osqueryd path") + }) + + t.Run("successful collection", func(t *testing.T) { + t.Parallel() + mockKnapsack := typesmocks.NewKnapsack(t) + ctx := context.Background() + + mockKnapsack.On("LatestOsquerydPath", mock.Anything).Return(osquerydPath) + mockKnapsack.On("SetEnrollmentDetails", mock.Anything).Return(nil) + + err := CollectAndSetEnrollmentDetails(ctx, mockKnapsack, 1*time.Second, 100*time.Millisecond) + require.NoError(t, err) + mockKnapsack.AssertExpectations(t) + }) + + t.Run("collection timeout", func(t *testing.T) { + t.Parallel() + mockKnapsack := typesmocks.NewKnapsack(t) + ctx := context.Background() + + mockKnapsack.On("LatestOsquerydPath", mock.Anything).Return("/usr/local/bin/osqueryd") + + err := CollectAndSetEnrollmentDetails(ctx, mockKnapsack, 100*time.Millisecond, 100*time.Millisecond) + require.Error(t, err) + require.Contains(t, err.Error(), "collecting enrollment details") + }) +} diff --git a/pkg/osquery/extension.go b/pkg/osquery/extension.go index 48eca2f62..c68b8f206 100644 --- a/pkg/osquery/extension.go +++ b/pkg/osquery/extension.go @@ -9,7 +9,6 @@ import ( "encoding/json" "fmt" "log/slog" - "os" "sync" "sync/atomic" "time" @@ -19,7 +18,6 @@ import ( "github.com/kolide/launcher/ee/agent/storage" "github.com/kolide/launcher/ee/agent/types" "github.com/kolide/launcher/ee/uninstall" - "github.com/kolide/launcher/pkg/backoff" "github.com/kolide/launcher/pkg/osquery/runtime/history" "github.com/kolide/launcher/pkg/service" "github.com/kolide/launcher/pkg/traces" @@ -400,36 +398,13 @@ func (e *Extension) Enroll(ctx context.Context) (string, bool, error) { // We used to see the enrollment details fail, but now that we're running as an exec, // it seems less likely. Try a couple times, but backoff fast. - enrollDetails := getRuntimeEnrollDetails() - if osqPath := e.knapsack.LatestOsquerydPath(ctx); osqPath == "" { - e.slogger.Log(ctx, slog.LevelInfo, - "skipping enrollment osquery details, no osqueryd path, this is probably CI", - ) - span.AddEvent("skipping_enrollment_details") - } else { - if err := backoff.WaitFor(func() error { - err = getOsqEnrollDetails(ctx, osqPath, &enrollDetails) - if err != nil { - e.slogger.Log(ctx, slog.LevelDebug, - "getOsqEnrollDetails failed in backoff", - "err", err, - ) - } - return err - }, 30*time.Second, 5*time.Second); err != nil { - if os.Getenv("LAUNCHER_DEBUG_ENROLL_DETAILS_REQUIRED") == "true" { - return "", true, fmt.Errorf("query osq enrollment details: %w", err) - } - - e.slogger.Log(ctx, slog.LevelError, - "failed to get osq enrollment details with retries, moving on", - "err", err, - ) - traces.SetError(span, fmt.Errorf("query osq enrollment details: %w", err)) - } else { - span.AddEvent("got_enrollment_details") - } + enrollDetails, err := e.knapsack.GetEnrollmentDetails() + if err != nil { + err = fmt.Errorf("getting enrollment details: %w", err) + traces.SetError(span, err) + return "", true, err } + // If no cached node key, enroll for new node key // note that we set invalid two ways. Via the return, _or_ via isNodeInvaliderr keyString, invalid, err := e.serviceClient.RequestEnrollment(ctx, enrollSecret, identifier, enrollDetails) diff --git a/pkg/osquery/extension_test.go b/pkg/osquery/extension_test.go index 8e8cbc5d5..de561a0d8 100644 --- a/pkg/osquery/extension_test.go +++ b/pkg/osquery/extension_test.go @@ -60,6 +60,7 @@ func makeKnapsack(t *testing.T, db *bbolt.DB) types.Knapsack { m.On("Slogger").Return(multislogger.NewNopLogger()) m.On("ReadEnrollSecret").Maybe().Return("enroll_secret", nil) m.On("RootDirectory").Maybe().Return("whatever") + m.On("GetEnrollmentDetails").Maybe().Return(types.EnrollmentDetails{}, nil) return m } @@ -70,6 +71,7 @@ func TestNewExtensionEmptyEnrollSecret(t *testing.T) { m.On("ConfigStore").Return(storageci.NewStore(t, multislogger.NewNopLogger(), storage.ConfigStore.String())) m.On("Slogger").Return(multislogger.NewNopLogger()) m.On("ReadEnrollSecret").Maybe().Return("", errors.New("test")) + m.On("GetEnrollmentDetails").Maybe().Return(types.EnrollmentDetails{}, nil) // We should be able to make an extension despite an empty enroll secret e, err := NewExtension(context.TODO(), &mock.KolideService{}, m, ulid.New(), ExtensionOpts{}) @@ -219,6 +221,7 @@ func TestExtensionEnroll(t *testing.T) { k.On("Slogger").Return(multislogger.NewNopLogger()) expectedEnrollSecret := "foo_secret" k.On("ReadEnrollSecret").Maybe().Return(expectedEnrollSecret, nil) + k.On("GetEnrollmentDetails").Return(types.EnrollmentDetails{}, nil) e, err := NewExtension(context.TODO(), m, k, types.DefaultRegistrationID, ExtensionOpts{}) require.Nil(t, err) @@ -642,6 +645,7 @@ func TestExtensionWriteBufferedLogsEnrollmentInvalid(t *testing.T) { k.On("LatestOsquerydPath", testifymock.Anything).Maybe().Return("") k.On("Slogger").Return(multislogger.NewNopLogger()) k.On("ReadEnrollSecret").Maybe().Return("enroll_secret", nil) + k.On("GetEnrollmentDetails").Return(types.EnrollmentDetails{}, nil) e, err := NewExtension(context.TODO(), m, k, ulid.New(), ExtensionOpts{}) require.Nil(t, err) @@ -1034,6 +1038,7 @@ func TestExtensionGetQueriesEnrollmentInvalid(t *testing.T) { k.On("LatestOsquerydPath", testifymock.Anything).Maybe().Return("") k.On("Slogger").Return(multislogger.NewNopLogger()) k.On("ReadEnrollSecret").Return("enroll_secret", nil) + k.On("GetEnrollmentDetails").Return(types.EnrollmentDetails{}, nil) e, err := NewExtension(context.TODO(), m, k, ulid.New(), ExtensionOpts{}) require.Nil(t, err) diff --git a/pkg/osquery/runtime/osqueryinstance_test.go b/pkg/osquery/runtime/osqueryinstance_test.go index 471f898cb..254011df5 100644 --- a/pkg/osquery/runtime/osqueryinstance_test.go +++ b/pkg/osquery/runtime/osqueryinstance_test.go @@ -262,6 +262,7 @@ func TestLaunch(t *testing.T) { k.On("ReadEnrollSecret").Return("", nil) k.On("LatestOsquerydPath", mock.Anything).Return(testOsqueryBinary) k.On("OsqueryHealthcheckStartupDelay").Return(10 * time.Second) + k.On("GetEnrollmentDetails").Return(types.EnrollmentDetails{}, nil) k.On("RegisterChangeObserver", mock.Anything, keys.UpdateChannel).Maybe() k.On("RegisterChangeObserver", mock.Anything, keys.PinnedLauncherVersion).Maybe() k.On("RegisterChangeObserver", mock.Anything, keys.PinnedOsquerydVersion).Maybe() diff --git a/pkg/osquery/runtime/runtime_posix_test.go b/pkg/osquery/runtime/runtime_posix_test.go index 8529e0077..08c920d57 100644 --- a/pkg/osquery/runtime/runtime_posix_test.go +++ b/pkg/osquery/runtime/runtime_posix_test.go @@ -56,6 +56,7 @@ func TestOsquerySlowStart(t *testing.T) { k.On("LogMaxBytesPerBatch").Return(0).Maybe() k.On("Transport").Return("jsonrpc").Maybe() k.On("ReadEnrollSecret").Return("", nil).Maybe() + k.On("GetEnrollmentDetails").Return(types.EnrollmentDetails{}, nil).Maybe() k.On("InModernStandby").Return(false).Maybe() k.On("RegisterChangeObserver", mock.Anything, keys.UpdateChannel).Maybe() k.On("RegisterChangeObserver", mock.Anything, keys.PinnedLauncherVersion).Maybe() @@ -111,6 +112,7 @@ func TestExtensionSocketPath(t *testing.T) { k.On("LogMaxBytesPerBatch").Return(0).Maybe() k.On("Transport").Return("jsonrpc").Maybe() k.On("ReadEnrollSecret").Return("", nil).Maybe() + k.On("GetEnrollmentDetails").Return(types.EnrollmentDetails{}, nil).Maybe() k.On("InModernStandby").Return(false).Maybe() k.On("RegisterChangeObserver", mock.Anything, keys.UpdateChannel).Maybe() k.On("RegisterChangeObserver", mock.Anything, keys.PinnedLauncherVersion).Maybe() diff --git a/pkg/osquery/runtime/runtime_test.go b/pkg/osquery/runtime/runtime_test.go index 4fb39a2ed..f4141c8fc 100644 --- a/pkg/osquery/runtime/runtime_test.go +++ b/pkg/osquery/runtime/runtime_test.go @@ -144,6 +144,7 @@ func TestBadBinaryPath(t *testing.T) { k.On("LogMaxBytesPerBatch").Return(0).Maybe() k.On("Transport").Return("jsonrpc").Maybe() k.On("ReadEnrollSecret").Return("", nil).Maybe() + k.On("GetEnrollmentDetails").Return(types.EnrollmentDetails{}, nil).Maybe() k.On("RegisterChangeObserver", mock.Anything, keys.UpdateChannel).Maybe() k.On("RegisterChangeObserver", mock.Anything, keys.PinnedLauncherVersion).Maybe() k.On("RegisterChangeObserver", mock.Anything, keys.PinnedOsquerydVersion).Maybe() @@ -188,6 +189,7 @@ func TestWithOsqueryFlags(t *testing.T) { k.On("LogMaxBytesPerBatch").Return(0).Maybe() k.On("Transport").Return("jsonrpc").Maybe() k.On("ReadEnrollSecret").Return("", nil).Maybe() + k.On("GetEnrollmentDetails").Return(types.EnrollmentDetails{}, nil).Maybe() k.On("InModernStandby").Return(false).Maybe() k.On("RegisterChangeObserver", mock.Anything, keys.UpdateChannel).Maybe() k.On("RegisterChangeObserver", mock.Anything, keys.PinnedLauncherVersion).Maybe() @@ -228,6 +230,7 @@ func TestFlagsChanged(t *testing.T) { k.On("LogMaxBytesPerBatch").Return(0).Maybe() k.On("Transport").Return("jsonrpc").Maybe() k.On("ReadEnrollSecret").Return("", nil).Maybe() + k.On("GetEnrollmentDetails").Return(types.EnrollmentDetails{}, nil).Maybe() k.On("InModernStandby").Return(false).Maybe() k.On("RegisterChangeObserver", mock.Anything, keys.UpdateChannel).Maybe() k.On("RegisterChangeObserver", mock.Anything, keys.PinnedLauncherVersion).Maybe() @@ -429,6 +432,7 @@ func TestSimplePath(t *testing.T) { k.On("LogMaxBytesPerBatch").Return(0).Maybe() k.On("Transport").Return("jsonrpc").Maybe() k.On("ReadEnrollSecret").Return("", nil).Maybe() + k.On("GetEnrollmentDetails").Return(types.EnrollmentDetails{}, nil).Maybe() k.On("InModernStandby").Return(false).Maybe() k.On("RegisterChangeObserver", mock.Anything, keys.UpdateChannel).Maybe() k.On("RegisterChangeObserver", mock.Anything, keys.PinnedLauncherVersion).Maybe() @@ -473,6 +477,7 @@ func TestMultipleInstances(t *testing.T) { k.On("LogMaxBytesPerBatch").Return(0).Maybe() k.On("Transport").Return("jsonrpc").Maybe() k.On("ReadEnrollSecret").Return("", nil).Maybe() + k.On("GetEnrollmentDetails").Return(types.EnrollmentDetails{}, nil).Maybe() k.On("InModernStandby").Return(false).Maybe() k.On("RegisterChangeObserver", mock.Anything, keys.UpdateChannel).Maybe() k.On("RegisterChangeObserver", mock.Anything, keys.PinnedLauncherVersion).Maybe() @@ -540,6 +545,7 @@ func TestRunnerHandlesImmediateShutdownWithMultipleInstances(t *testing.T) { k.On("LogMaxBytesPerBatch").Return(0).Maybe() k.On("Transport").Return("jsonrpc").Maybe() k.On("ReadEnrollSecret").Return("", nil).Maybe() + k.On("GetEnrollmentDetails").Return(types.EnrollmentDetails{}, nil).Maybe() k.On("InModernStandby").Return(false).Maybe() k.On("RegisterChangeObserver", mock.Anything, keys.UpdateChannel).Maybe() k.On("RegisterChangeObserver", mock.Anything, keys.PinnedLauncherVersion).Maybe() @@ -599,6 +605,7 @@ func TestMultipleShutdowns(t *testing.T) { k.On("LogMaxBytesPerBatch").Return(0).Maybe() k.On("Transport").Return("jsonrpc").Maybe() k.On("ReadEnrollSecret").Return("", nil).Maybe() + k.On("GetEnrollmentDetails").Return(types.EnrollmentDetails{}, nil).Maybe() k.On("InModernStandby").Return(false).Maybe() k.On("RegisterChangeObserver", mock.Anything, keys.UpdateChannel).Maybe() k.On("RegisterChangeObserver", mock.Anything, keys.PinnedLauncherVersion).Maybe() @@ -639,6 +646,7 @@ func TestOsqueryDies(t *testing.T) { k.On("LogMaxBytesPerBatch").Return(0).Maybe() k.On("Transport").Return("jsonrpc").Maybe() k.On("ReadEnrollSecret").Return("", nil).Maybe() + k.On("GetEnrollmentDetails").Return(types.EnrollmentDetails{}, nil).Maybe() k.On("InModernStandby").Return(false).Maybe() k.On("RegisterChangeObserver", mock.Anything, keys.UpdateChannel).Maybe() k.On("RegisterChangeObserver", mock.Anything, keys.PinnedLauncherVersion).Maybe() @@ -782,6 +790,7 @@ func setupOsqueryInstanceForTests(t *testing.T) (runner *Runner, logBytes *threa k.On("LogMaxBytesPerBatch").Return(0).Maybe() k.On("Transport").Return("jsonrpc").Maybe() k.On("ReadEnrollSecret").Return("", nil).Maybe() + k.On("GetEnrollmentDetails").Return(types.EnrollmentDetails{}, nil).Maybe() k.On("InModernStandby").Return(false).Maybe() k.On("RegisterChangeObserver", mock.Anything, keys.UpdateChannel).Maybe() k.On("RegisterChangeObserver", mock.Anything, keys.PinnedLauncherVersion).Maybe() diff --git a/pkg/service/request_enrollment.go b/pkg/service/request_enrollment.go index 61ee6a1d9..2b3b8bdf1 100644 --- a/pkg/service/request_enrollment.go +++ b/pkg/service/request_enrollment.go @@ -10,6 +10,7 @@ import ( "github.com/go-kit/kit/transport/http/jsonrpc" "github.com/kolide/kit/contexts/uuid" + "github.com/kolide/launcher/ee/agent/types" pb "github.com/kolide/launcher/pkg/pb/launcher" "github.com/kolide/launcher/pkg/traces" ) @@ -20,25 +21,7 @@ type enrollmentRequest struct { EnrollmentDetails EnrollmentDetails } -type EnrollmentDetails struct { - OSVersion string `json:"os_version"` - OSBuildID string `json:"os_build_id"` - OSPlatform string `json:"os_platform"` - Hostname string `json:"hostname"` - HardwareVendor string `json:"hardware_vendor"` - HardwareModel string `json:"hardware_model"` - HardwareSerial string `json:"hardware_serial"` - OsqueryVersion string `json:"osquery_version"` - LauncherHardwareKey string `json:"launcher_hardware_key"` - LauncherHardwareKeySource string `json:"launcher_hardware_key_source"` - LauncherLocalKey string `json:"launcher_local_key"` - LauncherVersion string `json:"launcher_version"` - OSName string `json:"os_name"` - OSPlatformLike string `json:"os_platform_like"` - GOOS string `json:"goos"` - GOARCH string `json:"goarch"` - HardwareUUID string `json:"hardware_uuid"` -} +type EnrollmentDetails = types.EnrollmentDetails type enrollmentResponse struct { jsonRpcResponse