diff --git a/changes/issue-2062-team-maintainer-run-new b/changes/issue-2062-team-maintainer-run-new new file mode 100644 index 0000000000..eed0492a76 --- /dev/null +++ b/changes/issue-2062-team-maintainer-run-new @@ -0,0 +1 @@ +* Allow team maintainers to run new queries in the team hosts. diff --git a/server/authz/policy.rego b/server/authz/policy.rego index 64e5179104..64ccb76a09 100644 --- a/server/authz/policy.rego +++ b/server/authz/policy.rego @@ -15,6 +15,7 @@ list := "list" write := "write" write_role := "write_role" run := "run" +run_new := "run_new" # Roles admin := "admin" @@ -265,6 +266,16 @@ allow { subject.global_role == maintainer action = run } +allow { + object.type == "query" + subject.global_role == admin + action = run_new +} +allow { + object.type == "query" + subject.global_role == maintainer + action = run_new +} # Team maintainer running a non-observers_can_run query must have the targets # filtered to only teams that they maintain allow { @@ -274,6 +285,22 @@ allow { action == run } +# Team maintainer can run a new query +allow { + object.type == "query" + # If role is maintainer on any team + team_role(subject, subject.teams[_].id) == maintainer + action == run_new +} + +# Team admin can run a new query +allow { + object.type == "query" + # If role is maintainer on any team + team_role(subject, subject.teams[_].id) == admin + action == run_new +} + # (Team) observers can run only if observers_can_run allow { object.type == "query" diff --git a/server/fleet/authz.go b/server/fleet/authz.go index 94cda02869..118dcce871 100644 --- a/server/fleet/authz.go +++ b/server/fleet/authz.go @@ -11,4 +11,6 @@ const ( ActionWriteRole = "write_role" // ActionRun is the action for running a live query. ActionRun = "run" + // ActionRunNew is the action for running a new live query. + ActionRunNew = "run_new" ) diff --git a/server/service/service_campaigns.go b/server/service/service_campaigns.go index 0802a2f99c..ec1cd90e17 100644 --- a/server/service/service_campaigns.go +++ b/server/service/service_campaigns.go @@ -61,7 +61,7 @@ func (svc Service) NewDistributedQueryCampaign(ctx context.Context, queryString } queryString = query.Query } else { - if err := svc.authz.Authorize(ctx, &fleet.Query{}, fleet.ActionWrite); err != nil { + if err := svc.authz.Authorize(ctx, &fleet.Query{}, fleet.ActionRunNew); err != nil { return nil, err } query = &fleet.Query{ diff --git a/server/service/service_osquery_test.go b/server/service/service_osquery_test.go index 30d7b9fa56..00570228cb 100644 --- a/server/service/service_osquery_test.go +++ b/server/service/service_osquery_test.go @@ -1746,6 +1746,63 @@ func TestObserversCanOnlyRunDistributedCampaigns(t *testing.T) { require.NoError(t, err) } +func TestTeamMaintainerCanRunNewDistributedCampaigns(t *testing.T) { + ds := new(mock.Store) + rs := &mock.QueryResultStore{ + HealthCheckFunc: func() error { + return nil + }, + } + lq := &live_query.MockLiveQuery{} + mockClock := clock.NewMockClock() + svc := newTestServiceWithClock(ds, rs, lq, mockClock) + + ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) { + return &fleet.AppConfig{}, nil + } + + ds.NewDistributedQueryCampaignFunc = func(ctx context.Context, camp *fleet.DistributedQueryCampaign) (*fleet.DistributedQueryCampaign, error) { + return camp, nil + } + ds.QueryFunc = func(ctx context.Context, id uint) (*fleet.Query, error) { + return &fleet.Query{ + ID: 42, + Name: "query", + Query: "select 1;", + ObserverCanRun: false, + }, nil + } + viewerCtx := viewer.NewContext(context.Background(), viewer.Viewer{ + User: &fleet.User{ID: 0, Teams: []fleet.UserTeam{{Role: fleet.RoleMaintainer}}}, + }) + + q := "select year, month, day, hour, minutes, seconds from time" + ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activityType string, details *map[string]interface{}) error { + return nil + } + //var gotQuery *fleet.Query + ds.NewQueryFunc = func(ctx context.Context, query *fleet.Query, opts ...fleet.OptionalArg) (*fleet.Query, error) { + //gotQuery = query + query.ID = 42 + return query, nil + } + ds.NewDistributedQueryCampaignTargetFunc = func(ctx context.Context, target *fleet.DistributedQueryCampaignTarget) (*fleet.DistributedQueryCampaignTarget, error) { + return target, nil + } + ds.CountHostsInTargetsFunc = func(ctx context.Context, filter fleet.TeamFilter, targets fleet.HostTargets, now time.Time) (fleet.TargetMetrics, error) { + return fleet.TargetMetrics{}, nil + } + ds.HostIDsInTargetsFunc = func(ctx context.Context, filter fleet.TeamFilter, targets fleet.HostTargets) ([]uint, error) { + return []uint{1, 3, 5}, nil + } + ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activityType string, details *map[string]interface{}) error { + return nil + } + lq.On("RunQuery", "0", "select year, month, day, hour, minutes, seconds from time", []uint{1, 3, 5}).Return(nil) + _, err := svc.NewDistributedQueryCampaign(viewerCtx, q, nil, fleet.HostTargets{HostIDs: []uint{2}, LabelIDs: []uint{1}}) + require.NoError(t, err) +} + func TestPolicyQueries(t *testing.T) { mockClock := clock.NewMockClock() ds := new(mock.Store)