Pull decorators from config options (#1749)

Previously decorators were stored in a separate table. Now they are stored
directly with the config so that they can be modified on a per-platform basis.

Delete now unused decorators code.
This commit is contained in:
Zachary Wasserman 2018-05-03 10:14:07 -07:00 committed by GitHub
parent a0d05f4e2a
commit 1d9e37b069
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 85 additions and 1066 deletions

View file

@ -1,39 +0,0 @@
package datastore
import (
"testing"
"github.com/kolide/fleet/server/kolide"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func testDecorators(t *testing.T, ds kolide.Datastore) {
decorator := &kolide.Decorator{
Query: "select from something",
Type: kolide.DecoratorInterval,
Interval: 60,
}
decorator, err := ds.NewDecorator(decorator)
require.Nil(t, err)
require.True(t, decorator.ID > 0)
result, err := ds.Decorator(decorator.ID)
require.Nil(t, err)
assert.Equal(t, decorator.Query, result.Query)
results, err := ds.ListDecorators()
require.Nil(t, err)
assert.Len(t, results, 1)
decorator.Query = "select foo from bar;"
err = ds.SaveDecorator(decorator)
require.Nil(t, err)
result, err = ds.Decorator(decorator.ID)
require.Nil(t, err)
assert.Equal(t, "select foo from bar;", result.Query)
err = ds.DeleteDecorator(decorator.ID)
require.Nil(t, err)
result, err = ds.Decorator(decorator.ID)
assert.NotNil(t, err)
}

View file

@ -59,7 +59,6 @@ var testFunctions = [...]func(*testing.T, kolide.Datastore){
testOptionsToConfig,
testGetPackByName,
testGetQueryByName,
testDecorators,
testFileIntegrityMonitoring,
testYARAStore,
testAddLabelToPackTwice,

View file

@ -1,99 +0,0 @@
package mysql
import (
"database/sql"
"github.com/pkg/errors"
"github.com/kolide/fleet/server/kolide"
)
func (ds *Datastore) SaveDecorator(dec *kolide.Decorator, opts ...kolide.OptionalArg) error {
db := ds.getTransaction(opts)
sqlStatement :=
"UPDATE decorators SET " +
"`name` = ?, " +
"`query` = ?, " +
"`type` = ?, " +
"`interval` = ? " +
"WHERE id = ?"
_, err := db.Exec(
sqlStatement,
dec.Name,
dec.Query,
dec.Type,
dec.Interval,
dec.ID,
)
if err != nil {
return errors.Wrap(err, "saving decorator")
}
return nil
}
func (ds *Datastore) NewDecorator(decorator *kolide.Decorator, opts ...kolide.OptionalArg) (*kolide.Decorator, error) {
db := ds.getTransaction(opts)
sqlStatement :=
"INSERT INTO decorators (" +
"`name`," +
"`query`," +
"`type`," +
"`interval` ) " +
"VALUES (?, ?, ?, ?)"
result, err := db.Exec(sqlStatement, decorator.Name, decorator.Query, decorator.Type, decorator.Interval)
if err != nil {
return nil, errors.Wrap(err, "creating decorator")
}
id, _ := result.LastInsertId()
decorator.ID = uint(id)
return decorator, nil
}
func (ds *Datastore) DeleteDecorator(id uint) error {
sqlStatement := `
DELETE FROM decorators
WHERE id = ?
`
res, err := ds.db.Exec(sqlStatement, id)
if err != nil {
return errors.Wrap(err, "deleting decorator")
}
deleted, _ := res.RowsAffected()
if deleted < 1 {
return notFound("Decorator").WithID(id)
}
return nil
}
func (ds *Datastore) Decorator(id uint) (*kolide.Decorator, error) {
sqlStatement := `
SELECT *
FROM decorators
WHERE id = ?
`
var result kolide.Decorator
err := ds.db.Get(&result, sqlStatement, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, notFound("Decorator").WithID(id)
}
return nil, errors.Wrap(err, "retrieving decorator")
}
return &result, nil
}
func (ds *Datastore) ListDecorators(opts ...kolide.OptionalArg) ([]*kolide.Decorator, error) {
db := ds.getTransaction(opts)
sqlStatement := `
SELECT *
FROM decorators
ORDER by built_in DESC, name ASC
`
var results []*kolide.Decorator
err := db.Select(&results, sqlStatement)
if err != nil {
return nil, errors.Wrap(err, "listing decorators")
}
return results, nil
}

View file

@ -3,6 +3,8 @@ package data
import (
"database/sql"
"encoding/json"
"fmt"
"strconv"
"github.com/jmoiron/sqlx"
"github.com/jmoiron/sqlx/reflectx"
@ -15,21 +17,14 @@ func init() {
}
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"`
Options map[string]interface{} `json:"options"`
FilePaths map[string][]string `json:"file_paths,omitempty"`
Decorators kolide.Decorators `json:"decorators"`
}
func Up_20171212182458(tx *sql.Tx) error {
// Migrate pre fleetctl osquery options to the new osquery options
// formats.
txx := sqlx.Tx{Tx: tx, Mapper: reflectx.NewMapperFunc("db", sqlx.NameMapper)}
// Get basic osquery options
@ -70,10 +65,39 @@ func Up_20171212182458(tx *sql.Tx) error {
fimConfig[sectionName] = append(fimConfig[sectionName], fileName)
}
query = `
SELECT *
FROM decorators
ORDER by built_in DESC, name ASC
`
var decorators []*kolide.Decorator
err = txx.Select(&decorators, query)
if err != nil {
return errors.Wrap(err, "retrieving decorators")
}
decConfig := kolide.Decorators{
Interval: make(map[string][]string),
}
for _, dec := range decorators {
switch dec.Type {
case kolide.DecoratorLoad:
decConfig.Load = append(decConfig.Load, dec.Query)
case kolide.DecoratorAlways:
decConfig.Always = append(decConfig.Always, dec.Query)
case kolide.DecoratorInterval:
key := strconv.Itoa(int(dec.Interval))
decConfig.Interval[key] = append(decConfig.Interval[key], dec.Query)
default:
fmt.Printf("Unable to migrate decorator. Please migrate manually: '%s'\n", dec.Query)
}
}
// Create config JSON
config := configForExport{
Options: optConfig,
FilePaths: fimConfig,
Options: optConfig,
FilePaths: fimConfig,
Decorators: decConfig,
}
confJSON, err := json.Marshal(config)
if err != nil {

View file

@ -15,7 +15,6 @@ type Datastore interface {
InviteStore
ScheduledQueryStore
OptionStore
DecoratorStore
FileIntegrityMonitoringStore
YARAStore
OsqueryOptionsStore

View file

@ -1,38 +1,13 @@
package kolide
import (
"context"
"errors"
"strings"
)
// DecoratorStore methods to manipulate decorator queries.
// See https://osquery.readthedocs.io/en/stable/deployment/configuration/
type DecoratorStore interface {
// NewDecorator creates a decorator query.
NewDecorator(decorator *Decorator, opts ...OptionalArg) (*Decorator, error)
// DeleteDecorator removes a decorator query.
DeleteDecorator(id uint) error
// Decorator retrieves a decorator query with supplied ID.
Decorator(id uint) (*Decorator, error)
// ListDecorators returns all decorator queries.
ListDecorators(opts ...OptionalArg) ([]*Decorator, error)
// SaveDecorator updates an existing decorator
SaveDecorator(dec *Decorator, opts ...OptionalArg) error
}
// DecoratorService exposes decorators data so it can be manipulated by
// end users
type DecoratorService interface {
// ListDecorators returns decorators
ListDecorators(ctx context.Context) ([]*Decorator, error)
// DeleteDecorator removes an existing decorator if it is not built-in
DeleteDecorator(ctx context.Context, id uint) error
// NewDecorator creates a new decorator
NewDecorator(ctx context.Context, payload DecoratorPayload) (*Decorator, error)
// ModifyDecorator updates an existing decorator
ModifyDecorator(ctx context.Context, payload DecoratorPayload) (*Decorator, error)
}
// DEPRECATED
// Decorators are now stored as JSON in the config, so these types are only
// useful for migrating existing Fleet installations.
// DecoratorType refers to the allowable types of decorator queries.
// See https://osquery.readthedocs.io/en/stable/deployment/configuration/

View file

@ -15,6 +15,5 @@ type Service interface {
TargetService
ScheduledQueryService
OptionService
DecoratorService
FileIntegrityMonitoringService
}

View file

@ -4,7 +4,6 @@ package mock
//go:generate mockimpl -o datastore_invites.go "s *InviteStore" "kolide.InviteStore"
//go:generate mockimpl -o datastore_appconfig.go "s *AppConfigStore" "kolide.AppConfigStore"
//go:generate mockimpl -o datastore_labels.go "s *LabelStore" "kolide.LabelStore"
//go:generate mockimpl -o datastore_decorators.go "s *DecoratorStore" "kolide.DecoratorStore"
//go:generate mockimpl -o datastore_options.go "s *OptionStore" "kolide.OptionStore"
//go:generate mockimpl -o datastore_packs.go "s *PackStore" "kolide.PackStore"
//go:generate mockimpl -o datastore_hosts.go "s *HostStore" "kolide.HostStore"
@ -28,7 +27,6 @@ type Store struct {
OsqueryOptionsStore
FileIntegrityMonitoringStore
AppConfigStore
DecoratorStore
HostStore
InviteStore
LabelStore

View file

@ -1,59 +0,0 @@
// Automatically generated by mockimpl. DO NOT EDIT!
package mock
import "github.com/kolide/fleet/server/kolide"
var _ kolide.DecoratorStore = (*DecoratorStore)(nil)
type NewDecoratorFunc func(decorator *kolide.Decorator, opts ...kolide.OptionalArg) (*kolide.Decorator, error)
type DeleteDecoratorFunc func(id uint) error
type DecoratorFunc func(id uint) (*kolide.Decorator, error)
type ListDecoratorsFunc func(opts ...kolide.OptionalArg) ([]*kolide.Decorator, error)
type SaveDecoratorFunc func(dec *kolide.Decorator, opts ...kolide.OptionalArg) error
type DecoratorStore struct {
NewDecoratorFunc NewDecoratorFunc
NewDecoratorFuncInvoked bool
DeleteDecoratorFunc DeleteDecoratorFunc
DeleteDecoratorFuncInvoked bool
DecoratorFunc DecoratorFunc
DecoratorFuncInvoked bool
ListDecoratorsFunc ListDecoratorsFunc
ListDecoratorsFuncInvoked bool
SaveDecoratorFunc SaveDecoratorFunc
SaveDecoratorFuncInvoked bool
}
func (s *DecoratorStore) NewDecorator(decorator *kolide.Decorator, opts ...kolide.OptionalArg) (*kolide.Decorator, error) {
s.NewDecoratorFuncInvoked = true
return s.NewDecoratorFunc(decorator, opts...)
}
func (s *DecoratorStore) DeleteDecorator(id uint) error {
s.DeleteDecoratorFuncInvoked = true
return s.DeleteDecoratorFunc(id)
}
func (s *DecoratorStore) Decorator(id uint) (*kolide.Decorator, error) {
s.DecoratorFuncInvoked = true
return s.DecoratorFunc(id)
}
func (s *DecoratorStore) ListDecorators(opts ...kolide.OptionalArg) ([]*kolide.Decorator, error) {
s.ListDecoratorsFuncInvoked = true
return s.ListDecoratorsFunc(opts...)
}
func (s *DecoratorStore) SaveDecorator(dec *kolide.Decorator, opts ...kolide.OptionalArg) error {
s.SaveDecoratorFuncInvoked = true
return s.SaveDecoratorFunc(dec, opts...)
}

View file

@ -1,81 +0,0 @@
package service
import (
"context"
"github.com/go-kit/kit/endpoint"
"github.com/kolide/fleet/server/kolide"
)
type listDecoratorResponse struct {
Decorators []*kolide.Decorator `json:"decorators"`
Err error `json:"error,omitempty"`
}
func (r listDecoratorResponse) error() error { return r.Err }
func makeListDecoratorsEndpoint(svc kolide.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
decs, err := svc.ListDecorators(ctx)
if err != nil {
return listDecoratorResponse{Err: err}, nil
}
return listDecoratorResponse{Decorators: decs}, nil
}
}
type newDecoratorRequest struct {
Payload kolide.DecoratorPayload `json:"payload"`
}
type decoratorResponse struct {
Decorator *kolide.Decorator `json:"decorator,omitempty"`
Err error `json:"error,omitempty"`
}
func (r decoratorResponse) error() error { return r.Err }
func makeNewDecoratorEndpoint(svc kolide.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
r := request.(newDecoratorRequest)
dec, err := svc.NewDecorator(ctx, r.Payload)
if err != nil {
return decoratorResponse{Err: err}, nil
}
return decoratorResponse{Decorator: dec}, nil
}
}
type deleteDecoratorRequest struct {
ID uint
}
type deleteDecoratorResponse struct {
Err error `json:"error,omitempty"`
}
func (r deleteDecoratorResponse) error() error { return r.Err }
func makeDeleteDecoratorEndpoint(svc kolide.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
r := request.(deleteDecoratorRequest)
err := svc.DeleteDecorator(ctx, r.ID)
if err != nil {
return deleteDecoratorResponse{Err: err}, nil
}
return deleteDecoratorResponse{}, nil
}
}
func makeModifyDecoratorEndpoint(svc kolide.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
r := request.(newDecoratorRequest)
dec, err := svc.ModifyDecorator(ctx, r.Payload)
if err != nil {
return decoratorResponse{Err: err}, nil
}
return decoratorResponse{Decorator: dec}, nil
}
}

View file

@ -1,203 +0,0 @@
package service
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"testing"
"github.com/kolide/fleet/server/kolide"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func setupDecoratorTest(r *testResource) {
decs := []kolide.Decorator{
kolide.Decorator{
Type: kolide.DecoratorLoad,
Query: "select something from foo;",
},
kolide.Decorator{
Type: kolide.DecoratorLoad,
Query: "select bar from foo;",
},
kolide.Decorator{
Type: kolide.DecoratorAlways,
Query: "select x from y;",
},
kolide.Decorator{
Type: kolide.DecoratorInterval,
Query: "select name from system_info;",
Interval: 3600,
},
}
for _, d := range decs {
r.ds.NewDecorator(&d)
}
}
func testModifyDecorator(t *testing.T, r *testResource) {
dec := &kolide.Decorator{
Name: "foo",
Type: kolide.DecoratorLoad,
Query: "select foo from bar;",
}
dec, err := r.ds.NewDecorator(dec)
require.Nil(t, err)
buffer := bytes.NewBufferString(`{
"payload": {
"type": "always",
"name": "bar",
"query": "select baz from boom;"
}
}`)
req, err := http.NewRequest("PATCH", r.server.URL+fmt.Sprintf("/api/v1/kolide/decorators/%d", dec.ID), buffer)
require.Nil(t, err)
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", r.adminToken))
client := &http.Client{}
resp, err := client.Do(req)
require.Nil(t, err)
var decResp decoratorResponse
err = json.NewDecoder(resp.Body).Decode(&decResp)
require.Nil(t, err)
require.NotNil(t, decResp.Decorator)
assert.Equal(t, "select baz from boom;", decResp.Decorator.Query)
assert.Equal(t, kolide.DecoratorAlways, decResp.Decorator.Type)
assert.Equal(t, "bar", decResp.Decorator.Name)
}
// This test verifies that we can submit the same payload twice without
// raising an error
func testModifyDecoratorNoChanges(t *testing.T, r *testResource) {
dec := &kolide.Decorator{
Type: kolide.DecoratorLoad,
Query: "select foo from bar;",
}
dec, err := r.ds.NewDecorator(dec)
require.Nil(t, err)
buffer := bytes.NewBufferString(`{
"payload": {
"type": "load",
"query": "select foo from bar;"
}
}`)
req, err := http.NewRequest("PATCH", r.server.URL+fmt.Sprintf("/api/v1/kolide/decorators/%d", dec.ID), buffer)
require.Nil(t, err)
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", r.adminToken))
client := &http.Client{}
resp, err := client.Do(req)
require.Nil(t, err)
var decResp decoratorResponse
err = json.NewDecoder(resp.Body).Decode(&decResp)
require.Nil(t, err)
require.NotNil(t, decResp.Decorator)
assert.Equal(t, "select foo from bar;", decResp.Decorator.Query)
assert.Equal(t, kolide.DecoratorLoad, decResp.Decorator.Type)
}
func testListDecorator(t *testing.T, r *testResource) {
setupDecoratorTest(r)
req, err := http.NewRequest("GET", r.server.URL+"/api/v1/kolide/decorators", nil)
require.Nil(t, err)
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", r.adminToken))
client := &http.Client{}
resp, err := client.Do(req)
require.Nil(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
var decs listDecoratorResponse
err = json.NewDecoder(resp.Body).Decode(&decs)
require.Nil(t, err)
assert.Len(t, decs.Decorators, 4)
}
func testNewDecorator(t *testing.T, r *testResource) {
buffer := bytes.NewBufferString(
`{
"payload": {
"type": "load",
"query": "select x from y;"
}
}`)
req, err := http.NewRequest("POST", r.server.URL+"/api/v1/kolide/decorators", buffer)
require.Nil(t, err)
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", r.adminToken))
client := &http.Client{}
resp, err := client.Do(req)
require.Nil(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
var dec decoratorResponse
err = json.NewDecoder(resp.Body).Decode(&dec)
require.Nil(t, err)
require.NotNil(t, dec.Decorator)
assert.Equal(t, kolide.DecoratorLoad, dec.Decorator.Type)
assert.Equal(t, "select x from y;", dec.Decorator.Query)
}
// invalid json
func testNewDecoratorFailType(t *testing.T, r *testResource) {
buffer := bytes.NewBufferString(
`{
"payload": {
"type": "zip",
"query": "select x from y;"
}
}`)
req, err := http.NewRequest("POST", r.server.URL+"/api/v1/kolide/decorators", buffer)
require.Nil(t, err)
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", r.adminToken))
client := &http.Client{}
resp, err := client.Do(req)
require.Nil(t, err)
require.Equal(t, http.StatusUnprocessableEntity, resp.StatusCode)
var errStruct mockValidationError
err = json.NewDecoder(resp.Body).Decode(&errStruct)
require.Nil(t, err)
require.Len(t, errStruct.Errors, 1)
assert.Equal(t, "invalid value, must be load, always, or interval", errStruct.Errors[0].Reason)
}
func testNewDecoratorFailValidation(t *testing.T, r *testResource) {
buffer := bytes.NewBufferString(
`{
"payload": {
"type": "interval",
"query": "select x from y;",
"interval": 3601
}
}`)
req, err := http.NewRequest("POST", r.server.URL+"/api/v1/kolide/decorators", buffer)
require.Nil(t, err)
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", r.adminToken))
client := &http.Client{}
resp, err := client.Do(req)
require.Nil(t, err)
require.Equal(t, http.StatusUnprocessableEntity, resp.StatusCode)
var errStruct mockValidationError
err = json.NewDecoder(resp.Body).Decode(&errStruct)
require.Nil(t, err)
require.Len(t, errStruct.Errors, 1)
assert.Equal(t, "must be divisible by 60", errStruct.Errors[0].Reason)
}
func testDeleteDecorator(t *testing.T, r *testResource) {
setupDecoratorTest(r)
req, err := http.NewRequest("DELETE", r.server.URL+"/api/v1/kolide/decorators/1", nil)
require.Nil(t, err)
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", r.adminToken))
client := &http.Client{}
resp, err := client.Do(req)
require.Nil(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
decs, _ := r.ds.ListDecorators()
assert.Len(t, decs, 3)
}

View file

@ -111,13 +111,6 @@ var testFunctions = [...]func(*testing.T, *testResource){
testNonAdminUserSetAdmin,
testAdminUserSetEnabled,
testNonAdminUserSetEnabled,
testModifyDecorator,
testListDecorator,
testNewDecorator,
testNewDecoratorFailType,
testNewDecoratorFailValidation,
testDeleteDecorator,
testModifyDecoratorNoChanges,
}
func TestEndpoints(t *testing.T) {

View file

@ -58,10 +58,6 @@ type KolideEndpoints struct {
GetLabel endpoint.Endpoint
ListLabels endpoint.Endpoint
DeleteLabel endpoint.Endpoint
ListDecorators endpoint.Endpoint
NewDecorator endpoint.Endpoint
ModifyDecorator endpoint.Endpoint
DeleteDecorator endpoint.Endpoint
GetHost endpoint.Endpoint
DeleteHost endpoint.Endpoint
ListHosts endpoint.Endpoint
@ -137,10 +133,6 @@ func MakeKolideServerEndpoints(svc kolide.Service, jwtKey string) KolideEndpoint
GetLabel: authenticatedUser(jwtKey, svc, makeGetLabelEndpoint(svc)),
ListLabels: authenticatedUser(jwtKey, svc, makeListLabelsEndpoint(svc)),
DeleteLabel: authenticatedUser(jwtKey, svc, makeDeleteLabelEndpoint(svc)),
ListDecorators: authenticatedUser(jwtKey, svc, makeListDecoratorsEndpoint(svc)),
NewDecorator: authenticatedUser(jwtKey, svc, makeNewDecoratorEndpoint(svc)),
ModifyDecorator: authenticatedUser(jwtKey, svc, makeModifyDecoratorEndpoint(svc)),
DeleteDecorator: authenticatedUser(jwtKey, svc, makeDeleteDecoratorEndpoint(svc)),
SearchTargets: authenticatedUser(jwtKey, svc, makeSearchTargetsEndpoint(svc)),
GetOptions: authenticatedUser(jwtKey, svc, mustBeAdmin(makeGetOptionsEndpoint(svc))),
ModifyOptions: authenticatedUser(jwtKey, svc, mustBeAdmin(makeModifyOptionsEndpoint(svc))),
@ -203,10 +195,6 @@ type kolideHandlers struct {
GetLabel http.Handler
ListLabels http.Handler
DeleteLabel http.Handler
ListDecorators http.Handler
NewDecorator http.Handler
ModifyDecorator http.Handler
DeleteDecorator http.Handler
GetHost http.Handler
DeleteHost http.Handler
ListHosts http.Handler
@ -272,10 +260,6 @@ func makeKolideKitHandlers(e KolideEndpoints, opts []kithttp.ServerOption) *koli
GetLabel: newServer(e.GetLabel, decodeGetLabelRequest),
ListLabels: newServer(e.ListLabels, decodeListLabelsRequest),
DeleteLabel: newServer(e.DeleteLabel, decodeDeleteLabelRequest),
ListDecorators: newServer(e.ListDecorators, decodeNoParamsRequest),
NewDecorator: newServer(e.NewDecorator, decodeNewDecoratorRequest),
ModifyDecorator: newServer(e.ModifyDecorator, decodeModifyDecoratorRequest),
DeleteDecorator: newServer(e.DeleteDecorator, decodeDeleteDecoratorRequest),
GetHost: newServer(e.GetHost, decodeGetHostRequest),
DeleteHost: newServer(e.DeleteHost, decodeDeleteHostRequest),
ListHosts: newServer(e.ListHosts, decodeListHostsRequest),
@ -382,11 +366,6 @@ func attachKolideAPIRoutes(r *mux.Router, h *kolideHandlers) {
r.Handle("/api/v1/kolide/labels", h.ListLabels).Methods("GET").Name("list_labels")
r.Handle("/api/v1/kolide/labels/{id}", h.DeleteLabel).Methods("DELETE").Name("delete_label")
r.Handle("/api/v1/kolide/decorators", h.ListDecorators).Methods("GET").Name("list_decorators")
r.Handle("/api/v1/kolide/decorators", h.NewDecorator).Methods("POST").Name("create_decorator")
r.Handle("/api/v1/kolide/decorators/{id}", h.ModifyDecorator).Methods("PATCH").Name("modify_decorator")
r.Handle("/api/v1/kolide/decorators/{id}", h.DeleteDecorator).Methods("DELETE").Name("delete_decorator")
r.Handle("/api/v1/kolide/hosts", h.ListHosts).Methods("GET").Name("list_hosts")
r.Handle("/api/v1/kolide/host_summary", h.GetHostSummary).Methods("GET").Name("get_host_summary")
r.Handle("/api/v1/kolide/hosts/{id}", h.GetHost).Methods("GET").Name("get_host")

View file

@ -1,69 +0,0 @@
package service
import (
"context"
"time"
"github.com/kolide/fleet/server/kolide"
)
func (mw loggingMiddleware) ListDecorators(ctx context.Context) ([]*kolide.Decorator, error) {
var (
decs []*kolide.Decorator
err error
)
defer func(begin time.Time) {
mw.logger.Log(
"method", "ListDecorators",
"err", err,
"took", time.Since(begin),
)
}(time.Now())
decs, err = mw.Service.ListDecorators(ctx)
return decs, err
}
func (mw loggingMiddleware) NewDecorator(ctx context.Context, payload kolide.DecoratorPayload) (*kolide.Decorator, error) {
var (
dec *kolide.Decorator
err error
)
defer func(begin time.Time) {
mw.logger.Log(
"method", "NewDecorator",
"err", err,
"took", time.Since(begin),
)
}(time.Now())
dec, err = mw.Service.NewDecorator(ctx, payload)
return dec, err
}
func (mw loggingMiddleware) ModifyDecorator(ctx context.Context, payload kolide.DecoratorPayload) (*kolide.Decorator, error) {
var (
dec *kolide.Decorator
err error
)
defer func(begin time.Time) {
mw.logger.Log(
"method", "ModifyDecorator",
"err", err,
"took", time.Since(begin),
)
}(time.Now())
dec, err = mw.Service.ModifyDecorator(ctx, payload)
return dec, err
}
func (mw loggingMiddleware) DeleteDecorator(ctx context.Context, id uint) error {
var err error
defer func(begin time.Time) {
mw.logger.Log(
"method", "DeleteDecorator",
"err", err,
"took", time.Since(begin),
)
}(time.Now())
err = mw.Service.DeleteDecorator(ctx, id)
return err
}

View file

@ -1,62 +0,0 @@
package service
import (
"context"
"fmt"
"time"
"github.com/kolide/fleet/server/kolide"
)
func (mw metricsMiddleware) ListDecorators(ctx context.Context) ([]*kolide.Decorator, error) {
var (
decs []*kolide.Decorator
err error
)
defer func(begin time.Time) {
lvs := []string{"method", "ListDecorators", "error", fmt.Sprint(err != nil)}
mw.requestCount.With(lvs...).Add(1)
mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds())
}(time.Now())
decs, err = mw.Service.ListDecorators(ctx)
return decs, err
}
func (mw metricsMiddleware) NewDecorator(ctx context.Context, payload kolide.DecoratorPayload) (*kolide.Decorator, error) {
var (
dec *kolide.Decorator
err error
)
defer func(begin time.Time) {
lvs := []string{"method", "NewDecorator", "error", fmt.Sprint(err != nil)}
mw.requestCount.With(lvs...).Add(1)
mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds())
}(time.Now())
dec, err = mw.Service.NewDecorator(ctx, payload)
return dec, err
}
func (mw metricsMiddleware) ModifyDecorator(ctx context.Context, payload kolide.DecoratorPayload) (*kolide.Decorator, error) {
var (
dec *kolide.Decorator
err error
)
defer func(begin time.Time) {
lvs := []string{"method", "ModifyDecorator", "error", fmt.Sprint(err != nil)}
mw.requestCount.With(lvs...).Add(1)
mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds())
}(time.Now())
dec, err = mw.Service.ModifyDecorator(ctx, payload)
return dec, err
}
func (mw metricsMiddleware) DeleteDecorator(ctx context.Context, id uint) error {
var err error
defer func(begin time.Time) {
lvs := []string{"method", "DeleteDecorator", "error", fmt.Sprint(err != nil)}
mw.requestCount.With(lvs...).Add(1)
mw.requestLatency.With(lvs...).Observe(time.Since(begin).Seconds())
}(time.Now())
err = mw.Service.DeleteDecorator(ctx, id)
return err
}

View file

@ -1,52 +0,0 @@
package service
import (
"context"
"github.com/kolide/fleet/server/kolide"
)
func (svc service) ListDecorators(ctx context.Context) ([]*kolide.Decorator, error) {
return svc.ds.ListDecorators()
}
func (svc service) DeleteDecorator(ctx context.Context, uid uint) error {
return svc.ds.DeleteDecorator(uid)
}
func (svc service) NewDecorator(ctx context.Context, payload kolide.DecoratorPayload) (*kolide.Decorator, error) {
var dec kolide.Decorator
if payload.Name != nil {
dec.Name = *payload.Name
}
dec.Query = *payload.Query
dec.Type = *payload.DecoratorType
if payload.Interval != nil {
dec.Interval = *payload.Interval
}
return svc.ds.NewDecorator(&dec)
}
func (svc service) ModifyDecorator(ctx context.Context, payload kolide.DecoratorPayload) (*kolide.Decorator, error) {
dec, err := svc.ds.Decorator(payload.ID)
if err != nil {
return nil, err
}
if payload.Name != nil {
dec.Name = *payload.Name
}
if payload.DecoratorType != nil {
dec.Type = *payload.DecoratorType
}
if payload.Query != nil {
dec.Query = *payload.Query
}
if payload.Interval != nil {
dec.Interval = *payload.Interval
}
err = svc.ds.SaveDecorator(dec)
if err != nil {
return nil, err
}
return dec, nil
}

View file

@ -106,36 +106,6 @@ func (svc service) GetClientConfig(ctx context.Context) (map[string]interface{},
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: " + err.Error()}
}
decConfig := kolide.Decorators{
Interval: make(map[string][]string),
}
for _, dec := range decorators {
switch dec.Type {
case kolide.DecoratorLoad:
decConfig.Load = append(decConfig.Load, dec.Query)
case kolide.DecoratorAlways:
decConfig.Always = append(decConfig.Always, dec.Query)
case kolide.DecoratorInterval:
key := strconv.Itoa(int(dec.Interval))
decConfig.Interval[key] = append(decConfig.Interval[key], dec.Query)
default:
svc.logger.Log("component", "service", "method", "GetClientConfig", "err",
"unknown decorator type")
}
}
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.ds.ListPacksForHost(host.ID)
if err != nil {
return nil, osqueryError{message: "database error: " + err.Error()}

View file

@ -326,9 +326,6 @@ func TestLabelQueries(t *testing.T) {
func TestGetClientConfig(t *testing.T) {
ds := new(mock.Store)
ds.ListDecoratorsFunc = func(opt ...kolide.OptionalArg) ([]*kolide.Decorator, error) {
return []*kolide.Decorator{}, nil
}
ds.ListPacksForHostFunc = func(hid uint) ([]*kolide.Pack, error) {
return []*kolide.Pack{}, nil
}
@ -351,10 +348,29 @@ func TestGetClientConfig(t *testing.T) {
}
}
ds.OptionsForPlatformFunc = func(platform string) (json.RawMessage, error) {
return json.RawMessage(`{"options":{
"distributed_interval": 11,
"logger_tls_period": 33
}}`), nil
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.SaveHostFunc = func(host *kolide.Host) error {
return nil
@ -371,14 +387,33 @@ func TestGetClientConfig(t *testing.T) {
"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;"},
},
}
expectedConfig := map[string]interface{}{
"options": expectedOptions,
"decorators": expectedDecorators,
"foo": "bar",
}
// No packs loaded yet
conf, err := svc.GetClientConfig(ctx1)
require.Nil(t, err)
assert.Equal(t, map[string]interface{}{"options": expectedOptions}, conf)
assert.Equal(t, expectedConfig, conf)
conf, err = svc.GetClientConfig(ctx2)
require.Nil(t, err)
assert.Equal(t, map[string]interface{}{"options": expectedOptions}, conf)
assert.Equal(t, expectedConfig, conf)
// Now add packs
ds.ListPacksForHostFunc = func(hid uint) ([]*kolide.Pack, error) {
@ -977,9 +1012,6 @@ func TestUpdateHostIntervals(t *testing.T) {
svc, err := newTestService(ds, nil)
require.Nil(t, err)
ds.ListDecoratorsFunc = func(opt ...kolide.OptionalArg) ([]*kolide.Decorator, error) {
return []*kolide.Decorator{}, nil
}
ds.ListPacksForHostFunc = func(hid uint) ([]*kolide.Pack, error) {
return []*kolide.Pack{}, nil
}

View file

@ -1,38 +0,0 @@
package service
import (
"context"
"encoding/json"
"net/http"
)
func decodeNewDecoratorRequest(ctx context.Context, req *http.Request) (interface{}, error) {
var dec newDecoratorRequest
err := json.NewDecoder(req.Body).Decode(&dec)
if err != nil {
return nil, err
}
return dec, nil
}
func decodeDeleteDecoratorRequest(ctx context.Context, req *http.Request) (interface{}, error) {
id, err := idFromRequest(req, "id")
if err != nil {
return nil, err
}
return deleteDecoratorRequest{ID: id}, nil
}
func decodeModifyDecoratorRequest(ctx context.Context, req *http.Request) (interface{}, error) {
var request newDecoratorRequest
id, err := idFromRequest(req, "id")
if err != nil {
return nil, err
}
err = json.NewDecoder(req.Body).Decode(&request)
if err != nil {
return nil, err
}
request.Payload.ID = id
return request, nil
}

View file

@ -1,104 +0,0 @@
package service
import (
"context"
"github.com/kolide/fleet/server/kolide"
)
func validateNewDecoratorType(payload kolide.DecoratorPayload, invalid *invalidArgumentError) {
if payload.DecoratorType == nil {
invalid.Append("type", "missing required argument")
return
}
if *payload.DecoratorType == kolide.DecoratorUndefined {
invalid.Append("type", "invalid value, must be load, always, or interval")
return
}
if *payload.DecoratorType == kolide.DecoratorInterval {
if payload.Interval == nil {
invalid.Append("interval", "missing required argument")
return
}
if *payload.Interval%60 != 0 {
invalid.Append("interval", "must be divisible by 60")
return
}
}
}
// NewDecorator validator checks to make sure that a valid decorator type exists and
// if the decorator is of an interval type, an interval value is present and is
// divisable by 60
// See https://osquery.readthedocs.io/en/stable/deployment/configuration/
func (mw validationMiddleware) NewDecorator(ctx context.Context, payload kolide.DecoratorPayload) (*kolide.Decorator, error) {
invalid := &invalidArgumentError{}
validateNewDecoratorType(payload, invalid)
if payload.Query == nil {
invalid.Append("query", "missing required argument")
}
if invalid.HasErrors() {
return nil, invalid
}
return mw.Service.NewDecorator(ctx, payload)
}
func (mw validationMiddleware) validateModifyDecoratorType(payload kolide.DecoratorPayload, invalid *invalidArgumentError) error {
if payload.DecoratorType != nil {
if *payload.DecoratorType == kolide.DecoratorUndefined {
invalid.Append("type", "invalid value, must be load, always, or interval")
return nil
}
if *payload.DecoratorType == kolide.DecoratorInterval {
// special processing for interval type
existingDec, err := mw.ds.Decorator(payload.ID)
if err != nil {
// if decorator is not present we want to return a 404 to the client
return err
}
// if the type has changed from always or load to interval we need to
// check suitability of interval value
if existingDec.Type != kolide.DecoratorInterval {
if payload.Interval == nil {
invalid.Append("interval", "missing required argument")
return nil
}
}
}
if payload.Interval != nil {
if *payload.Interval%60 != 0 {
invalid.Append("interval", "value must be divisible by 60")
}
}
}
return nil
}
func (mw validationMiddleware) ModifyDecorator(ctx context.Context, payload kolide.DecoratorPayload) (*kolide.Decorator, error) {
invalid := &invalidArgumentError{}
err := mw.validateModifyDecoratorType(payload, invalid)
if err != nil {
return nil, err
}
if invalid.HasErrors() {
return nil, invalid
}
return mw.Service.ModifyDecorator(ctx, payload)
}
func (mw validationMiddleware) DeleteDecorator(ctx context.Context, id uint) error {
invalid := &invalidArgumentError{}
dec, err := mw.ds.Decorator(id)
if err != nil {
return err
}
if dec.BuiltIn {
invalid.Append("decorator", "read only")
return invalid
}
return mw.Service.DeleteDecorator(ctx, id)
}

View file

@ -1,143 +0,0 @@
package service
import (
"context"
"testing"
"github.com/kolide/fleet/server/kolide"
"github.com/kolide/fleet/server/mock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var dtPtr = func(t kolide.DecoratorType) *kolide.DecoratorType { return &t }
func TestDecoratorValidation(t *testing.T) {
ds := mock.Store{}
ds.DecoratorFunc = func(id uint) (*kolide.Decorator, error) {
return &kolide.Decorator{
ID: 1,
Query: "select x from y;",
Type: kolide.DecoratorAlways,
}, nil
}
ds.SaveDecoratorFunc = func(dec *kolide.Decorator, opts ...kolide.OptionalArg) error {
return nil
}
svc := &service{
ds: &ds,
}
validator := validationMiddleware{
Service: svc,
ds: &ds,
}
payload := kolide.DecoratorPayload{
ID: uint(1),
DecoratorType: dtPtr(kolide.DecoratorInterval),
Interval: uintPtr(3600),
}
dec, err := validator.ModifyDecorator(context.Background(), payload)
require.Nil(t, err)
assert.Equal(t, kolide.DecoratorInterval, dec.Type)
assert.Equal(t, uint(3600), dec.Interval)
}
func TestDecoratorValidationIntervalMissing(t *testing.T) {
ds := mock.Store{}
ds.DecoratorFunc = func(id uint) (*kolide.Decorator, error) {
return &kolide.Decorator{
ID: 1,
Query: "select x from y;",
Type: kolide.DecoratorAlways,
}, nil
}
ds.SaveDecoratorFunc = func(dec *kolide.Decorator, opts ...kolide.OptionalArg) error {
return nil
}
svc := &service{
ds: &ds,
}
validator := validationMiddleware{
Service: svc,
ds: &ds,
}
payload := kolide.DecoratorPayload{
ID: uint(1),
DecoratorType: dtPtr(kolide.DecoratorInterval),
}
_, err := validator.ModifyDecorator(context.Background(), payload)
require.NotNil(t, err)
r, ok := err.(*invalidArgumentError)
require.True(t, ok)
assert.Equal(t, "missing required argument", (*r)[0].reason)
}
func TestDecoratorValidationIntervalSameType(t *testing.T) {
ds := mock.Store{}
ds.DecoratorFunc = func(id uint) (*kolide.Decorator, error) {
return &kolide.Decorator{
ID: 1,
Query: "select x from y;",
Type: kolide.DecoratorInterval,
Interval: 600,
}, nil
}
ds.SaveDecoratorFunc = func(dec *kolide.Decorator, opts ...kolide.OptionalArg) error {
return nil
}
svc := &service{
ds: &ds,
}
validator := validationMiddleware{
Service: svc,
ds: &ds,
}
payload := kolide.DecoratorPayload{
ID: uint(1),
DecoratorType: dtPtr(kolide.DecoratorInterval),
Interval: uintPtr(1200),
}
dec, err := validator.ModifyDecorator(context.Background(), payload)
require.Nil(t, err)
assert.Equal(t, uint(1200), dec.Interval)
}
func TestDecoratorValidationIntervalInvalid(t *testing.T) {
ds := mock.Store{}
ds.DecoratorFunc = func(id uint) (*kolide.Decorator, error) {
return &kolide.Decorator{
ID: 1,
Query: "select x from y;",
Type: kolide.DecoratorInterval,
Interval: 600,
}, nil
}
ds.SaveDecoratorFunc = func(dec *kolide.Decorator, opts ...kolide.OptionalArg) error {
return nil
}
svc := &service{
ds: &ds,
}
validator := validationMiddleware{
Service: svc,
ds: &ds,
}
payload := kolide.DecoratorPayload{
ID: uint(1),
DecoratorType: dtPtr(kolide.DecoratorInterval),
Interval: uintPtr(1203),
}
_, err := validator.ModifyDecorator(context.Background(), payload)
require.NotNil(t, err)
r, ok := err.(*invalidArgumentError)
require.True(t, ok)
assert.Equal(t, "value must be divisible by 60", (*r)[0].reason)
}