Add ability to mark policy as critical. (#8896)
This commit is contained in:
@@ -0,0 +1 @@
|
||||
* Add ability to mark policy as critical.
|
||||
@@ -62,7 +62,8 @@
|
||||
"resolution": "Some resolution",
|
||||
"team_id": 1,
|
||||
"updated_at": "0001-01-01T00:00:00Z",
|
||||
"created_at": "0001-01-01T00:00:00Z"
|
||||
"created_at": "0001-01-01T00:00:00Z",
|
||||
"critical": false
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
@@ -76,7 +77,8 @@
|
||||
"response": "fails",
|
||||
"team_id": null,
|
||||
"updated_at": "0001-01-01T00:00:00Z",
|
||||
"created_at": "0001-01-01T00:00:00Z"
|
||||
"created_at": "0001-01-01T00:00:00Z",
|
||||
"critical": false
|
||||
}
|
||||
],
|
||||
"status": "offline",
|
||||
|
||||
@@ -51,6 +51,7 @@ spec:
|
||||
team_id: 1
|
||||
created_at: "0001-01-01T00:00:00Z"
|
||||
updated_at: "0001-01-01T00:00:00Z"
|
||||
critical: false
|
||||
- author_email: "alice@example.com"
|
||||
author_id: 1
|
||||
author_name: Alice
|
||||
@@ -63,6 +64,7 @@ spec:
|
||||
team_id: null
|
||||
created_at: "0001-01-01T00:00:00Z"
|
||||
updated_at: "0001-01-01T00:00:00Z"
|
||||
critical: false
|
||||
policy_updated_at: "0001-01-01T00:00:00Z"
|
||||
public_ip: ""
|
||||
primary_ip: ""
|
||||
|
||||
@@ -2128,7 +2128,8 @@ Returns the information of the specified host.
|
||||
"description": "this is a query",
|
||||
"resolution": "fix with these steps...",
|
||||
"platform": "windows,linux",
|
||||
"response": "pass"
|
||||
"response": "pass",
|
||||
"critical": false
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
@@ -2137,7 +2138,8 @@ Returns the information of the specified host.
|
||||
"description": "this is another query",
|
||||
"resolution": "fix with these other steps...",
|
||||
"platform": "darwin",
|
||||
"response": "fail"
|
||||
"response": "fail",
|
||||
"critical": false
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
@@ -2146,7 +2148,8 @@ Returns the information of the specified host.
|
||||
"description": "",
|
||||
"resolution": "",
|
||||
"platform": "",
|
||||
"response": ""
|
||||
"response": "",
|
||||
"critical": false
|
||||
}
|
||||
],
|
||||
"issues": {
|
||||
@@ -2319,8 +2322,9 @@ Returns the information of the host specified using the `uuid`, `osquery_host_id
|
||||
"platform": "darwin,linux",
|
||||
"created_at": "2022-09-02T18:52:19Z",
|
||||
"updated_at": "2022-09-02T18:52:19Z",
|
||||
"response": "fail"
|
||||
},
|
||||
"response": "fail",
|
||||
"critical": false
|
||||
}
|
||||
],
|
||||
"batteries": [
|
||||
{
|
||||
@@ -3370,6 +3374,7 @@ For example, a policy might ask “Is Gatekeeper enabled on macOS devices?“ Th
|
||||
"name": "Gatekeeper enabled",
|
||||
"query": "SELECT 1 FROM gatekeeper WHERE assessments_enabled = 1;",
|
||||
"description": "Checks if gatekeeper is enabled on macOS devices",
|
||||
"critical": false,
|
||||
"author_id": 42,
|
||||
"author_name": "John",
|
||||
"author_email": "john@example.com",
|
||||
@@ -3386,6 +3391,7 @@ For example, a policy might ask “Is Gatekeeper enabled on macOS devices?“ Th
|
||||
"name": "Windows machines with encrypted hard disks",
|
||||
"query": "SELECT 1 FROM bitlocker_info WHERE protection_status = 1;",
|
||||
"description": "Checks if the hard disk is encrypted on Windows devices",
|
||||
"critical": true,
|
||||
"author_id": 43,
|
||||
"author_name": "Alice",
|
||||
"author_email": "alice@example.com",
|
||||
@@ -3426,6 +3432,7 @@ For example, a policy might ask “Is Gatekeeper enabled on macOS devices?“ Th
|
||||
"name": "Gatekeeper enabled",
|
||||
"query": "SELECT 1 FROM gatekeeper WHERE assessments_enabled = 1;",
|
||||
"description": "Checks if gatekeeper is enabled on macOS devices",
|
||||
"critical": false,
|
||||
"author_id": 42,
|
||||
"author_name": "John",
|
||||
"author_email": "john@example.com",
|
||||
@@ -3461,6 +3468,7 @@ An error is returned if both "query" and "query_id" are set on the request.
|
||||
| resolution | string | body | The resolution steps for the policy. |
|
||||
| query_id | integer | body | An existing query's ID (legacy). |
|
||||
| platform | string | body | Comma-separated target platforms, currently supported values are "windows", "linux", "darwin". The default, an empty string means target all platforms. |
|
||||
| critical | boolean | body | Mark policy as critical/high impact. |
|
||||
|
||||
Either `query` or `query_id` must be provided.
|
||||
|
||||
@@ -3476,7 +3484,8 @@ Either `query` or `query_id` must be provided.
|
||||
"query": "SELECT 1 FROM gatekeeper WHERE assessments_enabled = 1;",
|
||||
"description": "Checks if gatekeeper is enabled on macOS devices",
|
||||
"resolution": "Resolution steps",
|
||||
"platform": "darwin"
|
||||
"platform": "darwin",
|
||||
"critical": true
|
||||
}
|
||||
```
|
||||
|
||||
@@ -3491,6 +3500,7 @@ 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",
|
||||
"critical": true,
|
||||
"author_id": 42,
|
||||
"author_name": "John",
|
||||
"author_email": "john@example.com",
|
||||
@@ -3530,6 +3540,7 @@ 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",
|
||||
"critical": true,
|
||||
"author_id": 42,
|
||||
"author_name": "John",
|
||||
"author_email": "john@example.com",
|
||||
@@ -3590,6 +3601,7 @@ Where `query_id` references an existing `query`.
|
||||
| description | string | body | The query's description. |
|
||||
| resolution | string | body | The resolution steps for the policy. |
|
||||
| platform | string | body | Comma-separated target platforms, currently supported values are "windows", "linux", "darwin". The default, an empty string means target all platforms. |
|
||||
| critical | boolean | body | Mark policy as critical/high impact. |
|
||||
|
||||
#### Example Edit Policy
|
||||
|
||||
@@ -3602,6 +3614,7 @@ 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",
|
||||
"critical": true,
|
||||
"resolution": "Resolution steps",
|
||||
"platform": "darwin"
|
||||
}
|
||||
@@ -3618,6 +3631,7 @@ 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",
|
||||
"critical": true,
|
||||
"author_id": 43,
|
||||
"author_name": "John",
|
||||
"author_email": "john@example.com",
|
||||
@@ -3654,7 +3668,7 @@ Team policies work the same as policies, but at the team level.
|
||||
|
||||
| Name | Type | In | Description |
|
||||
| ------------------ | ------- | ---- | ------------------------------------------------------------------------------------------------------------- |
|
||||
| id | integer | url | Required. Defines what team id to operate on |
|
||||
| id | integer | url | Required. Defines what team id to operate on |
|
||||
|
||||
#### Example
|
||||
|
||||
@@ -3672,6 +3686,7 @@ Team policies work the same as policies, but at the team level.
|
||||
"name": "Gatekeeper enabled",
|
||||
"query": "SELECT 1 FROM gatekeeper WHERE assessments_enabled = 1;",
|
||||
"description": "Checks if gatekeeper is enabled on macOS devices",
|
||||
"critical": true,
|
||||
"author_id": 42,
|
||||
"author_name": "John",
|
||||
"author_email": "john@example.com",
|
||||
@@ -3688,6 +3703,7 @@ Team policies work the same as policies, but at the team level.
|
||||
"name": "Windows machines with encrypted hard disks",
|
||||
"query": "SELECT 1 FROM bitlocker_info WHERE protection_status = 1;",
|
||||
"description": "Checks if the hard disk is encrypted on Windows devices",
|
||||
"critical": false,
|
||||
"author_id": 43,
|
||||
"author_name": "Alice",
|
||||
"author_email": "alice@example.com",
|
||||
@@ -3706,6 +3722,7 @@ Team policies work the same as policies, but at the team level.
|
||||
"name": "Arbitrary Test Policy (all platforms) (all teams)",
|
||||
"query": "SELECT 1 FROM osquery_info WHERE 1=1;",
|
||||
"description": "If you're seeing this, mostly likely this is because someone is testing out failing policies in dogfood. You can ignore this.",
|
||||
"critical": true,
|
||||
"author_id": 77,
|
||||
"author_name": "Test Admin",
|
||||
"author_email": "test@admin.com",
|
||||
@@ -3747,6 +3764,7 @@ Team policies work the same as policies, but at the team level.
|
||||
"name": "Gatekeeper enabled",
|
||||
"query": "SELECT 1 FROM gatekeeper WHERE assessments_enabled = 1;",
|
||||
"description": "Checks if gatekeeper is enabled on macOS devices",
|
||||
"critical": true,
|
||||
"author_id": 42,
|
||||
"author_name": "John",
|
||||
"author_email": "john@example.com",
|
||||
@@ -3778,6 +3796,7 @@ The semantics for creating a team policy are the same as for global policies, se
|
||||
| resolution | string | body | The resolution steps for the policy. |
|
||||
| query_id | integer | body | An existing query's ID (legacy). |
|
||||
| platform | string | body | Comma-separated target platforms, currently supported values are "windows", "linux", "darwin". The default, an empty string means target all platforms. |
|
||||
| critical | boolean | body | Mark policy as critical/high impact. |
|
||||
|
||||
Either `query` or `query_id` must be provided.
|
||||
|
||||
@@ -3792,6 +3811,7 @@ 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",
|
||||
"critical": true,
|
||||
"resolution": "Resolution steps",
|
||||
"platform": "darwin"
|
||||
}
|
||||
@@ -3808,6 +3828,7 @@ 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",
|
||||
"critical": true,
|
||||
"author_id": 42,
|
||||
"author_name": "John",
|
||||
"author_email": "john@example.com",
|
||||
@@ -3870,6 +3891,7 @@ Either `query` or `query_id` must be provided.
|
||||
| description | string | body | The query's description. |
|
||||
| resolution | string | body | The resolution steps for the policy. |
|
||||
| platform | string | body | Comma-separated target platforms, currently supported values are "windows", "linux", "darwin". The default, an empty string means target all platforms. |
|
||||
| critical | boolean | body | Mark policy as critical/high impact. |
|
||||
|
||||
#### Example Edit Policy
|
||||
|
||||
@@ -3882,6 +3904,7 @@ 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",
|
||||
"critical": true,
|
||||
"resolution": "Resolution steps",
|
||||
"platform": "darwin"
|
||||
}
|
||||
@@ -3898,6 +3921,7 @@ 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",
|
||||
"critical": true,
|
||||
"author_id": 43,
|
||||
"author_name": "John",
|
||||
"author_email": "john@example.com",
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package tables
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func init() {
|
||||
MigrationClient.AddMigration(Up_20221130114928, Down_20221130114928)
|
||||
}
|
||||
|
||||
func Up_20221130114928(tx *sql.Tx) error {
|
||||
_, err := tx.Exec(`
|
||||
ALTER TABLE policies ADD COLUMN critical TINYINT(1) NOT NULL DEFAULT FALSE;
|
||||
`)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "adding column critical")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Down_20221130114928(*sql.Tx) error {
|
||||
return nil
|
||||
}
|
||||
@@ -16,6 +16,11 @@ import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
const policyCols = `
|
||||
p.id, p.team_id, p.resolution, p.name, p.query, p.description,
|
||||
p.author_id, p.platforms, p.created_at, p.updated_at, p.critical
|
||||
`
|
||||
|
||||
func (ds *Datastore) NewGlobalPolicy(ctx context.Context, authorID *uint, args fleet.PolicyPayload) (*fleet.Policy, error) {
|
||||
if args.QueryID != nil {
|
||||
q, err := ds.Query(ctx, *args.QueryID)
|
||||
@@ -27,8 +32,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, platforms) VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
args.Name, args.Query, args.Description, args.Resolution, authorID, args.Platform,
|
||||
`INSERT INTO policies (name, query, description, resolution, author_id, platforms, critical) VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
args.Name, args.Query, args.Description, args.Resolution, authorID, args.Platform, args.Critical,
|
||||
)
|
||||
switch {
|
||||
case err == nil:
|
||||
@@ -59,7 +64,7 @@ func policyDB(ctx context.Context, q sqlx.QueryerContext, id uint, teamID *uint)
|
||||
|
||||
var policy fleet.Policy
|
||||
err := sqlx.GetContext(ctx, q, &policy,
|
||||
fmt.Sprintf(`SELECT p.*,
|
||||
fmt.Sprintf(`SELECT `+policyCols+`,
|
||||
COALESCE(u.name, '<deleted>') AS author_name,
|
||||
COALESCE(u.email, '') AS author_email,
|
||||
(select count(*) from policy_membership where policy_id=p.id and passes=true) as passing_host_count,
|
||||
@@ -83,10 +88,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 = ?, platforms = ?
|
||||
SET name = ?, query = ?, description = ?, resolution = ?, platforms = ?, critical = ?
|
||||
WHERE id = ?
|
||||
`
|
||||
result, err := ds.writer.ExecContext(ctx, sql, p.Name, p.Query, p.Description, p.Resolution, p.Platform, p.ID)
|
||||
result, err := ds.writer.ExecContext(ctx, sql, p.Name, p.Query, p.Description, p.Resolution, p.Platform, p.Critical, p.ID)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "updating policy")
|
||||
}
|
||||
@@ -292,16 +297,7 @@ func listPoliciesDB(ctx context.Context, q sqlx.QueryerContext, teamID, countsFo
|
||||
ctx,
|
||||
q,
|
||||
&policies,
|
||||
fmt.Sprintf(`SELECT p.id,
|
||||
p.team_id,
|
||||
p.resolution,
|
||||
p.name,
|
||||
p.query,
|
||||
p.description,
|
||||
p.author_id,
|
||||
p.platforms,
|
||||
p.created_at,
|
||||
p.updated_at,
|
||||
fmt.Sprintf(`SELECT `+policyCols+`,
|
||||
COALESCE(u.name, '<deleted>') AS author_name,
|
||||
COALESCE(u.email, '') AS author_email,
|
||||
%s
|
||||
@@ -316,16 +312,7 @@ func listPoliciesDB(ctx context.Context, q sqlx.QueryerContext, teamID, countsFo
|
||||
}
|
||||
|
||||
func (ds *Datastore) PoliciesByID(ctx context.Context, ids []uint) (map[uint]*fleet.Policy, error) {
|
||||
sql := `SELECT p.id,
|
||||
p.team_id,
|
||||
p.resolution,
|
||||
p.name,
|
||||
p.query,
|
||||
p.description,
|
||||
p.author_id,
|
||||
p.platforms,
|
||||
p.created_at,
|
||||
p.updated_at,
|
||||
sql := `SELECT ` + policyCols + `,
|
||||
COALESCE(u.name, '<deleted>') AS author_name,
|
||||
COALESCE(u.email, '') AS author_email,
|
||||
(select count(*) from policy_membership where policy_id=p.id and passes=true) as passing_host_count,
|
||||
@@ -440,8 +427,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, platforms) VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
args.Name, args.Query, args.Description, teamID, args.Resolution, authorID, args.Platform)
|
||||
`INSERT INTO policies (name, query, description, team_id, resolution, author_id, platforms, critical) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
args.Name, args.Query, args.Description, teamID, args.Resolution, authorID, args.Platform, args.Critical)
|
||||
switch {
|
||||
case err == nil:
|
||||
// OK
|
||||
@@ -495,19 +482,21 @@ func (ds *Datastore) ApplyPolicySpecs(ctx context.Context, authorID uint, specs
|
||||
author_id,
|
||||
resolution,
|
||||
team_id,
|
||||
platforms
|
||||
) VALUES ( ?, ?, ?, ?, ?, (SELECT IFNULL(MIN(id), NULL) FROM teams WHERE name = ?), ? )
|
||||
platforms,
|
||||
critical
|
||||
) 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),
|
||||
platforms = VALUES(platforms)
|
||||
platforms = VALUES(platforms),
|
||||
critical = VALUES(critical)
|
||||
`
|
||||
for _, spec := range specs {
|
||||
res, err := tx.ExecContext(ctx,
|
||||
sql, spec.Name, spec.Query, spec.Description, authorID, spec.Resolution, spec.Team, spec.Platform,
|
||||
sql, spec.Name, spec.Query, spec.Description, authorID, spec.Resolution, spec.Team, spec.Platform, spec.Critical,
|
||||
)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "exec ApplyPolicySpecs insert")
|
||||
|
||||
@@ -1296,52 +1296,59 @@ func testPoliciesSave(t *testing.T, ds *Datastore) {
|
||||
var nfe *notFoundError
|
||||
require.True(t, errors.As(err, &nfe))
|
||||
|
||||
gp, err := ds.NewGlobalPolicy(ctx, &user1.ID, fleet.PolicyPayload{
|
||||
payload := fleet.PolicyPayload{
|
||||
Name: "global query",
|
||||
Query: "select 1;",
|
||||
Description: "global query desc",
|
||||
Resolution: "global query resolution",
|
||||
})
|
||||
}
|
||||
gp, err := ds.NewGlobalPolicy(ctx, &user1.ID, payload)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, gp.Name, payload.Name)
|
||||
require.Equal(t, gp.Query, payload.Query)
|
||||
require.Equal(t, gp.Description, payload.Description)
|
||||
require.Equal(t, *gp.Resolution, payload.Resolution)
|
||||
require.Equal(t, gp.Critical, payload.Critical)
|
||||
|
||||
tp1, err := ds.NewTeamPolicy(ctx, team1.ID, &user1.ID, fleet.PolicyPayload{
|
||||
payload = fleet.PolicyPayload{
|
||||
Name: "team1 query",
|
||||
Query: "select 2;",
|
||||
Description: "team1 query desc",
|
||||
Resolution: "team1 query resolution",
|
||||
})
|
||||
Critical: true,
|
||||
}
|
||||
tp1, err := ds.NewTeamPolicy(ctx, team1.ID, &user1.ID, payload)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tp1.Name, payload.Name)
|
||||
require.Equal(t, tp1.Query, payload.Query)
|
||||
require.Equal(t, tp1.Description, payload.Description)
|
||||
require.Equal(t, *tp1.Resolution, payload.Resolution)
|
||||
require.Equal(t, tp1.Critical, payload.Critical)
|
||||
|
||||
// Change name only of a global query.
|
||||
gp.Name = "global query updated"
|
||||
err = ds.SavePolicy(ctx, gp)
|
||||
gp2 := *gp
|
||||
gp2.Name = "global query updated"
|
||||
gp2.Critical = true
|
||||
err = ds.SavePolicy(ctx, &gp2)
|
||||
require.NoError(t, err)
|
||||
gp, err = ds.Policy(ctx, gp.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "global query updated", gp.Name)
|
||||
assert.Equal(t, "select 1;", gp.Query)
|
||||
assert.Equal(t, "global query desc", gp.Description)
|
||||
require.NotNil(t, gp.Resolution)
|
||||
assert.Equal(t, "global query resolution", *gp.Resolution)
|
||||
require.NotNil(t, gp.AuthorID)
|
||||
assert.Equal(t, user1.ID, *gp.AuthorID)
|
||||
gp2.UpdateCreateTimestamps = gp.UpdateCreateTimestamps
|
||||
require.Equal(t, &gp2, gp)
|
||||
|
||||
// Change name, query, description and resolution of a team policy.
|
||||
tp1.Name = "team1 query updated"
|
||||
tp1.Query = "select 12;"
|
||||
tp1.Description = "team1 query desc updated"
|
||||
tp1.Resolution = ptr.String("team1 query resolution updated")
|
||||
err = ds.SavePolicy(ctx, tp1)
|
||||
tp2 := *tp1
|
||||
tp2.Name = "team1 query updated"
|
||||
tp2.Query = "select 12;"
|
||||
tp2.Description = "team1 query desc updated"
|
||||
tp2.Resolution = ptr.String("team1 query resolution updated")
|
||||
tp2.Critical = false
|
||||
err = ds.SavePolicy(ctx, &tp2)
|
||||
require.NoError(t, err)
|
||||
tp1, err = ds.Policy(ctx, tp1.ID)
|
||||
tp2.UpdateCreateTimestamps = tp1.UpdateCreateTimestamps
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "team1 query updated", tp1.Name)
|
||||
assert.Equal(t, "select 12;", tp1.Query)
|
||||
assert.Equal(t, "team1 query desc updated", tp1.Description)
|
||||
require.NotNil(t, tp1.Resolution)
|
||||
assert.Equal(t, "team1 query resolution updated", *tp1.Resolution)
|
||||
require.NotNil(t, tp1.AuthorID)
|
||||
assert.Equal(t, user1.ID, *tp1.AuthorID)
|
||||
require.Equal(t, tp1, &tp2)
|
||||
}
|
||||
|
||||
func testPoliciesDelUser(t *testing.T, ds *Datastore) {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -19,6 +19,8 @@ type PolicyPayload struct {
|
||||
Name string
|
||||
// Query is the policy query (ignored if QueryID != nil).
|
||||
Query string
|
||||
// Critical marks the policy as high impact.
|
||||
Critical bool
|
||||
// Description is the policy description text (ignored if QueryID != nil).
|
||||
Description string
|
||||
// Resolution indicates the steps needed to solve a failing policy.
|
||||
@@ -106,6 +108,8 @@ type ModifyPolicyPayload struct {
|
||||
// Platform is a comma-separated string to indicate the target platforms.
|
||||
// If non-nil, empty string targets all platforms.
|
||||
Platform *string `json:"platform"`
|
||||
// Critical marks the policy as high impact.
|
||||
Critical *bool `json:"critical"`
|
||||
}
|
||||
|
||||
// Verify verifies the policy payload is valid.
|
||||
@@ -136,6 +140,8 @@ type PolicyData struct {
|
||||
Name string `json:"name" db:"name"`
|
||||
// Query is the actual query to run on the osquery agents.
|
||||
Query string `json:"query" db:"query"`
|
||||
// Critical marks the policy as high impact.
|
||||
Critical bool `json:"critical" db:"critical"`
|
||||
// Description describes the policy.
|
||||
Description string `json:"description" db:"description"`
|
||||
// AuthorID is the ID of the author of the policy.
|
||||
@@ -198,6 +204,8 @@ type PolicySpec struct {
|
||||
Query string `json:"query"`
|
||||
// Description describes the policy.
|
||||
Description string `json:"description"`
|
||||
// Critical marks the policy as high impact.
|
||||
Critical bool `json:"critical"`
|
||||
// Resolution describes how to solve a failing policy.
|
||||
Resolution string `json:"resolution,omitempty"`
|
||||
// Team is the name of the team.
|
||||
|
||||
@@ -321,6 +321,9 @@ func (svc *Service) modifyPolicy(ctx context.Context, teamID *uint, id uint, p f
|
||||
if p.Platform != nil {
|
||||
policy.Platform = *p.Platform
|
||||
}
|
||||
if p.Critical != nil {
|
||||
policy.Critical = *p.Critical
|
||||
}
|
||||
logging.WithExtras(ctx, "name", policy.Name, "sql", policy.Query)
|
||||
|
||||
err = svc.ds.SavePolicy(ctx, policy)
|
||||
|
||||
@@ -43,6 +43,7 @@ func TestTriggerFailingPoliciesWebhookBasic(t *testing.T) {
|
||||
TeamID: nil,
|
||||
Resolution: ptr.String("policy1 resolution"),
|
||||
Platform: "darwin",
|
||||
Critical: true,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -121,7 +122,8 @@ func TestTriggerFailingPoliciesWebhookBasic(t *testing.T) {
|
||||
"created_at": "0001-01-01T00:00:00Z",
|
||||
"updated_at": "0001-01-01T00:00:00Z",
|
||||
"passing_host_count": 0,
|
||||
"failing_host_count": 0
|
||||
"failing_host_count": 0,
|
||||
"critical": true
|
||||
},
|
||||
"hosts": [
|
||||
{
|
||||
@@ -304,7 +306,8 @@ func TestTriggerFailingPoliciesWebhookTeam(t *testing.T) {
|
||||
"created_at": "0001-01-01T00:00:00Z",
|
||||
"updated_at": "0001-01-01T00:00:00Z",
|
||||
"passing_host_count": 0,
|
||||
"failing_host_count": 0
|
||||
"failing_host_count": 0,
|
||||
"critical": false
|
||||
},
|
||||
"hosts": [
|
||||
{
|
||||
|
||||
+20
-15
@@ -73,7 +73,9 @@ This issue was created automatically by your Fleet Jira integration.
|
||||
)),
|
||||
|
||||
FailingPolicyDescription: template.Must(template.New("").Parse(
|
||||
`Hosts:
|
||||
`{{ if .PolicyCritical }}This policy is marked as *Critical* in Fleet.
|
||||
|
||||
{{ end }}Hosts:
|
||||
{{ $end := len .Hosts }}{{ if gt $end 50 }}{{ $end = 50 }}{{ end }}
|
||||
{{ range slice .Hosts 0 $end }}
|
||||
* [{{ .DisplayName }}|{{ $.FleetURL }}/hosts/{{ .ID }}]
|
||||
@@ -102,11 +104,12 @@ type jiraVulnTplArgs struct {
|
||||
}
|
||||
|
||||
type jiraFailingPoliciesTplArgs struct {
|
||||
FleetURL string
|
||||
PolicyID uint
|
||||
PolicyName string
|
||||
TeamID *uint
|
||||
Hosts []fleet.PolicySetHost
|
||||
FleetURL string
|
||||
PolicyID uint
|
||||
PolicyName string
|
||||
PolicyCritical bool
|
||||
TeamID *uint
|
||||
Hosts []fleet.PolicySetHost
|
||||
}
|
||||
|
||||
// JiraClient defines the method required for the client that makes API calls
|
||||
@@ -304,11 +307,12 @@ func (j *Jira) runVuln(ctx context.Context, cli JiraClient, args jiraArgs) error
|
||||
|
||||
func (j *Jira) runFailingPolicy(ctx context.Context, cli JiraClient, args jiraArgs) error {
|
||||
tplArgs := &jiraFailingPoliciesTplArgs{
|
||||
FleetURL: j.FleetURL,
|
||||
PolicyName: args.FailingPolicy.PolicyName,
|
||||
PolicyID: args.FailingPolicy.PolicyID,
|
||||
TeamID: args.FailingPolicy.TeamID,
|
||||
Hosts: args.FailingPolicy.Hosts,
|
||||
FleetURL: j.FleetURL,
|
||||
PolicyName: args.FailingPolicy.PolicyName,
|
||||
PolicyID: args.FailingPolicy.PolicyID,
|
||||
PolicyCritical: args.FailingPolicy.PolicyCritical,
|
||||
TeamID: args.FailingPolicy.TeamID,
|
||||
Hosts: args.FailingPolicy.Hosts,
|
||||
}
|
||||
|
||||
createdIssue, err := j.createTemplatedIssue(ctx, cli, jiraTemplates.FailingPolicySummary, jiraTemplates.FailingPolicyDescription, tplArgs)
|
||||
@@ -424,10 +428,11 @@ func QueueJiraFailingPolicyJob(ctx context.Context, ds fleet.Datastore, logger k
|
||||
level.Info(logger).Log(attrs...)
|
||||
|
||||
args := &failingPolicyArgs{
|
||||
PolicyID: policy.ID,
|
||||
PolicyName: policy.Name,
|
||||
Hosts: hosts,
|
||||
TeamID: policy.TeamID,
|
||||
PolicyID: policy.ID,
|
||||
PolicyName: policy.Name,
|
||||
PolicyCritical: policy.Critical,
|
||||
Hosts: hosts,
|
||||
TeamID: policy.TeamID,
|
||||
}
|
||||
job, err := QueueJob(ctx, ds, jiraName, jiraArgs{FailingPolicy: args})
|
||||
if err != nil {
|
||||
|
||||
@@ -311,10 +311,12 @@ func TestJiraQueueFailingPolicyJob(t *testing.T) {
|
||||
}
|
||||
|
||||
type mockJiraClient struct {
|
||||
opts externalsvc.JiraOptions
|
||||
opts externalsvc.JiraOptions
|
||||
issues []jira.Issue
|
||||
}
|
||||
|
||||
func (c *mockJiraClient) CreateJiraIssue(ctx context.Context, issue *jira.Issue) (*jira.Issue, error) {
|
||||
c.issues = append(c.issues, *issue)
|
||||
return &jira.Issue{}, nil
|
||||
}
|
||||
|
||||
@@ -377,6 +379,7 @@ func TestJiraRunClientUpdate(t *testing.T) {
|
||||
}
|
||||
|
||||
var projectKeys []string
|
||||
var clients []*mockJiraClient
|
||||
jiraJob := &Jira{
|
||||
FleetURL: "http://example.com",
|
||||
Datastore: ds,
|
||||
@@ -384,7 +387,9 @@ func TestJiraRunClientUpdate(t *testing.T) {
|
||||
NewClientFunc: func(opts *externalsvc.JiraOptions) (JiraClient, error) {
|
||||
// keep track of project keys received in calls to NewClientFunc
|
||||
projectKeys = append(projectKeys, opts.ProjectKey)
|
||||
return &mockJiraClient{opts: *opts}, nil
|
||||
client := &mockJiraClient{opts: *opts}
|
||||
clients = append(clients, client)
|
||||
return client, nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -398,7 +403,7 @@ func TestJiraRunClientUpdate(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// run it globally again - it will reuse the cached client
|
||||
err = jiraJob.Run(ctx, json.RawMessage(`{"failing_policy":{"policy_id": 1, "policy_name": "test-policy", "hosts": []}}`))
|
||||
err = jiraJob.Run(ctx, json.RawMessage(`{"failing_policy":{"policy_id": 1, "policy_name": "test-policy", "hosts": [], "policy_critical": true}}`))
|
||||
require.NoError(t, err)
|
||||
|
||||
// run it for team 123 a second time
|
||||
@@ -413,4 +418,15 @@ func TestJiraRunClientUpdate(t *testing.T) {
|
||||
require.Equal(t, []string{"0", "1", "2"}, projectKeys)
|
||||
require.Equal(t, 5, globalCount) // app config is requested every time
|
||||
require.Equal(t, 3, teamCount)
|
||||
require.Len(t, clients, 3)
|
||||
|
||||
require.Len(t, clients[0].issues, 2)
|
||||
require.NotContains(t, clients[0].issues[0].Fields.Description, "Critical")
|
||||
require.Contains(t, clients[0].issues[1].Fields.Description, "Critical")
|
||||
|
||||
require.Len(t, clients[1].issues, 1)
|
||||
require.NotContains(t, clients[1].issues[0].Fields.Description, "Critical")
|
||||
|
||||
require.Len(t, clients[2].issues, 1)
|
||||
require.NotContains(t, clients[2].issues[0].Fields.Description, "Critical")
|
||||
}
|
||||
|
||||
@@ -37,10 +37,11 @@ type Job interface {
|
||||
// failingPolicyArgs are the args common to all integrations that can process
|
||||
// failing policies.
|
||||
type failingPolicyArgs struct {
|
||||
PolicyID uint `json:"policy_id"`
|
||||
PolicyName string `json:"policy_name"`
|
||||
Hosts []fleet.PolicySetHost `json:"hosts"`
|
||||
TeamID *uint `json:"team_id,omitempty"`
|
||||
PolicyID uint `json:"policy_id"`
|
||||
PolicyName string `json:"policy_name"`
|
||||
PolicyCritical bool `json:"policy_critical"`
|
||||
Hosts []fleet.PolicySetHost `json:"hosts"`
|
||||
TeamID *uint `json:"team_id,omitempty"`
|
||||
}
|
||||
|
||||
// vulnArgs are the args common to all integrations that can process
|
||||
|
||||
+10
-13
@@ -74,7 +74,9 @@ This ticket was created automatically by your Fleet Zendesk integration.
|
||||
)),
|
||||
|
||||
FailingPolicyDescription: template.Must(template.New("").Parse(
|
||||
`Hosts:
|
||||
`{{ if .PolicyCritical }}This policy is marked as **Critical** in Fleet.
|
||||
|
||||
{{ end }}Hosts:
|
||||
{{ $end := len .Hosts }}{{ if gt $end 50 }}{{ $end = 50 }}{{ end }}
|
||||
{{ range slice .Hosts 0 $end }}
|
||||
* [{{ .DisplayName }}]({{ $.FleetURL }}/hosts/{{ .ID }})
|
||||
@@ -102,13 +104,7 @@ type zendeskVulnTplArgs struct {
|
||||
CISAKnownExploit *bool
|
||||
}
|
||||
|
||||
type zendeskFailingPoliciesTplArgs struct {
|
||||
FleetURL string
|
||||
PolicyID uint
|
||||
PolicyName string
|
||||
TeamID *uint
|
||||
Hosts []fleet.PolicySetHost
|
||||
}
|
||||
type zendeskFailingPoliciesTplArgs jiraFailingPoliciesTplArgs
|
||||
|
||||
// ZendeskClient defines the method required for the client that makes API calls
|
||||
// to Zendesk.
|
||||
@@ -305,11 +301,12 @@ func (z *Zendesk) runVuln(ctx context.Context, cli ZendeskClient, args zendeskAr
|
||||
|
||||
func (z *Zendesk) runFailingPolicy(ctx context.Context, cli ZendeskClient, args zendeskArgs) error {
|
||||
tplArgs := &zendeskFailingPoliciesTplArgs{
|
||||
FleetURL: z.FleetURL,
|
||||
PolicyName: args.FailingPolicy.PolicyName,
|
||||
PolicyID: args.FailingPolicy.PolicyID,
|
||||
TeamID: args.FailingPolicy.TeamID,
|
||||
Hosts: args.FailingPolicy.Hosts,
|
||||
FleetURL: z.FleetURL,
|
||||
PolicyName: args.FailingPolicy.PolicyName,
|
||||
PolicyID: args.FailingPolicy.PolicyID,
|
||||
PolicyCritical: args.FailingPolicy.PolicyCritical,
|
||||
TeamID: args.FailingPolicy.TeamID,
|
||||
Hosts: args.FailingPolicy.Hosts,
|
||||
}
|
||||
|
||||
createdTicket, err := z.createTemplatedTicket(ctx, cli, zendeskTemplates.FailingPolicySummary, zendeskTemplates.FailingPolicyDescription, tplArgs)
|
||||
|
||||
@@ -296,10 +296,12 @@ func TestZendeskQueueFailingPolicyJob(t *testing.T) {
|
||||
}
|
||||
|
||||
type mockZendeskClient struct {
|
||||
opts externalsvc.ZendeskOptions
|
||||
opts externalsvc.ZendeskOptions
|
||||
tickets []zendesk.Ticket
|
||||
}
|
||||
|
||||
func (c *mockZendeskClient) CreateZendeskTicket(ctx context.Context, ticket *zendesk.Ticket) (*zendesk.Ticket, error) {
|
||||
c.tickets = append(c.tickets, *ticket)
|
||||
return &zendesk.Ticket{}, nil
|
||||
}
|
||||
|
||||
@@ -362,6 +364,7 @@ func TestZendeskRunClientUpdate(t *testing.T) {
|
||||
}
|
||||
|
||||
var groupIDs []int64
|
||||
var clients []*mockZendeskClient
|
||||
zendeskJob := &Zendesk{
|
||||
FleetURL: "http://example.com",
|
||||
Datastore: ds,
|
||||
@@ -369,7 +372,9 @@ func TestZendeskRunClientUpdate(t *testing.T) {
|
||||
NewClientFunc: func(opts *externalsvc.ZendeskOptions) (ZendeskClient, error) {
|
||||
// keep track of group IDs received in calls to NewClientFunc
|
||||
groupIDs = append(groupIDs, opts.GroupID)
|
||||
return &mockZendeskClient{opts: *opts}, nil
|
||||
c := &mockZendeskClient{opts: *opts}
|
||||
clients = append(clients, c)
|
||||
return c, nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -384,7 +389,7 @@ func TestZendeskRunClientUpdate(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// run it globally again - it will reuse the cached client
|
||||
err = zendeskJob.Run(ctx, json.RawMessage(`{"failing_policy":{"policy_id": 1, "policy_name": "test-policy", "hosts": []}}`))
|
||||
err = zendeskJob.Run(ctx, json.RawMessage(`{"failing_policy":{"policy_id": 1, "policy_name": "test-policy", "hosts": [], "policy_critical": true}}`))
|
||||
require.NoError(t, err)
|
||||
|
||||
// run it for team 123 a second time
|
||||
@@ -399,4 +404,16 @@ func TestZendeskRunClientUpdate(t *testing.T) {
|
||||
require.Equal(t, []int64{0, 1, 2}, groupIDs)
|
||||
require.Equal(t, 5, globalCount) // app config is requested every time
|
||||
require.Equal(t, 3, teamCount)
|
||||
|
||||
require.Len(t, clients, 3)
|
||||
|
||||
require.Len(t, clients[0].tickets, 2)
|
||||
require.NotContains(t, clients[0].tickets[0].Comment.Body, "Critical")
|
||||
require.Contains(t, clients[0].tickets[1].Comment.Body, "Critical")
|
||||
|
||||
require.Len(t, clients[1].tickets, 1)
|
||||
require.NotContains(t, clients[1].tickets[0].Comment.Body, "Critical")
|
||||
|
||||
require.Len(t, clients[2].tickets, 1)
|
||||
require.NotContains(t, clients[2].tickets[0].Comment.Body, "Critical")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user