mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 08:55:24 +00:00
254 lines
9.0 KiB
Go
254 lines
9.0 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"github.com/stretchr/testify/require"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
"github.com/fleetdm/fleet/v4/server/mock"
|
|
"github.com/fleetdm/fleet/v4/server/ptr"
|
|
"github.com/fleetdm/fleet/v4/server/pubsub"
|
|
)
|
|
|
|
type nopLiveQuery struct{}
|
|
|
|
func (nopLiveQuery) RunQuery(name, sql string, hostIDs []uint) error {
|
|
return nil
|
|
}
|
|
|
|
func (nopLiveQuery) StopQuery(name string) error {
|
|
return nil
|
|
}
|
|
|
|
func (nopLiveQuery) QueriesForHost(hostID uint) (map[string]string, error) {
|
|
return map[string]string{}, nil
|
|
}
|
|
|
|
func (nopLiveQuery) QueryCompletedByHost(name string, hostID uint) error {
|
|
return nil
|
|
}
|
|
|
|
func TestLiveQueryAuth(t *testing.T) {
|
|
ds := new(mock.Store)
|
|
qr := pubsub.NewInmemQueryResults()
|
|
svc, ctx := newTestService(t, ds, qr, nopLiveQuery{})
|
|
|
|
teamMaintainer := &fleet.User{ID: 42, Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleMaintainer}}}
|
|
query1ObsCanRun := &fleet.Query{
|
|
ID: 1,
|
|
AuthorID: ptr.Uint(teamMaintainer.ID),
|
|
Name: "q1",
|
|
Query: "SELECT 1",
|
|
ObserverCanRun: true,
|
|
}
|
|
query2ObsCannotRun := &fleet.Query{
|
|
ID: 2,
|
|
AuthorID: ptr.Uint(teamMaintainer.ID),
|
|
Name: "q2",
|
|
Query: "SELECT 2",
|
|
ObserverCanRun: false,
|
|
}
|
|
|
|
var lastCreatedQuery *fleet.Query
|
|
ds.NewQueryFunc = func(ctx context.Context, query *fleet.Query, opts ...fleet.OptionalArg) (*fleet.Query, error) {
|
|
q := *query
|
|
vw, ok := viewer.FromContext(ctx)
|
|
q.ID = 123
|
|
if ok {
|
|
q.AuthorID = ptr.Uint(vw.User.ID)
|
|
}
|
|
lastCreatedQuery = &q
|
|
return &q, nil
|
|
}
|
|
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
|
|
return &fleet.AppConfig{ServerSettings: fleet.ServerSettings{LiveQueryDisabled: false}}, nil
|
|
}
|
|
ds.NewDistributedQueryCampaignFunc = func(ctx context.Context, camp *fleet.DistributedQueryCampaign) (*fleet.DistributedQueryCampaign, error) {
|
|
return camp, nil
|
|
}
|
|
ds.NewDistributedQueryCampaignTargetFunc = func(ctx context.Context, target *fleet.DistributedQueryCampaignTarget) (*fleet.DistributedQueryCampaignTarget, error) {
|
|
return target, nil
|
|
}
|
|
ds.HostIDsInTargetsFunc = func(ctx context.Context, filters fleet.TeamFilter, targets fleet.HostTargets) ([]uint, error) {
|
|
return []uint{1}, nil
|
|
}
|
|
ds.HostIDsByNameFunc = func(ctx context.Context, filter fleet.TeamFilter, names []string) ([]uint, error) {
|
|
return nil, nil
|
|
}
|
|
ds.LabelIDsByNameFunc = func(ctx context.Context, names []string) ([]uint, error) {
|
|
return nil, nil
|
|
}
|
|
ds.CountHostsInTargetsFunc = func(ctx context.Context, filters fleet.TeamFilter, targets fleet.HostTargets, now time.Time) (fleet.TargetMetrics, error) {
|
|
return fleet.TargetMetrics{}, nil
|
|
}
|
|
var queryName, querySQL string
|
|
ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activityType string, details *map[string]interface{}) error {
|
|
name := (*details)["query_name"]
|
|
if name == nil {
|
|
queryName = ""
|
|
} else {
|
|
queryName = name.(string)
|
|
}
|
|
querySQL = (*details)["query_sql"].(string)
|
|
return nil
|
|
}
|
|
ds.QueryFunc = func(ctx context.Context, id uint) (*fleet.Query, error) {
|
|
if id == 1 {
|
|
return query1ObsCanRun, nil
|
|
}
|
|
if id == 2 {
|
|
return query2ObsCannotRun, nil
|
|
}
|
|
if lastCreatedQuery != nil {
|
|
q := lastCreatedQuery
|
|
lastCreatedQuery = nil
|
|
return q, nil
|
|
}
|
|
return &fleet.Query{ID: 8888, AuthorID: ptr.Uint(6666)}, nil
|
|
}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
user *fleet.User
|
|
teamID *uint // to use as host target
|
|
shouldFailRunNew bool
|
|
shouldFailRunObsCan bool
|
|
shouldFailRunObsCannot bool
|
|
}{
|
|
{
|
|
name: "global admin",
|
|
user: &fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)},
|
|
teamID: nil,
|
|
shouldFailRunNew: false,
|
|
shouldFailRunObsCan: false,
|
|
shouldFailRunObsCannot: false,
|
|
},
|
|
{
|
|
name: "global maintainer",
|
|
user: &fleet.User{GlobalRole: ptr.String(fleet.RoleMaintainer)},
|
|
teamID: nil,
|
|
shouldFailRunNew: false,
|
|
shouldFailRunObsCan: false,
|
|
shouldFailRunObsCannot: false,
|
|
},
|
|
{
|
|
name: "global observer",
|
|
user: &fleet.User{GlobalRole: ptr.String(fleet.RoleObserver)},
|
|
teamID: nil,
|
|
shouldFailRunNew: true,
|
|
shouldFailRunObsCan: false,
|
|
shouldFailRunObsCannot: true,
|
|
},
|
|
{
|
|
name: "team maintainer",
|
|
user: teamMaintainer,
|
|
teamID: nil,
|
|
shouldFailRunNew: false,
|
|
shouldFailRunObsCan: false,
|
|
shouldFailRunObsCannot: false,
|
|
},
|
|
{
|
|
name: "team admin, no team target",
|
|
user: &fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleAdmin}}},
|
|
teamID: nil,
|
|
shouldFailRunNew: false,
|
|
shouldFailRunObsCan: false,
|
|
shouldFailRunObsCannot: false,
|
|
},
|
|
{
|
|
name: "team admin, target not set to own team",
|
|
user: &fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleAdmin}}},
|
|
teamID: ptr.Uint(2),
|
|
shouldFailRunNew: false,
|
|
shouldFailRunObsCan: true, // fails observer can run, as they are not part of that team, even as observer
|
|
shouldFailRunObsCannot: true,
|
|
},
|
|
{
|
|
name: "team admin, target set to own team",
|
|
user: &fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleAdmin}}},
|
|
teamID: ptr.Uint(1),
|
|
shouldFailRunNew: false,
|
|
shouldFailRunObsCan: false,
|
|
shouldFailRunObsCannot: false,
|
|
},
|
|
{
|
|
name: "team observer, no team target",
|
|
user: &fleet.User{ID: 48, Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleObserver}}},
|
|
teamID: nil,
|
|
shouldFailRunNew: true,
|
|
shouldFailRunObsCan: false,
|
|
shouldFailRunObsCannot: true,
|
|
},
|
|
{
|
|
name: "team observer, target not set to own team",
|
|
user: &fleet.User{ID: 48, Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleObserver}}},
|
|
teamID: ptr.Uint(2),
|
|
shouldFailRunNew: true,
|
|
shouldFailRunObsCan: true,
|
|
shouldFailRunObsCannot: true,
|
|
},
|
|
{
|
|
name: "team observer, target set to own team",
|
|
user: &fleet.User{ID: 48, Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleObserver}}},
|
|
teamID: ptr.Uint(1),
|
|
shouldFailRunNew: true,
|
|
shouldFailRunObsCan: false,
|
|
shouldFailRunObsCannot: true,
|
|
},
|
|
}
|
|
for _, tt := range testCases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ctx := viewer.NewContext(ctx, viewer.Viewer{User: tt.user})
|
|
|
|
var tms []uint
|
|
// Testing RunNew is tricky, because RunNew authorization is done, then
|
|
// the query is created, and then the Run authorization is applied to
|
|
// that now-existing query, so we have to make sure that the Run does not
|
|
// cause a Forbidden error. To this end, the ds.NewQuery mock always sets
|
|
// the AuthorID to the context user, and if the user is member of a team,
|
|
// always set that team as a host target. This will prevent the Run
|
|
// action from failing, if RunNew did succeed.
|
|
if len(tt.user.Teams) > 0 {
|
|
tms = []uint{tt.user.Teams[0].ID}
|
|
}
|
|
_, err := svc.NewDistributedQueryCampaign(ctx, query1ObsCanRun.Query, nil, fleet.HostTargets{TeamIDs: tms})
|
|
checkAuthErr(t, tt.shouldFailRunNew, err)
|
|
checkActivity := func(t testing.TB, err error, expectName, expectSQL string) {
|
|
if err != nil {
|
|
return
|
|
}
|
|
require.Equal(t, expectName, queryName)
|
|
require.Equal(t, expectSQL, querySQL)
|
|
}
|
|
checkActivity(t, err, "", query1ObsCanRun.Query)
|
|
|
|
if tt.teamID != nil {
|
|
tms = []uint{*tt.teamID}
|
|
}
|
|
_, err = svc.NewDistributedQueryCampaign(ctx, query1ObsCanRun.Query, ptr.Uint(query1ObsCanRun.ID), fleet.HostTargets{TeamIDs: tms})
|
|
checkAuthErr(t, tt.shouldFailRunObsCan, err)
|
|
checkActivity(t, err, query1ObsCanRun.Name, query1ObsCanRun.Query)
|
|
|
|
_, err = svc.NewDistributedQueryCampaign(ctx, query2ObsCannotRun.Query, ptr.Uint(query2ObsCannotRun.ID), fleet.HostTargets{TeamIDs: tms})
|
|
checkAuthErr(t, tt.shouldFailRunObsCannot, err)
|
|
checkActivity(t, err, query2ObsCannotRun.Name, query2ObsCannotRun.Query)
|
|
|
|
// tests with a team target cannot run the "ByNames" calls, as there's no way
|
|
// to pass a team target with this call.
|
|
if tt.teamID == nil {
|
|
_, err = svc.NewDistributedQueryCampaignByNames(ctx, query1ObsCanRun.Query, nil, nil, nil)
|
|
checkAuthErr(t, tt.shouldFailRunNew, err)
|
|
|
|
_, err = svc.NewDistributedQueryCampaignByNames(ctx, query1ObsCanRun.Query, ptr.Uint(query1ObsCanRun.ID), nil, nil)
|
|
checkAuthErr(t, tt.shouldFailRunObsCan, err)
|
|
|
|
_, err = svc.NewDistributedQueryCampaignByNames(ctx, query2ObsCannotRun.Query, ptr.Uint(query2ObsCannotRun.ID), nil, nil)
|
|
checkAuthErr(t, tt.shouldFailRunObsCannot, err)
|
|
}
|
|
})
|
|
}
|
|
}
|