diff --git a/glide.lock b/glide.lock index 2ba22bb1a0..154d637c32 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 08220902abb9783f0474c20495d92f407f4028b56ab95879b867c0dec4aab7cc -updated: 2017-03-29T10:16:52.853564516-07:00 +hash: 1baa8047c7c6da3ca484e0bfebd881a17181a76ccccbddb2cff087c3f6de97c9 +updated: 2017-04-05T15:21:30.066640809-07:00 imports: - name: github.com/alecthomas/template version: a0175ee3bccc567396460bf5acd36800cb10c49c @@ -128,7 +128,7 @@ imports: subpackages: - mem - name: github.com/spf13/cast - version: e31f36ffc91a2ba9ddb72a4b6a607ff9b3d3cb63 + version: ce135a4ebeee6cfe9a26c93ee0d37825f26113c7 - name: github.com/spf13/cobra version: 37c3f8060359192150945916cbc2d72bce804b4d - name: github.com/spf13/jwalterweatherman diff --git a/glide.yaml b/glide.yaml index 49195a1837..242acacbd1 100644 --- a/glide.yaml +++ b/glide.yaml @@ -42,7 +42,6 @@ import: version: fd703108daeb23d77c124d12978e9b6c4f28f034 - package: github.com/spf13/cobra - package: github.com/spf13/viper -- package: github.com/spf13/cast - package: gopkg.in/natefinch/lumberjack.v2 version: v2.0 - package: github.com/golang/mock @@ -69,3 +68,5 @@ import: - package: github.com/ryanuber/go-license - package: github.com/igm/sockjs-go - package: github.com/e-dard/netbug +- package: github.com/spf13/cast + version: ~1.0.0 diff --git a/server/datastore/mysql/hosts.go b/server/datastore/mysql/hosts.go index 1d2ad9c330..3014604378 100644 --- a/server/datastore/mysql/hosts.go +++ b/server/datastore/mysql/hosts.go @@ -173,7 +173,10 @@ func (d *Datastore) SaveHost(host *kolide.Host) error { platform_like = ?, code_name = ?, cpu_logical_cores = ?, - seen_time = ? + seen_time = ?, + distributed_interval = ?, + config_tls_refresh = ?, + logger_tls_period = ? WHERE id = ? ` @@ -207,6 +210,9 @@ func (d *Datastore) SaveHost(host *kolide.Host) error { host.CodeName, host.CPULogicalCores, host.SeenTime, + host.DistributedInterval, + host.ConfigTLSRefresh, + host.LoggerTLSPeriod, host.ID) if err != nil { tx.Rollback() diff --git a/server/datastore/mysql/migrations/tables/20170331111922_AddIntervalsToHosts.go b/server/datastore/mysql/migrations/tables/20170331111922_AddIntervalsToHosts.go new file mode 100644 index 0000000000..69a4bc61bd --- /dev/null +++ b/server/datastore/mysql/migrations/tables/20170331111922_AddIntervalsToHosts.go @@ -0,0 +1,27 @@ +package tables + +import "database/sql" + +func init() { + MigrationClient.AddMigration(Up_20170331111922, Down_20170331111922) +} + +func Up_20170331111922(tx *sql.Tx) error { + _, err := tx.Exec( + "ALTER TABLE `hosts` " + + "ADD COLUMN `distributed_interval` int DEFAULT 0, " + + "ADD COLUMN `logger_tls_period` int DEFAULT 0, " + + "ADD COLUMN `config_tls_refresh` int DEFAULT 0;", + ) + return err +} + +func Down_20170331111922(tx *sql.Tx) error { + _, err := tx.Exec( + "ALTER TABLE `hosts` " + + "DROP COLUMN `distributed_interval`, " + + "DROP COLUMN `logger_tls_period`, " + + "DROP COLUMN `config_tls_refresh`;", + ) + return err +} diff --git a/server/kolide/hosts.go b/server/kolide/hosts.go index 5aa8d724fd..76c87f9e7e 100644 --- a/server/kolide/hosts.go +++ b/server/kolide/hosts.go @@ -86,6 +86,9 @@ type Host struct { // can be found in the NetworkInterfaces element with the same ip_address. PrimaryNetworkInterfaceID *uint `json:"primary_ip_id,omitempty" db:"primary_ip_id"` NetworkInterfaces []*NetworkInterface `json:"network_interfaces" db:"-"` + DistributedInterval uint `json:"distributed_interval" db:"distributed_interval"` + ConfigTLSRefresh uint `json:"config_tls_refresh" db:"config_tls_refresh"` + LoggerTLSPeriod uint `json:"logger_tls_period" db:"logger_tls_period"` } // HostSummary is a structure which represents a data summary about the total diff --git a/server/mock/datastore.go b/server/mock/datastore.go index d526aad5ff..a5800c9768 100644 --- a/server/mock/datastore.go +++ b/server/mock/datastore.go @@ -5,30 +5,33 @@ package mock //go:generate mockimpl -o datastore_appconfig.go "s *AppConfigStore" "kolide.AppConfigStore" //go:generate mockimpl -o datastore_licenses.go "s *LicenseStore" "kolide.LicenseStore" //go:generate mockimpl -o datastore_labels.go "s *LabelStore" "kolide.LabelStore" -//go:generate mockimpl -o dateastore_decorators.go "s *DecoratorStore" "kolide.DecoratorStore" +//go:generate mockimpl -o datastore_decorators.go "s *DecoratorStore" "kolide.DecoratorStore" +//go:generate mockimpl -o datastore_options.go "s *OptionStore" "kolide.OptionStore" +//go:generate mockimpl -o datastore_packs.go "s *PackStore" "kolide.PackStore" +//go:generate mockimpl -o datastore_hosts.go "s *HostStore" "kolide.HostStore" import "github.com/kolide/kolide/server/kolide" var _ kolide.Datastore = (*Store)(nil) type Store struct { - kolide.HostStore - kolide.PackStore kolide.CampaignStore kolide.SessionStore kolide.PasswordResetStore kolide.QueryStore - kolide.OptionStore kolide.ScheduledQueryStore kolide.FileIntegrityMonitoringStore kolide.YARAStore kolide.TargetStore - LicenseStore - InviteStore - UserStore AppConfigStore - LabelStore DecoratorStore + HostStore + InviteStore + LabelStore + LicenseStore + OptionStore + PackStore + UserStore } func (m *Store) Drop() error { diff --git a/server/mock/dateastore_decorators.go b/server/mock/datastore_decorators.go similarity index 100% rename from server/mock/dateastore_decorators.go rename to server/mock/datastore_decorators.go diff --git a/server/mock/datastore_hosts.go b/server/mock/datastore_hosts.go new file mode 100644 index 0000000000..ba56e75ca1 --- /dev/null +++ b/server/mock/datastore_hosts.go @@ -0,0 +1,123 @@ +// Automatically generated by mockimpl. DO NOT EDIT! + +package mock + +import ( + "time" + + "github.com/kolide/kolide/server/kolide" +) + +var _ kolide.HostStore = (*HostStore)(nil) + +type NewHostFunc func(host *kolide.Host) (*kolide.Host, error) + +type SaveHostFunc func(host *kolide.Host) error + +type DeleteHostFunc func(hid uint) error + +type HostFunc func(id uint) (*kolide.Host, error) + +type ListHostsFunc func(opt kolide.ListOptions) ([]*kolide.Host, error) + +type EnrollHostFunc func(osqueryHostId string, nodeKeySize int) (*kolide.Host, error) + +type AuthenticateHostFunc func(nodeKey string) (*kolide.Host, error) + +type MarkHostSeenFunc func(host *kolide.Host, t time.Time) error + +type GenerateHostStatusStatisticsFunc func(now time.Time, onlineInterval time.Duration) (online uint, offline uint, mia uint, new uint, err error) + +type SearchHostsFunc func(query string, omit ...uint) ([]*kolide.Host, error) + +type DistributedQueriesForHostFunc func(host *kolide.Host) (map[uint]string, error) + +type HostStore struct { + NewHostFunc NewHostFunc + NewHostFuncInvoked bool + + SaveHostFunc SaveHostFunc + SaveHostFuncInvoked bool + + DeleteHostFunc DeleteHostFunc + DeleteHostFuncInvoked bool + + HostFunc HostFunc + HostFuncInvoked bool + + ListHostsFunc ListHostsFunc + ListHostsFuncInvoked bool + + EnrollHostFunc EnrollHostFunc + EnrollHostFuncInvoked bool + + AuthenticateHostFunc AuthenticateHostFunc + AuthenticateHostFuncInvoked bool + + MarkHostSeenFunc MarkHostSeenFunc + MarkHostSeenFuncInvoked bool + + GenerateHostStatusStatisticsFunc GenerateHostStatusStatisticsFunc + GenerateHostStatusStatisticsFuncInvoked bool + + SearchHostsFunc SearchHostsFunc + SearchHostsFuncInvoked bool + + DistributedQueriesForHostFunc DistributedQueriesForHostFunc + DistributedQueriesForHostFuncInvoked bool +} + +func (s *HostStore) NewHost(host *kolide.Host) (*kolide.Host, error) { + s.NewHostFuncInvoked = true + return s.NewHostFunc(host) +} + +func (s *HostStore) SaveHost(host *kolide.Host) error { + s.SaveHostFuncInvoked = true + return s.SaveHostFunc(host) +} + +func (s *HostStore) DeleteHost(hid uint) error { + s.DeleteHostFuncInvoked = true + return s.DeleteHostFunc(hid) +} + +func (s *HostStore) Host(id uint) (*kolide.Host, error) { + s.HostFuncInvoked = true + return s.HostFunc(id) +} + +func (s *HostStore) ListHosts(opt kolide.ListOptions) ([]*kolide.Host, error) { + s.ListHostsFuncInvoked = true + return s.ListHostsFunc(opt) +} + +func (s *HostStore) EnrollHost(osqueryHostId string, nodeKeySize int) (*kolide.Host, error) { + s.EnrollHostFuncInvoked = true + return s.EnrollHostFunc(osqueryHostId, nodeKeySize) +} + +func (s *HostStore) AuthenticateHost(nodeKey string) (*kolide.Host, error) { + s.AuthenticateHostFuncInvoked = true + return s.AuthenticateHostFunc(nodeKey) +} + +func (s *HostStore) MarkHostSeen(host *kolide.Host, t time.Time) error { + s.MarkHostSeenFuncInvoked = true + return s.MarkHostSeenFunc(host, t) +} + +func (s *HostStore) GenerateHostStatusStatistics(now time.Time, onlineInterval time.Duration) (online uint, offline uint, mia uint, new uint, err error) { + s.GenerateHostStatusStatisticsFuncInvoked = true + return s.GenerateHostStatusStatisticsFunc(now, onlineInterval) +} + +func (s *HostStore) SearchHosts(query string, omit ...uint) ([]*kolide.Host, error) { + s.SearchHostsFuncInvoked = true + return s.SearchHostsFunc(query, omit...) +} + +func (s *HostStore) DistributedQueriesForHost(host *kolide.Host) (map[uint]string, error) { + s.DistributedQueriesForHostFuncInvoked = true + return s.DistributedQueriesForHostFunc(host) +} diff --git a/server/mock/datastore_options.go b/server/mock/datastore_options.go new file mode 100644 index 0000000000..3affaa0458 --- /dev/null +++ b/server/mock/datastore_options.go @@ -0,0 +1,69 @@ +// Automatically generated by mockimpl. DO NOT EDIT! + +package mock + +import "github.com/kolide/kolide/server/kolide" + +var _ kolide.OptionStore = (*OptionStore)(nil) + +type SaveOptionsFunc func(opts []kolide.Option) error + +type ListOptionsFunc func() ([]kolide.Option, error) + +type OptionFunc func(id uint) (*kolide.Option, error) + +type OptionByNameFunc func(name string) (*kolide.Option, error) + +type GetOsqueryConfigOptionsFunc func() (map[string]interface{}, error) + +type ResetOptionsFunc func() ([]kolide.Option, error) + +type OptionStore struct { + SaveOptionsFunc SaveOptionsFunc + SaveOptionsFuncInvoked bool + + ListOptionsFunc ListOptionsFunc + ListOptionsFuncInvoked bool + + OptionFunc OptionFunc + OptionFuncInvoked bool + + OptionByNameFunc OptionByNameFunc + OptionByNameFuncInvoked bool + + GetOsqueryConfigOptionsFunc GetOsqueryConfigOptionsFunc + GetOsqueryConfigOptionsFuncInvoked bool + + ResetOptionsFunc ResetOptionsFunc + ResetOptionsFuncInvoked bool +} + +func (s *OptionStore) SaveOptions(opts []kolide.Option) error { + s.SaveOptionsFuncInvoked = true + return s.SaveOptionsFunc(opts) +} + +func (s *OptionStore) ListOptions() ([]kolide.Option, error) { + s.ListOptionsFuncInvoked = true + return s.ListOptionsFunc() +} + +func (s *OptionStore) Option(id uint) (*kolide.Option, error) { + s.OptionFuncInvoked = true + return s.OptionFunc(id) +} + +func (s *OptionStore) OptionByName(name string) (*kolide.Option, error) { + s.OptionByNameFuncInvoked = true + return s.OptionByNameFunc(name) +} + +func (s *OptionStore) GetOsqueryConfigOptions() (map[string]interface{}, error) { + s.GetOsqueryConfigOptionsFuncInvoked = true + return s.GetOsqueryConfigOptionsFunc() +} + +func (s *OptionStore) ResetOptions() ([]kolide.Option, error) { + s.ResetOptionsFuncInvoked = true + return s.ResetOptionsFunc() +} diff --git a/server/mock/datastore_packs.go b/server/mock/datastore_packs.go new file mode 100644 index 0000000000..32967e18c1 --- /dev/null +++ b/server/mock/datastore_packs.go @@ -0,0 +1,139 @@ +// Automatically generated by mockimpl. DO NOT EDIT! + +package mock + +import "github.com/kolide/kolide/server/kolide" + +var _ kolide.PackStore = (*PackStore)(nil) + +type NewPackFunc func(pack *kolide.Pack) (*kolide.Pack, error) + +type SavePackFunc func(pack *kolide.Pack) error + +type DeletePackFunc func(pid uint) error + +type PackFunc func(pid uint) (*kolide.Pack, error) + +type ListPacksFunc func(opt kolide.ListOptions) ([]*kolide.Pack, error) + +type PackByNameFunc func(name string) (*kolide.Pack, bool, error) + +type AddLabelToPackFunc func(lid uint, pid uint) error + +type RemoveLabelFromPackFunc func(lid uint, pid uint) error + +type ListLabelsForPackFunc func(pid uint) ([]*kolide.Label, error) + +type AddHostToPackFunc func(hid uint, pid uint) error + +type RemoveHostFromPackFunc func(hid uint, pid uint) error + +type ListHostsInPackFunc func(pid uint, opt kolide.ListOptions) ([]uint, error) + +type ListExplicitHostsInPackFunc func(pid uint, opt kolide.ListOptions) ([]uint, error) + +type PackStore struct { + NewPackFunc NewPackFunc + NewPackFuncInvoked bool + + SavePackFunc SavePackFunc + SavePackFuncInvoked bool + + DeletePackFunc DeletePackFunc + DeletePackFuncInvoked bool + + PackFunc PackFunc + PackFuncInvoked bool + + ListPacksFunc ListPacksFunc + ListPacksFuncInvoked bool + + PackByNameFunc PackByNameFunc + PackByNameFuncInvoked bool + + AddLabelToPackFunc AddLabelToPackFunc + AddLabelToPackFuncInvoked bool + + RemoveLabelFromPackFunc RemoveLabelFromPackFunc + RemoveLabelFromPackFuncInvoked bool + + ListLabelsForPackFunc ListLabelsForPackFunc + ListLabelsForPackFuncInvoked bool + + AddHostToPackFunc AddHostToPackFunc + AddHostToPackFuncInvoked bool + + RemoveHostFromPackFunc RemoveHostFromPackFunc + RemoveHostFromPackFuncInvoked bool + + ListHostsInPackFunc ListHostsInPackFunc + ListHostsInPackFuncInvoked bool + + ListExplicitHostsInPackFunc ListExplicitHostsInPackFunc + ListExplicitHostsInPackFuncInvoked bool +} + +func (s *PackStore) NewPack(pack *kolide.Pack) (*kolide.Pack, error) { + s.NewPackFuncInvoked = true + return s.NewPackFunc(pack) +} + +func (s *PackStore) SavePack(pack *kolide.Pack) error { + s.SavePackFuncInvoked = true + return s.SavePackFunc(pack) +} + +func (s *PackStore) DeletePack(pid uint) error { + s.DeletePackFuncInvoked = true + return s.DeletePackFunc(pid) +} + +func (s *PackStore) Pack(pid uint) (*kolide.Pack, error) { + s.PackFuncInvoked = true + return s.PackFunc(pid) +} + +func (s *PackStore) ListPacks(opt kolide.ListOptions) ([]*kolide.Pack, error) { + s.ListPacksFuncInvoked = true + return s.ListPacksFunc(opt) +} + +func (s *PackStore) PackByName(name string) (*kolide.Pack, bool, error) { + s.PackByNameFuncInvoked = true + return s.PackByNameFunc(name) +} + +func (s *PackStore) AddLabelToPack(lid uint, pid uint) error { + s.AddLabelToPackFuncInvoked = true + return s.AddLabelToPackFunc(lid, pid) +} + +func (s *PackStore) RemoveLabelFromPack(lid uint, pid uint) error { + s.RemoveLabelFromPackFuncInvoked = true + return s.RemoveLabelFromPackFunc(lid, pid) +} + +func (s *PackStore) ListLabelsForPack(pid uint) ([]*kolide.Label, error) { + s.ListLabelsForPackFuncInvoked = true + return s.ListLabelsForPackFunc(pid) +} + +func (s *PackStore) AddHostToPack(hid uint, pid uint) error { + s.AddHostToPackFuncInvoked = true + return s.AddHostToPackFunc(hid, pid) +} + +func (s *PackStore) RemoveHostFromPack(hid uint, pid uint) error { + s.RemoveHostFromPackFuncInvoked = true + return s.RemoveHostFromPackFunc(hid, pid) +} + +func (s *PackStore) ListHostsInPack(pid uint, opt kolide.ListOptions) ([]uint, error) { + s.ListHostsInPackFuncInvoked = true + return s.ListHostsInPackFunc(pid, opt) +} + +func (s *PackStore) ListExplicitHostsInPack(pid uint, opt kolide.ListOptions) ([]uint, error) { + s.ListExplicitHostsInPackFuncInvoked = true + return s.ListExplicitHostsInPackFunc(pid, opt) +} diff --git a/server/service/service_osquery.go b/server/service/service_osquery.go index 6fcb40ff39..d4547d9c07 100644 --- a/server/service/service_osquery.go +++ b/server/service/service_osquery.go @@ -13,6 +13,7 @@ import ( "github.com/kolide/kolide/server/kolide" "github.com/kolide/kolide/server/pubsub" "github.com/pkg/errors" + "github.com/spf13/cast" ) type osqueryError struct { @@ -148,6 +149,32 @@ func (svc service) GetClientConfig(ctx context.Context) (*kolide.OsqueryConfig, } } + // Save interval values if they have been updated. Note + // config_tls_refresh can only be set in the osquery flags so is + // ignored here. + saveHost := false + + distributedIntervalVal, ok := config.Options["distributed_interval"] + distributedInterval, err := cast.ToUintE(distributedIntervalVal) + if ok && err == nil && host.DistributedInterval != distributedInterval { + host.DistributedInterval = distributedInterval + saveHost = true + } + + loggerTLSPeriodVal, ok := config.Options["logger_tls_period"] + loggerTLSPeriod, err := cast.ToUintE(loggerTLSPeriodVal) + if ok && err == nil && host.LoggerTLSPeriod != loggerTLSPeriod { + host.LoggerTLSPeriod = loggerTLSPeriod + saveHost = true + } + + if saveHost { + err := svc.ds.SaveHost(&host) + if err != nil { + return nil, err + } + } + return config, nil } @@ -209,109 +236,6 @@ var detailQueries = map[string]struct { Query string IngestFunc func(logger log.Logger, host *kolide.Host, rows []map[string]string) error }{ - "osquery_info": { - Query: "select * from osquery_info limit 1", - IngestFunc: func(logger log.Logger, host *kolide.Host, rows []map[string]string) error { - if len(rows) != 1 { - logger.Log("component", "service", "method", "IngestFunc", "err", - fmt.Sprintf("detail_query_osquery_info expected single result got %d", len(rows))) - return nil - } - - host.OsqueryVersion = rows[0]["version"] - - return nil - }, - }, - "system_info": { - Query: "select * from system_info limit 1", - IngestFunc: func(logger log.Logger, host *kolide.Host, rows []map[string]string) error { - if len(rows) != 1 { - logger.Log("component", "service", "method", "IngestFunc", "err", - fmt.Sprintf("detail_query_system_info expected single result got %d", len(rows))) - return nil - } - - var err error - host.PhysicalMemory, err = strconv.Atoi(rows[0]["physical_memory"]) - if err != nil { - return err - } - host.HostName = rows[0]["hostname"] - host.UUID = rows[0]["uuid"] - host.CPUType = rows[0]["cpu_type"] - host.CPUSubtype = rows[0]["cpu_subtype"] - host.CPUBrand = rows[0]["cpu_brand"] - host.CPUPhysicalCores, err = strconv.Atoi(rows[0]["cpu_physical_cores"]) - if err != nil { - return err - } - host.CPULogicalCores, err = strconv.Atoi(rows[0]["cpu_logical_cores"]) - if err != nil { - return err - } - host.HardwareVendor = rows[0]["hardware_vendor"] - host.HardwareModel = rows[0]["hardware_model"] - host.HardwareVersion = rows[0]["hardware_version"] - host.HardwareSerial = rows[0]["hardware_serial"] - host.ComputerName = rows[0]["computer_name"] - return nil - }, - }, - "os_version": { - Query: "select * from os_version limit 1", - IngestFunc: func(logger log.Logger, host *kolide.Host, rows []map[string]string) error { - if len(rows) != 1 { - logger.Log("component", "service", "method", "IngestFunc", "err", - fmt.Sprintf("detail_query_os_version expected single result got %d", len(rows))) - return nil - } - - host.OSVersion = fmt.Sprintf( - "%s %s.%s.%s", - rows[0]["name"], - rows[0]["major"], - rows[0]["minor"], - rows[0]["patch"], - ) - host.OSVersion = strings.Trim(host.OSVersion, ".") - - if build, ok := rows[0]["build"]; ok { - host.Build = build - } - - host.Platform = rows[0]["platform"] - host.PlatformLike = rows[0]["platform_like"] - host.CodeName = rows[0]["code_name"] - - // On centos6 there is an osquery bug that leaves - // platform empty. Here we workaround. - if host.Platform == "" && - strings.Contains(strings.ToLower(rows[0]["name"]), "centos") { - host.Platform = "centos" - } - - return nil - }, - }, - "uptime": { - Query: "select * from uptime limit 1", - IngestFunc: func(logger log.Logger, host *kolide.Host, rows []map[string]string) error { - if len(rows) != 1 { - logger.Log("component", "service", "method", "IngestFunc", "err", - fmt.Sprintf("detail_query_uptime expected single result got %d", len(rows))) - return nil - } - - uptimeSeconds, err := strconv.Atoi(rows[0]["total_seconds"]) - if err != nil { - return err - } - host.Uptime = time.Duration(uptimeSeconds) * time.Second - - return nil - }, - }, "network_interface": { Query: `select * from interface_details id join interface_addresses ia on ia.interface = id.interface where broadcast != "" @@ -371,6 +295,144 @@ var detailQueries = map[string]struct { host.NetworkInterfaces = networkInterfaces + return nil + }, + }, + "os_version": { + Query: "select * from os_version limit 1", + IngestFunc: func(logger log.Logger, host *kolide.Host, rows []map[string]string) error { + if len(rows) != 1 { + logger.Log("component", "service", "method", "IngestFunc", "err", + fmt.Sprintf("detail_query_os_version expected single result got %d", len(rows))) + return nil + } + + host.OSVersion = fmt.Sprintf( + "%s %s.%s.%s", + rows[0]["name"], + rows[0]["major"], + rows[0]["minor"], + rows[0]["patch"], + ) + host.OSVersion = strings.Trim(host.OSVersion, ".") + + if build, ok := rows[0]["build"]; ok { + host.Build = build + } + + host.Platform = rows[0]["platform"] + host.PlatformLike = rows[0]["platform_like"] + host.CodeName = rows[0]["code_name"] + + // On centos6 there is an osquery bug that leaves + // platform empty. Here we workaround. + if host.Platform == "" && + strings.Contains(strings.ToLower(rows[0]["name"]), "centos") { + host.Platform = "centos" + } + + return nil + }, + }, + "osquery_flags": { + // Collect the interval info (used for online status + // calculation) from the osquery flags. We typically control + // distributed_interval (but it's not required), and typically + // do not control config_tls_refresh. + Query: `select name, value from osquery_flags where name in ("distributed_interval", "config_tls_refresh", "logger_tls_period")`, + IngestFunc: func(logger log.Logger, host *kolide.Host, rows []map[string]string) error { + for _, row := range rows { + switch row["name"] { + + case "distributed_interval": + interval, err := strconv.Atoi(row["value"]) + if err != nil { + return errors.Wrap(err, "parsing distributed_interval") + } + host.DistributedInterval = uint(interval) + + case "config_tls_refresh": + interval, err := strconv.Atoi(row["value"]) + if err != nil { + return errors.Wrap(err, "parsing config_tls_refresh") + } + host.ConfigTLSRefresh = uint(interval) + + case "logger_tls_period": + interval, err := strconv.Atoi(row["value"]) + if err != nil { + return errors.Wrap(err, "parsing logger_tls_period") + } + host.LoggerTLSPeriod = uint(interval) + } + } + return nil + }, + }, + "osquery_info": { + Query: "select * from osquery_info limit 1", + IngestFunc: func(logger log.Logger, host *kolide.Host, rows []map[string]string) error { + if len(rows) != 1 { + logger.Log("component", "service", "method", "IngestFunc", "err", + fmt.Sprintf("detail_query_osquery_info expected single result got %d", len(rows))) + return nil + } + + host.OsqueryVersion = rows[0]["version"] + + return nil + }, + }, + "system_info": { + Query: "select * from system_info limit 1", + IngestFunc: func(logger log.Logger, host *kolide.Host, rows []map[string]string) error { + if len(rows) != 1 { + logger.Log("component", "service", "method", "IngestFunc", "err", + fmt.Sprintf("detail_query_system_info expected single result got %d", len(rows))) + return nil + } + + var err error + host.PhysicalMemory, err = strconv.Atoi(rows[0]["physical_memory"]) + if err != nil { + return err + } + host.HostName = rows[0]["hostname"] + host.UUID = rows[0]["uuid"] + host.CPUType = rows[0]["cpu_type"] + host.CPUSubtype = rows[0]["cpu_subtype"] + host.CPUBrand = rows[0]["cpu_brand"] + host.CPUPhysicalCores, err = strconv.Atoi(rows[0]["cpu_physical_cores"]) + if err != nil { + return err + } + host.CPULogicalCores, err = strconv.Atoi(rows[0]["cpu_logical_cores"]) + if err != nil { + return err + } + host.HardwareVendor = rows[0]["hardware_vendor"] + host.HardwareModel = rows[0]["hardware_model"] + host.HardwareVersion = rows[0]["hardware_version"] + host.HardwareSerial = rows[0]["hardware_serial"] + host.ComputerName = rows[0]["computer_name"] + return nil + }, + }, + "uptime": { + Query: "select * from uptime limit 1", + IngestFunc: func(logger log.Logger, host *kolide.Host, rows []map[string]string) error { + if len(rows) != 1 { + logger.Log("component", "service", "method", "IngestFunc", "err", + fmt.Sprintf("detail_query_uptime expected single result got %d", len(rows))) + return nil + } + + uptimeSeconds, err := strconv.Atoi(rows[0]["total_seconds"]) + if err != nil { + return err + } + host.Uptime = time.Duration(uptimeSeconds) * time.Second + return nil }, }, diff --git a/server/service/service_osquery_test.go b/server/service/service_osquery_test.go index 6044119047..33143b9a84 100644 --- a/server/service/service_osquery_test.go +++ b/server/service/service_osquery_test.go @@ -17,6 +17,7 @@ import ( "github.com/kolide/kolide/server/contexts/viewer" "github.com/kolide/kolide/server/datastore/inmem" "github.com/kolide/kolide/server/kolide" + "github.com/kolide/kolide/server/mock" "github.com/kolide/kolide/server/pubsub" "github.com/kolide/kolide/server/test" "github.com/stretchr/testify/assert" @@ -523,6 +524,20 @@ func TestDetailQueries(t *testing.T) { "seconds": "13", "total_seconds": "1730893" } +], +"kolide_detail_query_osquery_flags": [ + { + "name":"config_tls_refresh", + "value":"10" + }, + { + "name":"distributed_interval", + "value":"5" + }, + { + "name":"logger_tls_period", + "value":"60" + } ] } ` @@ -553,6 +568,11 @@ func TestDetailQueries(t *testing.T) { // uptime assert.Equal(t, 1730893*time.Second, host.Uptime) + // osquery_flags + assert.Equal(t, uint(10), host.ConfigTLSRefresh) + assert.Equal(t, uint(5), host.DistributedInterval) + assert.Equal(t, uint(60), host.LoggerTLSPeriod) + mockClock.AddTime(1 * time.Minute) // Now no detail queries should be required @@ -768,6 +788,138 @@ func TestOrphanedQueryCampaign(t *testing.T) { assert.Equal(t, kolide.QueryComplete, campaign.Status) } +func TestUpdateHostIntervals(t *testing.T) { + ds := new(mock.Store) + + svc, err := newTestService(ds, nil) + require.Nil(t, err) + + ds.ListDecoratorsFunc = func() ([]*kolide.Decorator, error) { + return []*kolide.Decorator{}, nil + } + ds.ListPacksFunc = func(opt kolide.ListOptions) ([]*kolide.Pack, error) { + return []*kolide.Pack{}, nil + } + ds.ListLabelsForHostFunc = func(hid uint) ([]kolide.Label, error) { + return []kolide.Label{}, nil + } + + var testCases = []struct { + initHost kolide.Host + finalHost kolide.Host + configOptions map[string]interface{} + saveHostCalled bool + }{ + // Both updated + { + kolide.Host{ + ConfigTLSRefresh: 60, + }, + kolide.Host{ + DistributedInterval: 11, + LoggerTLSPeriod: 33, + ConfigTLSRefresh: 60, + }, + map[string]interface{}{ + "distributed_interval": 11, + "logger_tls_period": 33, + "logger_plugin": "tls", + }, + true, + }, + // Only logger_tls_period updated + { + kolide.Host{ + DistributedInterval: 11, + ConfigTLSRefresh: 60, + }, + kolide.Host{ + DistributedInterval: 11, + LoggerTLSPeriod: 33, + ConfigTLSRefresh: 60, + }, + map[string]interface{}{ + "distributed_interval": 11, + "logger_tls_period": 33, + }, + true, + }, + // Only distributed_interval updated + { + kolide.Host{ + ConfigTLSRefresh: 60, + LoggerTLSPeriod: 33, + }, + kolide.Host{ + DistributedInterval: 11, + LoggerTLSPeriod: 33, + ConfigTLSRefresh: 60, + }, + map[string]interface{}{ + "distributed_interval": 11, + "logger_tls_period": 33, + }, + true, + }, + // Kolide not managing distributed_interval + { + kolide.Host{ + ConfigTLSRefresh: 60, + DistributedInterval: 11, + }, + kolide.Host{ + DistributedInterval: 11, + LoggerTLSPeriod: 33, + ConfigTLSRefresh: 60, + }, + map[string]interface{}{ + "logger_tls_period": 33, + }, + true, + }, + // SaveHost should not be called with no changes + { + kolide.Host{ + DistributedInterval: 11, + LoggerTLSPeriod: 33, + ConfigTLSRefresh: 60, + }, + kolide.Host{ + DistributedInterval: 11, + LoggerTLSPeriod: 33, + ConfigTLSRefresh: 60, + }, + map[string]interface{}{ + "distributed_interval": 11, + "logger_tls_period": 33, + }, + false, + }, + } + + for _, tt := range testCases[4:] { + t.Run("", func(t *testing.T) { + ctx := hostctx.NewContext(context.Background(), tt.initHost) + + ds.GetOsqueryConfigOptionsFunc = func() (map[string]interface{}, error) { + return tt.configOptions, nil + } + + saveHostCalled := false + ds.SaveHostFunc = func(host *kolide.Host) error { + saveHostCalled = true + assert.Equal(t, tt.finalHost, *host) + return nil + } + + _, err = svc.GetClientConfig(ctx) + require.Nil(t, err) + assert.Equal(t, tt.saveHostCalled, saveHostCalled) + }) + } + +} + func setupOsqueryTests(t *testing.T) (kolide.Datastore, kolide.Service, *clock.MockClock) { ds, err := inmem.New(config.TestConfig()) require.Nil(t, err)