From e4358a92bc76cff01d8d5604b7550b9b32ffc402 Mon Sep 17 00:00:00 2001 From: Zach Wasserman Date: Thu, 3 Jun 2021 18:53:43 -0700 Subject: [PATCH] Filter hosts and label counts by teams (#949) - Add TeamFilter to relevant host and label methods. - Pass appropriate filter in service methods. The dashboard should now show the appropriate hosts for a user's team membership. --- server/datastore/datastore_hosts.go | 73 +++++++++++++++------------- server/datastore/datastore_labels.go | 13 +++-- server/datastore/inmem/hosts.go | 4 +- server/datastore/inmem/labels.go | 6 +-- server/datastore/mysql/hosts.go | 40 ++++++++------- server/datastore/mysql/labels.go | 49 +++++++++++-------- server/kolide/hosts.go | 6 +-- server/kolide/labels.go | 6 +-- server/mock/datastore_hosts.go | 18 +++---- server/mock/datastore_labels.go | 18 +++---- server/service/service_campaigns.go | 14 ++++-- server/service/service_hosts.go | 25 ++++++++-- server/service/service_hosts_test.go | 9 ++-- server/service/service_labels.go | 15 +++++- 14 files changed, 173 insertions(+), 123 deletions(-) diff --git a/server/datastore/datastore_hosts.go b/server/datastore/datastore_hosts.go index 857a10603c..1f8e920f4e 100644 --- a/server/datastore/datastore_hosts.go +++ b/server/datastore/datastore_hosts.go @@ -250,28 +250,29 @@ func testListHosts(t *testing.T, ds kolide.Datastore) { hosts = append(hosts, host) } - hosts2, err := ds.ListHosts(kolide.HostListOptions{}) + filter := kolide.TeamFilter{User: test.UserAdmin} + hosts2, err := ds.ListHosts(filter, kolide.HostListOptions{}) require.Nil(t, err) assert.Equal(t, len(hosts), len(hosts2)) // Test with logic for only a few hosts - hosts2, err = ds.ListHosts(kolide.HostListOptions{ListOptions: kolide.ListOptions{PerPage: 4, Page: 0}}) + hosts2, err = ds.ListHosts(filter, kolide.HostListOptions{ListOptions: kolide.ListOptions{PerPage: 4, Page: 0}}) require.Nil(t, err) assert.Equal(t, 4, len(hosts2)) err = ds.DeleteHost(hosts[0].ID) require.Nil(t, err) - hosts2, err = ds.ListHosts(kolide.HostListOptions{}) + hosts2, err = ds.ListHosts(filter, kolide.HostListOptions{}) require.Nil(t, err) assert.Equal(t, len(hosts)-1, len(hosts2)) - hosts, err = ds.ListHosts(kolide.HostListOptions{}) + hosts, err = ds.ListHosts(filter, kolide.HostListOptions{}) require.Nil(t, err) require.Equal(t, len(hosts2), len(hosts)) err = ds.SaveHost(hosts[0]) require.Nil(t, err) - hosts2, err = ds.ListHosts(kolide.HostListOptions{}) + hosts2, err = ds.ListHosts(filter, kolide.HostListOptions{}) require.Nil(t, err) require.Equal(t, hosts[0].ID, hosts2[0].ID) } @@ -288,25 +289,27 @@ func testListHostsFilterAdditional(t *testing.T, ds kolide.Datastore) { }) require.Nil(t, err) + filter := kolide.TeamFilter{User: test.UserAdmin} + // Add additional additional := json.RawMessage(`{"field1": "v1", "field2": "v2"}`) h.Additional = &additional require.NoError(t, ds.SaveHostAdditional(h)) - hosts, err := ds.ListHosts(kolide.HostListOptions{}) + hosts, err := ds.ListHosts(filter, kolide.HostListOptions{}) require.Nil(t, err) assert.Nil(t, hosts[0].Additional) - hosts, err = ds.ListHosts(kolide.HostListOptions{AdditionalFilters: []string{"field1", "field2"}}) + hosts, err = ds.ListHosts(filter, kolide.HostListOptions{AdditionalFilters: []string{"field1", "field2"}}) require.Nil(t, err) assert.Equal(t, &additional, hosts[0].Additional) - hosts, err = ds.ListHosts(kolide.HostListOptions{AdditionalFilters: []string{"*"}}) + hosts, err = ds.ListHosts(filter, kolide.HostListOptions{AdditionalFilters: []string{"*"}}) require.Nil(t, err) assert.Equal(t, &additional, hosts[0].Additional) additional = json.RawMessage(`{"field1": "v1", "missing": null}`) - hosts, err = ds.ListHosts(kolide.HostListOptions{AdditionalFilters: []string{"field1", "missing"}}) + hosts, err = ds.ListHosts(filter, kolide.HostListOptions{AdditionalFilters: []string{"field1", "missing"}}) require.Nil(t, err) assert.Equal(t, &additional, hosts[0].Additional) } @@ -328,19 +331,21 @@ func testListHostsStatus(t *testing.T, ds kolide.Datastore) { } } - hosts, err := ds.ListHosts(kolide.HostListOptions{StatusFilter: "online"}) + filter := kolide.TeamFilter{User: test.UserAdmin} + + hosts, err := ds.ListHosts(filter, kolide.HostListOptions{StatusFilter: "online"}) require.Nil(t, err) assert.Equal(t, 1, len(hosts)) - hosts, err = ds.ListHosts(kolide.HostListOptions{StatusFilter: "offline"}) + hosts, err = ds.ListHosts(filter, kolide.HostListOptions{StatusFilter: "offline"}) require.Nil(t, err) assert.Equal(t, 9, len(hosts)) - hosts, err = ds.ListHosts(kolide.HostListOptions{StatusFilter: "mia"}) + hosts, err = ds.ListHosts(filter, kolide.HostListOptions{StatusFilter: "mia"}) require.Nil(t, err) assert.Equal(t, 0, len(hosts)) - hosts, err = ds.ListHosts(kolide.HostListOptions{StatusFilter: "new"}) + hosts, err = ds.ListHosts(filter, kolide.HostListOptions{StatusFilter: "new"}) require.Nil(t, err) assert.Equal(t, 10, len(hosts)) } @@ -364,47 +369,49 @@ func testListHostsQuery(t *testing.T, ds kolide.Datastore) { hosts = append(hosts, host) } - gotHosts, err := ds.ListHosts(kolide.HostListOptions{}) + filter := kolide.TeamFilter{User: test.UserAdmin} + + gotHosts, err := ds.ListHosts(filter, kolide.HostListOptions{}) require.Nil(t, err) assert.Equal(t, len(hosts), len(gotHosts)) - gotHosts, err = ds.ListHosts(kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "00"}}) + gotHosts, err = ds.ListHosts(filter, kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "00"}}) require.Nil(t, err) assert.Equal(t, 10, len(gotHosts)) - gotHosts, err = ds.ListHosts(kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "000"}}) + gotHosts, err = ds.ListHosts(filter, kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "000"}}) require.Nil(t, err) assert.Equal(t, 1, len(gotHosts)) - gotHosts, err = ds.ListHosts(kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "192.168."}}) + gotHosts, err = ds.ListHosts(filter, kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "192.168."}}) require.Nil(t, err) assert.Equal(t, 10, len(gotHosts)) - gotHosts, err = ds.ListHosts(kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "192.168.1.1"}}) + gotHosts, err = ds.ListHosts(filter, kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "192.168.1.1"}}) require.Nil(t, err) assert.Equal(t, 1, len(gotHosts)) - gotHosts, err = ds.ListHosts(kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "hostname%00"}}) + gotHosts, err = ds.ListHosts(filter, kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "hostname%00"}}) require.Nil(t, err) assert.Equal(t, 10, len(gotHosts)) - gotHosts, err = ds.ListHosts(kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "hostname%003"}}) + gotHosts, err = ds.ListHosts(filter, kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "hostname%003"}}) require.Nil(t, err) assert.Equal(t, 1, len(gotHosts)) - gotHosts, err = ds.ListHosts(kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "uuid_"}}) + gotHosts, err = ds.ListHosts(filter, kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "uuid_"}}) require.Nil(t, err) assert.Equal(t, 10, len(gotHosts)) - gotHosts, err = ds.ListHosts(kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "uuid_006"}}) + gotHosts, err = ds.ListHosts(filter, kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "uuid_006"}}) require.Nil(t, err) assert.Equal(t, 1, len(gotHosts)) - gotHosts, err = ds.ListHosts(kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "serial"}}) + gotHosts, err = ds.ListHosts(filter, kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "serial"}}) require.Nil(t, err) assert.Equal(t, 10, len(gotHosts)) - gotHosts, err = ds.ListHosts(kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "serial009"}}) + gotHosts, err = ds.ListHosts(filter, kolide.HostListOptions{ListOptions: kolide.ListOptions{MatchQuery: "serial009"}}) require.Nil(t, err) assert.Equal(t, 1, len(gotHosts)) } @@ -551,8 +558,7 @@ func testSearchHosts(t *testing.T, ds kolide.Datastore) { } func testSearchHostsLimit(t *testing.T, ds kolide.Datastore) { - user := &kolide.User{GlobalRole: ptr.String(kolide.RoleAdmin)} - filter := kolide.TeamFilter{User: user} + filter := kolide.TeamFilter{User: test.UserAdmin} for i := 0; i < 15; i++ { _, err := ds.NewHost(&kolide.Host{ @@ -573,14 +579,10 @@ func testSearchHostsLimit(t *testing.T, ds kolide.Datastore) { } func testGenerateHostStatusStatistics(t *testing.T, ds kolide.Datastore) { - if ds.Name() == "inmem" { - fmt.Println("Busted test skipped for inmem") - return - } - + filter := kolide.TeamFilter{User: test.UserAdmin} mockClock := clock.NewMockClock() - online, offline, mia, new, err := ds.GenerateHostStatusStatistics(mockClock.Now()) + online, offline, mia, new, err := ds.GenerateHostStatusStatistics(filter, mockClock.Now()) assert.Nil(t, err) assert.Equal(t, uint(0), online) assert.Equal(t, uint(0), offline) @@ -640,14 +642,14 @@ func testGenerateHostStatusStatistics(t *testing.T, ds kolide.Datastore) { }) require.Nil(t, err) - online, offline, mia, new, err = ds.GenerateHostStatusStatistics(mockClock.Now()) + online, offline, mia, new, err = ds.GenerateHostStatusStatistics(filter, mockClock.Now()) assert.Nil(t, err) assert.Equal(t, uint(2), online) assert.Equal(t, uint(1), offline) assert.Equal(t, uint(1), mia) assert.Equal(t, uint(4), new) - online, offline, mia, new, err = ds.GenerateHostStatusStatistics(mockClock.Now().Add(1 * time.Hour)) + online, offline, mia, new, err = ds.GenerateHostStatusStatistics(filter, mockClock.Now().Add(1*time.Hour)) assert.Nil(t, err) assert.Equal(t, uint(0), online) assert.Equal(t, uint(3), offline) @@ -811,7 +813,8 @@ func testHostIDsByName(t *testing.T, ds kolide.Datastore) { require.Nil(t, err) } - hosts, err := ds.HostIDsByName([]string{"foo.2.local", "foo.1.local", "foo.5.local"}) + filter := kolide.TeamFilter{User: test.UserAdmin} + hosts, err := ds.HostIDsByName(filter, []string{"foo.2.local", "foo.1.local", "foo.5.local"}) require.Nil(t, err) sort.Slice(hosts, func(i, j int) bool { return hosts[i] < hosts[j] }) assert.Equal(t, hosts, []uint{2, 3, 6}) diff --git a/server/datastore/datastore_labels.go b/server/datastore/datastore_labels.go index 43eead06c7..4582772c58 100644 --- a/server/datastore/datastore_labels.go +++ b/server/datastore/datastore_labels.go @@ -335,9 +335,10 @@ func testListHostsInLabel(t *testing.T, db kolide.Datastore) { err = db.ApplyLabelSpecs([]*kolide.LabelSpec{l1}) require.Nil(t, err) - { + filter := kolide.TeamFilter{User: test.UserAdmin} - hosts, err := db.ListHostsInLabel(l1.ID, kolide.HostListOptions{}) + { + hosts, err := db.ListHostsInLabel(filter, l1.ID, kolide.HostListOptions{}) require.Nil(t, err) assert.Len(t, hosts, 0) } @@ -348,7 +349,7 @@ func testListHostsInLabel(t *testing.T, db kolide.Datastore) { } { - hosts, err := db.ListHostsInLabel(l1.ID, kolide.HostListOptions{}) + hosts, err := db.ListHostsInLabel(filter, l1.ID, kolide.HostListOptions{}) require.Nil(t, err) assert.Len(t, hosts, 3) } @@ -408,11 +409,13 @@ func testListUniqueHostsInLabels(t *testing.T, db kolide.Datastore) { assert.Nil(t, err) } - uniqueHosts, err := db.ListUniqueHostsInLabels([]uint{l1.ID, l2.ID}) + filter := kolide.TeamFilter{User: test.UserAdmin} + + uniqueHosts, err := db.ListUniqueHostsInLabels(filter, []uint{l1.ID, l2.ID}) assert.Nil(t, err) assert.Equal(t, len(hosts), len(uniqueHosts)) - labels, err := db.ListLabels(kolide.ListOptions{}) + labels, err := db.ListLabels(filter, kolide.ListOptions{}) require.Nil(t, err) require.Len(t, labels, 2) diff --git a/server/datastore/inmem/hosts.go b/server/datastore/inmem/hosts.go index 21783c3fb4..02e97194f4 100644 --- a/server/datastore/inmem/hosts.go +++ b/server/datastore/inmem/hosts.go @@ -60,7 +60,7 @@ func (d *Datastore) Host(id uint) (*kolide.Host, error) { return host, nil } -func (d *Datastore) ListHosts(opt kolide.HostListOptions) ([]*kolide.Host, error) { +func (d *Datastore) ListHosts(filter kolide.TeamFilter, opt kolide.HostListOptions) ([]*kolide.Host, error) { d.mtx.Lock() defer d.mtx.Unlock() @@ -132,7 +132,7 @@ func (d *Datastore) ListHosts(opt kolide.HostListOptions) ([]*kolide.Host, error return hosts, nil } -func (d *Datastore) GenerateHostStatusStatistics(now time.Time) (online, offline, mia, new uint, err error) { +func (d *Datastore) GenerateHostStatusStatistics(filter kolide.TeamFilter, now time.Time) (online, offline, mia, new uint, err error) { d.mtx.Lock() defer d.mtx.Unlock() diff --git a/server/datastore/inmem/labels.go b/server/datastore/inmem/labels.go index 58ac4eeda2..a1a46c87fb 100644 --- a/server/datastore/inmem/labels.go +++ b/server/datastore/inmem/labels.go @@ -115,7 +115,7 @@ func (d *Datastore) Label(lid uint) (*kolide.Label, error) { return label, nil } -func (d *Datastore) ListLabels(opt kolide.ListOptions) ([]*kolide.Label, error) { +func (d *Datastore) ListLabels(filter kolide.TeamFilter, opt kolide.ListOptions) ([]*kolide.Label, error) { d.mtx.Lock() defer d.mtx.Unlock() // We need to sort by keys to provide reliable ordering @@ -177,7 +177,7 @@ func (d *Datastore) SearchLabels(filter kolide.TeamFilter, query string, omit .. return results, nil } -func (d *Datastore) ListHostsInLabel(lid uint, opt kolide.HostListOptions) ([]*kolide.Host, error) { +func (d *Datastore) ListHostsInLabel(filter kolide.TeamFilter, lid uint, opt kolide.HostListOptions) ([]*kolide.Host, error) { var hosts []*kolide.Host d.mtx.Lock() @@ -192,7 +192,7 @@ func (d *Datastore) ListHostsInLabel(lid uint, opt kolide.HostListOptions) ([]*k return hosts, nil } -func (d *Datastore) ListUniqueHostsInLabels(labels []uint) ([]*kolide.Host, error) { +func (d *Datastore) ListUniqueHostsInLabels(filter kolide.TeamFilter, labels []uint) ([]*kolide.Host, error) { var hosts []*kolide.Host labelSet := map[uint]bool{} diff --git a/server/datastore/mysql/hosts.go b/server/datastore/mysql/hosts.go index c655dd82ff..0d82c8eaa0 100644 --- a/server/datastore/mysql/hosts.go +++ b/server/datastore/mysql/hosts.go @@ -282,7 +282,7 @@ func (d *Datastore) Host(id uint) (*kolide.Host, error) { return host, nil } -func (d *Datastore) ListHosts(opt kolide.HostListOptions) ([]*kolide.Host, error) { +func (d *Datastore) ListHosts(filter kolide.TeamFilter, opt kolide.HostListOptions) ([]*kolide.Host, error) { sql := `SELECT h.id, h.osquery_host_id, @@ -345,9 +345,10 @@ func (d *Datastore) ListHosts(opt kolide.HostListOptions) ([]*kolide.Host, error ` } - sql += `FROM hosts h LEFT JOIN teams t ON (h.team_id = t.id) -WHERE TRUE - ` + sql += fmt.Sprintf(`FROM hosts h LEFT JOIN teams t ON (h.team_id = t.id) + WHERE TRUE AND %s + `, d.whereFilterHostsByTeams(filter, "h"), + ) switch opt.StatusFilter { case "new": sql += "AND DATE_ADD(h.created_at, INTERVAL 1 DAY) >= ?" @@ -388,19 +389,21 @@ func (d *Datastore) CleanupIncomingHosts(now time.Time) error { return nil } -func (d *Datastore) GenerateHostStatusStatistics(now time.Time) (online, offline, mia, new uint, e error) { +func (d *Datastore) GenerateHostStatusStatistics(filter kolide.TeamFilter, now time.Time) (online, offline, mia, new uint, e error) { // The logic in this function should remain synchronized with // host.Status and CountHostsInTargets sqlStatement := fmt.Sprintf(` - SELECT - COALESCE(SUM(CASE WHEN DATE_ADD(seen_time, INTERVAL 30 DAY) <= ? THEN 1 ELSE 0 END), 0) mia, - COALESCE(SUM(CASE WHEN DATE_ADD(seen_time, INTERVAL LEAST(distributed_interval, config_tls_refresh) + %d SECOND) <= ? AND DATE_ADD(seen_time, INTERVAL 30 DAY) >= ? THEN 1 ELSE 0 END), 0) offline, - COALESCE(SUM(CASE WHEN DATE_ADD(seen_time, INTERVAL LEAST(distributed_interval, config_tls_refresh) + %d SECOND) > ? THEN 1 ELSE 0 END), 0) online, - COALESCE(SUM(CASE WHEN DATE_ADD(created_at, INTERVAL 1 DAY) >= ? THEN 1 ELSE 0 END), 0) new - FROM hosts - LIMIT 1; - `, kolide.OnlineIntervalBuffer, kolide.OnlineIntervalBuffer) + SELECT + COALESCE(SUM(CASE WHEN DATE_ADD(seen_time, INTERVAL 30 DAY) <= ? THEN 1 ELSE 0 END), 0) mia, + COALESCE(SUM(CASE WHEN DATE_ADD(seen_time, INTERVAL LEAST(distributed_interval, config_tls_refresh) + %d SECOND) <= ? AND DATE_ADD(seen_time, INTERVAL 30 DAY) >= ? THEN 1 ELSE 0 END), 0) offline, + COALESCE(SUM(CASE WHEN DATE_ADD(seen_time, INTERVAL LEAST(distributed_interval, config_tls_refresh) + %d SECOND) > ? THEN 1 ELSE 0 END), 0) online, + COALESCE(SUM(CASE WHEN DATE_ADD(created_at, INTERVAL 1 DAY) >= ? THEN 1 ELSE 0 END), 0) new + FROM hosts WHERE %s + LIMIT 1; + `, kolide.OnlineIntervalBuffer, kolide.OnlineIntervalBuffer, + d.whereFilterHostsByTeams(filter, "hosts"), + ) counts := struct { MIA uint `db:"mia"` @@ -704,15 +707,16 @@ func (d *Datastore) SearchHosts(filter kolide.TeamFilter, query string, omit ... } -func (d *Datastore) HostIDsByName(hostnames []string) ([]uint, error) { +func (d *Datastore) HostIDsByName(filter kolide.TeamFilter, hostnames []string) ([]uint, error) { if len(hostnames) == 0 { return []uint{}, nil } - sqlStatement := ` - SELECT id FROM hosts - WHERE host_name IN (?) - ` + sqlStatement := fmt.Sprintf(` + SELECT id FROM hosts + WHERE host_name IN (?) AND %s + `, d.whereFilterHostsByTeams(filter, "hosts"), + ) sql, args, err := sqlx.In(sqlStatement, hostnames) if err != nil { diff --git a/server/datastore/mysql/labels.go b/server/datastore/mysql/labels.go index 32080e061f..a79bdf7e07 100644 --- a/server/datastore/mysql/labels.go +++ b/server/datastore/mysql/labels.go @@ -243,11 +243,14 @@ func (d *Datastore) Label(lid uint) (*kolide.Label, error) { } // ListLabels returns all labels limited or sorted by kolide.ListOptions. -func (d *Datastore) ListLabels(opt kolide.ListOptions) ([]*kolide.Label, error) { - query := ` - SELECT *, (SELECT COUNT(1) FROM label_membership WHERE label_id = id) AS host_count - FROM labels - ` +func (d *Datastore) ListLabels(filter kolide.TeamFilter, opt kolide.ListOptions) ([]*kolide.Label, error) { + query := fmt.Sprintf(` + SELECT *, + (SELECT COUNT(1) FROM label_membership lm JOIN hosts h ON (lm.host_id = h.id) WHERE label_id = l.id AND %s) AS host_count + FROM labels l + `, d.whereFilterHostsByTeams(filter, "h"), + ) + query = appendListOptionsToSQL(query, opt) labels := []*kolide.Label{} @@ -389,14 +392,16 @@ func (d *Datastore) ListLabelsForHost(hid uint) ([]*kolide.Label, error) { // ListHostsInLabel returns a list of kolide.Host that are associated // with kolide.Label referened by Label ID -func (d *Datastore) ListHostsInLabel(lid uint, opt kolide.HostListOptions) ([]*kolide.Host, error) { - sql := ` - SELECT h.* - FROM label_membership lm - JOIN hosts h - ON lm.host_id = h.id - WHERE lm.label_id = ? - ` +func (d *Datastore) ListHostsInLabel(filter kolide.TeamFilter, lid uint, opt kolide.HostListOptions) ([]*kolide.Host, error) { + sql := fmt.Sprintf(` + SELECT h.* + FROM label_membership lm + JOIN hosts h + ON lm.host_id = h.id + WHERE lm.label_id = ? AND %s + `, d.whereFilterHostsByTeams(filter, "h"), + ) + params := []interface{}{lid} sql, params = searchLike(sql, params, opt.MatchQuery, hostSearchColumns...) @@ -410,18 +415,20 @@ func (d *Datastore) ListHostsInLabel(lid uint, opt kolide.HostListOptions) ([]*k return hosts, nil } -func (d *Datastore) ListUniqueHostsInLabels(labels []uint) ([]*kolide.Host, error) { +func (d *Datastore) ListUniqueHostsInLabels(filter kolide.TeamFilter, labels []uint) ([]*kolide.Host, error) { if len(labels) == 0 { return []*kolide.Host{}, nil } - sqlStatement := ` - SELECT DISTINCT h.* - FROM label_membership lm - JOIN hosts h - ON lm.host_id = h.id - WHERE lm.label_id IN (?) - ` + sqlStatement := fmt.Sprintf(` + SELECT DISTINCT h.* + FROM label_membership lm + JOIN hosts h + ON lm.host_id = h.id + WHERE lm.label_id IN (?) AND %s + `, d.whereFilterHostsByTeams(filter, "h"), + ) + query, args, err := sqlx.In(sqlStatement, labels) if err != nil { return nil, errors.Wrap(err, "building query listing unique hosts in labels") diff --git a/server/kolide/hosts.go b/server/kolide/hosts.go index 710d340265..d36470894f 100644 --- a/server/kolide/hosts.go +++ b/server/kolide/hosts.go @@ -47,7 +47,7 @@ type HostStore interface { // provided host enrollment cooldown, by returning an error if the host has // enrolled within the cooldown period. EnrollHost(osqueryHostId, nodeKey string, teamID *uint, cooldown time.Duration) (*Host, error) - ListHosts(opt HostListOptions) ([]*Host, error) + ListHosts(filter TeamFilter, opt HostListOptions) ([]*Host, error) // AuthenticateHost authenticates and returns host metadata by node key. // This method should not return the host "additional" information as this // is not typically necessary for the operations performed by the osquery @@ -66,9 +66,9 @@ type HostStore interface { CleanupIncomingHosts(now time.Time) error // GenerateHostStatusStatistics retrieves the count of online, offline, // MIA and new hosts. - GenerateHostStatusStatistics(now time.Time) (online, offline, mia, new uint, err error) + GenerateHostStatusStatistics(filter TeamFilter, now time.Time) (online, offline, mia, new uint, err error) // HostIDsByName Retrieve the IDs associated with the given hostnames - HostIDsByName(hostnames []string) ([]uint, error) + HostIDsByName(filter TeamFilter, hostnames []string) ([]uint, error) // HostByIdentifier returns one host matching the provided identifier. // Possible matches can be on osquery_host_identifier, node_key, UUID, or // hostname. diff --git a/server/kolide/labels.go b/server/kolide/labels.go index b74e699936..e1b2ff29c5 100644 --- a/server/kolide/labels.go +++ b/server/kolide/labels.go @@ -21,7 +21,7 @@ type LabelStore interface { SaveLabel(label *Label) (*Label, error) DeleteLabel(name string) error Label(lid uint) (*Label, error) - ListLabels(opt ListOptions) ([]*Label, error) + ListLabels(filter TeamFilter, opt ListOptions) ([]*Label, error) // LabelQueriesForHost returns the label queries that should be executed // for the given host. The cutoff is the minimum timestamp a query @@ -41,12 +41,12 @@ type LabelStore interface { // ListHostsInLabel returns a slice of hosts in the label with the // given ID. - ListHostsInLabel(lid uint, opt HostListOptions) ([]*Host, error) + ListHostsInLabel(filter TeamFilter, lid uint, opt HostListOptions) ([]*Host, error) // ListUniqueHostsInLabels returns a slice of all of the hosts in the // given label IDs. A host will only appear once in the results even if // it is in multiple of the provided labels. - ListUniqueHostsInLabels(labels []uint) ([]*Host, error) + ListUniqueHostsInLabels(filter TeamFilter, labels []uint) ([]*Host, error) SearchLabels(filter TeamFilter, query string, omit ...uint) ([]*Label, error) diff --git a/server/mock/datastore_hosts.go b/server/mock/datastore_hosts.go index 1a28c0265a..2696240571 100644 --- a/server/mock/datastore_hosts.go +++ b/server/mock/datastore_hosts.go @@ -20,7 +20,7 @@ type HostFunc func(id uint) (*kolide.Host, error) type HostByIdentifierFunc func(identifier string) (*kolide.Host, error) -type ListHostsFunc func(opt kolide.HostListOptions) ([]*kolide.Host, error) +type ListHostsFunc func(filter kolide.TeamFilter, opt kolide.HostListOptions) ([]*kolide.Host, error) type EnrollHostFunc func(osqueryHostId, nodeKey string, teamID *uint, cooldown time.Duration) (*kolide.Host, error) @@ -34,11 +34,11 @@ type CleanupIncomingHostsFunc func(t time.Time) error type SearchHostsFunc func(filter kolide.TeamFilter, query string, omit ...uint) ([]*kolide.Host, error) -type GenerateHostStatusStatisticsFunc func(now time.Time) (online uint, offline uint, mia uint, new uint, err error) +type GenerateHostStatusStatisticsFunc func(filter kolide.TeamFilter, now time.Time) (online uint, offline uint, mia uint, new uint, err error) type DistributedQueriesForHostFunc func(host *kolide.Host) (map[uint]string, error) -type HostIDsByNameFunc func(hostnames []string) ([]uint, error) +type HostIDsByNameFunc func(filter kolide.TeamFilter, hostnames []string) ([]uint, error) type AddHostsToTeamFunc func(teamID *uint, hostIDs []uint) error @@ -122,9 +122,9 @@ func (s *HostStore) HostByIdentifier(identifier string) (*kolide.Host, error) { return s.HostByIdentifierFunc(identifier) } -func (s *HostStore) ListHosts(opt kolide.HostListOptions) ([]*kolide.Host, error) { +func (s *HostStore) ListHosts(filter kolide.TeamFilter, opt kolide.HostListOptions) ([]*kolide.Host, error) { s.ListHostsFuncInvoked = true - return s.ListHostsFunc(opt) + return s.ListHostsFunc(filter, opt) } func (s *HostStore) EnrollHost(osqueryHostId, nodeKey string, teamID *uint, cooldown time.Duration) (*kolide.Host, error) { @@ -157,9 +157,9 @@ func (s *HostStore) SearchHosts(filter kolide.TeamFilter, query string, omit ... return s.SearchHostsFunc(filter, query, omit...) } -func (s *HostStore) GenerateHostStatusStatistics(now time.Time) (online uint, offline uint, mia uint, new uint, err error) { +func (s *HostStore) GenerateHostStatusStatistics(filter kolide.TeamFilter, now time.Time) (online uint, offline uint, mia uint, new uint, err error) { s.GenerateHostStatusStatisticsFuncInvoked = true - return s.GenerateHostStatusStatisticsFunc(now) + return s.GenerateHostStatusStatisticsFunc(filter, now) } func (s *HostStore) DistributedQueriesForHost(host *kolide.Host) (map[uint]string, error) { @@ -167,9 +167,9 @@ func (s *HostStore) DistributedQueriesForHost(host *kolide.Host) (map[uint]strin return s.DistributedQueriesForHostFunc(host) } -func (s *HostStore) HostIDsByName(hostnames []string) ([]uint, error) { +func (s *HostStore) HostIDsByName(filter kolide.TeamFilter, hostnames []string) ([]uint, error) { s.HostIDsByNameFuncInvoked = true - return s.HostIDsByNameFunc(hostnames) + return s.HostIDsByNameFunc(filter, hostnames) } func (s *HostStore) AddHostsToTeam(teamID *uint, hostIDs []uint) error { diff --git a/server/mock/datastore_labels.go b/server/mock/datastore_labels.go index 8600484f0e..b6912174a9 100644 --- a/server/mock/datastore_labels.go +++ b/server/mock/datastore_labels.go @@ -24,7 +24,7 @@ type DeleteLabelFunc func(name string) error type LabelFunc func(lid uint) (*kolide.Label, error) -type ListLabelsFunc func(opt kolide.ListOptions) ([]*kolide.Label, error) +type ListLabelsFunc func(filter kolide.TeamFilter, opt kolide.ListOptions) ([]*kolide.Label, error) type LabelQueriesForHostFunc func(host *kolide.Host, cutoff time.Time) (map[string]string, error) @@ -32,9 +32,9 @@ type RecordLabelQueryExecutionsFunc func(host *kolide.Host, results map[uint]boo type ListLabelsForHostFunc func(hid uint) ([]*kolide.Label, error) -type ListHostsInLabelFunc func(lid uint, opt kolide.HostListOptions) ([]*kolide.Host, error) +type ListHostsInLabelFunc func(filter kolide.TeamFilter, lid uint, opt kolide.HostListOptions) ([]*kolide.Host, error) -type ListUniqueHostsInLabelsFunc func(labels []uint) ([]*kolide.Host, error) +type ListUniqueHostsInLabelsFunc func(filter kolide.TeamFilter, labels []uint) ([]*kolide.Host, error) type SearchLabelsFunc func(filter kolide.TeamFilter, query string, omit ...uint) ([]*kolide.Label, error) @@ -122,9 +122,9 @@ func (s *LabelStore) Label(lid uint) (*kolide.Label, error) { return s.LabelFunc(lid) } -func (s *LabelStore) ListLabels(opt kolide.ListOptions) ([]*kolide.Label, error) { +func (s *LabelStore) ListLabels(filter kolide.TeamFilter, opt kolide.ListOptions) ([]*kolide.Label, error) { s.ListLabelsFuncInvoked = true - return s.ListLabelsFunc(opt) + return s.ListLabelsFunc(filter, opt) } func (s *LabelStore) LabelQueriesForHost(host *kolide.Host, cutoff time.Time) (map[string]string, error) { @@ -142,14 +142,14 @@ func (s *LabelStore) ListLabelsForHost(hid uint) ([]*kolide.Label, error) { return s.ListLabelsForHostFunc(hid) } -func (s *LabelStore) ListHostsInLabel(lid uint, opt kolide.HostListOptions) ([]*kolide.Host, error) { +func (s *LabelStore) ListHostsInLabel(filter kolide.TeamFilter, lid uint, opt kolide.HostListOptions) ([]*kolide.Host, error) { s.ListHostsInLabelFuncInvoked = true - return s.ListHostsInLabelFunc(lid, opt) + return s.ListHostsInLabelFunc(filter, lid, opt) } -func (s *LabelStore) ListUniqueHostsInLabels(labels []uint) ([]*kolide.Host, error) { +func (s *LabelStore) ListUniqueHostsInLabels(filter kolide.TeamFilter, labels []uint) ([]*kolide.Host, error) { s.ListUniqueHostsInLabelsFuncInvoked = true - return s.ListUniqueHostsInLabelsFunc(labels) + return s.ListUniqueHostsInLabelsFunc(filter, labels) } func (s *LabelStore) SearchLabels(filter kolide.TeamFilter, query string, omit ...uint) ([]*kolide.Label, error) { diff --git a/server/service/service_campaigns.go b/server/service/service_campaigns.go index 1ab277c85f..c06ac221cf 100644 --- a/server/service/service_campaigns.go +++ b/server/service/service_campaigns.go @@ -16,7 +16,13 @@ import ( ) func (svc Service) NewDistributedQueryCampaignByNames(ctx context.Context, queryString string, queryID *uint, hosts []string, labels []string) (*kolide.DistributedQueryCampaign, error) { - hostIDs, err := svc.ds.HostIDsByName(hosts) + vc, ok := viewer.FromContext(ctx) + if !ok { + return nil, kolide.ErrNoContext + } + filter := kolide.TeamFilter{User: vc.User, IncludeObserver: true} + + hostIDs, err := svc.ds.HostIDsByName(filter, hosts) if err != nil { return nil, errors.Wrap(err, "finding host IDs") } @@ -26,8 +32,6 @@ func (svc Service) NewDistributedQueryCampaignByNames(ctx context.Context, query return nil, errors.Wrap(err, "finding label IDs") } - // TODO handle teams - targets := kolide.HostTargets{HostIDs: hostIDs, LabelIDs: labelIDs} return svc.NewDistributedQueryCampaign(ctx, queryString, queryID, targets) } @@ -69,6 +73,8 @@ func (svc Service) NewDistributedQueryCampaign(ctx context.Context, queryString return nil, errors.Wrap(err, "new query") } + filter := kolide.TeamFilter{User: vc.User, IncludeObserver: query.ObserverCanRun} + campaign, err := svc.ds.NewDistributedQueryCampaign(&kolide.DistributedQueryCampaign{ QueryID: query.ID, Status: kolide.QueryWaiting, @@ -114,8 +120,6 @@ func (svc Service) NewDistributedQueryCampaign(ctx context.Context, queryString } } - filter := kolide.TeamFilter{User: vc.User} - hostIDs, err := svc.ds.HostIDsInTargets(filter, targets) if err != nil { return nil, errors.Wrap(err, "get target IDs") diff --git a/server/service/service_hosts.go b/server/service/service_hosts.go index a9fca181b5..f0f01820c9 100644 --- a/server/service/service_hosts.go +++ b/server/service/service_hosts.go @@ -3,6 +3,7 @@ package service import ( "context" + "github.com/fleetdm/fleet/server/contexts/viewer" "github.com/fleetdm/fleet/server/kolide" "github.com/pkg/errors" ) @@ -12,7 +13,13 @@ func (svc Service) ListHosts(ctx context.Context, opt kolide.HostListOptions) ([ return nil, err } - return svc.ds.ListHosts(opt) + vc, ok := viewer.FromContext(ctx) + if !ok { + return nil, kolide.ErrNoContext + } + filter := kolide.TeamFilter{User: vc.User, IncludeObserver: true} + + return svc.ds.ListHosts(filter, opt) } func (svc Service) GetHost(ctx context.Context, id uint) (*kolide.HostDetail, error) { @@ -73,8 +80,13 @@ func (svc Service) GetHostSummary(ctx context.Context) (*kolide.HostSummary, err if err := svc.authz.Authorize(ctx, &kolide.Host{}, kolide.ActionList); err != nil { return nil, err } + vc, ok := viewer.FromContext(ctx) + if !ok { + return nil, kolide.ErrNoContext + } + filter := kolide.TeamFilter{User: vc.User, IncludeObserver: true} - online, offline, mia, new, err := svc.ds.GenerateHostStatusStatistics(svc.clock.Now()) + online, offline, mia, new, err := svc.ds.GenerateHostStatusStatistics(filter, svc.clock.Now()) if err != nil { return nil, err } @@ -131,6 +143,11 @@ func (svc Service) AddHostsToTeamByFilter(ctx context.Context, teamID *uint, opt if err := svc.authz.Authorize(ctx, &kolide.Team{}, kolide.ActionWrite); err != nil { return err } + vc, ok := viewer.FromContext(ctx) + if !ok { + return kolide.ErrNoContext + } + filter := kolide.TeamFilter{User: vc.User, IncludeObserver: true} if opt.StatusFilter != "" && lid != nil { return kolide.NewInvalidArgumentError("status", "may not be provided with label_id") @@ -142,9 +159,9 @@ func (svc Service) AddHostsToTeamByFilter(ctx context.Context, teamID *uint, opt var hosts []*kolide.Host var err error if lid != nil { - hosts, err = svc.ds.ListHostsInLabel(*lid, opt) + hosts, err = svc.ds.ListHostsInLabel(filter, *lid, opt) } else { - hosts, err = svc.ds.ListHosts(opt) + hosts, err = svc.ds.ListHosts(filter, opt) } if err != nil { return err diff --git a/server/service/service_hosts_test.go b/server/service/service_hosts_test.go index 718da3cdb8..5b3855eae7 100644 --- a/server/service/service_hosts_test.go +++ b/server/service/service_hosts_test.go @@ -48,7 +48,8 @@ func TestDeleteHost(t *testing.T) { err = svc.DeleteHost(test.UserContext(test.UserAdmin), host.ID) assert.Nil(t, err) - hosts, err := ds.ListHosts(kolide.HostListOptions{}) + filter := kolide.TeamFilter{User: test.UserAdmin} + hosts, err := ds.ListHosts(filter, kolide.HostListOptions{}) assert.Nil(t, err) assert.Len(t, hosts, 0) @@ -113,7 +114,7 @@ func TestAddHostsToTeamByFilter(t *testing.T) { expectedHostIDs := []uint{1, 2, 4} expectedTeam := (*uint)(nil) - ds.ListHostsFunc = func(opt kolide.HostListOptions) ([]*kolide.Host, error) { + ds.ListHostsFunc = func(filter kolide.TeamFilter, opt kolide.HostListOptions) ([]*kolide.Host, error) { var hosts []*kolide.Host for _, id := range expectedHostIDs { hosts = append(hosts, &kolide.Host{ID: id}) @@ -137,7 +138,7 @@ func TestAddHostsToTeamByFilterLabel(t *testing.T) { expectedTeam := ptr.Uint(1) expectedLabel := ptr.Uint(2) - ds.ListHostsInLabelFunc = func(lid uint, opt kolide.HostListOptions) ([]*kolide.Host, error) { + ds.ListHostsInLabelFunc = func(filter kolide.TeamFilter, lid uint, opt kolide.HostListOptions) ([]*kolide.Host, error) { assert.Equal(t, *expectedLabel, lid) var hosts []*kolide.Host for _, id := range expectedHostIDs { @@ -157,7 +158,7 @@ func TestAddHostsToTeamByFilterEmptyHosts(t *testing.T) { ds := new(mock.Store) svc := newTestService(ds, nil, nil) - ds.ListHostsFunc = func(opt kolide.HostListOptions) ([]*kolide.Host, error) { + ds.ListHostsFunc = func(filter kolide.TeamFilter, opt kolide.HostListOptions) ([]*kolide.Host, error) { return []*kolide.Host{}, nil } ds.AddHostsToTeamFunc = func(teamID *uint, hostIDs []uint) error { diff --git a/server/service/service_labels.go b/server/service/service_labels.go index a30616c71e..2b270c80b4 100644 --- a/server/service/service_labels.go +++ b/server/service/service_labels.go @@ -3,6 +3,7 @@ package service import ( "context" + "github.com/fleetdm/fleet/server/contexts/viewer" "github.com/fleetdm/fleet/server/kolide" "github.com/pkg/errors" ) @@ -94,8 +95,13 @@ func (svc *Service) ListLabels(ctx context.Context, opt kolide.ListOptions) ([]* if err := svc.authz.Authorize(ctx, &kolide.Label{}, kolide.ActionRead); err != nil { return nil, err } + vc, ok := viewer.FromContext(ctx) + if !ok { + return nil, kolide.ErrNoContext + } + filter := kolide.TeamFilter{User: vc.User, IncludeObserver: true} - return svc.ds.ListLabels(opt) + return svc.ds.ListLabels(filter, opt) } func (svc *Service) GetLabel(ctx context.Context, id uint) (*kolide.Label, error) { @@ -130,8 +136,13 @@ func (svc *Service) ListHostsInLabel(ctx context.Context, lid uint, opt kolide.H if err := svc.authz.Authorize(ctx, &kolide.Label{}, kolide.ActionRead); err != nil { return nil, err } + vc, ok := viewer.FromContext(ctx) + if !ok { + return nil, kolide.ErrNoContext + } + filter := kolide.TeamFilter{User: vc.User, IncludeObserver: true} - return svc.ds.ListHostsInLabel(lid, opt) + return svc.ds.ListHostsInLabel(filter, lid, opt) } func (svc *Service) ListLabelsForHost(ctx context.Context, hid uint) ([]*kolide.Label, error) {