Add platforms field to policies (#3181)

* Add platforms field to policies

* Fix fleetctl tests

* PR review changes

* Add missing tests

* Add changes for ListPoliciesForHost
This commit is contained in:
Lucas Manuel Rodriguez 2021-12-03 15:33:33 -03:00 committed by GitHub
parent 142006cbdd
commit b9a408704e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 506 additions and 108 deletions

View File

@ -0,0 +1 @@
* Add "platforms" field to policies.

View File

@ -261,7 +261,7 @@ func TestGetHosts(t *testing.T) {
return make([]*fleet.Pack, 0), nil
}
defaultPolicyQuery := "select 1 from osquery_info where start_time > 1;"
ds.ListPoliciesForHostFunc = func(ctx context.Context, hid uint) ([]*fleet.HostPolicy, error) {
ds.ListPoliciesForHostFunc = func(ctx context.Context, host *fleet.Host) ([]*fleet.HostPolicy, error) {
return []*fleet.HostPolicy{
{
PolicyData: fleet.PolicyData{

View File

@ -56,6 +56,7 @@
"id":1,
"query":"select 1 from osquery_info where start_time > 1;",
"name":"query1",
"platforms": "",
"description":"Some description",
"author_email":"alice@example.com",
"author_id":1,
@ -70,6 +71,7 @@
"id":2,
"query":"select 1 from osquery_info where start_time > 1;",
"name":"query2",
"platforms": "",
"description":"",
"author_email":"alice@example.com",
"author_id":1,

View File

@ -43,6 +43,7 @@ spec:
id: 1
description: "Some description"
name: query1
platforms: ""
query: select 1 from osquery_info where start_time > 1;
resolution: "Some resolution"
response: passes
@ -55,6 +56,7 @@ spec:
id: 2
description: ""
name: query2
platforms: ""
query: select 1 from osquery_info where start_time > 1;
response: fails
team_id: null

View File

@ -802,6 +802,7 @@ If the scheduled queries haven't run on the host yet, the stats have zero values
"query": "select * from foo;",
"description": "this is a query",
"resolution": "fix with these steps...",
"platforms": "windows,linux",
"response": "pass"
},
{
@ -810,6 +811,7 @@ If the scheduled queries haven't run on the host yet, the stats have zero values
"query": "select * from bar;",
"description": "this is another query",
"resolution": "fix with these other steps...",
"platforms": "darwin",
"response": "fail"
},
{
@ -818,6 +820,7 @@ If the scheduled queries haven't run on the host yet, the stats have zero values
"query": "select * from baz;",
"description": "",
"resolution": "",
"platforms": "",
"response": ""
}
],
@ -3444,6 +3447,7 @@ For example, a policy might ask “Is Gatekeeper enabled on macOS devices?“ Th
"author_name": "John",
"author_email": "john@example.com",
"resolution": "Resolution steps",
"platforms": "darwin",
"passing_host_count": 2000,
"failing_host_count": 300
},
@ -3455,6 +3459,8 @@ For example, a policy might ask “Is Gatekeeper enabled on macOS devices?“ Th
"author_id": 43,
"author_name": "Alice",
"author_email": "alice@example.com",
"resolution": "Resolution steps",
"platforms": "windows",
"passing_host_count": 2300,
"failing_host_count": 0
}
@ -3491,6 +3497,7 @@ For example, a policy might ask “Is Gatekeeper enabled on macOS devices?“ Th
"author_name": "John",
"author_email": "john@example.com",
"resolution": "Resolution steps",
"platforms": "darwin",
"passing_host_count": 2000,
"failing_host_count": 300
}
@ -3517,6 +3524,7 @@ An error is returned if both "query" and "query_id" are set on the request.
| description | string | body | The query's description. |
| resolution | string | body | The resolution steps for the policy. |
| query_id | integer | body | An existing query's ID (legacy). |
| platforms | string | body | Comma-separated target platforms, currently supported values are "windows", "linux", "darwin". The default, an empty string means target all platforms |
Either `query` or `query_id` must be provided.
@ -3531,7 +3539,8 @@ Either `query` or `query_id` must be provided.
"name": "Gatekeeper enabled",
"query": "SELECT 1 FROM gatekeeper WHERE assessments_enabled = 1;",
"description": "Checks if gatekeeper is enabled on macOS devices",
"resolution": "Resolution steps"
"resolution": "Resolution steps",
"platforms": "darwin"
}
```
@ -3550,6 +3559,7 @@ Either `query` or `query_id` must be provided.
"author_name": "John",
"author_email": "john@example.com",
"resolution": "Resolution steps",
"platforms": "darwin",
"passing_host_count": 0,
"failing_host_count": 0
}
@ -3585,6 +3595,7 @@ Where `query_id` references an existing `query`.
"author_name": "John",
"author_email": "john@example.com",
"resolution": "Resolution steps",
"platforms": "darwin",
"passing_host_count": 0,
"failing_host_count": 0
}
@ -3636,6 +3647,8 @@ Where `query_id` references an existing `query`.
| query | string | body | The query in SQL. |
| description | string | body | The query's description. |
| resolution | string | body | The resolution steps for the policy. |
| platforms | string | body | Comma-separated target platforms, currently supported values are "windows", "linux", "darwin". The default, an empty string means target all platforms |
#### Example Edit Policy
@ -3648,7 +3661,8 @@ Where `query_id` references an existing `query`.
"name": "Gatekeeper enabled",
"query": "SELECT 1 FROM gatekeeper WHERE assessments_enabled = 1;",
"description": "Checks if gatekeeper is enabled on macOS devices",
"resolution": "Resolution steps"
"resolution": "Resolution steps",
"platforms": "darwin"
}
```
@ -3667,6 +3681,7 @@ Where `query_id` references an existing `query`.
"author_name": "John",
"author_email": "john@example.com",
"resolution": "Resolution steps",
"platforms": "darwin",
"passing_host_count": 0,
"failing_host_count": 0
}
@ -3718,6 +3733,7 @@ Team policies work the same as policies, but at the team level.
"author_email": "john@example.com",
"team_id": 1,
"resolution": "Resolution steps",
"platforms": "darwin",
"passing_host_count": 2000,
"failing_host_count": 300
},
@ -3730,6 +3746,8 @@ Team policies work the same as policies, but at the team level.
"author_name": "Alice",
"author_email": "alice@example.com",
"team_id": 1,
"resolution": "Resolution steps",
"platforms": "windows",
"passing_host_count": 2300,
"failing_host_count": 0
}
@ -3768,6 +3786,7 @@ Team policies work the same as policies, but at the team level.
"author_email": "john@example.com",
"team_id": 1,
"resolution": "Resolution steps",
"platforms": "darwin",
"passing_host_count": 0,
"failing_host_count": 0
}
@ -3790,6 +3809,7 @@ The semantics for creating a team policy are the same as for global policies, se
| description | string | body | The query's description. |
| resolution | string | body | The resolution steps for the policy. |
| query_id | integer | body | An existing query's ID (legacy). |
| platforms | string | body | Comma-separated target platforms, currently supported values are "windows", "linux", "darwin". The default, an empty string means target all platforms |
Either `query` or `query_id` must be provided.
@ -3804,7 +3824,8 @@ Either `query` or `query_id` must be provided.
"name": "Gatekeeper enabled",
"query": "SELECT 1 FROM gatekeeper WHERE assessments_enabled = 1;",
"description": "Checks if gatekeeper is enabled on macOS devices",
"resolution": "Resolution steps"
"resolution": "Resolution steps",
"platforms": "darwin"
}
```
@ -3824,6 +3845,7 @@ Either `query` or `query_id` must be provided.
"author_email": "john@example.com",
"team_id": 1,
"resolution": "Resolution steps",
"platforms": "darwin",
"passing_host_count": 0,
"failing_host_count": 0
}
@ -3877,6 +3899,8 @@ Either `query` or `query_id` must be provided.
| query | string | body | The query in SQL. |
| description | string | body | The query's description. |
| resolution | string | body | The resolution steps for the policy. |
| platforms | string | body | Comma-separated target platforms, currently supported values are "windows", "linux", "darwin". The default, an empty string means target all platforms |
#### Example Edit Policy
@ -3889,7 +3913,8 @@ Either `query` or `query_id` must be provided.
"name": "Gatekeeper enabled",
"query": "SELECT 1 FROM gatekeeper WHERE assessments_enabled = 1;",
"description": "Checks if gatekeeper is enabled on macOS devices",
"resolution": "Resolution steps"
"resolution": "Resolution steps",
"plarforms": "darwin"
}
```
@ -3908,6 +3933,7 @@ Either `query` or `query_id` must be provided.
"author_name": "John",
"author_email": "john@example.com",
"resolution": "Resolution steps",
"platforms": "darwin",
"team_id": 2,
"passing_host_count": 0,
"failing_host_count": 0

