Retrieve and store host checkin intervals (#1473)

We now track the `config_tls_refresh`, `distributed_interval` and
`logger_tls_period` flag values for each host. Each value is updated by a
detail query agains the `osquery_flags` table, because they may be specified
outside of Kolide. The flags that can be specified within Kolide are also
updated when a config is returned to the host that changes their value.

This will enable us to do a more accurate per-host online status calculation as
discussed in #1419.
This commit is contained in:
Zachary Wasserman 2017-04-06 11:55:24 -07:00 committed by GitHub
parent 6a3ea3fc8c
commit d7cd91c0e4
12 changed files with 701 additions and 116 deletions

6
glide.lock generated
View file

@ -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

View file

@ -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

View file

@ -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()

View file

@ -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
}

View file

@ -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

View file

@ -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 {

View file

@ -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)
}

View file

@ -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()
}

View file

@ -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)
}

View file

@ -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
},
},

View file

@ -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)