From d1ef37b92cbd6c9b9b0e2bf13046ea57caf041a0 Mon Sep 17 00:00:00 2001 From: John Murphy Date: Sat, 31 Dec 2016 11:56:54 -0600 Subject: [PATCH] Osquery Options - /config/osquery-options #365 (#729) --- server/datastore/datastore_options_test.go | 16 ++++++++++++++++ server/datastore/datastore_test.go | 1 + server/datastore/inmem/options.go | 12 ++++++++++++ server/datastore/mysql/options.go | 22 ++++++++++++++++++++++ server/kolide/options.go | 3 +++ server/kolide/osquery.go | 11 +++-------- server/service/service_osquery.go | 12 +++++++----- server/service/service_osquery_test.go | 12 ++++++++++-- server/service/validation_options.go | 6 +++--- 9 files changed, 77 insertions(+), 18 deletions(-) diff --git a/server/datastore/datastore_options_test.go b/server/datastore/datastore_options_test.go index f20dc0a22f..506547ba0a 100644 --- a/server/datastore/datastore_options_test.go +++ b/server/datastore/datastore_options_test.go @@ -56,3 +56,19 @@ func testOptions(t *testing.T, ds kolide.Datastore) { assert.Equal(t, false, opt2.GetValue()) } + +func testOptionsToConfig(t *testing.T, ds kolide.Datastore) { + resp, err := ds.GetOsqueryConfigOptions() + require.Nil(t, err) + assert.Len(t, resp, 10) + assert.Equal(t, "/api/v1/osquery/distributed/read", resp["distributed_tls_read_endpoint"]) + opt, _ := ds.OptionByName("aws_profile_name") + assert.False(t, opt.OptionSet()) + opt.SetValue("zip") + err = ds.SaveOptions([]kolide.Option{*opt}) + require.Nil(t, err) + resp, err = ds.GetOsqueryConfigOptions() + require.Nil(t, err) + assert.Len(t, resp, 11) + assert.Equal(t, "zip", resp["aws_profile_name"]) +} diff --git a/server/datastore/datastore_test.go b/server/datastore/datastore_test.go index 1505f34460..cd5840992c 100644 --- a/server/datastore/datastore_test.go +++ b/server/datastore/datastore_test.go @@ -58,4 +58,5 @@ var testFunctions = [...]func(*testing.T, kolide.Datastore){ testSaveScheduledQuery, testOptions, testNewScheduledQuery, + testOptionsToConfig, } diff --git a/server/datastore/inmem/options.go b/server/datastore/inmem/options.go index f2bfb86c3d..3e35e61a78 100644 --- a/server/datastore/inmem/options.go +++ b/server/datastore/inmem/options.go @@ -72,3 +72,15 @@ func (d *Datastore) ListOptions() ([]kolide.Option, error) { sortutil.AscByField(result, "Name") return result, nil } + +func (d *Datastore) GetOsqueryConfigOptions() (map[string]interface{}, error) { + d.mtx.Lock() + defer d.mtx.Unlock() + optConfig := map[string]interface{}{} + for _, opt := range d.options { + if opt.OptionSet() { + optConfig[opt.Name] = opt.GetValue() + } + } + return optConfig, nil +} diff --git a/server/datastore/mysql/options.go b/server/datastore/mysql/options.go index b81b49c90a..644db50482 100644 --- a/server/datastore/mysql/options.go +++ b/server/datastore/mysql/options.go @@ -94,3 +94,25 @@ func (d *Datastore) ListOptions() ([]kolide.Option, error) { } return opts, nil } + +func (d *Datastore) GetOsqueryConfigOptions() (map[string]interface{}, error) { + // Retrieve all the options that are set. The value field is JSON formatted so + // to retrieve options that are set, we check JSON null keyword + sqlStatement := ` + SELECT * + FROM options + WHERE value != "null" + ` + var opts []kolide.Option + if err := d.db.Select(&opts, sqlStatement); err != nil { + if err == sql.ErrNoRows { + return nil, notFound("Option") + } + return nil, errors.Wrap(err, "select from options") + } + optConfig := map[string]interface{}{} + for _, opt := range opts { + optConfig[opt.Name] = opt.GetValue() + } + return optConfig, nil +} diff --git a/server/kolide/options.go b/server/kolide/options.go index 66b997bdaa..2668e1463f 100644 --- a/server/kolide/options.go +++ b/server/kolide/options.go @@ -22,6 +22,9 @@ type OptionStore interface { Option(id uint) (*Option, error) // OptionByName returns an option uniquely identified by name OptionByName(name string) (*Option, error) + // GetOsqueryConfigOptions returns options in a format that will be the options + // section of osquery configuration + GetOsqueryConfigOptions() (map[string]interface{}, error) } // OptionService interface describes methods that operate on osquery options diff --git a/server/kolide/osquery.go b/server/kolide/osquery.go index 743e8d435e..83dd784e5c 100644 --- a/server/kolide/osquery.go +++ b/server/kolide/osquery.go @@ -39,11 +39,6 @@ type PackContent struct { type Packs map[string]PackContent -type OsqueryOptions struct { - PackDelimiter string `json:"pack_delimiter,omitempty"` - DisableDistributed bool `json:"disable_distributed"` -} - type Decorators struct { Load []string `json:"load,omitempty"` Always []string `json:"always,omitempty"` @@ -51,9 +46,9 @@ type Decorators struct { } type OsqueryConfig struct { - Options OsqueryOptions `json:"options,omitempty"` - Decorators Decorators `json:"decorators,omitempty"` - Packs Packs `json:"packs,omitempty"` + Options map[string]interface{} `json:"options"` + Decorators Decorators `json:"decorators,omitempty"` + Packs Packs `json:"packs,omitempty"` } type OsqueryResultLog struct { diff --git a/server/service/service_osquery.go b/server/service/service_osquery.go index f5c8fb8a7e..366ea1b4f0 100644 --- a/server/service/service_osquery.go +++ b/server/service/service_osquery.go @@ -64,12 +64,14 @@ func (svc service) GetClientConfig(ctx context.Context) (*kolide.OsqueryConfig, return nil, osqueryError{message: "internal error: missing host from request context"} } + options, err := svc.ds.GetOsqueryConfigOptions() + if err != nil { + return nil, osqueryError{message: "internal error: unable to fetch configuration options"} + } + config := &kolide.OsqueryConfig{ - Options: kolide.OsqueryOptions{ - PackDelimiter: "/", - DisableDistributed: false, - }, - Packs: kolide.Packs{}, + Options: options, + Packs: kolide.Packs{}, } packs, err := svc.ListPacksForHost(ctx, host.ID) diff --git a/server/service/service_osquery_test.go b/server/service/service_osquery_test.go index 41145f7973..72bc8dc5d0 100644 --- a/server/service/service_osquery_test.go +++ b/server/service/service_osquery_test.go @@ -405,8 +405,16 @@ func TestGetClientConfig(t *testing.T) { config, err := svc.GetClientConfig(ctx) require.Nil(t, err) assert.NotNil(t, config) - assert.False(t, config.Options.DisableDistributed) - assert.Equal(t, "/", config.Options.PackDelimiter) + 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 diff --git a/server/service/validation_options.go b/server/service/validation_options.go index e81af29233..990d021041 100644 --- a/server/service/validation_options.go +++ b/server/service/validation_options.go @@ -1,7 +1,7 @@ package service import ( - "fmt" + "errors" "github.com/kolide/kolide-ose/server/kolide" "golang.org/x/net/context" @@ -25,8 +25,8 @@ func (mw validationMiddleware) ModifyOptions(ctx context.Context, req kolide.Opt } var ( - errTypeMismatch = fmt.Errorf("type mismatch") - errInvalidType = fmt.Errorf("invalid option type") + errTypeMismatch = errors.New("type mismatch") + errInvalidType = errors.New("invalid option type") ) func validateValueMapsToOptionType(opt kolide.Option) error {