View File

@ -13,6 +13,7 @@ import (
"github.com/doug-martin/goqu/v9"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/go-kit/kit/log/level"
"github.com/jmoiron/sqlx"
)
@ -263,17 +264,6 @@ func saveHostPackStatsDB(ctx context.Context, db sqlx.ExecerContext, host *fleet
return nil
}
// schQueriesPlatformFromHost converts the platform from a Host.Platform
// string to a scheduled query platform string.
func schQueryPlatformFromHost(hostPlatform string) string {
switch hostPlatform {
case "ubuntu", "rhel", "debian":
return "linux"
default: // darwin, windows
return hostPlatform
}
}
// loadhostPacksStatsDB will load all the pack stats for the given host. The scheduled
// queries that haven't run yet are returned with zero values.
func loadHostPackStatsDB(ctx context.Context, db sqlx.QueryerContext, hid uint, hostPlatform string) ([]fleet.PackStats, error) {
@ -328,7 +318,7 @@ func loadHostPackStatsDB(ctx context.Context, db sqlx.QueryerContext, hid uint,
goqu.I("sq.platform").IsNull(),
// scheduled_queries.platform can be a comma-separated list of
// platforms, e.g. "darwin,windows".
goqu.L("FIND_IN_SET(?, sq.platform)", schQueryPlatformFromHost(hostPlatform)).Neq(0),
goqu.L("FIND_IN_SET(?, sq.platform)", fleet.PlatformFromHost(hostPlatform)).Neq(0),
),
)
sql, args, err := ds.ToSQL()
@ -1004,7 +994,11 @@ func (d *Datastore) DeleteHosts(ctx context.Context, ids []uint) error {
return nil
}
func (d *Datastore) ListPoliciesForHost(ctx context.Context, hid uint) (packs []*fleet.HostPolicy, err error) {
func (d *Datastore) ListPoliciesForHost(ctx context.Context, host *fleet.Host) ([]*fleet.HostPolicy, error) {
if host.FleetPlatform() == "" {
// We log to help troubleshooting in case this happens.
level.Error(d.logger).Log("err", fmt.Sprintf("host %d with empty platform", host.ID))
}
query := `SELECT p.*,
COALESCE(u.name, '<deleted>') AS author_name,
COALESCE(u.email, '') AS author_email,
@ -1017,10 +1011,11 @@ func (d *Datastore) ListPoliciesForHost(ctx context.Context, hid uint) (packs []
FROM policies p
LEFT JOIN policy_membership pm ON (p.id=pm.policy_id AND host_id=?)
LEFT JOIN users u ON p.author_id = u.id
WHERE p.team_id IS NULL OR p.team_id = (select team_id from hosts WHERE id = ?)`
WHERE (p.team_id IS NULL OR p.team_id = (select team_id from hosts WHERE id = ?))
AND (p.platforms IS NULL OR p.platforms = "" OR FIND_IN_SET(?, p.platforms) != 0)`
var policies []*fleet.HostPolicy
if err := sqlx.SelectContext(ctx, d.reader, &policies, query, hid, hid); err != nil {
if err := sqlx.SelectContext(ctx, d.reader, &policies, query, host.ID, host.ID, host.FleetPlatform()); err != nil {
return nil, ctxerr.Wrap(ctx, err, "get host policies")
}
return policies, nil

View File

@ -18,6 +18,7 @@ func Up_20211116184030(tx *sql.Tx) error {
ADD COLUMN query mediumtext NOT NULL,
ADD COLUMN description mediumtext NOT NULL,
ADD COLUMN author_id int(10) unsigned DEFAULT NULL,
ADD COLUMN platforms VARCHAR(255) NOT NULL DEFAULT '',
ADD KEY idx_policies_author_id (author_id),
ADD KEY idx_policies_team_id (team_id),

View File

@ -7,8 +7,10 @@ import (
"strings"
"time"
"github.com/doug-martin/goqu/v9"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/go-kit/kit/log/level"
"github.com/jmoiron/sqlx"
)
@ -23,8 +25,8 @@ func (ds *Datastore) NewGlobalPolicy(ctx context.Context, authorID *uint, args f
args.Description = q.Description
}
res, err := ds.writer.ExecContext(ctx,
`INSERT INTO policies (name, query, description, resolution, author_id) VALUES (?, ?, ?, ?, ?)`,
args.Name, args.Query, args.Description, args.Resolution, authorID,
`INSERT INTO policies (name, query, description, resolution, author_id, platforms) VALUES (?, ?, ?, ?, ?, ?)`,
args.Name, args.Query, args.Description, args.Resolution, authorID, args.Platforms,
)
switch {
case err == nil:
@ -74,10 +76,10 @@ func policyDB(ctx context.Context, q sqlx.QueryerContext, id uint, teamID *uint)
func (ds *Datastore) SavePolicy(ctx context.Context, p *fleet.Policy) error {
sql := `
UPDATE policies
SET name = ?, query = ?, description = ?, resolution = ?
SET name = ?, query = ?, description = ?, resolution = ?, platforms = ?
WHERE id = ?
`
result, err := ds.writer.ExecContext(ctx, sql, p.Name, p.Query, p.Description, p.Resolution, p.ID)
result, err := ds.writer.ExecContext(ctx, sql, p.Name, p.Query, p.Description, p.Resolution, p.Platforms, p.ID)
if err != nil {
return ctxerr.Wrap(ctx, err, "updating policy")
}
@ -212,44 +214,46 @@ func deletePolicyDB(ctx context.Context, q sqlx.ExtContext, ids []uint, teamID *
return ids, nil
}
// PolicyQueriesForHost returns the policy queries that are to be executed on the given host.
func (ds *Datastore) PolicyQueriesForHost(ctx context.Context, host *fleet.Host) (map[string]string, error) {
var globalRows, teamRows []struct {
Id string `db:"id"`
var rows []struct {
ID string `db:"id"`
Query string `db:"query"`
}
err := sqlx.SelectContext(
ctx,
ds.reader,
&globalRows,
`SELECT p.id, p.query FROM policies p WHERE team_id is NULL`,
if host.FleetPlatform() == "" {
// We log to help troubleshooting in case this happens, as the host
// won't be receiving any policies targeted for specific platforms.
level.Error(ds.logger).Log("err", fmt.Sprintf("host %d with empty platform", host.ID))
}
q := dialect.From("policies").Select(
goqu.I("id"),
goqu.I("query"),
).Where(
goqu.And(
goqu.Or(
goqu.I("platforms").Eq(""),
goqu.L("FIND_IN_SET(?, ?)",
host.FleetPlatform(),
goqu.I("platforms"),
).Neq(0),
),
goqu.Or(
goqu.I("team_id").IsNull(), // global policies
goqu.I("team_id").Eq(host.TeamID), // team policies
),
),
)
sql, args, err := q.ToSQL()
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "selecting policies sql build")
}
if err := sqlx.SelectContext(ctx, ds.reader, &rows, sql, args...); err != nil {
return nil, ctxerr.Wrap(ctx, err, "selecting policies for host")
}
results := map[string]string{}
if host.TeamID != nil {
err := sqlx.SelectContext(
ctx,
ds.reader,
&teamRows,
`SELECT p.id, p.query FROM policies p WHERE team_id = ?`,
*host.TeamID,
)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "selecting policies for host in team")
}
results := make(map[string]string)
for _, row := range rows {
results[row.ID] = row.Query
}
for _, row := range globalRows {
results[row.Id] = row.Query
}
for _, row := range teamRows {
results[row.Id] = row.Query
}
return results, nil
}
@ -264,8 +268,8 @@ func (ds *Datastore) NewTeamPolicy(ctx context.Context, teamID uint, authorID *u
args.Description = q.Description
}
res, err := ds.writer.ExecContext(ctx,
`INSERT INTO policies (name, query, description, team_id, resolution, author_id) VALUES (?, ?, ?, ?, ?, ?)`,
args.Name, args.Query, args.Description, teamID, args.Resolution, authorID)
`INSERT INTO policies (name, query, description, team_id, resolution, author_id, platforms) VALUES (?, ?, ?, ?, ?, ?, ?)`,
args.Name, args.Query, args.Description, teamID, args.Resolution, authorID, args.Platforms)
switch {
case err == nil:
// OK
@ -307,19 +311,21 @@ func (ds *Datastore) ApplyPolicySpecs(ctx context.Context, authorID uint, specs
description,
author_id,
resolution,
team_id
) VALUES ( ?, ?, ?, ?, ?, (SELECT IFNULL(MIN(id), NULL) FROM teams WHERE name = ?) )
team_id,
platforms
) VALUES ( ?, ?, ?, ?, ?, (SELECT IFNULL(MIN(id), NULL) FROM teams WHERE name = ?), ? )
ON DUPLICATE KEY UPDATE
name = VALUES(name),
query = VALUES(query),
description = VALUES(description),
author_id = VALUES(author_id),
resolution = VALUES(resolution),
team_id = VALUES(team_id)
team_id = VALUES(team_id),
platforms = VALUES(platforms)
`
for _, spec := range specs {
if _, err := tx.ExecContext(ctx,
sql, spec.Name, spec.Query, spec.Description, authorID, spec.Resolution, spec.Team,
sql, spec.Name, spec.Query, spec.Description, authorID, spec.Resolution, spec.Team, spec.Platforms,
); err != nil {
return ctxerr.Wrap(ctx, err, "exec ApplyPolicySpecs insert")
}

View File

@ -4,12 +4,16 @@ import (
"context"
"errors"
"fmt"
"sort"
"strconv"
"testing"
"time"
"github.com/fleetdm/fleet/v4/server"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/ptr"
"github.com/fleetdm/fleet/v4/server/test"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -28,6 +32,7 @@ func TestPolicies(t *testing.T) {
{"TeamPolicyLegacy", testTeamPolicyLegacy},
{"TeamPolicyProprietary", testTeamPolicyProprietary},
{"PolicyQueriesForHost", testPolicyQueriesForHost},
{"PolicyQueriesForHostPlatforms", testPolicyQueriesForHostPlatforms},
{"TeamPolicyTransfer", testTeamPolicyTransfer},
{"ApplyPolicySpec", testApplyPolicySpec},
{"Save", testPoliciesSave},
@ -518,6 +523,244 @@ func testTeamPolicyProprietary(t *testing.T, ds *Datastore) {
require.Equal(t, user1.ID, *team2Policies[0].AuthorID)
}
func newTestHostWithPlatform(t *testing.T, ds *Datastore, hostname, platform string, teamID *uint) *fleet.Host {
nodeKey, err := server.GenerateRandomText(32)
require.NoError(t, err)
host, err := ds.NewHost(context.Background(), &fleet.Host{
OsqueryHostID: uuid.NewString(),
DetailUpdatedAt: time.Now(),
LabelUpdatedAt: time.Now(),
PolicyUpdatedAt: time.Now(),
SeenTime: time.Now(),
NodeKey: nodeKey,
UUID: uuid.NewString(),
Hostname: hostname,
Platform: platform,
})
require.NoError(t, err)
if teamID != nil {
err := ds.AddHostsToTeam(context.Background(), teamID, []uint{host.ID})
require.NoError(t, err)
host, err = ds.Host(context.Background(), host.ID, false)
require.NoError(t, err)
}
return host
}
func newTestPolicy(t *testing.T, ds *Datastore, user *fleet.User, name, platforms string, teamID *uint) *fleet.Policy {
query := fmt.Sprintf("select %s;", name)
if teamID == nil {
gp, err := ds.NewGlobalPolicy(context.Background(), &user.ID, fleet.PolicyPayload{
Name: name,
Query: query,
Platforms: platforms,
})
require.NoError(t, err)
return gp
}
tp, err := ds.NewTeamPolicy(context.Background(), *teamID, &user.ID, fleet.PolicyPayload{
Name: name,
Query: query,
Platforms: platforms,
})
require.NoError(t, err)
return tp
}
type expectedPolicyResults struct {
policyQueries map[string]string
hostPolicies []*fleet.HostPolicy
}
func expectedPolicyQueries(policies ...*fleet.Policy) expectedPolicyResults {
queries := make(map[string]string)
for _, policy := range policies {
queries[strconv.Itoa(int(policy.ID))] = policy.Query
}
hostPolicies := make([]*fleet.HostPolicy, len(policies))
for i := range policies {
hostPolicies[i] = &fleet.HostPolicy{
PolicyData: policies[i].PolicyData,
}
}
return expectedPolicyResults{
policyQueries: queries,
hostPolicies: hostPolicies,
}
}
func testPolicyQueriesForHostPlatforms(t *testing.T, ds *Datastore) {
user1 := test.NewUser(t, ds, "Alice", "alice@example.com", true)
team1, err := ds.NewTeam(context.Background(), &fleet.Team{Name: "team1"})
require.NoError(t, err)
team2, err := ds.NewTeam(context.Background(), &fleet.Team{Name: "team2"})
require.NoError(t, err)
// Global hosts:
var global *uint
host1GlobalUbuntu := newTestHostWithPlatform(t, ds, "host1_global_ubuntu", "ubuntu", global)
host2GlobalDarwin := newTestHostWithPlatform(t, ds, "host2_global_darwin", "darwin", global)
host3GlobalWindows := newTestHostWithPlatform(t, ds, "host3_global_windows", "windows", global)
host4GlobalEmpty := newTestHostWithPlatform(t, ds, "host4_global_empty_platform", "", global)
// team1 hosts:
host1t1Rhel := newTestHostWithPlatform(t, ds, "host1_team1_ubuntu", "rhel", &team1.ID)
host2t1Darwin := newTestHostWithPlatform(t, ds, "host2_team1_darwin", "darwin", &team1.ID)
host3t1Windows := newTestHostWithPlatform(t, ds, "host3_team1_windows", "windows", &team1.ID)
host4t1Empty := newTestHostWithPlatform(t, ds, "host4_team1_empty_platform", "", &team1.ID)
// team2 hosts
host1t2Debian := newTestHostWithPlatform(t, ds, "host1_team2_ubuntu", "debian", &team2.ID)
host2t2Darwin := newTestHostWithPlatform(t, ds, "host2_team2_darwin", "darwin", &team2.ID)
host3t2Windows := newTestHostWithPlatform(t, ds, "host3_team2_windows", "windows", &team2.ID)
host4t2Empty := newTestHostWithPlatform(t, ds, "host4_team2_empty_platform", "", &team2.ID)
// Global policies:
policy1GlobalLinuxDarwin := newTestPolicy(t, ds, user1, "policy1_global_linux_darwin", "linux,darwin", global)
policy2GlobalWindows := newTestPolicy(t, ds, user1, "policy2_global_windows", "windows", global)
policy3GlobalAll := newTestPolicy(t, ds, user1, "policy3_global_all", "", global)
// Team1 policies:
policy1t1Darwin := newTestPolicy(t, ds, user1, "policy1_team1_darwin", "darwin", &team1.ID)
policy2t1Windows := newTestPolicy(t, ds, user1, "policy2_team1_windows", "windows", &team1.ID)
policy3t1All := newTestPolicy(t, ds, user1, "policy3_team1_all", "", &team1.ID)
// Team2 policies:
policy1t2LinuxDarwin := newTestPolicy(t, ds, user1, "policy1_team2_linux_darwin", "linux,darwin", &team2.ID)
policy2t2Windows := newTestPolicy(t, ds, user1, "policy2_team2_windows", "windows", &team2.ID)
policy3t2All1 := newTestPolicy(t, ds, user1, "policy3_team2_all1", "linux,darwin,windows", &team2.ID)
policy4t2All2 := newTestPolicy(t, ds, user1, "policy4_team2_all2", "", &team2.ID)
for _, tc := range []struct {
host *fleet.Host
expectedPolicies expectedPolicyResults
}{
{
host: host1GlobalUbuntu,
expectedPolicies: expectedPolicyQueries(
policy1GlobalLinuxDarwin,
policy3GlobalAll,
),
},
{
host: host2GlobalDarwin,
expectedPolicies: expectedPolicyQueries(
policy1GlobalLinuxDarwin,
policy3GlobalAll,
),
},
{
host: host3GlobalWindows,
expectedPolicies: expectedPolicyQueries(
policy2GlobalWindows,
policy3GlobalAll,
),
},
{
host: host4GlobalEmpty,
expectedPolicies: expectedPolicyQueries(
policy3GlobalAll,
),
},
{
host: host1t1Rhel,
expectedPolicies: expectedPolicyQueries(
policy1GlobalLinuxDarwin,
policy3GlobalAll,
policy3t1All,
),
},
{
host: host2t1Darwin,
expectedPolicies: expectedPolicyQueries(
policy1GlobalLinuxDarwin,
policy3GlobalAll,
policy3t1All,
policy1t1Darwin,
),
},
{
host: host3t1Windows,
expectedPolicies: expectedPolicyQueries(
policy2GlobalWindows,
policy3GlobalAll,
policy3t1All,
policy2t1Windows,
),
},
{
host: host4t1Empty,
expectedPolicies: expectedPolicyQueries(
policy3GlobalAll,
policy3t1All,
),
},
{
host: host1t2Debian,
expectedPolicies: expectedPolicyQueries(
policy1GlobalLinuxDarwin,
policy3GlobalAll,
policy1t2LinuxDarwin,
policy3t2All1,
policy4t2All2,
),
},
{
host: host2t2Darwin,
expectedPolicies: expectedPolicyQueries(
policy1GlobalLinuxDarwin,
policy3GlobalAll,
policy1t2LinuxDarwin,
policy3t2All1,
policy4t2All2,
),
},
{
host: host3t2Windows,
expectedPolicies: expectedPolicyQueries(
policy2GlobalWindows,
policy3GlobalAll,
policy2t2Windows,
policy3t2All1,
policy4t2All2,
),
},
{
host: host4t2Empty,
expectedPolicies: expectedPolicyQueries(
policy3GlobalAll,
policy4t2All2,
),
},
} {
t.Run(tc.host.Hostname, func(t *testing.T) {
// PolicyQueriesForHost is the endpoint used by osquery agents when they check in.
queries, err := ds.PolicyQueriesForHost(context.Background(), tc.host)
require.NoError(t, err)
require.Equal(t, tc.expectedPolicies.policyQueries, queries)
// ListPoliciesForHost is the endpoint used by fleet UI/API clients.
hostPolicies, err := ds.ListPoliciesForHost(context.Background(), tc.host)
require.NoError(t, err)
sort.Slice(hostPolicies, func(i, j int) bool {
return hostPolicies[i].ID < hostPolicies[j].ID
})
sort.Slice(tc.expectedPolicies.hostPolicies, func(i, j int) bool {
return tc.expectedPolicies.hostPolicies[i].ID < tc.expectedPolicies.hostPolicies[j].ID
})
require.Equal(t, tc.expectedPolicies.hostPolicies, hostPolicies)
})
}
}
func testPolicyQueriesForHost(t *testing.T, ds *Datastore) {
user1 := test.NewUser(t, ds, "Alice", "alice@example.com", true)
team1, err := ds.NewTeam(context.Background(), &fleet.Team{Name: "team1"})
@ -590,7 +833,7 @@ func testPolicyQueriesForHost(t *testing.T, ds *Datastore) {
require.NoError(t, ds.RecordPolicyQueryExecutions(context.Background(), host1, map[uint]*bool{tp.ID: ptr.Bool(false), gp.ID: nil}, time.Now(), false))
policies, err := ds.ListPoliciesForHost(context.Background(), host1.ID)
policies, err := ds.ListPoliciesForHost(context.Background(), host1)
require.NoError(t, err)
require.Len(t, policies, 2)
@ -617,7 +860,7 @@ func testPolicyQueriesForHost(t *testing.T, ds *Datastore) {
assert.NotNil(t, policies[1].Resolution)
assert.Equal(t, "some other gp resolution", *policies[1].Resolution)
policies, err = ds.ListPoliciesForHost(context.Background(), host2.ID)
policies, err = ds.ListPoliciesForHost(context.Background(), host2)
require.NoError(t, err)
require.Len(t, policies, 1)
@ -627,7 +870,7 @@ func testPolicyQueriesForHost(t *testing.T, ds *Datastore) {
require.NoError(t, ds.RecordPolicyQueryExecutions(context.Background(), host2, map[uint]*bool{gp.ID: ptr.Bool(true)}, time.Now(), false))
policies, err = ds.ListPoliciesForHost(context.Background(), host2.ID)
policies, err = ds.ListPoliciesForHost(context.Background(), host2)
require.NoError(t, err)
require.Len(t, policies, 1)
@ -642,7 +885,7 @@ func testPolicyQueriesForHost(t *testing.T, ds *Datastore) {
require.NoError(t, err)
require.NoError(t, ds.RecordPolicyQueryExecutions(context.Background(), host2, map[uint]*bool{uint(id): nil}, time.Now(), false))
policies, err = ds.ListPoliciesForHost(context.Background(), host2.ID)
policies, err = ds.ListPoliciesForHost(context.Background(), host2)
require.NoError(t, err)
require.Len(t, policies, 2)
@ -742,6 +985,7 @@ func testApplyPolicySpec(t *testing.T, ds *Datastore) {
Description: "query1 desc",
Resolution: "some resolution",
Team: "",
Platforms: "",
},
{
Name: "query2",
@ -749,6 +993,7 @@ func testApplyPolicySpec(t *testing.T, ds *Datastore) {
Description: "query2 desc",
Resolution: "some other resolution",
Team: "team1",
Platforms: "darwin",
},
{
Name: "query3",
@ -756,6 +1001,7 @@ func testApplyPolicySpec(t *testing.T, ds *Datastore) {
Description: "query3 desc",
Resolution: "some other good resolution",
Team: "team1",
Platforms: "windows,linux",
},
}))
@ -769,6 +1015,7 @@ func testApplyPolicySpec(t *testing.T, ds *Datastore) {
assert.Equal(t, user1.ID, *policies[0].AuthorID)
require.NotNil(t, policies[0].Resolution)
assert.Equal(t, "some resolution", *policies[0].Resolution)
assert.Equal(t, "", policies[0].Platforms)
teamPolicies, err := ds.ListTeamPolicies(ctx, team1.ID)
require.NoError(t, err)
@ -780,6 +1027,7 @@ func testApplyPolicySpec(t *testing.T, ds *Datastore) {
assert.Equal(t, user1.ID, *teamPolicies[0].AuthorID)
require.NotNil(t, teamPolicies[0].Resolution)
assert.Equal(t, "some other resolution", *teamPolicies[0].Resolution)
assert.Equal(t, "darwin", teamPolicies[0].Platforms)
assert.Equal(t, "query3", teamPolicies[1].Name)
assert.Equal(t, "select 3;", teamPolicies[1].Query)
@ -788,6 +1036,7 @@ func testApplyPolicySpec(t *testing.T, ds *Datastore) {
assert.Equal(t, user1.ID, *teamPolicies[1].AuthorID)
require.NotNil(t, teamPolicies[1].Resolution)
assert.Equal(t, "some other good resolution", *teamPolicies[1].Resolution)
assert.Equal(t, "windows,linux", teamPolicies[1].Platforms)
// Make sure apply is idempotent
require.NoError(t, ds.ApplyPolicySpecs(ctx, user1.ID, []*fleet.PolicySpec{
@ -797,6 +1046,7 @@ func testApplyPolicySpec(t *testing.T, ds *Datastore) {
Description: "query1 desc",
Resolution: "some resolution",
Team: "",
Platforms: "",
},
{
Name: "query2",
@ -804,6 +1054,7 @@ func testApplyPolicySpec(t *testing.T, ds *Datastore) {
Description: "query2 desc",
Resolution: "some other resolution",
Team: "team1",
Platforms: "darwin",
},
{
Name: "query3",
@ -811,6 +1062,7 @@ func testApplyPolicySpec(t *testing.T, ds *Datastore) {
Description: "query3 desc",
Resolution: "some other good resolution",
Team: "team1",
Platforms: "windows,linux",
},
}))
@ -829,6 +1081,7 @@ func testApplyPolicySpec(t *testing.T, ds *Datastore) {
Description: "query1 desc updated",
Resolution: "some resolution updated",
Team: "",
Platforms: "linux",
},
{
Name: "query2",
@ -836,6 +1089,7 @@ func testApplyPolicySpec(t *testing.T, ds *Datastore) {
Description: "query2 desc updated",
Resolution: "some other resolution updated",
Team: "team1",
Platforms: "windows",
},
}))
policies, err = ds.ListGlobalPolicies(ctx)
@ -849,6 +1103,7 @@ func testApplyPolicySpec(t *testing.T, ds *Datastore) {
assert.Equal(t, user1.ID, *policies[0].AuthorID)
require.NotNil(t, policies[0].Resolution)
assert.Equal(t, "some resolution updated", *policies[0].Resolution)
assert.Equal(t, "linux", policies[0].Platforms)
teamPolicies, err = ds.ListTeamPolicies(ctx, team1.ID)
require.NoError(t, err)
@ -862,6 +1117,7 @@ func testApplyPolicySpec(t *testing.T, ds *Datastore) {
assert.Equal(t, team1.ID, *teamPolicies[0].TeamID)
require.NotNil(t, teamPolicies[0].Resolution)
assert.Equal(t, "some other resolution updated", *teamPolicies[0].Resolution)
assert.Equal(t, "windows", teamPolicies[0].Platforms)
// The following will "move" the policy from global to a team.
require.NoError(t, ds.ApplyPolicySpecs(ctx, user1.ID, []*fleet.PolicySpec{
@ -871,6 +1127,7 @@ func testApplyPolicySpec(t *testing.T, ds *Datastore) {
Description: "query1 desc team1",
Resolution: "some resolution team1",
Team: "team1",
Platforms: "linux",
},
}))
teamPolicies, err = ds.ListTeamPolicies(ctx, team1.ID)
@ -887,6 +1144,7 @@ func testApplyPolicySpec(t *testing.T, ds *Datastore) {
Query: "select 53;",
Description: "query2 desc global",
Resolution: "some resolution global",
Platforms: "windows",
},
}))
teamPolicies, err = ds.ListTeamPolicies(ctx, team1.ID)

View File

@ -381,6 +381,7 @@ CREATE TABLE `policies` (
`query` mediumtext NOT NULL,
`description` mediumtext NOT NULL,
`author_id` int(10) unsigned DEFAULT NULL,
`platforms` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_policies_unique_name` (`name`),
KEY `idx_policies_author_id` (`author_id`),

View File

@ -213,7 +213,7 @@ type Datastore interface {
CountHostsInLabel(ctx context.Context, filter TeamFilter, lid uint, opt HostListOptions) (int, error)
// ListPoliciesForHost lists the policies that a host will check and whether they are passing
ListPoliciesForHost(ctx context.Context, hid uint) ([]*HostPolicy, error)
ListPoliciesForHost(ctx context.Context, host *Host) ([]*HostPolicy, error)
///////////////////////////////////////////////////////////////////////////////
// TargetStore

View File

@ -74,24 +74,25 @@ type Host struct {
// OsqueryHostID is the key used in the request context that is
// used to retrieve host information. It is sent from osquery and may currently be
// a GUID or a Host Name, but in either case, it MUST be unique
OsqueryHostID string `json:"-" db:"osquery_host_id"`
DetailUpdatedAt time.Time `json:"detail_updated_at" db:"detail_updated_at"` // Time that the host details were last updated
LabelUpdatedAt time.Time `json:"label_updated_at" db:"label_updated_at"` // Time that the host labels were last updated
PolicyUpdatedAt time.Time `json:"policy_updated_at" db:"policy_updated_at"` // Time that the host policies were last updated
LastEnrolledAt time.Time `json:"last_enrolled_at" db:"last_enrolled_at"` // Time that the host last enrolled
SeenTime time.Time `json:"seen_time" db:"seen_time"` // Time that the host was last "seen"
RefetchRequested bool `json:"refetch_requested" db:"refetch_requested"`
NodeKey string `json:"-" db:"node_key"`
Hostname string `json:"hostname" db:"hostname"` // there is a fulltext index on this field
UUID string `json:"uuid" db:"uuid"` // there is a fulltext index on this field
Platform string `json:"platform"`
OsqueryVersion string `json:"osquery_version" db:"osquery_version"`
OSVersion string `json:"os_version" db:"os_version"`
Build string `json:"build"`
PlatformLike string `json:"platform_like" db:"platform_like"`
CodeName string `json:"code_name" db:"code_name"`
Uptime time.Duration `json:"uptime"`
Memory int64 `json:"memory" sql:"type:bigint" db:"memory"`
OsqueryHostID string `json:"-" db:"osquery_host_id"`
DetailUpdatedAt time.Time `json:"detail_updated_at" db:"detail_updated_at"` // Time that the host details were last updated
LabelUpdatedAt time.Time `json:"label_updated_at" db:"label_updated_at"` // Time that the host labels were last updated
PolicyUpdatedAt time.Time `json:"policy_updated_at" db:"policy_updated_at"` // Time that the host policies were last updated
LastEnrolledAt time.Time `json:"last_enrolled_at" db:"last_enrolled_at"` // Time that the host last enrolled
SeenTime time.Time `json:"seen_time" db:"seen_time"` // Time that the host was last "seen"
RefetchRequested bool `json:"refetch_requested" db:"refetch_requested"`
NodeKey string `json:"-" db:"node_key"`
Hostname string `json:"hostname" db:"hostname"` // there is a fulltext index on this field
UUID string `json:"uuid" db:"uuid"` // there is a fulltext index on this field
// Platform is the host's platform as defined by osquery's os_version.platform.
Platform string `json:"platform"`
OsqueryVersion string `json:"osquery_version" db:"osquery_version"`
OSVersion string `json:"os_version" db:"os_version"`
Build string `json:"build"`
PlatformLike string `json:"platform_like" db:"platform_like"`
CodeName string `json:"code_name" db:"code_name"`
Uptime time.Duration `json:"uptime"`
Memory int64 `json:"memory" sql:"type:bigint" db:"memory"`
// system_info fields
CPUType string `json:"cpu_type" db:"cpu_type"`
CPUSubtype string `json:"cpu_subtype" db:"cpu_subtype"`
@ -209,3 +210,25 @@ func (h *Host) IsNew(now time.Time) bool {
}
return false
}
// FleetPlatform returns the host's generic platform as supported by Fleet.
func (h *Host) FleetPlatform() string {
return PlatformFromHost(h.Platform)
}
// PlatformFromHost converts the given host platform into
// the generic platforms known by osquery
// https://osquery.readthedocs.io/en/stable/deployment/configuration/
// and supported by Fleet.
//
// Returns empty string if hostPlatform is unknnown.
func PlatformFromHost(hostPlatform string) string {
switch hostPlatform {
case "ubuntu", "rhel", "debian":
return "linux"
case "darwin", "windows":
return hostPlatform
default:
return ""
}
}

View File

@ -2,6 +2,7 @@ package fleet
import (
"errors"
"strings"
)
// PolicyPayload holds data for policy creation.
@ -20,15 +21,20 @@ type PolicyPayload struct {
Query string
// Description is the policy description text (ignored if QueryID != nil).
Description string
// Resolution indicate the steps needed to solve a failing policy.
// Resolution indicates the steps needed to solve a failing policy.
Resolution string
// Platforms is a comma-separated string to indicate the target platforms.
//
// Empty string targets all platforms.
Platforms string
}
var (
errPolicyEmptyName = errors.New("policy name cannot be empty")
errPolicyEmptyQuery = errors.New("policy query cannot be empty")
errPolicyIDAndQuerySet = errors.New("both fields \"queryID\" and \"query\" cannot be set")
errPolicyInvalidQuery = errors.New("invalid policy query")
errPolicyEmptyName = errors.New("policy name cannot be empty")
errPolicyEmptyQuery = errors.New("policy query cannot be empty")
errPolicyIDAndQuerySet = errors.New("both fields \"queryID\" and \"query\" cannot be set")
errPolicyInvalidQuery = errors.New("invalid policy query")
errPolicyInvalidPlatform = errors.New("invalid policy platform")
)
// Verify verifies the policy payload is valid.
@ -45,6 +51,9 @@ func (p PolicyPayload) Verify() error {
return err
}
}
if err := verifyPolicyPlatforms(p.Platforms); err != nil {
return err
}
return nil
}
@ -65,6 +74,21 @@ func verifyPolicyQuery(query string) error {
return nil
}
func verifyPolicyPlatforms(platforms string) error {
if platforms == "" {
return nil
}
for _, s := range strings.Split(platforms, ",") {
switch strings.TrimSpace(s) {
case "windows", "linux", "darwin":
// OK
default:
return errPolicyInvalidPlatform
}
}
return nil
}
// ModifyPolicyPayload holds data for policy modification.
type ModifyPolicyPayload struct {
// Name is the name of the policy.
@ -75,6 +99,10 @@ type ModifyPolicyPayload struct {
Description *string `json:"description"`
// Resolution indicate the steps needed to solve a failing policy.
Resolution *string `json:"resolution"`
// Platforms is a comma-separated string to indicate the target platforms.
//
// Empty string targets all platforms.
Platforms *string `json:"platforms"`
}
// Verify verifies the policy payload is valid.
@ -89,6 +117,11 @@ func (p ModifyPolicyPayload) Verify() error {
return err
}
}
if p.Platforms != nil {
if err := verifyPolicyPlatforms(*p.Platforms); err != nil {
return err
}
}
return nil
}
@ -115,6 +148,10 @@ type PolicyData struct {
TeamID *uint `json:"team_id" db:"team_id"`
// Resolution describes how to solve a failing policy.
Resolution *string `json:"resolution,omitempty" db:"resolution"`
// Platforms is a comma-separated string to indicate the target platforms.
//
// Empty string targets all platforms.
Platforms string `json:"platforms" db:"platforms"`
UpdateCreateTimestamps
}
@ -149,12 +186,23 @@ type HostPolicy struct {
}
// PolicySpec is used to hold policy data to apply policy specs.
//
// Policies are currently identified by name (unique).
type PolicySpec struct {
Name string `json:"name"`
Query string `json:"query"`
// Name is the name of the policy.
Name string `json:"name"`
// Query is the policy's SQL query.
Query string `json:"query"`
// Description describes the policy.
Description string `json:"description"`
Resolution string `json:"resolution,omitempty"`
Team string `json:"team,omitempty"`
// Resolution describes how to solve a failing policy.
Resolution string `json:"resolution,omitempty"`
// Team is the name of the team.
Team string `json:"team,omitempty"`
// Platforms is a comma-separated string to indicate the target platforms.
//
// Empty string targets all platforms.
Platforms string `json:"platforms,omitempty"`
}
// Verify verifies the policy data is valid.
@ -165,5 +213,8 @@ func (p PolicySpec) Verify() error {
if err := verifyPolicyQuery(p.Query); err != nil {
return err
}
if err := verifyPolicyPlatforms(p.Platforms); err != nil {
return err
}
return nil
}

View File

@ -175,7 +175,7 @@ type CountHostsFunc func(ctx context.Context, filter fleet.TeamFilter, opt fleet
type CountHostsInLabelFunc func(ctx context.Context, filter fleet.TeamFilter, lid uint, opt fleet.HostListOptions) (int, error)
type ListPoliciesForHostFunc func(ctx context.Context, hid uint) ([]*fleet.HostPolicy, error)
type ListPoliciesForHostFunc func(ctx context.Context, host *fleet.Host) ([]*fleet.HostPolicy, error)
type CountHostsInTargetsFunc func(ctx context.Context, filter fleet.TeamFilter, targets fleet.HostTargets, now time.Time) (fleet.TargetMetrics, error)
@ -1203,9 +1203,9 @@ func (s *DataStore) CountHostsInLabel(ctx context.Context, filter fleet.TeamFilt
return s.CountHostsInLabelFunc(ctx, filter, lid, opt)
}
func (s *DataStore) ListPoliciesForHost(ctx context.Context, hid uint) ([]*fleet.HostPolicy, error) {
func (s *DataStore) ListPoliciesForHost(ctx context.Context, host *fleet.Host) ([]*fleet.HostPolicy, error) {
s.ListPoliciesForHostFuncInvoked = true
return s.ListPoliciesForHostFunc(ctx, hid)
return s.ListPoliciesForHostFunc(ctx, host)
}
func (s *DataStore) CountHostsInTargets(ctx context.Context, filter fleet.TeamFilter, targets fleet.HostTargets, now time.Time) (fleet.TargetMetrics, error) {

View File

@ -21,6 +21,7 @@ type globalPolicyRequest struct {
Name string `json:"name"`
Description string `json:"description"`
Resolution string `json:"resolution"`
Platforms string `json:"platforms"`
}
type globalPolicyResponse struct {
@ -38,6 +39,7 @@ func globalPolicyEndpoint(ctx context.Context, request interface{}, svc fleet.Se
Name: req.Name,
Description: req.Description,
Resolution: req.Resolution,
Platforms: req.Platforms,
})
if err != nil {
return globalPolicyResponse{Err: err}, nil

View File

@ -767,6 +767,7 @@ func (s *integrationTestSuite) TestGlobalPoliciesProprietary() {
NodeKey: fmt.Sprintf("%s%d", t.Name(), i),
UUID: fmt.Sprintf("%s%d", t.Name(), i),
Hostname: fmt.Sprintf("%sfoo.local%d", t.Name(), i),
Platform: "darwin",
})
require.NoError(t, err)
}
@ -792,6 +793,7 @@ func (s *integrationTestSuite) TestGlobalPoliciesProprietary() {
Query: "select * from osquery;",
Description: "Some description",
Resolution: "some global resolution",
Platforms: "darwin",
}
gpResp := globalPolicyResponse{}
s.DoJSON("POST", "/api/v1/fleet/global/policies", gpParams, http.StatusOK, &gpResp)
@ -805,6 +807,7 @@ func (s *integrationTestSuite) TestGlobalPoliciesProprietary() {
assert.NotNil(t, gpResp.Policy.AuthorID)
assert.Equal(t, "Test Name admin1@example.com", gpResp.Policy.AuthorName)
assert.Equal(t, "admin1@example.com", gpResp.Policy.AuthorEmail)
assert.Equal(t, "darwin", gpResp.Policy.Platforms)
mgpParams := modifyGlobalPolicyRequest{
ModifyPolicyPayload: fleet.ModifyPolicyPayload{
@ -812,6 +815,7 @@ func (s *integrationTestSuite) TestGlobalPoliciesProprietary() {
Query: ptr.String("select * from osquery_info;"),
Description: ptr.String("Some description updated"),
Resolution: ptr.String("some global resolution updated"),
Platforms: ptr.String(""),
},
}
mgpResp := modifyGlobalPolicyResponse{}
@ -822,6 +826,7 @@ func (s *integrationTestSuite) TestGlobalPoliciesProprietary() {
assert.Equal(t, "Some description updated", mgpResp.Policy.Description)
require.NotNil(t, mgpResp.Policy.Resolution)
assert.Equal(t, "some global resolution updated", *mgpResp.Policy.Resolution)
assert.Equal(t, "", mgpResp.Policy.Platforms)
ggpResp := getPolicyByIDResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/v1/fleet/global/policies/%d", gpResp.Policy.ID), getPolicyByIDRequest{}, http.StatusOK, &ggpResp)
@ -831,6 +836,7 @@ func (s *integrationTestSuite) TestGlobalPoliciesProprietary() {
assert.Equal(t, "Some description updated", ggpResp.Policy.Description)
require.NotNil(t, ggpResp.Policy.Resolution)
assert.Equal(t, "some global resolution updated", *ggpResp.Policy.Resolution)
assert.Equal(t, "", mgpResp.Policy.Platforms)
policiesResponse := listGlobalPoliciesResponse{}
s.DoJSON("GET", "/api/v1/fleet/global/policies", nil, http.StatusOK, &policiesResponse)
@ -840,6 +846,7 @@ func (s *integrationTestSuite) TestGlobalPoliciesProprietary() {
assert.Equal(t, "Some description updated", policiesResponse.Policies[0].Description)
require.NotNil(t, policiesResponse.Policies[0].Resolution)
assert.Equal(t, "some global resolution updated", *policiesResponse.Policies[0].Resolution)
assert.Equal(t, "", policiesResponse.Policies[0].Platforms)
listHostsURL := fmt.Sprintf("/api/v1/fleet/hosts?policy_id=%d", policiesResponse.Policies[0].ID)
listHostsResp := listHostsResponse{}
@ -890,6 +897,7 @@ func (s *integrationTestSuite) TestTeamPoliciesProprietary() {
NodeKey: fmt.Sprintf("%s%d", t.Name(), i),
UUID: fmt.Sprintf("%s%d", t.Name(), i),
Hostname: fmt.Sprintf("%sfoo.local%d", t.Name(), i),
Platform: "darwin",
})
require.NoError(t, err)
hosts[i] = h.ID
@ -902,6 +910,7 @@ func (s *integrationTestSuite) TestTeamPoliciesProprietary() {
Query: "select * from osquery;",
Description: "Some description",
Resolution: "some team resolution",
Platforms: "darwin",
}
tpResp := teamPolicyResponse{}
s.DoJSON("POST", fmt.Sprintf("/api/v1/fleet/teams/%d/policies", team1.ID), tpParams, http.StatusOK, &tpResp)
@ -922,6 +931,7 @@ func (s *integrationTestSuite) TestTeamPoliciesProprietary() {
Query: ptr.String("select * from osquery_info;"),
Description: ptr.String("Some description updated"),
Resolution: ptr.String("some team resolution updated"),
Platforms: ptr.String("darwin,windows"),
},
}
mtpResp := modifyTeamPolicyResponse{}
@ -932,6 +942,7 @@ func (s *integrationTestSuite) TestTeamPoliciesProprietary() {
assert.Equal(t, "Some description updated", mtpResp.Policy.Description)
require.NotNil(t, mtpResp.Policy.Resolution)
assert.Equal(t, "some team resolution updated", *mtpResp.Policy.Resolution)
assert.Equal(t, "darwin,windows", mtpResp.Policy.Platforms)
gtpResp := getPolicyByIDResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/v1/fleet/teams/%d/policies/%d", team1.ID, tpResp.Policy.ID), getPolicyByIDRequest{}, http.StatusOK, &gtpResp)
@ -941,6 +952,7 @@ func (s *integrationTestSuite) TestTeamPoliciesProprietary() {
assert.Equal(t, "Some description updated", gtpResp.Policy.Description)
require.NotNil(t, gtpResp.Policy.Resolution)
assert.Equal(t, "some team resolution updated", *gtpResp.Policy.Resolution)
assert.Equal(t, "darwin,windows", gtpResp.Policy.Platforms)
policiesResponse := listTeamPoliciesResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/v1/fleet/teams/%d/policies", team1.ID), nil, http.StatusOK, &policiesResponse)
@ -950,6 +962,7 @@ func (s *integrationTestSuite) TestTeamPoliciesProprietary() {
assert.Equal(t, "Some description updated", policiesResponse.Policies[0].Description)
require.NotNil(t, policiesResponse.Policies[0].Resolution)
assert.Equal(t, "some team resolution updated", *policiesResponse.Policies[0].Resolution)
assert.Equal(t, "darwin,windows", policiesResponse.Policies[0].Platforms)
listHostsURL := fmt.Sprintf("/api/v1/fleet/hosts?policy_id=%d", policiesResponse.Policies[0].ID)
listHostsResp := listHostsResponse{}
@ -1019,6 +1032,7 @@ func (s *integrationTestSuite) TestTeamPoliciesProprietaryInvalid() {
queryID *uint
name string
query string
platforms string
}{
{
tname: "set both QueryID and Query",
@ -1045,12 +1059,20 @@ func (s *integrationTestSuite) TestTeamPoliciesProprietaryInvalid() {
name: "Invalid query",
query: "ATTACH 'foo' AS bar;",
},
{
tname: "Invalid platforms",
testUpdate: true,
name: "Some query",
query: "select 42;",
platforms: "linux1",
},
} {
t.Run(tc.tname, func(t *testing.T) {
tpReq := teamPolicyRequest{
QueryID: tc.queryID,
Name: tc.name,
Query: tc.query,
QueryID: tc.queryID,
Name: tc.name,
Query: tc.query,
Platforms: tc.platforms,
}
tpResp := teamPolicyResponse{}
s.DoJSON("POST", fmt.Sprintf("/api/v1/fleet/teams/%d/policies", team1.ID), tpReq, http.StatusBadRequest, &tpResp)
@ -1061,8 +1083,9 @@ func (s *integrationTestSuite) TestTeamPoliciesProprietaryInvalid() {
if testUpdate {
tpReq := modifyTeamPolicyRequest{
ModifyPolicyPayload: fleet.ModifyPolicyPayload{
Name: ptr.String(tc.name),
Query: ptr.String(tc.query),
Name: ptr.String(tc.name),
Query: ptr.String(tc.query),
Platforms: ptr.String(tc.platforms),
},
}
tpResp := modifyTeamPolicyResponse{}
@ -1071,9 +1094,10 @@ func (s *integrationTestSuite) TestTeamPoliciesProprietaryInvalid() {
}
gpReq := globalPolicyRequest{
QueryID: tc.queryID,
Name: tc.name,
Query: tc.query,
QueryID: tc.queryID,
Name: tc.name,
Query: tc.query,
Platforms: tc.platforms,
}
gpResp := globalPolicyResponse{}
s.DoJSON("POST", "/api/v1/fleet/global/policies", gpReq, http.StatusBadRequest, &gpResp)
@ -1082,8 +1106,9 @@ func (s *integrationTestSuite) TestTeamPoliciesProprietaryInvalid() {
if testUpdate {
gpReq := modifyGlobalPolicyRequest{
ModifyPolicyPayload: fleet.ModifyPolicyPayload{
Name: ptr.String(tc.name),
Query: ptr.String(tc.query),
Name: ptr.String(tc.name),
Query: ptr.String(tc.query),
Platforms: ptr.String(tc.platforms),
},
}
gpResp := modifyGlobalPolicyResponse{}

View File

@ -61,7 +61,7 @@ func (svc Service) getHostDetails(ctx context.Context, host *fleet.Host) (*fleet
return nil, ctxerr.Wrap(ctx, err, "get packs for host")
}
policies, err := svc.ds.ListPoliciesForHost(ctx, host.ID)
policies, err := svc.ds.ListPoliciesForHost(ctx, host)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "get policies for host")
}

View File

@ -62,7 +62,7 @@ func TestHostDetails(t *testing.T) {
ds.LoadHostSoftwareFunc = func(ctx context.Context, host *fleet.Host) error {
return nil
}
ds.ListPoliciesForHostFunc = func(ctx context.Context, hid uint) ([]*fleet.HostPolicy, error) {
ds.ListPoliciesForHostFunc = func(ctx context.Context, host *fleet.Host) ([]*fleet.HostPolicy, error) {
return nil, nil
}
@ -228,7 +228,7 @@ func TestHostAuth(t *testing.T) {
ds.SaveHostFunc = func(ctx context.Context, host *fleet.Host) error {
return nil
}
ds.ListPoliciesForHostFunc = func(ctx context.Context, hid uint) ([]*fleet.HostPolicy, error) {
ds.ListPoliciesForHostFunc = func(ctx context.Context, host *fleet.Host) ([]*fleet.HostPolicy, error) {
return nil, nil
}
ds.DeleteHostsFunc = func(ctx context.Context, ids []uint) error {

View File

@ -23,6 +23,7 @@ type teamPolicyRequest struct {
Name string `json:"name"`
Description string `json:"description"`
Resolution string `json:"resolution"`
Platforms string `json:"platforms"`
}
type teamPolicyResponse struct {
@ -40,6 +41,7 @@ func teamPolicyEndpoint(ctx context.Context, request interface{}, svc fleet.Serv
Query: req.Query,
Description: req.Description,
Resolution: req.Resolution,
Platforms: req.Platforms,
})
if err != nil {
return teamPolicyResponse{Err: err}, nil
@ -260,6 +262,9 @@ func (svc Service) modifyPolicy(ctx context.Context, teamID *uint, id uint, p fl
if p.Resolution != nil {
policy.Resolution = p.Resolution
}
if p.Platforms != nil {
policy.Platforms = *p.Platforms
}
logging.WithExtras(ctx, "name", policy.Name, "sql", policy.Query)
err = svc.ds.SavePolicy(ctx, policy)