diff --git a/cmd/fleetctl/apply.go b/cmd/fleetctl/apply.go index 2f49b6f388..dc2f5bef58 100644 --- a/cmd/fleetctl/apply.go +++ b/cmd/fleetctl/apply.go @@ -27,7 +27,6 @@ type specGroup struct { Queries []*kolide.QuerySpec Packs []*kolide.PackSpec Labels []*kolide.LabelSpec - Options *kolide.OptionsSpec AppConfig *kolide.AppConfigPayload EnrollSecret *kolide.EnrollSecretSpec } @@ -73,17 +72,6 @@ func specGroupFromBytes(b []byte) (*specGroup, error) { } specs.Labels = append(specs.Labels, labelSpec) - case kolide.OptionsKind: - if specs.Options != nil { - return nil, errors.New("options defined twice in the same file") - } - - var optionSpec *kolide.OptionsSpec - if err := yaml.Unmarshal(s.Spec, &optionSpec); err != nil { - return nil, errors.Wrap(err, "unmarshaling "+kind+" spec") - } - specs.Options = optionSpec - case kolide.AppConfigKind: if specs.AppConfig != nil { return nil, errors.New("config defined twice in the same file") @@ -175,13 +163,6 @@ func applyCommand() *cli.Command { fmt.Printf("[+] applied %d packs\n", len(specs.Packs)) } - if specs.Options != nil { - if err := fleet.ApplyOptions(specs.Options); err != nil { - return errors.Wrap(err, "applying options") - } - fmt.Printf("[+] applied options\n") - } - if specs.AppConfig != nil { if err := fleet.ApplyAppConfig(specs.AppConfig); err != nil { return errors.Wrap(err, "applying fleet config") diff --git a/cmd/fleetctl/get.go b/cmd/fleetctl/get.go index a183115890..cfcd9c6c45 100644 --- a/cmd/fleetctl/get.go +++ b/cmd/fleetctl/get.go @@ -120,24 +120,6 @@ func printPack(c *cli.Context, pack *kolide.PackSpec) error { return err } -func printOption(c *cli.Context, option *kolide.OptionsSpec) error { - spec := specGeneric{ - Kind: kolide.OptionsKind, - Version: kolide.ApiVersion, - Spec: option, - } - - var err error - - if c.Bool(jsonFlagName) { - err = printJSON(spec) - } else { - err = printYaml(spec) - } - - return err -} - func printSecret(c *cli.Context, secret *kolide.EnrollSecretSpec) error { spec := specGeneric{ Kind: kolide.EnrollSecretKind, @@ -209,7 +191,6 @@ func getCommand() *cli.Command { getQueriesCommand(), getPacksCommand(), getLabelsCommand(), - getOptionsCommand(), getHostsCommand(), getEnrollSecretCommand(), getAppConfigCommand(), @@ -484,38 +465,6 @@ func getLabelsCommand() *cli.Command { } } -func getOptionsCommand() *cli.Command { - return &cli.Command{ - Name: "options", - Usage: "Retrieve the osquery configuration", - Flags: []cli.Flag{ - jsonFlag(), - yamlFlag(), - configFlag(), - contextFlag(), - debugFlag(), - }, - Action: func(c *cli.Context) error { - fleet, err := clientFromCLI(c) - if err != nil { - return err - } - - options, err := fleet.GetOptions() - if err != nil { - return err - } - - err = printOption(c, options) - if err != nil { - return err - } - - return nil - }, - } -} - func getEnrollSecretCommand() *cli.Command { return &cli.Command{ Name: "enroll_secret", diff --git a/server/datastore/datastore.go b/server/datastore/datastore.go index 57324c6221..171a02c85e 100644 --- a/server/datastore/datastore.go +++ b/server/datastore/datastore.go @@ -79,9 +79,6 @@ var TestFunctions = [...]func(*testing.T, kolide.Datastore){ testCountHostsInTargets, testHostStatus, testHostIDsInTargets, - testApplyOsqueryOptions, - testApplyOsqueryOptionsNoOverrides, - testOsqueryOptionsForHost, testApplyQueries, testApplyPackSpecRoundtrip, testApplyPackSpecMissingQueries, diff --git a/server/datastore/datastore_osquery_options.go b/server/datastore/datastore_osquery_options.go deleted file mode 100644 index dabcf7c8d7..0000000000 --- a/server/datastore/datastore_osquery_options.go +++ /dev/null @@ -1,95 +0,0 @@ -package datastore - -import ( - "encoding/json" - "testing" - - "github.com/fleetdm/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) - }) - } -} diff --git a/server/datastore/mysql/migrations/tables/20171116163618_CreateTableOsqueryOptions.go b/server/datastore/mysql/migrations/tables/20171116163618_CreateTableOsqueryOptions.go index dddaf78126..db9c60f43c 100644 --- a/server/datastore/mysql/migrations/tables/20171116163618_CreateTableOsqueryOptions.go +++ b/server/datastore/mysql/migrations/tables/20171116163618_CreateTableOsqueryOptions.go @@ -8,7 +8,6 @@ import ( "github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx/reflectx" - "github.com/fleetdm/fleet/server/kolide" "github.com/pkg/errors" ) @@ -153,7 +152,7 @@ func migrateOptions(tx *sql.Tx) error { override_type, override_identifier, options ) VALUES (?, ?, ?) ` - if _, err = txx.Exec(query, kolide.OptionOverrideTypeDefault, "", string(confJSON)); err != nil { + if _, err = txx.Exec(query, 0, "", string(confJSON)); err != nil { return errors.Wrap(err, "saving converted options") } diff --git a/server/datastore/mysql/migrations/tables/20210510111225_TeamsAgentOptionsFollowup.go b/server/datastore/mysql/migrations/tables/20210510111225_TeamsAgentOptionsFollowup.go new file mode 100644 index 0000000000..c1323eaa75 --- /dev/null +++ b/server/datastore/mysql/migrations/tables/20210510111225_TeamsAgentOptionsFollowup.go @@ -0,0 +1,85 @@ +package tables + +import ( + "database/sql" + "encoding/json" + + "github.com/fleetdm/fleet/server/kolide" + "github.com/jmoiron/sqlx" + "github.com/jmoiron/sqlx/reflectx" + "github.com/pkg/errors" +) + +func init() { + MigrationClient.AddMigration(Up_20210510111225, Down_20210510111225) +} + +func Up_20210510111225(tx *sql.Tx) error { + existingOptions, err := copyOptions(tx) + if err != nil { + return errors.Wrap(err, "get existing options") + } + + sql := ` + ALTER TABLE app_configs + ADD COLUMN agent_options JSON + ` + if _, err := tx.Exec(sql); err != nil { + return errors.Wrap(err, "add column agent_options") + } + + sql = `UPDATE app_configs SET agent_options = ?` + if _, err := tx.Exec(sql, existingOptions); err != nil { + return errors.Wrap(err, "insert existing options") + } + + return nil +} + +// Below code copied and adapted from osquery options code removed in this commit. + +func copyOptions(tx *sql.Tx) (json.RawMessage, error) { + // Migrate pre teams osquery options to the new osquery option storage in app config. + txx := sqlx.Tx{Tx: tx, Mapper: reflectx.NewMapperFunc("db", sqlx.NameMapper)} + + var rows []optionsRow + if err := txx.Select(&rows, "SELECT * FROM osquery_options"); err != nil { + return nil, errors.Wrap(err, "selecting options") + } + + opt := &kolide.AgentOptions{ + Overrides: kolide.AgentOptionsOverrides{ + Platforms: make(map[string]json.RawMessage), + }, + } + for _, row := range rows { + switch row.OverrideType { + case 0: // was kolide.OptionOverrideTypeDefault + opt.Config = json.RawMessage(row.Options) + + case 1: // was kolide.OptionOverrideTypePlatform + opt.Overrides.Platforms[row.OverrideIdentifier] = json.RawMessage(row.Options) + + default: + return nil, errors.Errorf("unknown override type: %d", row.OverrideType) + } + } + + jsonVal, err := json.Marshal(opt) + if err != nil { + return nil, errors.Wrap(err, "marshal options") + } + + return jsonVal, nil +} + +type optionsRow struct { + ID int `db:"id"` + OverrideType int `db:"override_type"` + OverrideIdentifier string `db:"override_identifier"` + Options string `db:"options"` +} + +func Down_20210510111225(tx *sql.Tx) error { + return nil +} diff --git a/server/datastore/mysql/osquery_options.go b/server/datastore/mysql/osquery_options.go deleted file mode 100644 index 6039e1023d..0000000000 --- a/server/datastore/mysql/osquery_options.go +++ /dev/null @@ -1,132 +0,0 @@ -package mysql - -import ( - "database/sql" - "encoding/json" - "fmt" - - "github.com/go-kit/kit/log/level" - "github.com/fleetdm/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 -} diff --git a/server/kolide/agent_options.go b/server/kolide/agent_options.go new file mode 100644 index 0000000000..691bdfc311 --- /dev/null +++ b/server/kolide/agent_options.go @@ -0,0 +1,36 @@ +package kolide + +import ( + "context" + "encoding/json" +) + +type AgentOptionsService interface { + // AgentOptionsForHost gets the agent options for the provided host. + // + // The host information should be used for filtering based on team, + // platform, etc. + AgentOptionsForHost(ctx context.Context, host *Host) (json.RawMessage, error) +} + +type AgentOptions struct { + // Config is the base config options. + Config json.RawMessage `json:"config"` + // Overrides includes any platform-based overrides. + Overrides AgentOptionsOverrides `json:"overrides,omitempty"` +} + +type AgentOptionsOverrides struct { + // Platforms is a map from platform name to the config override. + Platforms map[string]json.RawMessage `json:"platforms,omitempty"` +} + +func (o *AgentOptions) ForPlatform(platform string) json.RawMessage { + // Return matching platform override if available. + if opt, ok := o.Overrides.Platforms[platform]; ok { + return opt + } + + // Otherwise return base config for team. + return o.Config +} diff --git a/server/kolide/app.go b/server/kolide/app.go index 24814e011a..cfade19d6a 100644 --- a/server/kolide/app.go +++ b/server/kolide/app.go @@ -167,6 +167,9 @@ type AppConfig struct { // AdditionalQueries is the set of additional queries that should be run // when collecting details from hosts. AdditionalQueries *json.RawMessage `db:"additional_queries"` + + // AgentOptions is the global agent options, including overrides. + AgentOptions json.RawMessage `db:"agent_options"` } const ( diff --git a/server/kolide/datastore.go b/server/kolide/datastore.go index 459d080239..5cd74f7345 100644 --- a/server/kolide/datastore.go +++ b/server/kolide/datastore.go @@ -14,7 +14,6 @@ type Datastore interface { AppConfigStore InviteStore ScheduledQueryStore - OsqueryOptionsStore CarveStore TeamStore SoftwareStore diff --git a/server/kolide/osquery_options.go b/server/kolide/osquery_options.go deleted file mode 100644 index 097e9bd282..0000000000 --- a/server/kolide/osquery_options.go +++ /dev/null @@ -1,50 +0,0 @@ -package kolide - -import ( - "context" - "encoding/json" -) - -type OsqueryOptionsStore interface { - ApplyOptions(options *OptionsSpec) error - GetOptions() (*OptionsSpec, error) - OptionsForPlatform(platform string) (json.RawMessage, error) -} - -type OsqueryOptionsService interface { - ApplyOptionsSpec(ctx context.Context, spec *OptionsSpec) error - GetOptionsSpec(ctx context.Context) (*OptionsSpec, error) -} - -type OptionsObject struct { - ObjectMetadata - 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 ( - OptionsKind = "options" -) - -// 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 -) diff --git a/server/kolide/osquery_options_test.go b/server/kolide/osquery_options_test.go deleted file mode 100644 index b0a96cf45a..0000000000 --- a/server/kolide/osquery_options_test.go +++ /dev/null @@ -1,119 +0,0 @@ -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 OptionsObject - 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"])) - } -} diff --git a/server/kolide/service.go b/server/kolide/service.go index 265d06a65d..4620662178 100644 --- a/server/kolide/service.go +++ b/server/kolide/service.go @@ -9,7 +9,7 @@ type Service interface { QueryService CampaignService OsqueryService - OsqueryOptionsService + AgentOptionsService HostService AppConfigService InviteService diff --git a/server/kolide/teams.go b/server/kolide/teams.go index ac7e33ef7e..3642aa3a58 100644 --- a/server/kolide/teams.go +++ b/server/kolide/teams.go @@ -9,14 +9,14 @@ import ( type TeamStore interface { // NewTeam creates a new Team object in the store. NewTeam(team *Team) (*Team, error) + // SaveTeam saves any changes to the team. + SaveTeam(team *Team) (*Team, error) // Team retrieves the Team by ID. Team(tid uint) (*Team, error) // Team deletes the Team by ID. DeleteTeam(tid uint) error // TeamByName retrieves the Team by Name. TeamByName(name string) (*Team, error) - // SaveTeam saves any changes to the team. - SaveTeam(team *Team) (*Team, error) // ListTeams lists teams with the ordering and filters in the provided // options. ListTeams(opt ListOptions) ([]*Team, error) diff --git a/server/mock/datastore.go b/server/mock/datastore.go index 0bbcca3226..b9c84d7f5c 100644 --- a/server/mock/datastore.go +++ b/server/mock/datastore.go @@ -21,12 +21,11 @@ var _ kolide.Datastore = (*Store)(nil) type Store struct { kolide.PasswordResetStore - kolide.TeamStore + TeamStore TargetStore SessionStore CampaignStore ScheduledQueryStore - OsqueryOptionsStore AppConfigStore HostStore InviteStore diff --git a/server/mock/datastore_osquery_options.go b/server/mock/datastore_osquery_options.go deleted file mode 100644 index d2d7aefd44..0000000000 --- a/server/mock/datastore_osquery_options.go +++ /dev/null @@ -1,43 +0,0 @@ -// Automatically generated by mockimpl. DO NOT EDIT! - -package mock - -import ( - "encoding/json" - - "github.com/fleetdm/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) -} diff --git a/server/mock/datastore_teams.go b/server/mock/datastore_teams.go new file mode 100644 index 0000000000..a6331069a9 --- /dev/null +++ b/server/mock/datastore_teams.go @@ -0,0 +1,71 @@ +// Automatically generated by mockimpl. DO NOT EDIT! + +package mock + +import ( + "github.com/fleetdm/fleet/server/kolide" +) + +var _ kolide.TeamStore = (*TeamStore)(nil) + +type NewTeamFunc func(team *kolide.Team) (*kolide.Team, error) + +type SaveTeamFunc func(team *kolide.Team) (*kolide.Team, error) + +type DeleteTeamFunc func(tid uint) error + +type TeamFunc func(id uint) (*kolide.Team, error) + +type TeamByNameFunc func(name string) (*kolide.Team, error) + +type ListTeamsFunc func(opt kolide.ListOptions) ([]*kolide.Team, error) + +type TeamStore struct { + NewTeamFunc NewTeamFunc + NewTeamFuncInvoked bool + + SaveTeamFunc SaveTeamFunc + SaveTeamFuncInvoked bool + + DeleteTeamFunc DeleteTeamFunc + DeleteTeamFuncInvoked bool + + TeamFunc TeamFunc + TeamFuncInvoked bool + + TeamByNameFunc TeamByNameFunc + TeamByNameFuncInvoked bool + + ListTeamsFunc ListTeamsFunc + ListTeamsFuncInvoked bool +} + +func (s *TeamStore) NewTeam(team *kolide.Team) (*kolide.Team, error) { + s.NewTeamFuncInvoked = true + return s.NewTeamFunc(team) +} + +func (s *TeamStore) SaveTeam(team *kolide.Team) (*kolide.Team, error) { + s.SaveTeamFuncInvoked = true + return s.SaveTeamFunc(team) +} + +func (s *TeamStore) DeleteTeam(tid uint) error { + s.DeleteTeamFuncInvoked = true + return s.DeleteTeamFunc(tid) +} + +func (s *TeamStore) Team(id uint) (*kolide.Team, error) { + s.TeamFuncInvoked = true + return s.TeamFunc(id) +} + +func (s *TeamStore) TeamByName(identifier string) (*kolide.Team, error) { + s.TeamByNameFuncInvoked = true + return s.TeamByNameFunc(identifier) +} + +func (s *TeamStore) ListTeams(opt kolide.ListOptions) ([]*kolide.Team, error) { + s.ListTeamsFuncInvoked = true + return s.ListTeamsFunc(opt) +} diff --git a/server/service/client_options.go b/server/service/client_options.go deleted file mode 100644 index d3e864748a..0000000000 --- a/server/service/client_options.go +++ /dev/null @@ -1,73 +0,0 @@ -package service - -import ( - "encoding/json" - "net/http" - - "github.com/fleetdm/fleet/server/kolide" - "github.com/pkg/errors" -) - -// ApplyOptions sends the osquery options to be applied to the Fleet instance. -func (c *Client) ApplyOptions(spec *kolide.OptionsSpec) error { - req := applyOsqueryOptionsSpecRequest{Spec: spec} - response, err := c.AuthenticatedDo("POST", "/api/v1/fleet/spec/osquery_options", "", req) - if err != nil { - return errors.Wrap(err, "POST /api/v1/fleet/spec/osquery_options") - } - defer response.Body.Close() - - if response.StatusCode != http.StatusOK { - return errors.Errorf( - "apply options received status %d %s", - response.StatusCode, - extractServerErrorText(response.Body), - ) - } - - var responseBody applyOsqueryOptionsSpecResponse - err = json.NewDecoder(response.Body).Decode(&responseBody) - if err != nil { - return errors.Wrap(err, "decode apply options spec response") - } - - if responseBody.Err != nil { - return errors.Errorf("apply options spec: %s", responseBody.Err) - } - - return nil -} - -// GetOptions retrieves the configured osquery options. -func (c *Client) GetOptions() (*kolide.OptionsSpec, error) { - verb, path := "GET", "/api/v1/fleet/spec/osquery_options" - response, err := c.AuthenticatedDo(verb, path, "", nil) - if err != nil { - return nil, errors.Wrap(err, verb+" "+path) - } - defer response.Body.Close() - - switch response.StatusCode { - case http.StatusNotFound: - return nil, notFoundErr{} - } - if response.StatusCode != http.StatusOK { - return nil, errors.Errorf( - "get options received status %d %s", - response.StatusCode, - extractServerErrorText(response.Body), - ) - } - - var responseBody getOsqueryOptionsSpecResponse - err = json.NewDecoder(response.Body).Decode(&responseBody) - if err != nil { - return nil, errors.Wrap(err, "decode get options spec response") - } - - if responseBody.Err != nil { - return nil, errors.Errorf("get options spec: %s", responseBody.Err) - } - - return responseBody.Spec, nil -} diff --git a/server/service/endpoint_osquery_options.go b/server/service/endpoint_osquery_options.go deleted file mode 100644 index 9545bb415d..0000000000 --- a/server/service/endpoint_osquery_options.go +++ /dev/null @@ -1,54 +0,0 @@ -package service - -import ( - "context" - - "github.com/go-kit/kit/endpoint" - "github.com/fleetdm/fleet/server/kolide" -) - -//////////////////////////////////////////////////////////////////////////////// -// Apply Options Spec -//////////////////////////////////////////////////////////////////////////////// - -type applyOsqueryOptionsSpecRequest struct { - Spec *kolide.OptionsSpec `json:"spec"` -} - -type applyOsqueryOptionsSpecResponse struct { - Err error `json:"error,omitempty"` -} - -func (r applyOsqueryOptionsSpecResponse) error() error { return r.Err } - -func makeApplyOsqueryOptionsSpecEndpoint(svc kolide.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - req := request.(applyOsqueryOptionsSpecRequest) - err := svc.ApplyOptionsSpec(ctx, req.Spec) - if err != nil { - return applyOsqueryOptionsSpecResponse{Err: err}, nil - } - return applyOsqueryOptionsSpecResponse{}, nil - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Get Options Spec -//////////////////////////////////////////////////////////////////////////////// - -type getOsqueryOptionsSpecResponse struct { - Spec *kolide.OptionsSpec `json:"spec"` - Err error `json:"error,omitempty"` -} - -func (r getOsqueryOptionsSpecResponse) error() error { return r.Err } - -func makeGetOsqueryOptionsSpecEndpoint(svc kolide.Service) endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { - spec, err := svc.GetOptionsSpec(ctx) - if err != nil { - return getOsqueryOptionsSpecResponse{Err: err}, nil - } - return getOsqueryOptionsSpecResponse{Spec: spec}, nil - } -} diff --git a/server/service/handler.go b/server/service/handler.go index 016dffbdf0..b5d155753b 100644 --- a/server/service/handler.go +++ b/server/service/handler.go @@ -97,8 +97,6 @@ type KolideEndpoints struct { ListHosts endpoint.Endpoint GetHostSummary endpoint.Endpoint SearchTargets endpoint.Endpoint - ApplyOsqueryOptionsSpec endpoint.Endpoint - GetOsqueryOptionsSpec endpoint.Endpoint GetCertificate endpoint.Endpoint ChangeEmail endpoint.Endpoint InitiateSSO endpoint.Endpoint @@ -211,8 +209,6 @@ func MakeKolideServerEndpoints(svc kolide.Service, jwtKey, urlPrefix string, lim GetLabelSpecs: authenticatedUser(jwtKey, svc, makeGetLabelSpecsEndpoint(svc)), GetLabelSpec: authenticatedUser(jwtKey, svc, makeGetLabelSpecEndpoint(svc)), SearchTargets: authenticatedUser(jwtKey, svc, makeSearchTargetsEndpoint(svc)), - ApplyOsqueryOptionsSpec: authenticatedUser(jwtKey, svc, makeApplyOsqueryOptionsSpecEndpoint(svc)), - GetOsqueryOptionsSpec: authenticatedUser(jwtKey, svc, makeGetOsqueryOptionsSpecEndpoint(svc)), GetCertificate: authenticatedUser(jwtKey, svc, makeCertificateEndpoint(svc)), ChangeEmail: authenticatedUser(jwtKey, svc, makeChangeEmailEndpoint(svc)), ListCarves: authenticatedUser(jwtKey, svc, makeListCarvesEndpoint(svc)), @@ -323,8 +319,6 @@ type kolideHandlers struct { ListHosts http.Handler GetHostSummary http.Handler SearchTargets http.Handler - ApplyOsqueryOptionsSpec http.Handler - GetOsqueryOptionsSpec http.Handler GetCertificate http.Handler ChangeEmail http.Handler InitiateSSO http.Handler @@ -426,8 +420,6 @@ func makeKolideKitHandlers(e KolideEndpoints, opts []kithttp.ServerOption) *koli ListHosts: newServer(e.ListHosts, decodeListHostsRequest), GetHostSummary: newServer(e.GetHostSummary, decodeNoParamsRequest), SearchTargets: newServer(e.SearchTargets, decodeSearchTargetsRequest), - ApplyOsqueryOptionsSpec: newServer(e.ApplyOsqueryOptionsSpec, decodeApplyOsqueryOptionsSpecRequest), - GetOsqueryOptionsSpec: newServer(e.GetOsqueryOptionsSpec, decodeNoParamsRequest), GetCertificate: newServer(e.GetCertificate, decodeNoParamsRequest), ChangeEmail: newServer(e.ChangeEmail, decodeChangeEmailRequest), InitiateSSO: newServer(e.InitiateSSO, decodeInitiateSSORequest), @@ -644,9 +636,6 @@ func attachKolideAPIRoutes(r *mux.Router, h *kolideHandlers) { r.Handle("/api/v1/fleet/hosts/identifier/{identifier}", h.HostByIdentifier).Methods("GET").Name("host_by_identifier") r.Handle("/api/v1/fleet/hosts/{id}", h.DeleteHost).Methods("DELETE").Name("delete_host") - r.Handle("/api/v1/fleet/spec/osquery_options", h.ApplyOsqueryOptionsSpec).Methods("POST").Name("apply_osquery_options_spec") - r.Handle("/api/v1/fleet/spec/osquery_options", h.GetOsqueryOptionsSpec).Methods("GET").Name("get_osquery_options_spec") - r.Handle("/api/v1/fleet/targets", h.SearchTargets).Methods("POST").Name("search_targets") r.Handle("/api/v1/fleet/version", h.Version).Methods("GET").Name("version") diff --git a/server/service/logging_osquery_options.go b/server/service/logging_osquery_options.go deleted file mode 100644 index e021fa749b..0000000000 --- a/server/service/logging_osquery_options.go +++ /dev/null @@ -1,42 +0,0 @@ -package service - -import ( - "context" - "time" - - "github.com/fleetdm/fleet/server/contexts/viewer" - "github.com/fleetdm/fleet/server/kolide" -) - -func (mw loggingMiddleware) GetOptionsSpec(ctx context.Context) (spec *kolide.OptionsSpec, err error) { - defer func(begin time.Time) { - _ = mw.loggerDebug(err).Log( - "method", "GetOptionsSpec", - "err", err, - "took", time.Since(begin), - ) - }(time.Now()) - spec, err = mw.Service.GetOptionsSpec(ctx) - return spec, err -} - -func (mw loggingMiddleware) ApplyOptionsSpec(ctx context.Context, spec *kolide.OptionsSpec) (err error) { - var ( - loggedInUser = "unauthenticated" - ) - - if vc, ok := viewer.FromContext(ctx); ok { - - loggedInUser = vc.Username() - } - defer func(begin time.Time) { - _ = mw.loggerDebug(err).Log( - "method", "ApplyOptionsSpec", - "err", err, - "user", loggedInUser, - "took", time.Since(begin), - ) - }(time.Now()) - err = mw.Service.ApplyOptionsSpec(ctx, spec) - return err -} diff --git a/server/service/service_agent_options.go b/server/service/service_agent_options.go new file mode 100644 index 0000000000..daeea79fe5 --- /dev/null +++ b/server/service/service_agent_options.go @@ -0,0 +1,41 @@ +package service + +import ( + "context" + "encoding/json" + + "github.com/fleetdm/fleet/server/kolide" + "github.com/pkg/errors" +) + +func (svc service) AgentOptionsForHost(ctx context.Context, host *kolide.Host) (json.RawMessage, error) { + // If host has a team and team has non-empty options, prioritize that. + if host.TeamID.Valid { + team, err := svc.ds.Team(uint(host.TeamID.Int64)) + if err != nil { + return nil, errors.Wrap(err, "load team for host") + } + + if team.AgentOptions != nil && len(*team.AgentOptions) > 0 { + var options kolide.AgentOptions + if err := json.Unmarshal(*team.AgentOptions, &options); err != nil { + return nil, errors.Wrap(err, "unmarshal team agent options") + } + + return options.ForPlatform(host.Platform), nil + } + } + + // Otherwise return the appropriate override for global options. + appConfig, err := svc.ds.AppConfig() + if err != nil { + return nil, errors.Wrap(err, "load global agent options") + } + + var options kolide.AgentOptions + if err := json.Unmarshal(appConfig.AgentOptions, &options); err != nil { + return nil, errors.Wrap(err, "unmarshal global agent options") + } + + return options.ForPlatform(host.Platform), nil +} diff --git a/server/service/service_agent_options_test.go b/server/service/service_agent_options_test.go new file mode 100644 index 0000000000..1a396d5e8b --- /dev/null +++ b/server/service/service_agent_options_test.go @@ -0,0 +1,54 @@ +package service + +import ( + "context" + "encoding/json" + "testing" + + "github.com/fleetdm/fleet/server/kolide" + "github.com/fleetdm/fleet/server/mock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/guregu/null.v3" +) + +func TestAgentOptionsForHost(t *testing.T) { + ds := new(mock.Store) + svc, err := newTestService(ds, nil, nil) + require.NoError(t, err) + + teamID := uint(1) + ds.TeamFunc = func(tid uint) (*kolide.Team, error) { + assert.Equal(t, teamID, tid) + opt := json.RawMessage(`{"config":{"foo":"bar"},"overrides":{"platforms":{"darwin":{"foo":"override"}}}}`) + return &kolide.Team{AgentOptions: &opt}, nil + } + ds.AppConfigFunc = func() (*kolide.AppConfig, error) { + return &kolide.AppConfig{AgentOptions: json.RawMessage(`{"config":{"baz":"bar"},"overrides":{"platforms":{"darwin":{"foo":"override2"}}}}`)}, nil + } + + host := &kolide.Host{ + TeamID: null.IntFrom(int64(teamID)), + Platform: "darwin", + } + + opt, err := svc.AgentOptionsForHost(context.Background(), host) + require.NoError(t, err) + assert.JSONEq(t, `{"foo":"override"}`, string(opt)) + + host.Platform = "windows" + opt, err = svc.AgentOptionsForHost(context.Background(), host) + require.NoError(t, err) + assert.JSONEq(t, `{"foo":"bar"}`, string(opt)) + + // Should take gobal option with no team + host.TeamID.Valid = false + opt, err = svc.AgentOptionsForHost(context.Background(), host) + require.NoError(t, err) + assert.JSONEq(t, `{"baz":"bar"}`, string(opt)) + + host.Platform = "darwin" + opt, err = svc.AgentOptionsForHost(context.Background(), host) + require.NoError(t, err) + assert.JSONEq(t, `{"foo":"override2"}`, string(opt)) +} diff --git a/server/service/service_osquery.go b/server/service/service_osquery.go index 8cc1aee194..6089220836 100644 --- a/server/service/service_osquery.go +++ b/server/service/service_osquery.go @@ -197,7 +197,7 @@ func (svc service) GetClientConfig(ctx context.Context) (map[string]interface{}, return nil, osqueryError{message: "internal error: missing host from request context"} } - baseConfig, err := svc.ds.OptionsForPlatform(host.Platform) + baseConfig, err := svc.AgentOptionsForHost(ctx, &host) if err != nil { return nil, osqueryError{message: "internal error: fetching base config: " + err.Error()} } diff --git a/server/service/service_osquery_options.go b/server/service/service_osquery_options.go deleted file mode 100644 index 47ca755b8c..0000000000 --- a/server/service/service_osquery_options.go +++ /dev/null @@ -1,25 +0,0 @@ -package service - -import ( - "context" - - "github.com/fleetdm/fleet/server/kolide" - "github.com/pkg/errors" -) - -func (svc service) ApplyOptionsSpec(ctx context.Context, spec *kolide.OptionsSpec) error { - err := svc.ds.ApplyOptions(spec) - if err != nil { - return errors.Wrap(err, "apply options") - } - return nil -} - -func (svc service) GetOptionsSpec(ctx context.Context) (*kolide.OptionsSpec, error) { - spec, err := svc.ds.GetOptions() - if err != nil { - return nil, errors.Wrap(err, "get options from datastore") - } - - return spec, nil -} diff --git a/server/service/service_osquery_test.go b/server/service/service_osquery_test.go index 072b83b42b..af1df9a4eb 100644 --- a/server/service/service_osquery_test.go +++ b/server/service/service_osquery_test.go @@ -433,30 +433,8 @@ func TestGetClientConfig(t *testing.T) { return []*kolide.ScheduledQuery{}, nil } } - ds.OptionsForPlatformFunc = func(platform string) (json.RawMessage, error) { - return json.RawMessage(` -{ - "options":{ - "distributed_interval":11, - "logger_tls_period":33 - }, - "decorators":{ - "load":[ - "SELECT version FROM osquery_info;", - "SELECT uuid AS host_uuid FROM system_info;" - ], - "always":[ - "SELECT user AS username FROM logged_in_users WHERE user <> '' ORDER BY time LIMIT 1;" - ], - "interval":{ - "3600":[ - "SELECT total_seconds AS uptime FROM uptime;" - ] - } - }, - "foo": "bar" -} -`), nil + ds.AppConfigFunc = func() (*kolide.AppConfig, error) { + return &kolide.AppConfig{AgentOptions: json.RawMessage(`{"config":{"options":{"baz":"bar"}}}`)}, nil } ds.SaveHostFunc = func(host *kolide.Host) error { return nil @@ -469,27 +447,11 @@ func TestGetClientConfig(t *testing.T) { ctx2 := hostctx.NewContext(context.Background(), kolide.Host{ID: 2}) expectedOptions := map[string]interface{}{ - "distributed_interval": float64(11), - "logger_tls_period": float64(33), - } - - expectedDecorators := map[string]interface{}{ - "load": []interface{}{ - "SELECT version FROM osquery_info;", - "SELECT uuid AS host_uuid FROM system_info;", - }, - "always": []interface{}{ - "SELECT user AS username FROM logged_in_users WHERE user <> '' ORDER BY time LIMIT 1;", - }, - "interval": map[string]interface{}{ - "3600": []interface{}{"SELECT total_seconds AS uptime FROM uptime;"}, - }, + "baz": "bar", } expectedConfig := map[string]interface{}{ - "options": expectedOptions, - "decorators": expectedDecorators, - "foo": "bar", + "options": expectedOptions, } // No packs loaded yet @@ -1517,8 +1479,8 @@ func TestUpdateHostIntervals(t *testing.T) { t.Run("", func(t *testing.T) { ctx := hostctx.NewContext(context.Background(), tt.initHost) - ds.OptionsForPlatformFunc = func(platform string) (json.RawMessage, error) { - return tt.configOptions, nil + ds.AppConfigFunc = func() (*kolide.AppConfig, error) { + return &kolide.AppConfig{AgentOptions: json.RawMessage(`{"config":` + string(tt.configOptions) + `}`)}, nil } saveHostCalled := false diff --git a/server/service/transport_osquery_options.go b/server/service/transport_osquery_options.go deleted file mode 100644 index 3b46ec4de8..0000000000 --- a/server/service/transport_osquery_options.go +++ /dev/null @@ -1,16 +0,0 @@ -package service - -import ( - "context" - "encoding/json" - "net/http" -) - -func decodeApplyOsqueryOptionsSpecRequest(ctx context.Context, r *http.Request) (interface{}, error) { - var req applyOsqueryOptionsSpecRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - return nil, err - } - return req, nil - -}