mirror of
https://github.com/fleetdm/fleet
synced 2026-05-23 08:58:41 +00:00
Osquery Configuration Control (#244)
Label management APIs and an osquery config endpoint based on active pack and label state.
This commit is contained in:
parent
6d1c963bfa
commit
a03347489c
23 changed files with 1176 additions and 142 deletions
|
|
@ -298,7 +298,7 @@ func testLabels(t *testing.T, db kolide.Datastore) {
|
|||
assert.Empty(t, queries)
|
||||
|
||||
// No labels should match
|
||||
labels, err := db.LabelsForHost(host)
|
||||
labels, err := db.LabelsForHost(host.ID)
|
||||
assert.Nil(t, err)
|
||||
assert.Empty(t, labels)
|
||||
|
||||
|
|
@ -384,7 +384,7 @@ func testLabels(t *testing.T, db kolide.Datastore) {
|
|||
assert.Equal(t, expectQueries, queries)
|
||||
|
||||
// No labels should match with no results yet
|
||||
labels, err = db.LabelsForHost(host)
|
||||
labels, err = db.LabelsForHost(host.ID)
|
||||
assert.Nil(t, err)
|
||||
assert.Empty(t, labels)
|
||||
|
||||
|
|
@ -417,7 +417,7 @@ func testLabels(t *testing.T, db kolide.Datastore) {
|
|||
assert.Equal(t, expectQueries, queries)
|
||||
|
||||
// Now the two matching labels should be returned
|
||||
labels, err = db.LabelsForHost(host)
|
||||
labels, err = db.LabelsForHost(host.ID)
|
||||
assert.Nil(t, err)
|
||||
if assert.Len(t, labels, 2) {
|
||||
labelNames := []string{labels[0].Name, labels[1].Name}
|
||||
|
|
@ -435,7 +435,7 @@ func testLabels(t *testing.T, db kolide.Datastore) {
|
|||
|
||||
// There should still be no labels returned for a host that never
|
||||
// executed any label queries
|
||||
labels, err = db.LabelsForHost(&hosts[0])
|
||||
labels, err = db.LabelsForHost(hosts[0].ID)
|
||||
assert.Nil(t, err)
|
||||
assert.Empty(t, labels)
|
||||
}
|
||||
|
|
@ -529,7 +529,7 @@ func testDeletePack(t *testing.T, ds kolide.Datastore) {
|
|||
pack, err = ds.Pack(pack.ID)
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = ds.DeletePack(pack)
|
||||
err = ds.DeletePack(pack.ID)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.NotEqual(t, pack.ID, 0)
|
||||
|
|
@ -550,7 +550,7 @@ func testAddAndRemoveQueryFromPack(t *testing.T, ds kolide.Datastore) {
|
|||
}
|
||||
_, err = ds.NewQuery(q1)
|
||||
assert.Nil(t, err)
|
||||
err = ds.AddQueryToPack(q1, pack)
|
||||
err = ds.AddQueryToPack(q1.ID, pack.ID)
|
||||
assert.Nil(t, err)
|
||||
|
||||
q2 := &kolide.Query{
|
||||
|
|
@ -559,7 +559,7 @@ func testAddAndRemoveQueryFromPack(t *testing.T, ds kolide.Datastore) {
|
|||
}
|
||||
_, err = ds.NewQuery(q2)
|
||||
assert.Nil(t, err)
|
||||
err = ds.AddQueryToPack(q2, pack)
|
||||
err = ds.AddQueryToPack(q2.ID, pack.ID)
|
||||
assert.Nil(t, err)
|
||||
|
||||
queries, err := ds.GetQueriesInPack(pack)
|
||||
|
|
@ -573,3 +573,54 @@ func testAddAndRemoveQueryFromPack(t *testing.T, ds kolide.Datastore) {
|
|||
assert.Nil(t, err)
|
||||
assert.Len(t, queries, 1)
|
||||
}
|
||||
|
||||
func testManagingLabelsOnPacks(t *testing.T, ds kolide.Datastore) {
|
||||
mysqlQuery := &kolide.Query{
|
||||
Name: "MySQL",
|
||||
Query: "select pid from processes where name = 'mysqld';",
|
||||
}
|
||||
mysqlQuery, err := ds.NewQuery(mysqlQuery)
|
||||
assert.Nil(t, err)
|
||||
|
||||
osqueryRunningQuery := &kolide.Query{
|
||||
Name: "Is osquery currently running?",
|
||||
Query: "select pid from processes where name = 'osqueryd';",
|
||||
}
|
||||
osqueryRunningQuery, err = ds.NewQuery(osqueryRunningQuery)
|
||||
assert.Nil(t, err)
|
||||
|
||||
monitoringPack := &kolide.Pack{
|
||||
Name: "monitoring",
|
||||
}
|
||||
err = ds.NewPack(monitoringPack)
|
||||
assert.Nil(t, err)
|
||||
|
||||
mysqlLabel := &kolide.Label{
|
||||
Name: "MySQL Monitoring",
|
||||
QueryID: mysqlQuery.ID,
|
||||
}
|
||||
mysqlLabel, err = ds.NewLabel(mysqlLabel)
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = ds.AddLabelToPack(mysqlLabel.ID, monitoringPack.ID)
|
||||
assert.Nil(t, err)
|
||||
|
||||
labels, err := ds.GetLabelsForPack(monitoringPack)
|
||||
assert.Nil(t, err)
|
||||
assert.Len(t, labels, 1)
|
||||
assert.Equal(t, "MySQL Monitoring", labels[0].Name)
|
||||
|
||||
osqueryLabel := &kolide.Label{
|
||||
Name: "Osquery Monitoring",
|
||||
QueryID: osqueryRunningQuery.ID,
|
||||
}
|
||||
osqueryLabel, err = ds.NewLabel(osqueryLabel)
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = ds.AddLabelToPack(osqueryLabel.ID, monitoringPack.ID)
|
||||
assert.Nil(t, err)
|
||||
|
||||
labels, err = ds.GetLabelsForPack(monitoringPack)
|
||||
assert.Nil(t, err)
|
||||
assert.Len(t, labels, 2)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ var tables = [...]interface{}{
|
|||
&kolide.Label{},
|
||||
&kolide.LabelQueryExecution{},
|
||||
&kolide.Option{},
|
||||
&kolide.Target{},
|
||||
&kolide.DistributedQueryCampaign{},
|
||||
&kolide.DistributedQueryCampaignTarget{},
|
||||
&kolide.Query{},
|
||||
|
|
@ -301,6 +300,42 @@ func (orm gormDB) NewLabel(label *kolide.Label) (*kolide.Label, error) {
|
|||
return label, nil
|
||||
}
|
||||
|
||||
func (orm gormDB) SaveLabel(label *kolide.Label) error {
|
||||
if label == nil {
|
||||
return errors.New(
|
||||
"error saving label",
|
||||
"nil pointer passed to SaveLabel",
|
||||
)
|
||||
}
|
||||
return orm.DB.Save(label).Error
|
||||
}
|
||||
|
||||
func (orm gormDB) DeleteLabel(lid uint) error {
|
||||
err := orm.DB.Where("id = ?", lid).Delete(&kolide.Label{}).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return orm.DB.Where("target_id = ? and type = ?", lid, kolide.TargetLabel).Delete(&kolide.PackTarget{}).Error
|
||||
}
|
||||
|
||||
func (orm gormDB) Label(lid uint) (*kolide.Label, error) {
|
||||
label := &kolide.Label{
|
||||
ID: lid,
|
||||
}
|
||||
err := orm.DB.Where("id = ?", label.ID).First(&label).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return label, nil
|
||||
}
|
||||
|
||||
func (orm gormDB) Labels() ([]*kolide.Label, error) {
|
||||
var labels []*kolide.Label
|
||||
err := orm.DB.Find(&labels).Error
|
||||
return labels, err
|
||||
}
|
||||
|
||||
func (orm gormDB) LabelQueriesForHost(host *kolide.Host, cutoff time.Time) (map[string]string, error) {
|
||||
if host == nil {
|
||||
return nil, errors.New(
|
||||
|
|
@ -390,21 +425,14 @@ matches = VALUES(matches)
|
|||
return nil
|
||||
}
|
||||
|
||||
func (orm gormDB) LabelsForHost(host *kolide.Host) ([]kolide.Label, error) {
|
||||
if host == nil {
|
||||
return nil, errors.New(
|
||||
"error finding host queries",
|
||||
"nil pointer passed to LabelQueriesForHost",
|
||||
)
|
||||
}
|
||||
|
||||
func (orm gormDB) LabelsForHost(hid uint) ([]kolide.Label, error) {
|
||||
results := []kolide.Label{}
|
||||
err := orm.DB.Raw(`
|
||||
SELECT labels.* from labels, label_query_executions lqe
|
||||
WHERE lqe.host_id = ?
|
||||
AND lqe.label_id = labels.id
|
||||
AND lqe.matches
|
||||
`, host.ID).Scan(&results).Error
|
||||
`, hid).Scan(&results).Error
|
||||
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
return nil, errors.DatabaseError(err)
|
||||
|
|
@ -433,29 +461,22 @@ func (orm gormDB) SavePack(pack *kolide.Pack) error {
|
|||
return orm.DB.Save(pack).Error
|
||||
}
|
||||
|
||||
func (orm gormDB) DeletePack(pack *kolide.Pack) error {
|
||||
if pack == nil {
|
||||
return errors.New(
|
||||
"error deleting pack",
|
||||
"nil pointer passed to DeletePack",
|
||||
)
|
||||
}
|
||||
err := orm.DB.Delete(pack).Error
|
||||
func (orm gormDB) DeletePack(pid uint) error {
|
||||
err := orm.DB.Where("id = ?", pid).Delete(&kolide.Pack{}).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = orm.DB.Where("pack_id = ?", pack.ID).Delete(&kolide.PackQuery{}).Error
|
||||
err = orm.DB.Where("pack_id = ?", pid).Delete(&kolide.PackQuery{}).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return orm.DB.Where("pack_id = ?", pid).Delete(&kolide.PackTarget{}).Error
|
||||
}
|
||||
|
||||
func (orm gormDB) Pack(id uint) (*kolide.Pack, error) {
|
||||
func (orm gormDB) Pack(pid uint) (*kolide.Pack, error) {
|
||||
pack := &kolide.Pack{
|
||||
ID: id,
|
||||
ID: pid,
|
||||
}
|
||||
err := orm.DB.Where(pack).First(pack).Error
|
||||
if err != nil {
|
||||
|
|
@ -470,16 +491,10 @@ func (orm gormDB) Packs() ([]*kolide.Pack, error) {
|
|||
return packs, err
|
||||
}
|
||||
|
||||
func (orm gormDB) AddQueryToPack(query *kolide.Query, pack *kolide.Pack) error {
|
||||
if query == nil || pack == nil {
|
||||
return errors.New(
|
||||
"error adding query from pack",
|
||||
"nil pointer passed to AddQueryToPack",
|
||||
)
|
||||
}
|
||||
func (orm gormDB) AddQueryToPack(qid uint, pid uint) error {
|
||||
pq := &kolide.PackQuery{
|
||||
QueryID: query.ID,
|
||||
PackID: pack.ID,
|
||||
QueryID: qid,
|
||||
PackID: pid,
|
||||
}
|
||||
return orm.DB.Create(pq).Error
|
||||
}
|
||||
|
|
@ -555,3 +570,111 @@ func (orm gormDB) RemoveQueryFromPack(query *kolide.Query, pack *kolide.Pack) er
|
|||
}
|
||||
return orm.DB.Where(pq).Delete(pq).Error
|
||||
}
|
||||
|
||||
func (orm gormDB) AddLabelToPack(lid uint, pid uint) error {
|
||||
pt := &kolide.PackTarget{
|
||||
Type: kolide.TargetLabel,
|
||||
PackID: pid,
|
||||
TargetID: lid,
|
||||
}
|
||||
|
||||
return orm.DB.Create(pt).Error
|
||||
}
|
||||
|
||||
func (orm gormDB) ActivePacksForHost(hid uint) ([]*kolide.Pack, error) {
|
||||
packs := []*kolide.Pack{}
|
||||
|
||||
// we will need to give some subset of packs to this host based on the
|
||||
// labels which this host is known to belong to
|
||||
allPacks, err := orm.Packs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// pull the labels that this host belongs to
|
||||
labels, err := orm.LabelsForHost(hid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// in order to use o(1) array indexing in an o(n) loop vs a o(n^2) double
|
||||
// for loop iteration, we must create the array which may be indexed below
|
||||
labelIDs := map[uint]bool{}
|
||||
for _, label := range labels {
|
||||
labelIDs[label.ID] = true
|
||||
}
|
||||
|
||||
for _, pack := range allPacks {
|
||||
// for each pack, we must know what labels have been assigned to that
|
||||
// pack
|
||||
labelsForPack, err := orm.GetLabelsForPack(pack)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// o(n) iteration to determine whether or not a pack is enabled
|
||||
// in this case, n is len(labelsForPack)
|
||||
for _, label := range labelsForPack {
|
||||
if labelIDs[label.ID] {
|
||||
packs = append(packs, pack)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return packs, nil
|
||||
}
|
||||
|
||||
func (orm gormDB) GetLabelsForPack(pack *kolide.Pack) ([]*kolide.Label, error) {
|
||||
if pack == nil {
|
||||
return nil, errors.New(
|
||||
"error getting labels for pack",
|
||||
"nil pointer passed to GetLabelsForPack",
|
||||
)
|
||||
}
|
||||
|
||||
results := []*kolide.Label{}
|
||||
err := orm.DB.Raw(`
|
||||
SELECT
|
||||
l.id,
|
||||
l.created_at,
|
||||
l.updated_at,
|
||||
l.name,
|
||||
l.query_id
|
||||
FROM
|
||||
labels l
|
||||
JOIN
|
||||
pack_targets pt
|
||||
ON
|
||||
pt.target_id = l.id
|
||||
WHERE
|
||||
pt.type = ?
|
||||
AND
|
||||
pt.pack_id = ?;
|
||||
|
||||
`,
|
||||
kolide.TargetLabel, pack.ID).Scan(&results).Error
|
||||
|
||||
if err != nil && err != gorm.ErrRecordNotFound {
|
||||
return nil, errors.DatabaseError(err)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (orm gormDB) RemoveLabelFromPack(label *kolide.Label, pack *kolide.Pack) error {
|
||||
if label == nil || pack == nil {
|
||||
return errors.New(
|
||||
"error removing label from pack",
|
||||
"nil pointer passed to RemoveLabelFromPack",
|
||||
)
|
||||
}
|
||||
|
||||
pt := &kolide.PackTarget{
|
||||
Type: kolide.TargetLabel,
|
||||
PackID: pack.ID,
|
||||
TargetID: label.ID,
|
||||
}
|
||||
|
||||
return orm.DB.Delete(pt).Error
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,3 +117,8 @@ func TestAddAndRemoveQueryFromPack(t *testing.T) {
|
|||
ds := setup(t)
|
||||
testAddAndRemoveQueryFromPack(t, ds)
|
||||
}
|
||||
|
||||
func TestManagingLabelsOnPacks(t *testing.T) {
|
||||
ds := setup(t)
|
||||
testManagingLabelsOnPacks(t, ds)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,14 +26,14 @@ func (orm *inmem) NewLabel(label *kolide.Label) (*kolide.Label, error) {
|
|||
return &newLabel, nil
|
||||
}
|
||||
|
||||
func (orm *inmem) LabelsForHost(host *kolide.Host) ([]kolide.Label, error) {
|
||||
func (orm *inmem) LabelsForHost(hid uint) ([]kolide.Label, error) {
|
||||
orm.mtx.Lock()
|
||||
defer orm.mtx.Unlock()
|
||||
|
||||
// First get IDs of label executions for the host
|
||||
resLabels := []kolide.Label{}
|
||||
for _, lqe := range orm.labelQueryExecutions {
|
||||
if lqe.HostID == host.ID && lqe.Matches {
|
||||
if lqe.HostID == hid && lqe.Matches {
|
||||
if label := orm.labels[lqe.LabelID]; label != nil {
|
||||
resLabels = append(resLabels, *label)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ type Datastore interface {
|
|||
UserStore
|
||||
QueryStore
|
||||
PackStore
|
||||
OsqueryStore
|
||||
LabelStore
|
||||
HostStore
|
||||
PasswordResetStore
|
||||
SessionStore
|
||||
|
|
|
|||
|
|
@ -1 +1,61 @@
|
|||
package kolide
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type LabelStore interface {
|
||||
// Label methods
|
||||
NewLabel(Label *Label) (*Label, error)
|
||||
SaveLabel(Label *Label) error
|
||||
DeleteLabel(lid uint) error
|
||||
Label(lid uint) (*Label, error)
|
||||
Labels() ([]*Label, error)
|
||||
|
||||
// LabelQueriesForHost returns the label queries that should be executed
|
||||
// for the given host. The cutoff is the minimum timestamp a query
|
||||
// execution should have to be considered "fresh". Executions that are
|
||||
// not fresh will be repeated. Results are returned in a map of label
|
||||
// id -> query
|
||||
LabelQueriesForHost(host *Host, cutoff time.Time) (map[string]string, error)
|
||||
|
||||
// RecordLabelQueryExecutions saves the results of label queries. The
|
||||
// results map is a map of label id -> whether or not the label
|
||||
// matches. The time parameter is the timestamp to save with the query
|
||||
// execution.
|
||||
RecordLabelQueryExecutions(host *Host, results map[string]bool, t time.Time) error
|
||||
|
||||
// LabelsForHost returns the labels that the given host is in.
|
||||
LabelsForHost(hid uint) ([]Label, error)
|
||||
}
|
||||
|
||||
type LabelService interface {
|
||||
GetAllLabels(ctx context.Context) ([]*Label, error)
|
||||
GetLabel(ctx context.Context, id uint) (*Label, error)
|
||||
NewLabel(ctx context.Context, p LabelPayload) (*Label, error)
|
||||
ModifyLabel(ctx context.Context, id uint, p LabelPayload) (*Label, error)
|
||||
DeleteLabel(ctx context.Context, id uint) error
|
||||
}
|
||||
|
||||
type LabelPayload struct {
|
||||
Name *string
|
||||
QueryID *uint `json:"query_id"`
|
||||
}
|
||||
|
||||
type Label struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Name string `gorm:"not null;unique_index:idx_label_unique_name"`
|
||||
QueryID uint
|
||||
}
|
||||
|
||||
type LabelQueryExecution struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
UpdatedAt time.Time
|
||||
Matches bool
|
||||
LabelID uint // Note we manually specify a unique index on these
|
||||
HostID uint // fields in gormDB.Migrate
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,32 +1,9 @@
|
|||
package kolide
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type OsqueryStore interface {
|
||||
// LabelQueriesForHost returns the label queries that should be executed
|
||||
// for the given host. The cutoff is the minimum timestamp a query
|
||||
// execution should have to be considered "fresh". Executions that are
|
||||
// not fresh will be repeated. Results are returned in a map of label
|
||||
// id -> query
|
||||
LabelQueriesForHost(host *Host, cutoff time.Time) (map[string]string, error)
|
||||
|
||||
// RecordLabelQueryExecutions saves the results of label queries. The
|
||||
// results map is a map of label id -> whether or not the label
|
||||
// matches. The time parameter is the timestamp to save with the query
|
||||
// execution.
|
||||
RecordLabelQueryExecutions(host *Host, results map[string]bool, t time.Time) error
|
||||
|
||||
// NewLabel saves a new label.
|
||||
NewLabel(label *Label) (*Label, error)
|
||||
|
||||
// LabelsForHost returns the labels that the given host is in.
|
||||
LabelsForHost(host *Host) ([]Label, error)
|
||||
}
|
||||
|
||||
type OsqueryService interface {
|
||||
EnrollAgent(ctx context.Context, enrollSecret, hostIdentifier string) (string, error)
|
||||
AuthenticateHost(ctx context.Context, nodeKey string) (*Host, error)
|
||||
|
|
@ -62,7 +39,7 @@ type PackContent struct {
|
|||
|
||||
type Packs map[string]PackContent
|
||||
|
||||
type Options struct {
|
||||
type OsqueryOptions struct {
|
||||
PackDelimiter string `json:"pack_delimiter,omitempty"`
|
||||
DisableDistributed bool `json:"disable_distributed"`
|
||||
}
|
||||
|
|
@ -74,9 +51,9 @@ type Decorators struct {
|
|||
}
|
||||
|
||||
type OsqueryConfig struct {
|
||||
Options Options `json:"options,omitempty"`
|
||||
Decorators Decorators `json:"decorators,omitempty"`
|
||||
Packs Packs `json:"packs,omitempty"`
|
||||
Options OsqueryOptions `json:"options,omitempty"`
|
||||
Decorators Decorators `json:"decorators,omitempty"`
|
||||
Packs Packs `json:"packs,omitempty"`
|
||||
}
|
||||
|
||||
type OsqueryResultLog struct {
|
||||
|
|
|
|||
|
|
@ -10,14 +10,22 @@ type PackStore interface {
|
|||
// Pack methods
|
||||
NewPack(pack *Pack) error
|
||||
SavePack(pack *Pack) error
|
||||
DeletePack(pack *Pack) error
|
||||
Pack(id uint) (*Pack, error)
|
||||
DeletePack(pid uint) error
|
||||
Pack(pid uint) (*Pack, error)
|
||||
Packs() ([]*Pack, error)
|
||||
|
||||
// Modifying the queries in packs
|
||||
AddQueryToPack(query *Query, pack *Pack) error
|
||||
AddQueryToPack(qid uint, pid uint) error
|
||||
GetQueriesInPack(pack *Pack) ([]*Query, error)
|
||||
RemoveQueryFromPack(query *Query, pack *Pack) error
|
||||
|
||||
// Modifying the labels for packs
|
||||
AddLabelToPack(lid uint, pid uint) error
|
||||
GetLabelsForPack(pack *Pack) ([]*Label, error)
|
||||
RemoveLabelFromPack(label *Label, pack *Pack) error
|
||||
|
||||
// Packs from the host's perspective
|
||||
ActivePacksForHost(hid uint) ([]*Pack, error)
|
||||
}
|
||||
|
||||
type PackService interface {
|
||||
|
|
@ -30,6 +38,10 @@ type PackService interface {
|
|||
AddQueryToPack(ctx context.Context, qid, pid uint) error
|
||||
GetQueriesInPack(ctx context.Context, id uint) ([]*Query, error)
|
||||
RemoveQueryFromPack(ctx context.Context, qid, pid uint) error
|
||||
|
||||
AddLabelToPack(ctx context.Context, lid, pid uint) error
|
||||
GetLabelsForPack(ctx context.Context, pid uint) ([]*Label, error)
|
||||
RemoveLabelFromPack(ctx context.Context, lid, pid uint) error
|
||||
}
|
||||
|
||||
type Pack struct {
|
||||
|
|
@ -48,8 +60,16 @@ type PackQuery struct {
|
|||
QueryID uint
|
||||
}
|
||||
|
||||
type TargetType int
|
||||
|
||||
const (
|
||||
TargetLabel TargetType = iota
|
||||
TargetHost
|
||||
)
|
||||
|
||||
type PackTarget struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
Type TargetType
|
||||
PackID uint
|
||||
TargetID uint
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,38 +51,6 @@ type Query struct {
|
|||
Version string
|
||||
}
|
||||
|
||||
type Label struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Name string `gorm:"not null;unique_index:idx_label_unique_name"`
|
||||
QueryID uint
|
||||
}
|
||||
|
||||
type LabelQueryExecution struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
UpdatedAt time.Time
|
||||
Matches bool
|
||||
LabelID uint // Note we manually specify a unique index on these
|
||||
HostID uint // fields in gormDB.Migrate
|
||||
}
|
||||
|
||||
type TargetType int
|
||||
|
||||
const (
|
||||
TargetLabel TargetType = iota
|
||||
TargetHost TargetType = iota
|
||||
)
|
||||
|
||||
type Target struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
Type TargetType
|
||||
TargetID uint
|
||||
QueryID uint
|
||||
}
|
||||
|
||||
type DistributedQueryStatus int
|
||||
|
||||
const (
|
||||
|
|
@ -103,6 +71,7 @@ type DistributedQueryCampaign struct {
|
|||
|
||||
type DistributedQueryCampaignTarget struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
Type TargetType
|
||||
DistributedQueryCampaignID uint
|
||||
TargetID uint
|
||||
}
|
||||
|
|
@ -110,10 +79,10 @@ type DistributedQueryCampaignTarget struct {
|
|||
type DistributedQueryExecutionStatus int
|
||||
|
||||
const (
|
||||
ExecutionWaiting DistributedQueryExecutionStatus = iota
|
||||
ExecutionRequested DistributedQueryExecutionStatus = iota
|
||||
ExecutionSucceeded DistributedQueryExecutionStatus = iota
|
||||
ExecutionFailed DistributedQueryExecutionStatus = iota
|
||||
ExecutionWaiting DistributedQueryExecutionStatus = iota
|
||||
ExecutionRequested
|
||||
ExecutionSucceeded
|
||||
ExecutionFailed
|
||||
)
|
||||
|
||||
type DistributedQueryExecution struct {
|
||||
|
|
@ -136,9 +105,9 @@ type Option struct {
|
|||
type DecoratorType int
|
||||
|
||||
const (
|
||||
DecoratorLoad DecoratorType = iota
|
||||
DecoratorAlways DecoratorType = iota
|
||||
DecoratorInterval DecoratorType = iota
|
||||
DecoratorLoad DecoratorType = iota
|
||||
DecoratorAlways
|
||||
DecoratorInterval
|
||||
)
|
||||
|
||||
type Decorator struct {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ type Service interface {
|
|||
UserService
|
||||
SessionService
|
||||
PackService
|
||||
LabelService
|
||||
QueryService
|
||||
OsqueryService
|
||||
HostService
|
||||
|
|
|
|||
158
server/service/endpoint_labels.go
Normal file
158
server/service/endpoint_labels.go
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
"github.com/kolide/kolide-ose/server/kolide"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Get Label
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type getLabelRequest struct {
|
||||
ID uint
|
||||
}
|
||||
|
||||
type getLabelResponse struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
QueryID uint `json:"query_id"`
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (r getLabelResponse) error() error { return r.Err }
|
||||
|
||||
func makeGetLabelEndpoint(svc kolide.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(getLabelRequest)
|
||||
label, err := svc.GetLabel(ctx, req.ID)
|
||||
if err != nil {
|
||||
return getLabelResponse{Err: err}, nil
|
||||
}
|
||||
return getLabelResponse{
|
||||
ID: label.ID,
|
||||
Name: label.Name,
|
||||
QueryID: label.QueryID,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Get All Labels
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type getAllLabelsResponse struct {
|
||||
Labels []getLabelResponse `json:"labels"`
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (r getAllLabelsResponse) error() error { return r.Err }
|
||||
|
||||
func makeGetAllLabelsEndpoint(svc kolide.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
labels, err := svc.GetAllLabels(ctx)
|
||||
if err != nil {
|
||||
return getAllLabelsResponse{Err: err}, nil
|
||||
}
|
||||
var resp getAllLabelsResponse
|
||||
for _, label := range labels {
|
||||
resp.Labels = append(resp.Labels, getLabelResponse{
|
||||
ID: label.ID,
|
||||
Name: label.Name,
|
||||
QueryID: label.QueryID,
|
||||
})
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Create Label
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type createLabelRequest struct {
|
||||
payload kolide.LabelPayload
|
||||
}
|
||||
|
||||
type createLabelResponse struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
QueryID uint `json:"query_id"`
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (r createLabelResponse) error() error { return r.Err }
|
||||
|
||||
func makeCreateLabelEndpoint(svc kolide.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(createLabelRequest)
|
||||
label, err := svc.NewLabel(ctx, req.payload)
|
||||
if err != nil {
|
||||
return createLabelResponse{Err: err}, nil
|
||||
}
|
||||
return createLabelResponse{
|
||||
ID: label.ID,
|
||||
Name: label.Name,
|
||||
QueryID: label.QueryID,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Modify Label
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type modifyLabelRequest struct {
|
||||
ID uint
|
||||
payload kolide.LabelPayload
|
||||
}
|
||||
|
||||
type modifyLabelResponse struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
QueryID uint `json:"query_id"`
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (r modifyLabelResponse) error() error { return r.Err }
|
||||
|
||||
func makeModifyLabelEndpoint(svc kolide.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(modifyLabelRequest)
|
||||
label, err := svc.ModifyLabel(ctx, req.ID, req.payload)
|
||||
if err != nil {
|
||||
return modifyLabelResponse{Err: err}, nil
|
||||
}
|
||||
return modifyLabelResponse{
|
||||
ID: label.ID,
|
||||
Name: label.Name,
|
||||
QueryID: label.QueryID,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Delete Label
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type deleteLabelRequest struct {
|
||||
ID uint
|
||||
}
|
||||
|
||||
type deleteLabelResponse struct {
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (r deleteLabelResponse) error() error { return r.Err }
|
||||
|
||||
func makeDeleteLabelEndpoint(svc kolide.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(deleteLabelRequest)
|
||||
err := svc.DeleteLabel(ctx, req.ID)
|
||||
if err != nil {
|
||||
return deleteLabelResponse{Err: err}, nil
|
||||
}
|
||||
return deleteLabelResponse{}, nil
|
||||
}
|
||||
}
|
||||
|
|
@ -248,3 +248,90 @@ func makeDeleteQueryFromPackEndpoint(svc kolide.Service) endpoint.Endpoint {
|
|||
return deleteQueryFromPackResponse{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Add Label To Pack
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type addLabelToPackRequest struct {
|
||||
PackID uint
|
||||
LabelID uint
|
||||
}
|
||||
|
||||
type addLabelToPackResponse struct {
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (r addLabelToPackResponse) error() error { return r.Err }
|
||||
|
||||
func makeAddLabelToPackEndpoint(svc kolide.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(addLabelToPackRequest)
|
||||
err := svc.AddLabelToPack(ctx, req.LabelID, req.PackID)
|
||||
if err != nil {
|
||||
return addLabelToPackResponse{Err: err}, nil
|
||||
}
|
||||
return addLabelToPackResponse{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Get Labels For Pack
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type getLabelsForPackRequest struct {
|
||||
PackID uint
|
||||
}
|
||||
|
||||
type getLabelsForPackResponse struct {
|
||||
Labels []getLabelResponse
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (r getLabelsForPackResponse) error() error { return r.Err }
|
||||
|
||||
func makeGetLabelsForPackEndpoint(svc kolide.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(getLabelsForPackRequest)
|
||||
labels, err := svc.GetLabelsForPack(ctx, req.PackID)
|
||||
if err != nil {
|
||||
return getLabelsForPackResponse{Err: err}, nil
|
||||
}
|
||||
|
||||
var resp getLabelsForPackResponse
|
||||
for _, label := range labels {
|
||||
resp.Labels = append(resp.Labels, getLabelResponse{
|
||||
ID: label.ID,
|
||||
Name: label.Name,
|
||||
QueryID: label.QueryID,
|
||||
})
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Delete Label From Pack
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type deleteLabelFromPackRequest struct {
|
||||
LabelID uint
|
||||
PackID uint
|
||||
}
|
||||
|
||||
type deleteLabelFromPackResponse struct {
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (r deleteLabelFromPackResponse) error() error { return r.Err }
|
||||
|
||||
func makeDeleteLabelFromPackEndpoint(svc kolide.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(deleteLabelFromPackRequest)
|
||||
err := svc.RemoveLabelFromPack(ctx, req.LabelID, req.PackID)
|
||||
if err != nil {
|
||||
return deleteLabelFromPackResponse{Err: err}, nil
|
||||
}
|
||||
return deleteLabelFromPackResponse{}, nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,14 @@ type KolideEndpoints struct {
|
|||
GetDistributedQueries endpoint.Endpoint
|
||||
SubmitDistributedQueryResults endpoint.Endpoint
|
||||
SubmitLogs endpoint.Endpoint
|
||||
GetLabel endpoint.Endpoint
|
||||
GetAllLabels endpoint.Endpoint
|
||||
CreateLabel endpoint.Endpoint
|
||||
ModifyLabel endpoint.Endpoint
|
||||
DeleteLabel endpoint.Endpoint
|
||||
AddLabelToPack endpoint.Endpoint
|
||||
GetLabelsForPack endpoint.Endpoint
|
||||
DeleteLabelFromPack endpoint.Endpoint
|
||||
}
|
||||
|
||||
// MakeKolideServerEndpoints creates the Kolide API endpoints.
|
||||
|
|
@ -94,6 +102,14 @@ func MakeKolideServerEndpoints(svc kolide.Service, jwtKey string) KolideEndpoint
|
|||
GetDistributedQueries: authenticatedHost(svc, makeGetDistributedQueriesEndpoint(svc)),
|
||||
SubmitDistributedQueryResults: authenticatedHost(svc, makeSubmitDistributedQueryResultsEndpoint(svc)),
|
||||
SubmitLogs: authenticatedHost(svc, makeSubmitLogsEndpoint(svc)),
|
||||
GetLabel: authenticatedUser(jwtKey, svc, makeGetLabelEndpoint(svc)),
|
||||
GetAllLabels: authenticatedUser(jwtKey, svc, makeGetAllLabelsEndpoint(svc)),
|
||||
CreateLabel: authenticatedUser(jwtKey, svc, makeCreateLabelEndpoint(svc)),
|
||||
ModifyLabel: authenticatedUser(jwtKey, svc, makeModifyLabelEndpoint(svc)),
|
||||
DeleteLabel: authenticatedUser(jwtKey, svc, makeDeleteLabelEndpoint(svc)),
|
||||
AddLabelToPack: authenticatedUser(jwtKey, svc, makeAddLabelToPackEndpoint(svc)),
|
||||
GetLabelsForPack: authenticatedUser(jwtKey, svc, makeGetLabelsForPackEndpoint(svc)),
|
||||
DeleteLabelFromPack: authenticatedUser(jwtKey, svc, makeDeleteLabelFromPackEndpoint(svc)),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -134,6 +150,14 @@ type kolideHandlers struct {
|
|||
GetDistributedQueries *kithttp.Server
|
||||
SubmitDistributedQueryResults *kithttp.Server
|
||||
SubmitLogs *kithttp.Server
|
||||
GetLabel *kithttp.Server
|
||||
GetAllLabels *kithttp.Server
|
||||
CreateLabel *kithttp.Server
|
||||
ModifyLabel *kithttp.Server
|
||||
DeleteLabel *kithttp.Server
|
||||
AddLabelToPack *kithttp.Server
|
||||
GetLabelsForPack *kithttp.Server
|
||||
DeleteLabelFromPack *kithttp.Server
|
||||
}
|
||||
|
||||
func makeKolideKitHandlers(ctx context.Context, e KolideEndpoints, opts []kithttp.ServerOption) kolideHandlers {
|
||||
|
|
@ -177,6 +201,14 @@ func makeKolideKitHandlers(ctx context.Context, e KolideEndpoints, opts []kithtt
|
|||
GetDistributedQueries: newServer(e.GetDistributedQueries, decodeGetDistributedQueriesRequest),
|
||||
SubmitDistributedQueryResults: newServer(e.SubmitDistributedQueryResults, decodeSubmitDistributedQueryResultsRequest),
|
||||
SubmitLogs: newServer(e.SubmitLogs, decodeSubmitLogsRequest),
|
||||
GetLabel: newServer(e.GetLabel, decodeGetLabelRequest),
|
||||
GetAllLabels: newServer(e.GetAllLabels, decodeNoParamsRequest),
|
||||
CreateLabel: newServer(e.CreateLabel, decodeCreateLabelRequest),
|
||||
ModifyLabel: newServer(e.ModifyLabel, decodeModifyLabelRequest),
|
||||
DeleteLabel: newServer(e.DeleteLabel, decodeDeleteLabelRequest),
|
||||
AddLabelToPack: newServer(e.AddLabelToPack, decodeAddLabelToPackRequest),
|
||||
GetLabelsForPack: newServer(e.GetLabelsForPack, decodeGetLabelsForPackRequest),
|
||||
DeleteLabelFromPack: newServer(e.DeleteLabelFromPack, decodeDeleteLabelFromPackRequest),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -239,6 +271,14 @@ func attachKolideAPIRoutes(r *mux.Router, h kolideHandlers) {
|
|||
r.Handle("/api/v1/kolide/packs/{pid}/queries/{qid}", h.AddQueryToPack).Methods("POST")
|
||||
r.Handle("/api/v1/kolide/packs/{id}/queries", h.GetQueriesInPack).Methods("GET")
|
||||
r.Handle("/api/v1/kolide/packs/{pid}/queries/{qid}", h.DeleteQueryFromPack).Methods("DELETE")
|
||||
r.Handle("/api/v1/kolide/labels/{id}", h.GetLabel).Methods("GET")
|
||||
r.Handle("/api/v1/kolide/labels", h.GetAllLabels).Methods("GET")
|
||||
r.Handle("/api/v1/kolide/labels", h.CreateLabel).Methods("POST")
|
||||
r.Handle("/api/v1/kolide/labels/{id}", h.ModifyLabel).Methods("PATCH")
|
||||
r.Handle("/api/v1/kolide/labels/{id}", h.DeleteLabel).Methods("DELETE")
|
||||
r.Handle("/api/v1/kolide/packs/{pid}/labels/{lid}", h.AddLabelToPack).Methods("POST")
|
||||
r.Handle("/api/v1/kolide/packs/{pid}/labels", h.GetLabelsForPack).Methods("GET")
|
||||
r.Handle("/api/v1/kolide/packs/{pid}/labels/{lid}", h.DeleteLabelFromPack).Methods("DELETE")
|
||||
|
||||
r.Handle("/api/v1/osquery/enroll", h.EnrollAgent).Methods("POST")
|
||||
r.Handle("/api/v1/osquery/config", h.GetClientConfig).Methods("POST")
|
||||
|
|
|
|||
|
|
@ -155,6 +155,38 @@ func TestAPIRoutes(t *testing.T) {
|
|||
verb: "POST",
|
||||
uri: "/api/v1/osquery/log",
|
||||
},
|
||||
{
|
||||
verb: "GET",
|
||||
uri: "/api/v1/kolide/labels/1",
|
||||
},
|
||||
{
|
||||
verb: "GET",
|
||||
uri: "/api/v1/kolide/labels",
|
||||
},
|
||||
{
|
||||
verb: "POST",
|
||||
uri: "/api/v1/kolide/labels",
|
||||
},
|
||||
{
|
||||
verb: "PATCH",
|
||||
uri: "/api/v1/kolide/labels/1",
|
||||
},
|
||||
{
|
||||
verb: "DELETE",
|
||||
uri: "/api/v1/kolide/labels/1",
|
||||
},
|
||||
{
|
||||
verb: "POST",
|
||||
uri: "/api/v1/kolide/packs/1/labels/2",
|
||||
},
|
||||
{
|
||||
verb: "GET",
|
||||
uri: "/api/v1/kolide/packs/1/labels",
|
||||
},
|
||||
{
|
||||
verb: "DELETE",
|
||||
uri: "/api/v1/kolide/packs/1/labels/2",
|
||||
},
|
||||
}
|
||||
|
||||
for _, route := range routes {
|
||||
|
|
|
|||
59
server/service/service_labels.go
Normal file
59
server/service/service_labels.go
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"github.com/kolide/kolide-ose/server/kolide"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func (svc service) GetAllLabels(ctx context.Context) ([]*kolide.Label, error) {
|
||||
return svc.ds.Labels()
|
||||
}
|
||||
|
||||
func (svc service) GetLabel(ctx context.Context, id uint) (*kolide.Label, error) {
|
||||
return svc.ds.Label(id)
|
||||
}
|
||||
|
||||
func (svc service) NewLabel(ctx context.Context, p kolide.LabelPayload) (*kolide.Label, error) {
|
||||
label := &kolide.Label{}
|
||||
|
||||
if p.Name == nil {
|
||||
return nil, newInvalidArgumentError("name", "missing required argument")
|
||||
}
|
||||
label.Name = *p.Name
|
||||
|
||||
if p.QueryID != nil {
|
||||
label.QueryID = *p.QueryID
|
||||
}
|
||||
|
||||
label, err := svc.ds.NewLabel(label)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return label, nil
|
||||
}
|
||||
|
||||
func (svc service) ModifyLabel(ctx context.Context, id uint, p kolide.LabelPayload) (*kolide.Label, error) {
|
||||
label, err := svc.ds.Label(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if p.Name != nil {
|
||||
label.Name = *p.Name
|
||||
}
|
||||
|
||||
if p.QueryID != nil {
|
||||
label.QueryID = *p.QueryID
|
||||
}
|
||||
|
||||
err = svc.ds.SaveLabel(label)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return label, nil
|
||||
}
|
||||
|
||||
func (svc service) DeleteLabel(ctx context.Context, id uint) error {
|
||||
return svc.ds.DeleteLabel(id)
|
||||
}
|
||||
133
server/service/service_labels_test.go
Normal file
133
server/service/service_labels_test.go
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kolide/kolide-ose/server/datastore"
|
||||
"github.com/kolide/kolide-ose/server/kolide"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func TestGetAllLabels(t *testing.T) {
|
||||
ds, err := datastore.New("gorm-sqlite3", ":memory:")
|
||||
assert.Nil(t, err)
|
||||
|
||||
svc, err := newTestService(ds)
|
||||
assert.Nil(t, err)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
labels, err := svc.GetAllLabels(ctx)
|
||||
assert.Nil(t, err)
|
||||
assert.Len(t, labels, 0)
|
||||
|
||||
_, err = ds.NewLabel(&kolide.Label{
|
||||
Name: "foo",
|
||||
QueryID: 1,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
|
||||
labels, err = svc.GetAllLabels(ctx)
|
||||
assert.Nil(t, err)
|
||||
assert.Len(t, labels, 1)
|
||||
assert.Equal(t, "foo", labels[0].Name)
|
||||
}
|
||||
|
||||
func TestGetLabel(t *testing.T) {
|
||||
ds, err := datastore.New("gorm-sqlite3", ":memory:")
|
||||
assert.Nil(t, err)
|
||||
|
||||
svc, err := newTestService(ds)
|
||||
assert.Nil(t, err)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
label := &kolide.Label{
|
||||
Name: "foo",
|
||||
QueryID: 1,
|
||||
}
|
||||
label, err = ds.NewLabel(label)
|
||||
assert.Nil(t, err)
|
||||
assert.NotZero(t, label.ID)
|
||||
|
||||
labelVerify, err := svc.GetLabel(ctx, label.ID)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, label.ID, labelVerify.ID)
|
||||
}
|
||||
|
||||
func TestNewLabel(t *testing.T) {
|
||||
ds, err := datastore.New("gorm-sqlite3", ":memory:")
|
||||
assert.Nil(t, err)
|
||||
|
||||
svc, err := newTestService(ds)
|
||||
assert.Nil(t, err)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
name := "foo"
|
||||
queryID := uint(1)
|
||||
label, err := svc.NewLabel(ctx, kolide.LabelPayload{
|
||||
Name: &name,
|
||||
QueryID: &queryID,
|
||||
})
|
||||
assert.NotZero(t, label.ID)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
labels, err := ds.Labels()
|
||||
assert.Nil(t, err)
|
||||
assert.Len(t, labels, 1)
|
||||
assert.Equal(t, "foo", labels[0].Name)
|
||||
}
|
||||
|
||||
func TestModifyLabel(t *testing.T) {
|
||||
ds, err := datastore.New("gorm-sqlite3", ":memory:")
|
||||
assert.Nil(t, err)
|
||||
|
||||
svc, err := newTestService(ds)
|
||||
assert.Nil(t, err)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
label := &kolide.Label{
|
||||
Name: "foo",
|
||||
QueryID: 1,
|
||||
}
|
||||
label, err = ds.NewLabel(label)
|
||||
assert.Nil(t, err)
|
||||
assert.NotZero(t, label.ID)
|
||||
|
||||
newName := "bar"
|
||||
labelVerify, err := svc.ModifyLabel(ctx, label.ID, kolide.LabelPayload{
|
||||
Name: &newName,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, label.ID, labelVerify.ID)
|
||||
assert.Equal(t, "bar", labelVerify.Name)
|
||||
}
|
||||
|
||||
func TestDeleteLabel(t *testing.T) {
|
||||
ds, err := datastore.New("gorm-sqlite3", ":memory:")
|
||||
assert.Nil(t, err)
|
||||
|
||||
svc, err := newTestService(ds)
|
||||
assert.Nil(t, err)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
label := &kolide.Label{
|
||||
Name: "foo",
|
||||
QueryID: 1,
|
||||
}
|
||||
label, err = ds.NewLabel(label)
|
||||
assert.Nil(t, err)
|
||||
assert.NotZero(t, label.ID)
|
||||
|
||||
err = svc.DeleteLabel(ctx, label.ID)
|
||||
assert.Nil(t, err)
|
||||
|
||||
labels, err := ds.Labels()
|
||||
assert.Nil(t, err)
|
||||
assert.Len(t, labels, 0)
|
||||
}
|
||||
|
|
@ -54,8 +54,53 @@ func (svc service) EnrollAgent(ctx context.Context, enrollSecret, hostIdentifier
|
|||
}
|
||||
|
||||
func (svc service) GetClientConfig(ctx context.Context) (*kolide.OsqueryConfig, error) {
|
||||
var config kolide.OsqueryConfig
|
||||
return &config, nil
|
||||
host, ok := hostctx.FromContext(ctx)
|
||||
if !ok {
|
||||
return nil, osqueryError{message: "internal error: missing host from request context"}
|
||||
}
|
||||
|
||||
config := &kolide.OsqueryConfig{
|
||||
Options: kolide.OsqueryOptions{
|
||||
PackDelimiter: "/",
|
||||
DisableDistributed: false,
|
||||
},
|
||||
Packs: kolide.Packs{},
|
||||
}
|
||||
|
||||
packs, err := svc.ds.ActivePacksForHost(host.ID)
|
||||
if err != nil {
|
||||
return nil, osqueryError{message: "database error: " + err.Error()}
|
||||
}
|
||||
|
||||
for _, pack := range packs {
|
||||
// first, we must figure out what queries are in this pack
|
||||
queries, err := svc.ds.GetQueriesInPack(pack)
|
||||
if err != nil {
|
||||
return nil, osqueryError{message: "database error: " + err.Error()}
|
||||
}
|
||||
|
||||
// the serializable osquery config struct expects content in a
|
||||
// particular format, so we do the conversion here
|
||||
configQueries := kolide.Queries{}
|
||||
for _, query := range queries {
|
||||
configQueries[query.Name] = kolide.QueryContent{
|
||||
Query: query.Query,
|
||||
Interval: query.Interval,
|
||||
Platform: query.Platform,
|
||||
Version: query.Version,
|
||||
Snapshot: query.Snapshot,
|
||||
}
|
||||
}
|
||||
|
||||
// finally, we add the pack to the client config struct with all of
|
||||
// the packs queries
|
||||
config.Packs[pack.Name] = kolide.PackContent{
|
||||
Platform: pack.Platform,
|
||||
Queries: configQueries,
|
||||
}
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (svc service) SubmitStatusLogs(ctx context.Context, logs []kolide.OsqueryStatusLog) error {
|
||||
|
|
|
|||
|
|
@ -377,3 +377,87 @@ func TestGetDistributedQueries(t *testing.T) {
|
|||
assert.Nil(t, err)
|
||||
assert.Equal(t, expectQueries, queries)
|
||||
}
|
||||
|
||||
func TestGetClientConfig(t *testing.T) {
|
||||
ds, err := datastore.New("gorm-sqlite3", ":memory:")
|
||||
assert.Nil(t, err)
|
||||
|
||||
mockClock := clock.NewMockClock()
|
||||
|
||||
svc, err := newTestServiceWithClock(ds, mockClock)
|
||||
assert.Nil(t, err)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
hosts, err := ds.Hosts()
|
||||
require.Nil(t, err)
|
||||
require.Len(t, hosts, 0)
|
||||
|
||||
_, err = svc.EnrollAgent(ctx, "", "user.local")
|
||||
assert.Nil(t, err)
|
||||
|
||||
hosts, err = ds.Hosts()
|
||||
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)
|
||||
assert.False(t, config.Options.DisableDistributed)
|
||||
assert.Equal(t, "/", config.Options.PackDelimiter)
|
||||
|
||||
// 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
|
||||
|
||||
mysqlQuery := &kolide.Query{
|
||||
Name: "MySQL",
|
||||
Query: "select pid from processes where name = 'mysqld';",
|
||||
}
|
||||
mysqlQuery, err = ds.NewQuery(mysqlQuery)
|
||||
assert.Nil(t, err)
|
||||
|
||||
infoQuery := &kolide.Query{
|
||||
Name: "Info",
|
||||
Query: "select * from osquery_info;",
|
||||
Interval: 60,
|
||||
}
|
||||
infoQuery, err = ds.NewQuery(infoQuery)
|
||||
assert.Nil(t, err)
|
||||
|
||||
monitoringPack := &kolide.Pack{
|
||||
Name: "monitoring",
|
||||
}
|
||||
err = ds.NewPack(monitoringPack)
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = ds.AddQueryToPack(infoQuery.ID, monitoringPack.ID)
|
||||
assert.Nil(t, err)
|
||||
|
||||
mysqlLabel := &kolide.Label{
|
||||
Name: "MySQL Monitoring",
|
||||
QueryID: mysqlQuery.ID,
|
||||
}
|
||||
mysqlLabel, err = ds.NewLabel(mysqlLabel)
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = ds.AddLabelToPack(mysqlLabel.ID, monitoringPack.ID)
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = ds.RecordLabelQueryExecutions(host, map[string]bool{fmt.Sprintf("%d", mysqlQuery.ID): true}, mockClock.Now())
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,36 +54,11 @@ func (svc service) ModifyPack(ctx context.Context, id uint, p kolide.PackPayload
|
|||
}
|
||||
|
||||
func (svc service) DeletePack(ctx context.Context, id uint) error {
|
||||
pack, err := svc.ds.Pack(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = svc.ds.DeletePack(pack)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return svc.ds.DeletePack(id)
|
||||
}
|
||||
|
||||
func (svc service) AddQueryToPack(ctx context.Context, qid, pid uint) error {
|
||||
pack, err := svc.ds.Pack(pid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
query, err := svc.ds.Query(qid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = svc.ds.AddQueryToPack(query, pack)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return svc.ds.AddQueryToPack(qid, pid)
|
||||
}
|
||||
|
||||
func (svc service) GetQueriesInPack(ctx context.Context, id uint) ([]*kolide.Query, error) {
|
||||
|
|
@ -118,3 +93,39 @@ func (svc service) RemoveQueryFromPack(ctx context.Context, qid, pid uint) error
|
|||
|
||||
return nil
|
||||
}
|
||||
func (svc service) AddLabelToPack(ctx context.Context, lid, pid uint) error {
|
||||
return svc.ds.AddLabelToPack(lid, pid)
|
||||
}
|
||||
|
||||
func (svc service) GetLabelsForPack(ctx context.Context, pid uint) ([]*kolide.Label, error) {
|
||||
pack, err := svc.ds.Pack(pid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
labels, err := svc.ds.GetLabelsForPack(pack)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return labels, nil
|
||||
}
|
||||
|
||||
func (svc service) RemoveLabelFromPack(ctx context.Context, lid, pid uint) error {
|
||||
pack, err := svc.ds.Pack(pid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
label, err := svc.ds.Label(lid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = svc.ds.RemoveLabelFromPack(label, pack)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ func TestGetQueriesInPack(t *testing.T) {
|
|||
assert.Nil(t, err)
|
||||
assert.NotZero(t, query.ID)
|
||||
|
||||
err = ds.AddQueryToPack(query, pack)
|
||||
err = ds.AddQueryToPack(query.ID, pack.ID)
|
||||
assert.Nil(t, err)
|
||||
|
||||
queries, err := svc.GetQueriesInPack(ctx, pack.ID)
|
||||
|
|
@ -218,7 +218,7 @@ func TestRemoveQueryFromPack(t *testing.T) {
|
|||
assert.Nil(t, err)
|
||||
assert.NotZero(t, query.ID)
|
||||
|
||||
err = ds.AddQueryToPack(query, pack)
|
||||
err = ds.AddQueryToPack(query.ID, pack.ID)
|
||||
assert.Nil(t, err)
|
||||
|
||||
queries, err := ds.GetQueriesInPack(pack)
|
||||
|
|
|
|||
49
server/service/transport_labels.go
Normal file
49
server/service/transport_labels.go
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func decodeCreateLabelRequest(ctx context.Context, r *http.Request) (interface{}, error) {
|
||||
var req createLabelRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req.payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeModifyLabelRequest(ctx context.Context, r *http.Request) (interface{}, error) {
|
||||
id, err := idFromRequest(r, "id")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var req modifyLabelRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req.payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.ID = id
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeDeleteLabelRequest(ctx context.Context, r *http.Request) (interface{}, error) {
|
||||
id, err := idFromRequest(r, "id")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var req deleteLabelRequest
|
||||
req.ID = id
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeGetLabelRequest(ctx context.Context, r *http.Request) (interface{}, error) {
|
||||
id, err := idFromRequest(r, "id")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var req getLabelRequest
|
||||
req.ID = id
|
||||
return req, nil
|
||||
}
|
||||
90
server/service/transport_labels_test.go
Normal file
90
server/service/transport_labels_test.go
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func TestDecodeCreateLabelRequest(t *testing.T) {
|
||||
router := mux.NewRouter()
|
||||
router.HandleFunc("/api/v1/kolide/labels", func(writer http.ResponseWriter, request *http.Request) {
|
||||
r, err := decodeCreateLabelRequest(context.Background(), request)
|
||||
assert.Nil(t, err)
|
||||
|
||||
params := r.(createLabelRequest)
|
||||
assert.Equal(t, "foo", *params.payload.Name)
|
||||
assert.Equal(t, uint(4), *params.payload.QueryID)
|
||||
}).Methods("POST")
|
||||
|
||||
var body bytes.Buffer
|
||||
body.Write([]byte(`{
|
||||
"name": "foo",
|
||||
"query_id": 4
|
||||
}`))
|
||||
|
||||
router.ServeHTTP(
|
||||
httptest.NewRecorder(),
|
||||
httptest.NewRequest("POST", "/api/v1/kolide/labels", &body),
|
||||
)
|
||||
}
|
||||
|
||||
func TestDecodeModifyLabelRequest(t *testing.T) {
|
||||
router := mux.NewRouter()
|
||||
router.HandleFunc("/api/v1/kolide/labels/{id}", func(writer http.ResponseWriter, request *http.Request) {
|
||||
r, err := decodeModifyLabelRequest(context.Background(), request)
|
||||
assert.Nil(t, err)
|
||||
|
||||
params := r.(modifyLabelRequest)
|
||||
assert.Equal(t, "foo", *params.payload.Name)
|
||||
assert.Equal(t, uint(1), params.ID)
|
||||
}).Methods("PATCH")
|
||||
|
||||
var body bytes.Buffer
|
||||
body.Write([]byte(`{
|
||||
"name": "foo"
|
||||
}`))
|
||||
|
||||
router.ServeHTTP(
|
||||
httptest.NewRecorder(),
|
||||
httptest.NewRequest("PATCH", "/api/v1/kolide/labels/1", &body),
|
||||
)
|
||||
}
|
||||
|
||||
func TestDecodeDeleteLabelRequest(t *testing.T) {
|
||||
router := mux.NewRouter()
|
||||
router.HandleFunc("/api/v1/kolide/labels/{id}", func(writer http.ResponseWriter, request *http.Request) {
|
||||
r, err := decodeDeleteLabelRequest(context.Background(), request)
|
||||
assert.Nil(t, err)
|
||||
|
||||
params := r.(deleteLabelRequest)
|
||||
assert.Equal(t, uint(1), params.ID)
|
||||
}).Methods("DELETE")
|
||||
|
||||
router.ServeHTTP(
|
||||
httptest.NewRecorder(),
|
||||
httptest.NewRequest("DELETE", "/api/v1/kolide/labels/1", nil),
|
||||
)
|
||||
}
|
||||
|
||||
func TestDecodeGetLabelRequest(t *testing.T) {
|
||||
router := mux.NewRouter()
|
||||
router.HandleFunc("/api/v1/kolide/labels/{id}", func(writer http.ResponseWriter, request *http.Request) {
|
||||
r, err := decodeGetLabelRequest(context.Background(), request)
|
||||
assert.Nil(t, err)
|
||||
|
||||
params := r.(getLabelRequest)
|
||||
assert.Equal(t, uint(1), params.ID)
|
||||
}).Methods("GET")
|
||||
|
||||
router.ServeHTTP(
|
||||
httptest.NewRecorder(),
|
||||
httptest.NewRequest("GET", "/api/v1/kolide/labels/1", nil),
|
||||
)
|
||||
}
|
||||
|
|
@ -88,3 +88,43 @@ func decodeDeleteQueryFromPackRequest(ctx context.Context, r *http.Request) (int
|
|||
req.QueryID = qid
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeAddLabelToPackRequest(ctx context.Context, r *http.Request) (interface{}, error) {
|
||||
lid, err := idFromRequest(r, "lid")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pid, err := idFromRequest(r, "pid")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return addLabelToPackRequest{
|
||||
PackID: pid,
|
||||
LabelID: lid,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func decodeGetLabelsForPackRequest(ctx context.Context, r *http.Request) (interface{}, error) {
|
||||
pid, err := idFromRequest(r, "pid")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var req getLabelsForPackRequest
|
||||
req.PackID = pid
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func decodeDeleteLabelFromPackRequest(ctx context.Context, r *http.Request) (interface{}, error) {
|
||||
lid, err := idFromRequest(r, "lid")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pid, err := idFromRequest(r, "pid")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var req deleteLabelFromPackRequest
|
||||
req.PackID = pid
|
||||
req.LabelID = lid
|
||||
return req, nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue