Transition osquery options interfaces for compatibility with fleetctl (#1649)

- Refinements to options yaml definition
- Datastore and service implementations
- Migration to bring existing options into new table format
This commit is contained in:
Zachary Wasserman 2017-12-13 18:14:54 -05:00 committed by GitHub
parent 45165aa29a
commit 5e9fe9d5a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 1127 additions and 266 deletions

16
Gopkg.lock generated
View File

@ -66,6 +66,12 @@
revision = "47dc60e71eed504e3ef8e77ee3c6fe720f3be57f"
version = "v1.3.0"
[[projects]]
name = "github.com/ghodss/yaml"
packages = ["."]
revision = "0ca9ea5df5451ffdf184b4428c902747c2c11cd7"
version = "v1.0.0"
[[projects]]
name = "github.com/go-kit/kit"
packages = ["endpoint","log","log/level","metrics","metrics/internal/lv","metrics/prometheus","transport/grpc","transport/http"]
@ -166,7 +172,7 @@
branch = "master"
name = "github.com/kolide/launcher"
packages = ["service","service/internal/launcherproto","service/uuid"]
revision = "4b93ec1bd7ab0eaa302ddcfa8ed554950de8f38e"
revision = "9f53adbf07cbf4222885118c3aa599edcbc37dab"
[[projects]]
branch = "master"
@ -322,7 +328,7 @@
branch = "master"
name = "golang.org/x/text"
packages = ["collate","collate/build","internal/colltab","internal/gen","internal/tag","internal/triegen","internal/ucd","language","secure/bidirule","transform","unicode/bidi","unicode/cldr","unicode/norm","unicode/rangetable"]
revision = "75cc3cad82b5f47d3fb229ddda8c5167da14f294"
revision = "57961680700a5336d15015c8c50686ca5ba362a4"
[[projects]]
branch = "master"
@ -331,10 +337,10 @@
revision = "7f0da29060c682909f650ad8ed4e515bd74fa12a"
[[projects]]
branch = "master"
name = "google.golang.org/grpc"
packages = [".","balancer","balancer/roundrobin","codes","connectivity","credentials","encoding","grpclb/grpc_lb_v1/messages","grpclog","internal","keepalive","metadata","naming","peer","resolver","resolver/dns","resolver/manual","resolver/passthrough","stats","status","tap","transport"]
revision = "a4bf341022f076582fc2e9a802ce170a8938f81d"
revision = "be077907e29fdb945d351e4284eb5361e7f8924e"
version = "v1.8.1"
[[projects]]
name = "gopkg.in/natefinch/lumberjack.v2"
@ -351,6 +357,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "8bca5e6fdea71d7fa8f4251ba35e18142490728a17e4aa5de66cf8fd23d9c798"
inputs-digest = "eb90a37f82621ce0e2e040bd1f3ade7ef48913bb559c95f9c34f4929d7324990"
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -113,31 +113,55 @@ apiVersion: k8s.kolide.com/v1alpha1
kind: OsqueryOptions
spec:
config:
distributed_interval: 3
distributed_tls_max_attempts: 3
logger_plugin: tls
logger_tls_endpoint: /api/v1/osquery/log
logger_tls_period: 10
options:
distributed_interval: 3
distributed_tls_max_attempts: 3
logger_plugin: tls
logger_tls_endpoint: /api/v1/osquery/log
logger_tls_period: 10
overrides:
# Note configs in overrides take precedence over base configs
# Note configs in overrides take precedence over the default config defined
# under the config key above. With this config file, the base config would
# only be used for Windows hosts, while Mac and Linux hosts would pull
# these overrides.
platforms:
darwin:
disable_tables: chrome_extensions
docker_socket: /var/run/docker.sock
logger_tls_period: 60
fim:
interval: 500
groups:
- name: etc
paths:
- /etc/%%
- name: users
paths:
- /Users/%/Library/%%
- /Users/%/Documents/%%
options:
distributed_interval: 10
distributed_tls_max_attempts: 10
logger_plugin: tls
logger_tls_endpoint: /api/v1/osquery/log
logger_tls_period: 300
disable_tables: chrome_extensions
docker_socket: /var/run/docker.sock
file_paths:
users:
- /Users/%/Library/%%
- /Users/%/Documents/%%
etc:
- /etc/%%
linux:
schedule_timeout: 60
docker_socket: /etc/run/docker.sock
options:
distributed_interval: 10
distributed_tls_max_attempts: 3
logger_plugin: tls
logger_tls_endpoint: /api/v1/osquery/log
logger_tls_period: 60
schedule_timeout: 60
docker_socket: /etc/run/docker.sock
file_paths:
homes:
- /root/.ssh/%%
- /home/%/.ssh/%%
etc:
- /etc/%%
tmp:
- /tmp/%%
exclude_paths:
homes:
- /home/not_to_monitor/.ssh/%%
tmp:
- /tmp/too_many_events/
```
### Osquery Logging Decorators
@ -145,7 +169,6 @@ spec:
The following file describes logging decorators that should be applied on osquery instances. A decorator should reference an osquery query by name. Both of these resources can be included in the same file as such:
```yaml
---
apiVersion: k8s.kolide.com/v1alpha1
kind: OsqueryDecorator
spec:
@ -165,7 +188,6 @@ spec:
The following file describes the labels which hosts should be automatically grouped into. The label resource should reference the query by name. Both of these resources can be included in the same file as such:
```yaml
---
apiVersion: k8s.kolide.com/v1alpha1
kind: OsqueryLabel
spec:
@ -206,7 +228,6 @@ spec:
To define multiple queries in a file, concatenate multiple `OsqueryQuery` resources together in a single file with `---`. For example, consider a file that you might store at `queries/osquery_monitoring.yml`:
```yaml
---
apiVersion: k8s.kolide.com/v1alpha1
kind: OsqueryQuery
spec:

View File

@ -3,28 +3,52 @@ apiVersion: k8s.kolide.com/v1alpha1
kind: OsqueryOptions
spec:
config:
distributed_interval: 3
distributed_tls_max_attempts: 3
logger_plugin: tls
logger_tls_endpoint: /api/v1/osquery/log
logger_tls_period: 10
options:
distributed_interval: 3
distributed_tls_max_attempts: 3
logger_plugin: tls
logger_tls_endpoint: /api/v1/osquery/log
logger_tls_period: 10
overrides:
# Note configs in overrides take precedence over base configs
# Note configs in overrides take precedence over the default config defined
# under the config key above. With this config file, the base config would
# only be used for Windows hosts, while Mac and Linux hosts would pull
# these overrides.
platforms:
darwin:
disable_tables: chrome_extensions
docker_socket: /var/run/docker.sock
logger_tls_period: 60
fim:
interval: 500
groups:
- name: etc
paths:
- /etc/%%
- name: users
paths:
- /Users/%/Library/%%
- /Users/%/Documents/%%
options:
distributed_interval: 10
distributed_tls_max_attempts: 10
logger_plugin: tls
logger_tls_endpoint: /api/v1/osquery/log
logger_tls_period: 300
disable_tables: chrome_extensions
docker_socket: /var/run/docker.sock
file_paths:
users:
- /Users/%/Library/%%
- /Users/%/Documents/%%
etc:
- /etc/%%
linux:
schedule_timeout: 60
docker_socket: /etc/run/docker.sock
options:
distributed_interval: 10
distributed_tls_max_attempts: 3
logger_plugin: tls
logger_tls_endpoint: /api/v1/osquery/log
logger_tls_period: 60
schedule_timeout: 60
docker_socket: /etc/run/docker.sock
file_paths:
homes:
- /root/.ssh/%%
- /home/%/.ssh/%%
etc:
- /etc/%%
tmp:
- /tmp/%%
exclude_paths:
homes:
- /home/not_to_monitor/.ssh/%%
tmp:
- /tmp/too_many_events/

View File

@ -3,31 +3,55 @@ apiVersion: k8s.kolide.com/v1alpha1
kind: OsqueryOptions
spec:
config:
distributed_interval: 3
distributed_tls_max_attempts: 3
logger_plugin: tls
logger_tls_endpoint: /api/v1/osquery/log
logger_tls_period: 10
options:
distributed_interval: 3
distributed_tls_max_attempts: 3
logger_plugin: tls
logger_tls_endpoint: /api/v1/osquery/log
logger_tls_period: 10
overrides:
# Note configs in overrides take precedence over base configs
# Note configs in overrides take precedence over the default config defined
# under the config key above. With this config file, the base config would
# only be used for Windows hosts, while Mac and Linux hosts would pull
# these overrides.
platforms:
darwin:
disable_tables: chrome_extensions
docker_socket: /var/run/docker.sock
logger_tls_period: 60
fim:
interval: 500
groups:
- name: etc
paths:
- /etc/%%
- name: users
paths:
- /Users/%/Library/%%
- /Users/%/Documents/%%
options:
distributed_interval: 10
distributed_tls_max_attempts: 10
logger_plugin: tls
logger_tls_endpoint: /api/v1/osquery/log
logger_tls_period: 300
disable_tables: chrome_extensions
docker_socket: /var/run/docker.sock
file_paths:
users:
- /Users/%/Library/%%
- /Users/%/Documents/%%
etc:
- /etc/%%
linux:
schedule_timeout: 60
docker_socket: /etc/run/docker.sock
options:
distributed_interval: 10
distributed_tls_max_attempts: 3
logger_plugin: tls
logger_tls_endpoint: /api/v1/osquery/log
logger_tls_period: 60
schedule_timeout: 60
docker_socket: /etc/run/docker.sock
file_paths:
homes:
- /root/.ssh/%%
- /home/%/.ssh/%%
etc:
- /etc/%%
tmp:
- /tmp/%%
exclude_paths:
homes:
- /home/not_to_monitor/.ssh/%%
tmp:
- /tmp/too_many_events/
---
apiVersion: k8s.kolide.com/v1alpha1
kind: OsqueryDecorator

View File

@ -0,0 +1,95 @@
package datastore
import (
"encoding/json"
"testing"
"github.com/kolide/fleet/server/kolide"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func testApplyOsqueryOptions(t *testing.T, ds kolide.Datastore) {
expectedOpts := &kolide.OptionsSpec{
Config: json.RawMessage(`{"foo": "bar"}`),
Overrides: kolide.OptionsOverrides{
Platforms: map[string]json.RawMessage{
"darwin": json.RawMessage(`{"froob": "ling"}`),
},
},
}
err := ds.ApplyOptions(expectedOpts)
require.Nil(t, err)
retrievedOpts, err := ds.GetOptions()
require.Nil(t, err)
assert.Equal(t, expectedOpts, retrievedOpts)
// Re-apply and verify everything has been replaced.
expectedOpts = &kolide.OptionsSpec{
Config: json.RawMessage(`{"blue": "smurf"}`),
Overrides: kolide.OptionsOverrides{
Platforms: map[string]json.RawMessage{
"linux": json.RawMessage(`{"transitive": "nightfall"}`),
},
},
}
err = ds.ApplyOptions(expectedOpts)
require.Nil(t, err)
retrievedOpts, err = ds.GetOptions()
require.Nil(t, err)
assert.Equal(t, expectedOpts, retrievedOpts)
}
func testApplyOsqueryOptionsNoOverrides(t *testing.T, ds kolide.Datastore) {
expectedOpts := &kolide.OptionsSpec{
Config: json.RawMessage(`{}`),
}
err := ds.ApplyOptions(expectedOpts)
require.Nil(t, err)
retrievedOpts, err := ds.GetOptions()
require.Nil(t, err)
assert.Equal(t, expectedOpts.Config, retrievedOpts.Config)
assert.Empty(t, retrievedOpts.Overrides.Platforms)
}
func testOsqueryOptionsForHost(t *testing.T, ds kolide.Datastore) {
defaultOpts := json.RawMessage(`{"foo": "bar"}`)
darwinOpts := json.RawMessage(`{"darwin": "macintosh"}`)
linuxOpts := json.RawMessage(`{"linux": "FOSS"}`)
expectedOpts := &kolide.OptionsSpec{
Config: defaultOpts,
Overrides: kolide.OptionsOverrides{
Platforms: map[string]json.RawMessage{
"darwin": darwinOpts,
"linux": linuxOpts,
},
},
}
err := ds.ApplyOptions(expectedOpts)
require.Nil(t, err)
var testCases = []struct {
host kolide.Host
expectedOpts json.RawMessage
}{
{kolide.Host{Platform: "windows"}, defaultOpts},
{kolide.Host{Platform: "linux"}, linuxOpts},
{kolide.Host{Platform: "darwin"}, darwinOpts},
{kolide.Host{Platform: "some_other_platform"}, defaultOpts},
}
for _, tt := range testCases {
t.Run("", func(t *testing.T) {
opts, err := ds.OptionsForPlatform(tt.host.Platform)
require.Nil(t, err)
assert.Equal(t, tt.expectedOpts, opts)
})
}
}

View File

@ -79,4 +79,7 @@ var testFunctions = [...]func(*testing.T, kolide.Datastore){
testCountHostsInTargets,
testHostStatus,
testResetOptions,
testApplyOsqueryOptions,
testApplyOsqueryOptionsNoOverrides,
testOsqueryOptionsForHost,
}

View File

@ -40,6 +40,7 @@ type Datastore struct {
appConfig *kolide.AppConfig
config *config.KolideConfig
kolide.TargetStore
kolide.OsqueryOptionsStore
}
func New(config config.KolideConfig) (*Datastore, error) {

View File

@ -0,0 +1,99 @@
package data
import (
"database/sql"
"encoding/json"
"github.com/jmoiron/sqlx"
"github.com/jmoiron/sqlx/reflectx"
"github.com/kolide/fleet/server/kolide"
"github.com/pkg/errors"
)
func init() {
MigrationClient.AddMigration(Up_20171212182458, Down_20171212182458)
}
type configForExport struct {
Options map[string]interface{} `json:"options"`
FilePaths map[string][]string `json:"file_paths,omitempty"`
}
type yamlObjForExport struct {
ApiVersion string `json:"apiVersion"`
Kind string `json:"kind"`
Spec specForExport `json:"spec"`
}
type specForExport struct {
Config json.RawMessage `json:"config"`
}
func Up_20171212182458(tx *sql.Tx) error {
txx := sqlx.Tx{Tx: tx, Mapper: reflectx.NewMapperFunc("db", sqlx.NameMapper)}
// Get basic osquery options
query := `
SELECT *
FROM options
WHERE value != "null"
`
// Intentionally initialize empty instead of nil so that we generate a
// config with empty options rather than a null value.
var opts []kolide.Option
if err := txx.Select(&opts, query); err != nil && err != sql.ErrNoRows {
return errors.Wrap(err, "getting options")
}
optConfig := map[string]interface{}{}
for _, opt := range opts {
optConfig[opt.Name] = opt.GetValue()
}
// Get FIM paths from fim table
query = `
SELECT fim.section_name, mf.file
FROM file_integrity_monitorings AS fim
INNER JOIN file_integrity_monitoring_files AS mf
ON (fim.id = mf.file_integrity_monitoring_id)
`
rows, err := txx.Query(query)
if err != nil && err != sql.ErrNoRows {
return errors.Wrap(err, "retrieving fim paths")
}
fimConfig := kolide.FIMSections{}
for rows.Next() {
var sectionName, fileName string
err = rows.Scan(&sectionName, &fileName)
if err != nil {
return errors.Wrap(err, "retrieving path for fim section")
}
fimConfig[sectionName] = append(fimConfig[sectionName], fileName)
}
// Create config JSON
config := configForExport{
Options: optConfig,
FilePaths: fimConfig,
}
confJSON, err := json.Marshal(config)
if err != nil {
return errors.Wrap(err, "marshal config JSON")
}
// Save config JSON
query = `
INSERT INTO osquery_options (
override_type, override_identifier, options
) VALUES (?, ?, ?)
`
if _, err = txx.Exec(query, kolide.OptionOverrideTypeDefault, "", string(confJSON)); err != nil {
return errors.Wrap(err, "saving converted options")
}
return nil
}
func Down_20171212182458(tx *sql.Tx) error {
// No down migration
return nil
}

View File

@ -0,0 +1,24 @@
package tables
import "database/sql"
func init() {
MigrationClient.AddMigration(Up_20171116163618, Down_20171116163618)
}
func Up_20171116163618(tx *sql.Tx) error {
sqlStatement := "CREATE TABLE `osquery_options` (" +
"`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT," +
"`override_type` INT(1) NOT NULL, " +
"`override_identifier` VARCHAR(255) NOT NULL DEFAULT ''," +
"`options` JSON NOT NULL," +
"PRIMARY KEY (`id`)" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8;"
_, err := tx.Exec(sqlStatement)
return err
}
func Down_20171116163618(tx *sql.Tx) error {
_, err := tx.Exec("DROP TABLE IF EXISTS `osquery_options`;")
return err
}

View File

@ -0,0 +1,132 @@
package mysql
import (
"database/sql"
"encoding/json"
"fmt"
"github.com/go-kit/kit/log/level"
"github.com/kolide/fleet/server/kolide"
"github.com/pkg/errors"
)
type optionsRow struct {
ID int `db:"id"`
OverrideType kolide.OptionOverrideType `db:"override_type"`
OverrideIdentifier string `db:"override_identifier"`
Options string `db:"options"`
}
func (d *Datastore) ApplyOptions(spec *kolide.OptionsSpec) (err error) {
tx, err := d.db.Begin()
if err != nil {
return errors.Wrap(err, "begin ApplyOptions transaction")
}
defer func() {
if err != nil {
rbErr := tx.Rollback()
// It seems possible that there might be a case in
// which the error we are dealing with here was thrown
// by the call to tx.Commit(), and the docs suggest
// this call would then result in sql.ErrTxDone.
if rbErr != nil && rbErr != sql.ErrTxDone {
panic(fmt.Sprintf("got err '%s' rolling back after err '%s'", rbErr, err))
}
}
}()
// Clear all the existing options
_, err = tx.Exec("DELETE FROM osquery_options")
if err != nil {
return errors.Wrap(err, "delete existing options")
}
// Save new options
sql := `
INSERT INTO osquery_options (
override_type, override_identifier, options
) VALUES (?, ?, ?)
`
// Default options
_, err = tx.Exec(sql, kolide.OptionOverrideTypeDefault, "", string(spec.Config))
if err != nil {
return errors.Wrap(err, "saving default config")
}
// Platform overrides
for platform, opts := range spec.Overrides.Platforms {
_, err = tx.Exec(sql, kolide.OptionOverrideTypePlatform, platform, string(opts))
if err != nil {
return errors.Wrapf(err, "saving %s platform config", platform)
}
}
// Success!
err = tx.Commit()
if err != nil {
return errors.Wrap(err, "commit ApplyOptions transaction")
}
return nil
}
func (d *Datastore) GetOptions() (*kolide.OptionsSpec, error) {
var rows []optionsRow
if err := d.db.Select(&rows, "SELECT * FROM osquery_options"); err != nil {
return nil, errors.Wrap(err, "selecting options")
}
spec := &kolide.OptionsSpec{
Overrides: kolide.OptionsOverrides{
Platforms: make(map[string]json.RawMessage),
},
}
for _, row := range rows {
switch row.OverrideType {
case kolide.OptionOverrideTypeDefault:
spec.Config = json.RawMessage(row.Options)
case kolide.OptionOverrideTypePlatform:
spec.Overrides.Platforms[row.OverrideIdentifier] = json.RawMessage(row.Options)
default:
level.Info(d.logger).Log(
"err", "ignoring unkown override type",
"type", row.OverrideType,
)
}
}
return spec, nil
}
func (d *Datastore) OptionsForPlatform(platform string) (json.RawMessage, error) {
// SQL uses a custom ordering function to return the single correct
// config with the highest precedence override (the FIELD function
// defines this ordering). If there is no override, it returns the
// default.
sql := `
SELECT * FROM osquery_options
WHERE override_type = ? OR
(override_type = ? AND override_identifier = ?)
ORDER BY FIELD(override_type, ?, ?)
LIMIT 1
`
var row optionsRow
err := d.db.Get(
&row, sql,
kolide.OptionOverrideTypeDefault,
kolide.OptionOverrideTypePlatform, platform,
// Order of the following arguments defines precedence of
// overrides.
kolide.OptionOverrideTypePlatform, kolide.OptionOverrideTypeDefault,
)
if err != nil {
return nil, errors.Wrapf(err, "retrieving osquery options for platform '%s'", platform)
}
return json.RawMessage(row.Options), nil
}

View File

@ -18,6 +18,7 @@ type Datastore interface {
DecoratorStore
FileIntegrityMonitoringStore
YARAStore
OsqueryOptionsStore
Name() string
Drop() error
// MigrateTables creates and migrates the table schemas

View File

@ -0,0 +1,3 @@
package kolide
const ApiVersion = "k8s.kolide.com/v1alpha1"

View File

@ -8,7 +8,7 @@ import (
type OsqueryService interface {
EnrollAgent(ctx context.Context, enrollSecret, hostIdentifier string) (nodeKey string, err error)
AuthenticateHost(ctx context.Context, nodeKey string) (host *Host, err error)
GetClientConfig(ctx context.Context) (config *OsqueryConfig, err error)
GetClientConfig(ctx context.Context) (config map[string]interface{}, err error)
// GetDistributedQueries retrieves the distributed queries to run for
// the host in the provided context. These may be detail queries, label
// queries, or user-initiated distributed queries. A map from query
@ -60,18 +60,6 @@ type Decorators struct {
Interval map[string][]string `json:"interval,omitempty"`
}
// OsqueryConfig is a struct that can be serialized into a valid osquery config
// using Go's JSON tooling.
type OsqueryConfig struct {
Schedule map[string]QueryContent `json:"schedule,omitempty"`
Options map[string]interface{} `json:"options"`
Decorators Decorators `json:"decorators,omitempty"`
Packs Packs `json:"packs,omitempty"`
// FilePaths contains named collections of file paths used for
// FIM (File Integrity Monitoring)
FilePaths FIMSections `json:"file_paths,omitempty"`
}
// OsqueryStatusLog is the format of an osquery status log.
type OsqueryStatusLog struct {
Severity string `json:"severity"`

View File

@ -0,0 +1,48 @@
package kolide
import "encoding/json"
type OsqueryOptionsStore interface {
ApplyOptions(options *OptionsSpec) error
GetOptions() (*OptionsSpec, error)
OptionsForPlatform(platform string) (json.RawMessage, error)
}
type OsqueryOptionsService interface {
ApplyOptionsYaml(yml string) error
GetOptionsYaml() (string, error)
}
type OptionsYaml struct {
ApiVersion string `json:"apiVersion"`
Kind string `json:"kind"`
Spec OptionsSpec `json:"spec"`
}
type OptionsSpec struct {
Config json.RawMessage `json:"config"`
Overrides OptionsOverrides `json:"overrides,omitempty"`
}
type OptionsOverrides struct {
Platforms map[string]json.RawMessage `json:"platforms,omitempty"`
}
const (
OptionsSpecKind = "OsqueryOptions"
)
// OptionOverrideType is used to designate which override type a given set of
// options is used for. Currently the only supported override type is by
// platform.
type OptionOverrideType int
const (
// OptionOverrideTypeDefault indicates that this is the default config
// (provided to hosts when there is no override set for them).
OptionOverrideTypeDefault OptionOverrideType = iota
// OptionOverrideTypePlatform indicates that this is a
// platform-specific config override (with precedence over the default
// config).
OptionOverrideTypePlatform
)

View File

@ -0,0 +1,119 @@
package kolide
import (
"testing"
"github.com/ghodss/yaml"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestUnmarshalYaml(t *testing.T) {
y := []byte(`
apiVersion: k8s.kolide.com/v1alpha1
kind: OsqueryOptions
spec:
config:
options:
distributed_interval: 3
distributed_tls_max_attempts: 3
logger_plugin: tls
logger_tls_endpoint: /api/v1/osquery/log
logger_tls_period: 10
overrides:
platforms:
darwin:
options:
distributed_interval: 10
distributed_tls_max_attempts: 10
logger_plugin: tls
logger_tls_endpoint: /api/v1/osquery/log
logger_tls_period: 300
disable_tables: chrome_extensions
docker_socket: /var/run/docker.sock
file_paths:
users:
- /Users/%/Library/%%
- /Users/%/Documents/%%
etc:
- /etc/%%
linux:
options:
distributed_interval: 10
distributed_tls_max_attempts: 3
logger_plugin: tls
logger_tls_endpoint: /api/v1/osquery/log
logger_tls_period: 60
schedule_timeout: 60
docker_socket: /etc/run/docker.sock
frobulations:
- fire
- ice
`)
expectedConfig := `{
"options":{
"distributed_interval":3,
"distributed_tls_max_attempts":3,
"logger_plugin":"tls",
"logger_tls_endpoint":"/api/v1/osquery/log",
"logger_tls_period":10
}
}`
expectedDarwin := `{
"options":{
"disable_tables":"chrome_extensions",
"distributed_interval":10,
"distributed_tls_max_attempts":10,
"docker_socket":"/var/run/docker.sock",
"logger_plugin":"tls",
"logger_tls_endpoint":"/api/v1/osquery/log",
"logger_tls_period":300
},
"file_paths":{
"etc":[
"/etc/%%"
],
"users":[
"/Users/%/Library/%%",
"/Users/%/Documents/%%"
]
}
}`
expectedLinux := `{
"options":{
"distributed_interval":10,
"distributed_tls_max_attempts":3,
"docker_socket":"/etc/run/docker.sock",
"logger_plugin":"tls",
"logger_tls_endpoint":"/api/v1/osquery/log",
"logger_tls_period":60,
"schedule_timeout":60
},
"frobulations": [
"fire",
"ice"
]
}`
var foo OptionsYaml
err := yaml.Unmarshal(y, &foo)
require.Nil(t, err)
assert.JSONEq(t, expectedConfig, string(foo.Spec.Config))
platformOverrides := foo.Spec.Overrides.Platforms
assert.Len(t, platformOverrides, 2)
if assert.Contains(t, platformOverrides, "darwin") {
assert.JSONEq(t, expectedDarwin, string(platformOverrides["darwin"]))
}
if assert.Contains(t, platformOverrides, "linux") {
assert.JSONEq(t, expectedLinux, string(platformOverrides["linux"]))
}
}

View File

@ -45,10 +45,10 @@ func (svc *launcherWrapper) RequestConfig(ctx context.Context, nodeKey string) (
return "", false, errors.Wrap(err, "get config for launcher")
}
// Launcher manages plugins so remove them from configuration if they exist.
for _, optionName := range []string{"distributed_plugin", "logger_plugin"} {
if _, ok := config.Options[optionName]; ok {
delete(config.Options, optionName)
if options, ok := config["options"].(map[string]interface{}); ok {
// Launcher manages plugins so remove them from configuration if they exist.
for _, optionName := range []string{"distributed_plugin", "logger_plugin"} {
delete(options, optionName)
}
}

View File

@ -34,7 +34,7 @@ func TestLauncherRequestConfig(t *testing.T) {
require.Nil(t, err)
assert.True(t, tls.AuthenticateHostFuncInvoked)
assert.False(t, invalid)
assert.Equal(t, `{"options":{"key":"value"},"decorators":{}}`, config)
assert.JSONEq(t, `{"options":{"key":"value"},"decorators":{"deco":"foobar"}}`, config)
}
func TestLauncherRequestQueries(t *testing.T) {
@ -122,11 +122,14 @@ func newTLSService(t *testing.T) *mock.TLSService {
},
GetClientConfigFunc: func(
ctx context.Context,
) (config *kolide.OsqueryConfig, err error) {
return &kolide.OsqueryConfig{
Options: map[string]interface{}{
) (config map[string]interface{}, err error) {
return map[string]interface{}{
"options": map[string]interface{}{
"key": "value",
},
"decorators": map[string]interface{}{
"deco": "foobar",
},
}, nil
},

View File

@ -9,6 +9,8 @@ package mock
//go:generate mockimpl -o datastore_packs.go "s *PackStore" "kolide.PackStore"
//go:generate mockimpl -o datastore_hosts.go "s *HostStore" "kolide.HostStore"
//go:generate mockimpl -o datastore_fim.go "s *FileIntegrityMonitoringStore" "kolide.FileIntegrityMonitoringStore"
//go:generate mockimpl -o datastore_osquery_options.go "s *OsqueryOptionsStore" "kolide.OsqueryOptionsStore"
//go:generate mockimpl -o datastore_scheduled_queries.go "s *ScheduledQueryStore" "kolide.ScheduledQueryStore"
import "github.com/kolide/fleet/server/kolide"
@ -19,9 +21,10 @@ type Store struct {
kolide.SessionStore
kolide.PasswordResetStore
kolide.QueryStore
kolide.ScheduledQueryStore
kolide.YARAStore
kolide.TargetStore
ScheduledQueryStore
OsqueryOptionsStore
FileIntegrityMonitoringStore
AppConfigStore
DecoratorStore

View File

@ -0,0 +1,43 @@
// Automatically generated by mockimpl. DO NOT EDIT!
package mock
import (
"encoding/json"
"github.com/kolide/fleet/server/kolide"
)
var _ kolide.OsqueryOptionsStore = (*OsqueryOptionsStore)(nil)
type ApplyOptionsFunc func(options *kolide.OptionsSpec) error
type GetOptionsFunc func() (*kolide.OptionsSpec, error)
type OptionsForPlatformFunc func(platform string) (json.RawMessage, error)
type OsqueryOptionsStore struct {
ApplyOptionsFunc ApplyOptionsFunc
ApplyOptionsFuncInvoked bool
GetOptionsFunc GetOptionsFunc
GetOptionsFuncInvoked bool
OptionsForPlatformFunc OptionsForPlatformFunc
OptionsForPlatformFuncInvoked bool
}
func (s *OsqueryOptionsStore) ApplyOptions(options *kolide.OptionsSpec) error {
s.ApplyOptionsFuncInvoked = true
return s.ApplyOptionsFunc(options)
}
func (s *OsqueryOptionsStore) GetOptions() (*kolide.OptionsSpec, error) {
s.GetOptionsFuncInvoked = true
return s.GetOptionsFunc()
}
func (s *OsqueryOptionsStore) OptionsForPlatform(platform string) (json.RawMessage, error) {
s.OptionsForPlatformFuncInvoked = true
return s.OptionsForPlatformFunc(platform)
}

View File

@ -0,0 +1,59 @@
// Automatically generated by mockimpl. DO NOT EDIT!
package mock
import "github.com/kolide/fleet/server/kolide"
var _ kolide.ScheduledQueryStore = (*ScheduledQueryStore)(nil)
type NewScheduledQueryFunc func(sq *kolide.ScheduledQuery, opts ...kolide.OptionalArg) (*kolide.ScheduledQuery, error)
type SaveScheduledQueryFunc func(sq *kolide.ScheduledQuery) (*kolide.ScheduledQuery, error)
type DeleteScheduledQueryFunc func(id uint) error
type ScheduledQueryFunc func(id uint) (*kolide.ScheduledQuery, error)
type ListScheduledQueriesInPackFunc func(id uint, opts kolide.ListOptions) ([]*kolide.ScheduledQuery, error)
type ScheduledQueryStore struct {
NewScheduledQueryFunc NewScheduledQueryFunc
NewScheduledQueryFuncInvoked bool
SaveScheduledQueryFunc SaveScheduledQueryFunc
SaveScheduledQueryFuncInvoked bool
DeleteScheduledQueryFunc DeleteScheduledQueryFunc
DeleteScheduledQueryFuncInvoked bool
ScheduledQueryFunc ScheduledQueryFunc
ScheduledQueryFuncInvoked bool
ListScheduledQueriesInPackFunc ListScheduledQueriesInPackFunc
ListScheduledQueriesInPackFuncInvoked bool
}
func (s *ScheduledQueryStore) NewScheduledQuery(sq *kolide.ScheduledQuery, opts ...kolide.OptionalArg) (*kolide.ScheduledQuery, error) {
s.NewScheduledQueryFuncInvoked = true
return s.NewScheduledQueryFunc(sq, opts...)
}
func (s *ScheduledQueryStore) SaveScheduledQuery(sq *kolide.ScheduledQuery) (*kolide.ScheduledQuery, error) {
s.SaveScheduledQueryFuncInvoked = true
return s.SaveScheduledQueryFunc(sq)
}
func (s *ScheduledQueryStore) DeleteScheduledQuery(id uint) error {
s.DeleteScheduledQueryFuncInvoked = true
return s.DeleteScheduledQueryFunc(id)
}
func (s *ScheduledQueryStore) ScheduledQuery(id uint) (*kolide.ScheduledQuery, error) {
s.ScheduledQueryFuncInvoked = true
return s.ScheduledQueryFunc(id)
}
func (s *ScheduledQueryStore) ListScheduledQueriesInPack(id uint, opts kolide.ListOptions) ([]*kolide.ScheduledQuery, error) {
s.ListScheduledQueriesInPackFuncInvoked = true
return s.ListScheduledQueriesInPackFunc(id, opts)
}

View File

@ -15,7 +15,7 @@ type EnrollAgentFunc func(ctx context.Context, enrollSecret string, hostIdentifi
type AuthenticateHostFuncI func(ctx context.Context, nodeKey string) (host *kolide.Host, err error)
type GetClientConfigFunc func(ctx context.Context) (config *kolide.OsqueryConfig, err error)
type GetClientConfigFunc func(ctx context.Context) (config map[string]interface{}, err error)
type GetDistributedQueriesFunc func(ctx context.Context) (queries map[string]string, accelerate uint, err error)
@ -58,7 +58,7 @@ func (s *TLSService) AuthenticateHost(ctx context.Context, nodeKey string) (host
return s.AuthenticateHostFunc(ctx, nodeKey)
}
func (s *TLSService) GetClientConfig(ctx context.Context) (config *kolide.OsqueryConfig, err error) {
func (s *TLSService) GetClientConfig(ctx context.Context) (config map[string]interface{}, err error) {
s.GetClientConfigFuncInvoked = true
return s.GetClientConfigFunc(ctx)
}

View File

@ -44,8 +44,8 @@ type getClientConfigRequest struct {
}
type getClientConfigResponse struct {
kolide.OsqueryConfig
Err error `json:"error,omitempty"`
Config map[string]interface{}
Err error `json:"error,omitempty"`
}
func (r getClientConfigResponse) error() error { return r.Err }
@ -56,7 +56,7 @@ func makeGetClientConfigEndpoint(svc kolide.Service) endpoint.Endpoint {
if err != nil {
return getClientConfigResponse{Err: err}, nil
}
return getClientConfigResponse{OsqueryConfig: *config}, nil
return getClientConfigResponse{Config: config}, nil
}
}

View File

@ -48,9 +48,9 @@ func (mw loggingMiddleware) AuthenticateHost(ctx context.Context, nodeKey string
return host, err
}
func (mw loggingMiddleware) GetClientConfig(ctx context.Context) (*kolide.OsqueryConfig, error) {
func (mw loggingMiddleware) GetClientConfig(ctx context.Context) (map[string]interface{}, error) {
var (
config *kolide.OsqueryConfig
config map[string]interface{}
err error
)

View File

@ -89,39 +89,26 @@ func (svc service) EnrollAgent(ctx context.Context, enrollSecret, hostIdentifier
return host.NodeKey, nil
}
func (svc service) getFIMConfig(ctx context.Context, cfg *kolide.OsqueryConfig) (*kolide.OsqueryConfig, error) {
fimConfig, err := svc.GetFIM(ctx)
if err != nil {
return nil, osqueryError{message: "internal error: unable to fetch FIM configuration"}
}
if cfg.Schedule == nil {
cfg.Schedule = make(map[string]kolide.QueryContent)
}
removed := false
// file events scheduled query is required to run file integrity monitors
cfg.Schedule["file_events"] = kolide.QueryContent{
Query: "SELECT * FROM file_events;",
Interval: fimConfig.Interval,
Removed: &removed,
}
cfg.FilePaths = fimConfig.FilePaths
return cfg, nil
}
func (svc service) GetClientConfig(ctx context.Context) (*kolide.OsqueryConfig, error) {
func (svc service) GetClientConfig(ctx context.Context) (map[string]interface{}, error) {
host, ok := hostctx.FromContext(ctx)
if !ok {
return nil, osqueryError{message: "internal error: missing host from request context"}
}
options, err := svc.ds.GetOsqueryConfigOptions()
baseConfig, err := svc.ds.OptionsForPlatform(host.Platform)
if err != nil {
return nil, osqueryError{message: "internal error: unable to fetch configuration options"}
return nil, osqueryError{message: "internal error: fetching base config: " + err.Error()}
}
var config map[string]interface{}
err = json.Unmarshal(baseConfig, &config)
if err != nil {
return nil, osqueryError{message: "internal error: parsing base configuration: " + err.Error()}
}
decorators, err := svc.ds.ListDecorators()
if err != nil {
return nil, osqueryError{message: "internal error: unable to fetch decorators"}
return nil, osqueryError{message: "internal error: unable to fetch decorators: " + err.Error()}
}
decConfig := kolide.Decorators{
Interval: make(map[string][]string),
@ -141,10 +128,12 @@ func (svc service) GetClientConfig(ctx context.Context) (*kolide.OsqueryConfig,
}
}
config := &kolide.OsqueryConfig{
Options: options,
Decorators: decConfig,
Packs: kolide.Packs{},
if len(decConfig.Interval) > 0 || len(decConfig.Always) > 0 || len(decConfig.Load) > 0 {
decJSON, err := json.Marshal(decConfig)
if err != nil {
return nil, osqueryError{message: "internal error: marshal decorator JSON: " + err.Error()}
}
config["decorators"] = json.RawMessage(decJSON)
}
packs, err := svc.ListPacksForHost(ctx, host.ID)
@ -152,6 +141,7 @@ func (svc service) GetClientConfig(ctx context.Context) (*kolide.OsqueryConfig,
return nil, osqueryError{message: "database error: " + err.Error()}
}
packConfig := kolide.Packs{}
for _, pack := range packs {
// first, we must figure out what queries are in this pack
queries, err := svc.ds.ListScheduledQueriesInPack(pack.ID, kolide.ListOptions{})
@ -178,30 +168,40 @@ func (svc service) GetClientConfig(ctx context.Context) (*kolide.OsqueryConfig,
}
// finally, we add the pack to the client config struct with all of
// the packs queries
config.Packs[pack.Name] = kolide.PackContent{
// the pack's queries
packConfig[pack.Name] = kolide.PackContent{
Platform: pack.Platform,
Queries: configQueries,
}
}
if len(packConfig) > 0 {
packJSON, err := json.Marshal(packConfig)
if err != nil {
return nil, osqueryError{message: "internal error: marshal pack JSON: " + err.Error()}
}
config["packs"] = json.RawMessage(packJSON)
}
// 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
}
if options, ok := config["options"].(map[string]interface{}); ok {
distributedIntervalVal, ok := 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
loggerTLSPeriodVal, ok := options["logger_tls_period"]
loggerTLSPeriod, err := cast.ToUintE(loggerTLSPeriodVal)
if ok && err == nil && host.LoggerTLSPeriod != loggerTLSPeriod {
host.LoggerTLSPeriod = loggerTLSPeriod
saveHost = true
}
}
if saveHost {
@ -211,7 +211,7 @@ func (svc service) GetClientConfig(ctx context.Context) (*kolide.OsqueryConfig,
}
}
return svc.getFIMConfig(ctx, config)
return config, nil
}
// If osqueryWriters are based on bufio we want to flush after a batch of

View File

@ -0,0 +1,41 @@
package service
import (
"github.com/ghodss/yaml"
"github.com/kolide/fleet/server/kolide"
"github.com/pkg/errors"
)
func (svc service) ApplyOptionsYaml(yml string) error {
var conf kolide.OptionsYaml
err := yaml.Unmarshal([]byte(yml), &conf)
if err != nil {
return errors.Wrap(err, "unmarshal options YAML")
}
if conf.Kind != kolide.OptionsSpecKind {
return errors.Errorf("expected kind '%s', got '%s'", kolide.OptionsSpecKind, conf.Kind)
}
err = svc.ds.ApplyOptions(&conf.Spec)
return errors.Wrap(err, "apply options")
}
func (svc service) GetOptionsYaml() (string, error) {
spec, err := svc.ds.GetOptions()
if err != nil {
return "", errors.Wrap(err, "get options from datastore")
}
ymlObj := kolide.OptionsYaml{
ApiVersion: kolide.ApiVersion,
Kind: kolide.OptionsSpecKind,
Spec: *spec,
}
yml, err := yaml.Marshal(ymlObj)
if err != nil {
return "", errors.Wrap(err, "marshal options yaml")
}
return string(yml), nil
}

View File

@ -0,0 +1,138 @@
package service
import (
"encoding/json"
"testing"
"github.com/kolide/fleet/server/kolide"
"github.com/kolide/fleet/server/mock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestApplyOptionsYaml(t *testing.T) {
var testCases = []struct {
yml string
options *kolide.OptionsSpec
shouldErr bool
}{
{"notyaml", nil, true},
{
yml: `
apiVersion: k8s.kolide.com/v1alpha1
kind: OsqueryQuery
spec:
name: osquery_schedule
description: Report performance stats
query: select * from osquery_schedule
`, // Wrong kind of yaml
options: nil,
shouldErr: true,
},
{
yml: `
apiVersion: k8s.kolide.com/v1alpha1
kind: OsqueryOptions
spec:
config:
foo: bar
overrides:
# Note configs in overrides take precedence over base configs
platforms:
darwin:
bing: bang
`,
options: &kolide.OptionsSpec{
Config: json.RawMessage(`{"foo":"bar"}`),
Overrides: kolide.OptionsOverrides{
Platforms: map[string]json.RawMessage{
"darwin": json.RawMessage(`{"bing":"bang"}`),
},
},
},
shouldErr: false,
},
}
var gotOptions *kolide.OptionsSpec
ds := &mock.Store{
OsqueryOptionsStore: mock.OsqueryOptionsStore{
ApplyOptionsFunc: func(options *kolide.OptionsSpec) error {
gotOptions = options
return nil
},
},
}
svc := service{
ds: ds,
}
for _, tt := range testCases {
gotOptions = nil
t.Run("", func(t *testing.T) {
err := svc.ApplyOptionsYaml(tt.yml)
if tt.shouldErr {
assert.NotNil(t, err)
} else {
assert.Nil(t, err)
assert.Equal(t, tt.options, gotOptions)
}
})
}
}
func TestOptionsYamlRoundtrip(t *testing.T) {
var testCases = []struct {
spec kolide.OptionsSpec
}{
{
kolide.OptionsSpec{
json.RawMessage(`{"foo":"bar"}`),
kolide.OptionsOverrides{},
},
},
{
kolide.OptionsSpec{
json.RawMessage(`{"bing":"bang","boom":2}`),
kolide.OptionsOverrides{
map[string]json.RawMessage{
"foobar": json.RawMessage(`{"manzanita":"scratch"}`),
"froobling": json.RawMessage(`{"doornail":"mumble"}`),
},
},
},
},
}
var returnOptions, gotOptions *kolide.OptionsSpec
ds := &mock.Store{
OsqueryOptionsStore: mock.OsqueryOptionsStore{
GetOptionsFunc: func() (*kolide.OptionsSpec, error) {
return returnOptions, nil
},
ApplyOptionsFunc: func(options *kolide.OptionsSpec) error {
gotOptions = options
return nil
},
},
}
svc := service{
ds: ds,
}
for _, tt := range testCases {
t.Run("", func(t *testing.T) {
returnOptions = &tt.spec
gotOptions = nil
yml, err := svc.GetOptionsYaml()
require.Nil(t, err)
err = svc.ApplyOptionsYaml(yml)
require.Nil(t, err)
assert.Equal(t, returnOptions, gotOptions)
})
}
}

View File

@ -20,7 +20,6 @@ import (
"github.com/kolide/fleet/server/kolide"
"github.com/kolide/fleet/server/mock"
"github.com/kolide/fleet/server/pubsub"
"github.com/kolide/fleet/server/test"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -351,92 +350,105 @@ func TestLabelQueries(t *testing.T) {
}
func TestGetClientConfig(t *testing.T) {
ds, err := inmem.New(config.TestConfig())
require.Nil(t, err)
require.Nil(t, ds.MigrateData())
mockClock := clock.NewMockClock()
svc, err := newTestServiceWithClock(ds, nil, mockClock)
assert.Nil(t, err)
ctx := context.Background()
hosts, err := ds.ListHosts(kolide.ListOptions{})
require.Nil(t, err)
require.Len(t, hosts, 0)
_, err = svc.EnrollAgent(ctx, "", "user.local")
assert.Nil(t, err)
hosts, err = ds.ListHosts(kolide.ListOptions{})
require.Nil(t, err)
require.Len(t, hosts, 1)
host := hosts[0]
ctx = hostctx.NewContext(ctx, *host)
// with no queries, packs, labels, etc. verify the state of a fresh host
// asking for a config
config, err := svc.GetClientConfig(ctx)
require.Nil(t, err)
assert.NotNil(t, config)
val, ok := config.Options["disable_distributed"]
require.True(t, ok)
disabled, ok := val.(bool)
require.True(t, ok)
assert.False(t, disabled)
val, ok = config.Options["pack_delimiter"]
require.True(t, ok)
delim, ok := val.(string)
require.True(t, ok)
assert.Equal(t, "/", delim)
// this will be greater than 0 if we ever start inserting an administration
// pack
assert.Len(t, config.Packs, 0)
// let's populate the database with some info
infoQuery := &kolide.Query{
Name: "Info",
Query: "select * from osquery_info;",
ds := new(mock.Store)
ds.ListDecoratorsFunc = func(opt ...kolide.OptionalArg) ([]*kolide.Decorator, error) {
return []*kolide.Decorator{}, nil
}
infoQueryInterval := uint(60)
infoQuery, err = ds.NewQuery(infoQuery)
assert.Nil(t, err)
monitoringPack := &kolide.Pack{
Name: "monitoring",
ds.ListPacksFunc = func(opt kolide.ListOptions) ([]*kolide.Pack, error) {
return []*kolide.Pack{}, nil
}
_, err = ds.NewPack(monitoringPack)
assert.Nil(t, err)
test.NewScheduledQuery(t, ds, monitoringPack.ID, infoQuery.ID, infoQueryInterval, false, false)
mysqlLabel := &kolide.Label{
Name: "MySQL Monitoring",
Query: "select pid from processes where name = 'mysqld';",
ds.ListLabelsForHostFunc = func(hid uint) ([]kolide.Label, error) {
return []kolide.Label{
{ID: 1, Name: "foo_label"},
}, nil
}
ds.ListLabelsForPackFunc = func(pid uint) ([]*kolide.Label, error) {
switch pid {
case 1, 2:
return []*kolide.Label{
{ID: 1, Name: "foo_label"},
}, nil
default:
return []*kolide.Label{}, nil
}
}
ds.ListExplicitHostsInPackFunc = func(pid uint, opt kolide.ListOptions) ([]uint, error) {
switch pid {
case 4:
return []uint{1}, nil
default:
return []uint{}, nil
}
}
ds.ListScheduledQueriesInPackFunc = func(pid uint, opt kolide.ListOptions) ([]*kolide.ScheduledQuery, error) {
tru := true
switch pid {
case 1:
return []*kolide.ScheduledQuery{
{Name: "time", Query: "select * from time", Interval: 30},
}, nil
case 4:
return []*kolide.ScheduledQuery{
{Name: "foobar", Query: "select 3", Interval: 20},
{Name: "froobing", Query: "select 'guacamole'", Interval: 60, Snapshot: &tru},
}, nil
default:
return []*kolide.ScheduledQuery{}, nil
}
}
ds.OptionsForPlatformFunc = func(platform string) (json.RawMessage, error) {
return json.RawMessage(`{"options":{
"distributed_interval": 11,
"logger_tls_period": 33
}}`), nil
}
ds.SaveHostFunc = func(host *kolide.Host) error {
return nil
}
mysqlLabel, err = ds.NewLabel(mysqlLabel)
assert.Nil(t, err)
err = ds.AddLabelToPack(mysqlLabel.ID, monitoringPack.ID)
assert.Nil(t, err)
svc, err := newTestService(ds, nil)
require.Nil(t, err)
err = ds.RecordLabelQueryExecutions(
host,
map[uint]bool{mysqlLabel.ID: true},
mockClock.Now(),
ctx := hostctx.NewContext(context.Background(), kolide.Host{ID: 1})
expectedOptions := map[string]interface{}{
"distributed_interval": float64(11),
"logger_tls_period": float64(33),
}
// No packs loaded yet
conf, err := svc.GetClientConfig(ctx)
require.Nil(t, err)
assert.Equal(t, map[string]interface{}{"options": expectedOptions}, conf)
// Now add packs
ds.ListPacksFunc = func(opt kolide.ListOptions) ([]*kolide.Pack, error) {
return []*kolide.Pack{
{ID: 1, Name: "pack_by_label"},
{ID: 2, Name: "disabled_pack", Disabled: true},
{ID: 3, Name: "not_matching_pack"},
{ID: 4, Name: "pack_by_explicit_host"},
}, nil
}
conf, err = svc.GetClientConfig(ctx)
require.Nil(t, err)
assert.Equal(t, expectedOptions, conf["options"])
assert.JSONEq(t, `{
"pack_by_explicit_host": {
"queries": {
"foobar":{"query":"select 3","interval":20},
"froobing":{"query":"select 'guacamole'","interval":60,"snapshot":true}
}
},
"pack_by_label": {
"queries":{
"time":{"query":"select * from time","interval":30}
}
}
}`,
string(conf["packs"].(json.RawMessage)),
)
assert.Nil(t, err)
// with a minimal setup of packs, labels, and queries, will our host get the
// pack
config, err = svc.GetClientConfig(ctx)
require.Nil(t, err)
assert.Len(t, config.Packs, 1)
assert.Len(t, config.Packs["monitoring"].Queries, 1)
}
func TestDetailQueriesWithEmptyStrings(t *testing.T) {
@ -975,22 +987,11 @@ func TestUpdateHostIntervals(t *testing.T) {
ds.ListLabelsForHostFunc = func(hid uint) ([]kolide.Label, error) {
return []kolide.Label{}, nil
}
ds.AppConfigFunc = func() (*kolide.AppConfig, error) {
return &kolide.AppConfig{FIMInterval: 400}, nil
}
ds.FIMSectionsFunc = func() (kolide.FIMSections, error) {
sections := kolide.FIMSections{
"etc": []string{
"/etc/%%",
},
}
return sections, nil
}
var testCases = []struct {
initHost kolide.Host
finalHost kolide.Host
configOptions map[string]interface{}
configOptions json.RawMessage
saveHostCalled bool
}{
// Both updated
@ -1003,11 +1004,11 @@ func TestUpdateHostIntervals(t *testing.T) {
LoggerTLSPeriod: 33,
ConfigTLSRefresh: 60,
},
map[string]interface{}{
json.RawMessage(`{"options": {
"distributed_interval": 11,
"logger_tls_period": 33,
"logger_plugin": "tls",
},
"logger_plugin": "tls"
}}`),
true,
},
// Only logger_tls_period updated
@ -1021,10 +1022,10 @@ func TestUpdateHostIntervals(t *testing.T) {
LoggerTLSPeriod: 33,
ConfigTLSRefresh: 60,
},
map[string]interface{}{
json.RawMessage(`{"options": {
"distributed_interval": 11,
"logger_tls_period": 33,
},
"logger_tls_period": 33
}}`),
true,
},
// Only distributed_interval updated
@ -1038,10 +1039,10 @@ func TestUpdateHostIntervals(t *testing.T) {
LoggerTLSPeriod: 33,
ConfigTLSRefresh: 60,
},
map[string]interface{}{
json.RawMessage(`{"options": {
"distributed_interval": 11,
"logger_tls_period": 33,
},
"logger_tls_period": 33
}}`),
true,
},
// Kolide not managing distributed_interval
@ -1055,9 +1056,9 @@ func TestUpdateHostIntervals(t *testing.T) {
LoggerTLSPeriod: 33,
ConfigTLSRefresh: 60,
},
map[string]interface{}{
"logger_tls_period": 33,
},
json.RawMessage(`{"options":{
"logger_tls_period": 33
}}`),
true,
},
// SaveHost should not be called with no changes
@ -1072,21 +1073,19 @@ func TestUpdateHostIntervals(t *testing.T) {
LoggerTLSPeriod: 33,
ConfigTLSRefresh: 60,
},
map[string]interface{}{
json.RawMessage(`{"options":{
"distributed_interval": 11,
"logger_tls_period": 33,
},
"logger_tls_period": 33
}}`),
false,
},
}
for _, tt := range testCases {
ds.FIMSectionsFuncInvoked = false
t.Run("", func(t *testing.T) {
ctx := hostctx.NewContext(context.Background(), tt.initHost)
ds.GetOsqueryConfigOptionsFunc = func() (map[string]interface{}, error) {
ds.OptionsForPlatformFunc = func(platform string) (json.RawMessage, error) {
return tt.configOptions, nil
}
@ -1097,22 +1096,9 @@ func TestUpdateHostIntervals(t *testing.T) {
return nil
}
cfg, err := svc.GetClientConfig(ctx)
_, err := svc.GetClientConfig(ctx)
require.Nil(t, err)
assert.Equal(t, tt.saveHostCalled, saveHostCalled)
require.True(t, ds.FIMSectionsFuncInvoked)
require.Condition(t, func() bool {
_, ok := cfg.Schedule["file_events"]
return ok
})
assert.Equal(t, 400, int(cfg.Schedule["file_events"].Interval))
assert.Equal(t, "SELECT * FROM file_events;", cfg.Schedule["file_events"].Query)
require.NotNil(t, cfg.FilePaths)
require.Condition(t, func() bool {
_, ok := cfg.FilePaths["etc"]
return ok
})
assert.Len(t, cfg.FilePaths["etc"], 1)
})
}