Add critical policies as a premium feature (#8959)

* add premium feature critical policies

* update documentation

* add test for premium-only field

* update old change-file

* test policies more comprehensively

* also test team policies

* PATCH returns wrong timestamp, updating test
This commit is contained in:
Frank Sievertsen 2022-12-09 19:23:08 +01:00 committed by GitHub
parent e68535d468
commit a228dcb170
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 291 additions and 62 deletions

View File

@ -1 +1 @@
* Add ability to mark policy as critical.
* Add ability to mark policy as critical with Fleet Premium.

View File

@ -209,7 +209,7 @@ The API routes to control packs are supported for backwards compatibility.
"label_ids": [
8
],
"team_ids": [],
"team_ids": []
},
{
"created_at": "2021-01-19T17:08:31Z",
@ -223,7 +223,7 @@ The API routes to control packs are supported for backwards compatibility.
6
],
"team_ids": []
},
}
]
}
```
@ -558,14 +558,14 @@ These API routes are used by the `fleetctl` CLI tool. Users can manage Fleet wit
- [Get queries](#get-queries)
- [Get query](#get-query)
- [Apply queries](#apply-queries)
- [Apply policies](#aaply-policies)
- [Apply policies](#apply-policies)
- [Get packs](#get-packs)
- [Apply packs](#apply-packs)
- [Get pack by name](#get-pack-by-name)
- [Apply team](#apply-team)
- [Apply labels](#apply-labels)
- [Get labels](#get-labels)
- [Get label](#get-label-spec)
- [Get label](#get-label)
- [Get enroll secrets](#get-enroll-secrets)
- [Modify enroll secrets](#modify-enroll-secrets)
@ -816,19 +816,23 @@ NOTE: when updating a policy, team and platform will be ignored.
"name": "new policy",
"description": "This will be a new policy because a policy with the name 'new policy' doesn't exist in Fleet.",
"query": "SELECT * FROM osquery_info",
"resolution": "some resolution steps here"
"resolution": "some resolution steps here",
"critical": false
},
{
"name": "Is FileVault enabled on macOS devices?",
"query": "SELECT 1 FROM disk_encryption WHERE user_uuid IS NOT “” AND filevault_status = on LIMIT 1;",
"description": "Checks to make sure that the FileVault feature is enabled on macOS devices.",
"resolution": "Choose Apple menu > System Preferences, then click Security & Privacy. Click the FileVault tab. Click the Lock icon, then enter an administrator name and password. Click Turn On FileVault.",
"platform": "darwin"
"platform": "darwin",
"critical": true
}
]
}
```
The field `critical` is available in Fleet Premium.
##### Default response
`Status: 200`
@ -1098,7 +1102,7 @@ If the `name` is not already associated with an existing team, this API route cr
"secret": "bhD5kiX2J+KBgZSk118qO61ZIdX/v8On"
}
]
t }
}
]
}
```
@ -1336,8 +1340,8 @@ These API routes are used by the Fleet UI.
- [Count targets](#count-targets)
- [Run live query](#run-live-query)
- [Run live query by name](#run-live-query-by-name)
- [Retrieve live query results (standard WebSocket API)](#retrieve-live-query-results-standard-web-socket-api)
- [Retrieve live query results (SockJS)](#retrieve-live-query-results-sock-js)
- [Retrieve live query results (standard WebSocket API)](#retrieve-live-query-results-standard-websocket-api)
- [Retrieve live query results (SockJS)](#retrieve-live-query-results-sockjs)
### Check live query status

View File

@ -2290,7 +2290,7 @@ Returns the information of the host specified using the `uuid`, `osquery_host_id
"platform": "",
"label_type": "builtin",
"label_membership_type": "dynamic"
},
}
],
"packs": [
{
@ -3469,7 +3469,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. |
| critical | boolean | body | _Available in Fleet Premium_ Mark policy as critical/high impact. |
Either `query` or `query_id` must be provided.
@ -3602,7 +3602,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. |
| critical | boolean | body | _Available in Fleet Premium_ Mark policy as critical/high impact. |
#### Example Edit Policy
@ -3797,7 +3797,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. |
| critical | boolean | body | _Available in Fleet Premium_ Mark policy as critical/high impact. |
Either `query` or `query_id` must be provided.
@ -3892,7 +3892,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. |
| critical | boolean | body | _Available in Fleet Premium_ Mark policy as critical/high impact. |
#### Example Edit Policy
@ -5114,7 +5114,7 @@ _Available in Fleet Premium_
"agent_options": {
"config": {
"options": {
= "pack_delimiter": "/",
"pack_delimiter": "/",
"logger_tls_period": 10,
"distributed_plugin": "tls",
"disable_distributed": false,
@ -5151,7 +5151,7 @@ _Available in Fleet Premium_
"spec": {
"config": {
"options": {
= "pack_delimiter": "/",
"pack_delimiter": "/",
"logger_tls_period": 10,
"distributed_plugin": "tls",
"disable_distributed": false,
@ -5214,7 +5214,7 @@ _Available in Fleet Premium_
"agent_options": {
"config": {
"options": {
= "pack_delimiter": "/",
"pack_delimiter": "/",
"logger_tls_period": 10,
"distributed_plugin": "tls",
"disable_distributed": false,
@ -5370,7 +5370,7 @@ _Available in Fleet Premium_
"agent_options": {
"config": {
"options": {
= "pack_delimiter": "/",
"pack_delimiter": "/",
"logger_tls_period": 10,
"distributed_plugin": "tls",
"disable_distributed": false,

View File

@ -109,7 +109,7 @@ type ModifyPolicyPayload struct {
// If non-nil, empty string targets all platforms.
Platform *string `json:"platform"`
// Critical marks the policy as high impact.
Critical *bool `json:"critical"`
Critical *bool `json:"critical" premium:"true"`
}
// Verify verifies the policy payload is valid.

View File

@ -15,6 +15,7 @@ import (
"strings"
"github.com/fleetdm/fleet/v4/server/contexts/capabilities"
"github.com/fleetdm/fleet/v4/server/contexts/license"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/log"
@ -39,8 +40,13 @@ func parseTag(tag string) (string, bool, error) {
}
}
type fieldPair struct {
sf reflect.StructField
v reflect.Value
}
// allFields returns all the fields for a struct, including the ones from embedded structs
func allFields(ifv reflect.Value) []reflect.StructField {
func allFields(ifv reflect.Value) []fieldPair {
if ifv.Kind() == reflect.Ptr {
ifv = ifv.Elem()
}
@ -48,7 +54,7 @@ func allFields(ifv reflect.Value) []reflect.StructField {
return nil
}
var fields []reflect.StructField
var fields []fieldPair
if !ifv.IsValid() {
return nil
@ -63,7 +69,7 @@ func allFields(ifv reflect.Value) []reflect.StructField {
fields = append(fields, allFields(v)...)
continue
}
fields = append(fields, ifv.Type().Field(i))
fields = append(fields, fieldPair{sf: ifv.Type().Field(i), v: v})
}
return fields
@ -152,10 +158,11 @@ func makeDecoder(iface interface{}) kithttp.DecodeRequestFunc {
}
}
for _, f := range allFields(v) {
field := v.Elem().FieldByName(f.Name)
fields := allFields(v)
for _, fp := range fields {
field := fp.v
urlTagValue, ok := f.Tag.Lookup("url")
urlTagValue, ok := fp.sf.Tag.Lookup("url")
optional := false
var err error
@ -232,12 +239,12 @@ func makeDecoder(iface interface{}) kithttp.DecodeRequestFunc {
}
}
_, jsonExpected := f.Tag.Lookup("json")
_, jsonExpected := fp.sf.Tag.Lookup("json")
if jsonExpected && nilBody {
return nil, badRequest("Expected JSON Body")
}
queryTagValue, ok := f.Tag.Lookup("query")
queryTagValue, ok := fp.sf.Tag.Lookup("query")
if ok {
queryTagValue, optional, err = parseTag(queryTagValue)
@ -250,7 +257,7 @@ func makeDecoder(iface interface{}) kithttp.DecodeRequestFunc {
if optional {
continue
}
return nil, badRequest(fmt.Sprintf("Param %s is required", f.Name))
return nil, badRequest(fmt.Sprintf("Param %s is required", fp.sf.Name))
}
if field.Kind() == reflect.Ptr {
// create the new instance of whatever it is
@ -290,7 +297,7 @@ func makeDecoder(iface interface{}) kithttp.DecodeRequestFunc {
}
field.SetInt(int64(queryValInt))
default:
return nil, fmt.Errorf("Cant handle type for field %s %s", f.Name, field.Kind())
return nil, fmt.Errorf("Cant handle type for field %s %s", fp.sf.Name, field.Kind())
}
}
}
@ -302,6 +309,24 @@ func makeDecoder(iface interface{}) kithttp.DecodeRequestFunc {
}
}
if !license.IsPremium(ctx) {
for _, fp := range fields {
if prem, ok := fp.sf.Tag.Lookup("premium"); ok {
val, err := strconv.ParseBool(prem)
if err != nil {
return nil, err
}
if val && !fp.v.IsZero() {
return nil, &fleet.BadRequestError{Message: fmt.Sprintf(
"option %s requires a premium license",
fp.sf.Name,
)}
}
continue
}
}
}
return v.Interface(), nil
}
}

View File

@ -7,6 +7,7 @@ import (
"github.com/fleetdm/fleet/v4/server/authz"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/fleetdm/fleet/v4/server/contexts/license"
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/ptr"
@ -23,6 +24,7 @@ type globalPolicyRequest struct {
Description string `json:"description"`
Resolution string `json:"resolution"`
Platform string `json:"platform"`
Critical bool `json:"critical" premium:"true"`
}
type globalPolicyResponse struct {
@ -41,6 +43,7 @@ func globalPolicyEndpoint(ctx context.Context, request interface{}, svc fleet.Se
Description: req.Description,
Resolution: req.Resolution,
Platform: req.Platform,
Critical: req.Critical,
})
if err != nil {
return globalPolicyResponse{Err: err}, nil
@ -331,6 +334,11 @@ func (svc *Service) ApplyPolicySpecs(ctx context.Context, policies []*fleet.Poli
if !ok {
return errors.New("user must be authenticated to apply policies")
}
if !license.IsPremium(ctx) {
for i := range policies {
policies[i].Critical = false
}
}
if err := svc.ds.ApplyPolicySpecs(ctx, vc.UserID(), policies); err != nil {
return ctxerr.Wrap(ctx, err, "applying policy specs")
}

View File

@ -221,6 +221,13 @@ func (s *integrationTestSuite) TestPolicyDeletionLogsActivity() {
policyIDs = append(policyIDs, resp.Policy.PolicyData.ID)
}
// critical is premium only.
s.DoJSON("POST", "/api/latest/fleet/policies", fleet.PolicyPayload{
Name: "policy3",
Query: "select * from time;",
Critical: true,
}, http.StatusBadRequest, new(struct{}))
prevActivities := listActivitiesResponse{}
s.DoJSON("GET", "/api/latest/fleet/activities", nil, http.StatusOK, &prevActivities)
require.GreaterOrEqual(t, len(prevActivities.Activities), 2)

View File

@ -7,6 +7,8 @@ import (
"fmt"
"io"
"net/http"
"reflect"
"sort"
"strings"
"testing"
"time"
@ -1650,3 +1652,188 @@ func (s *integrationEnterpriseTestSuite) TestListHosts() {
require.Equal(t, uint(0), summaryResp.TotalsHostsCount)
require.Nil(t, summaryResp.LowDiskSpaceCount)
}
func (s *integrationEnterpriseTestSuite) TestGlobalPolicyCreateReadPatch() {
fields := []string{"Query", "Name", "Description", "Resolution", "Platform", "Critical"}
createPol1 := &globalPolicyResponse{}
createPol1Req := &globalPolicyRequest{
Query: "query",
Name: "name1",
Description: "description",
Resolution: "resolution",
Platform: "linux",
Critical: true,
}
s.DoJSON("POST", "/api/latest/fleet/policies", createPol1Req, http.StatusOK, &createPol1)
allEqual(s.T(), createPol1Req, createPol1.Policy, fields...)
createPol2 := &globalPolicyResponse{}
createPol2Req := &globalPolicyRequest{
Query: "query",
Name: "name2",
Description: "description",
Resolution: "resolution",
Platform: "linux",
Critical: false,
}
s.DoJSON("POST", "/api/latest/fleet/policies", createPol2Req, http.StatusOK, &createPol2)
allEqual(s.T(), createPol2Req, createPol2.Policy, fields...)
listPol := &listGlobalPoliciesResponse{}
s.DoJSON("GET", "/api/latest/fleet/policies", nil, http.StatusOK, listPol)
require.Len(s.T(), listPol.Policies, 2)
sort.Slice(listPol.Policies, func(i, j int) bool {
return listPol.Policies[i].Name < listPol.Policies[j].Name
})
require.Equal(s.T(), createPol1.Policy, listPol.Policies[0])
require.Equal(s.T(), createPol2.Policy, listPol.Policies[1])
patchPol1Req := &modifyGlobalPolicyRequest{
ModifyPolicyPayload: fleet.ModifyPolicyPayload{
Name: ptr.String("newName1"),
Query: ptr.String("newQuery"),
Description: ptr.String("newDescription"),
Resolution: ptr.String("newResolution"),
Platform: ptr.String("windows"),
Critical: ptr.Bool(false),
},
}
patchPol1 := &modifyGlobalPolicyResponse{}
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/policies/%d", createPol1.Policy.ID), patchPol1Req, http.StatusOK, patchPol1)
allEqual(s.T(), patchPol1Req, patchPol1.Policy, fields...)
patchPol2Req := &modifyGlobalPolicyRequest{
ModifyPolicyPayload: fleet.ModifyPolicyPayload{
Name: ptr.String("newName2"),
Query: ptr.String("newQuery"),
Description: ptr.String("newDescription"),
Resolution: ptr.String("newResolution"),
Platform: ptr.String("windows"),
Critical: ptr.Bool(true),
},
}
patchPol2 := &modifyGlobalPolicyResponse{}
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/policies/%d", createPol2.Policy.ID), patchPol2Req, http.StatusOK, patchPol2)
allEqual(s.T(), patchPol2Req, patchPol2.Policy, fields...)
listPol = &listGlobalPoliciesResponse{}
s.DoJSON("GET", "/api/latest/fleet/policies", nil, http.StatusOK, listPol)
require.Len(s.T(), listPol.Policies, 2)
sort.Slice(listPol.Policies, func(i, j int) bool {
return listPol.Policies[i].Name < listPol.Policies[j].Name
})
// not using require.Equal because "PATCH policies" returns the wrong updated timestamp.
allEqual(s.T(), patchPol1.Policy, listPol.Policies[0], fields...)
allEqual(s.T(), patchPol2.Policy, listPol.Policies[1], fields...)
getPol2 := &getPolicyByIDResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/policies/%d", createPol2.Policy.ID), nil, http.StatusOK, getPol2)
require.Equal(s.T(), listPol.Policies[1], getPol2.Policy)
}
func (s *integrationEnterpriseTestSuite) TestTeamPolicyCreateReadPatch() {
fields := []string{"Query", "Name", "Description", "Resolution", "Platform", "Critical"}
team1, err := s.ds.NewTeam(context.Background(), &fleet.Team{
ID: 42,
Name: "team1",
Description: "desc team1",
})
require.NoError(s.T(), err)
createPol1 := &teamPolicyResponse{}
createPol1Req := &teamPolicyRequest{
Query: "query",
Name: "name1",
Description: "description",
Resolution: "resolution",
Platform: "linux",
Critical: true,
}
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/teams/%d/policies", team1.ID), createPol1Req, http.StatusOK, &createPol1)
allEqual(s.T(), createPol1Req, createPol1.Policy, fields...)
createPol2 := &teamPolicyResponse{}
createPol2Req := &teamPolicyRequest{
Query: "query",
Name: "name2",
Description: "description",
Resolution: "resolution",
Platform: "linux",
Critical: false,
}
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/teams/%d/policies", team1.ID), createPol2Req, http.StatusOK, &createPol2)
allEqual(s.T(), createPol2Req, createPol2.Policy, fields...)
listPol := &listTeamPoliciesResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/teams/%d/policies", team1.ID), nil, http.StatusOK, listPol)
require.Len(s.T(), listPol.Policies, 2)
sort.Slice(listPol.Policies, func(i, j int) bool {
return listPol.Policies[i].Name < listPol.Policies[j].Name
})
require.Equal(s.T(), createPol1.Policy, listPol.Policies[0])
require.Equal(s.T(), createPol2.Policy, listPol.Policies[1])
patchPol1Req := &modifyTeamPolicyRequest{
ModifyPolicyPayload: fleet.ModifyPolicyPayload{
Name: ptr.String("newName1"),
Query: ptr.String("newQuery"),
Description: ptr.String("newDescription"),
Resolution: ptr.String("newResolution"),
Platform: ptr.String("windows"),
Critical: ptr.Bool(false),
},
}
patchPol1 := &modifyTeamPolicyResponse{}
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d/policies/%d", team1.ID, createPol1.Policy.ID), patchPol1Req, http.StatusOK, patchPol1)
allEqual(s.T(), patchPol1Req, patchPol1.Policy, fields...)
patchPol2Req := &modifyTeamPolicyRequest{
ModifyPolicyPayload: fleet.ModifyPolicyPayload{
Name: ptr.String("newName2"),
Query: ptr.String("newQuery"),
Description: ptr.String("newDescription"),
Resolution: ptr.String("newResolution"),
Platform: ptr.String("windows"),
Critical: ptr.Bool(true),
},
}
patchPol2 := &modifyTeamPolicyResponse{}
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d/policies/%d", team1.ID, createPol2.Policy.ID), patchPol2Req, http.StatusOK, patchPol2)
allEqual(s.T(), patchPol2Req, patchPol2.Policy, fields...)
listPol = &listTeamPoliciesResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/teams/%d/policies", team1.ID), nil, http.StatusOK, listPol)
require.Len(s.T(), listPol.Policies, 2)
sort.Slice(listPol.Policies, func(i, j int) bool {
return listPol.Policies[i].Name < listPol.Policies[j].Name
})
// not using require.Equal because "PATCH policies" returns the wrong updated timestamp.
allEqual(s.T(), patchPol1.Policy, listPol.Policies[0], fields...)
allEqual(s.T(), patchPol2.Policy, listPol.Policies[1], fields...)
getPol2 := &getPolicyByIDResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/teams/%d/policies/%d", team1.ID, createPol2.Policy.ID), nil, http.StatusOK, getPol2)
require.Equal(s.T(), listPol.Policies[1], getPol2.Policy)
}
// allEqual compares all fields of a struct.
// If a field is a pointer on one side but not on the other, then it follows that pointer. This is useful for optional
// arguments.
func allEqual(t *testing.T, expect, actual interface{}, fields ...string) {
require.NotEmpty(t, fields)
t.Helper()
expV := reflect.Indirect(reflect.ValueOf(expect))
actV := reflect.Indirect(reflect.ValueOf(actual))
for _, f := range fields {
e, a := expV.FieldByName(f), actV.FieldByName(f)
switch {
case e.Kind() == reflect.Ptr && a.Kind() != reflect.Ptr && !e.IsZero():
e = e.Elem()
case a.Kind() == reflect.Ptr && e.Kind() != reflect.Ptr && !a.IsZero():
a = a.Elem()
}
require.Equal(t, e.Interface(), a.Interface(), "%s", f)
}
}

View File

@ -26,6 +26,7 @@ type teamPolicyRequest struct {
Description string `json:"description"`
Resolution string `json:"resolution"`
Platform string `json:"platform"`
Critical bool `json:"critical" premium:"true"`
}
type teamPolicyResponse struct {
@ -44,6 +45,7 @@ func teamPolicyEndpoint(ctx context.Context, request interface{}, svc fleet.Serv
Description: req.Description,
Resolution: req.Resolution,
Platform: req.Platform,
Critical: req.Critical,
})
if err != nil {
return teamPolicyResponse{Err: err}, nil

View File

@ -170,7 +170,7 @@ func (ts *withServer) DoRawWithHeaders(
resp, err := client.Do(req)
require.NoError(t, err)
assert.Equal(t, expectedStatusCode, resp.StatusCode)
require.Equal(t, expectedStatusCode, resp.StatusCode)
return resp
}

View File

@ -103,15 +103,6 @@ type jiraVulnTplArgs struct {
CISAKnownExploit *bool
}
type jiraFailingPoliciesTplArgs struct {
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
// to Jira.
type JiraClient interface {
@ -306,14 +297,7 @@ 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,
PolicyCritical: args.FailingPolicy.PolicyCritical,
TeamID: args.FailingPolicy.TeamID,
Hosts: args.FailingPolicy.Hosts,
}
tplArgs := newFailingPoliciesTplArgs(j.FleetURL, args.FailingPolicy)
createdIssue, err := j.createTemplatedIssue(ctx, cli, jiraTemplates.FailingPolicySummary, jiraTemplates.FailingPolicyDescription, tplArgs)
if err != nil {

View File

@ -168,3 +168,23 @@ func (w *Worker) processJob(ctx context.Context, job *fleet.Job) error {
return j.Run(ctx, args)
}
type failingPoliciesTplArgs struct {
FleetURL string
PolicyID uint
PolicyName string
PolicyCritical bool
TeamID *uint
Hosts []fleet.PolicySetHost
}
func newFailingPoliciesTplArgs(fleetURL string, args *failingPolicyArgs) *failingPoliciesTplArgs {
return &failingPoliciesTplArgs{
FleetURL: fleetURL,
PolicyName: args.PolicyName,
PolicyID: args.PolicyID,
PolicyCritical: args.PolicyCritical,
TeamID: args.TeamID,
Hosts: args.Hosts,
}
}

View File

@ -104,8 +104,6 @@ type zendeskVulnTplArgs struct {
CISAKnownExploit *bool
}
type zendeskFailingPoliciesTplArgs jiraFailingPoliciesTplArgs
// ZendeskClient defines the method required for the client that makes API calls
// to Zendesk.
type ZendeskClient interface {
@ -300,14 +298,7 @@ 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,
PolicyCritical: args.FailingPolicy.PolicyCritical,
TeamID: args.FailingPolicy.TeamID,
Hosts: args.FailingPolicy.Hosts,
}
tplArgs := newFailingPoliciesTplArgs(z.FleetURL, args.FailingPolicy)
createdTicket, err := z.createTemplatedTicket(ctx, cli, zendeskTemplates.FailingPolicySummary, zendeskTemplates.FailingPolicyDescription, tplArgs)
if err != nil {
@ -418,6 +409,7 @@ func QueueZendeskFailingPolicyJob(ctx context.Context, ds fleet.Datastore, logge
args := &failingPolicyArgs{
PolicyID: policy.ID,
PolicyName: policy.Name,
PolicyCritical: policy.Critical,
TeamID: policy.TeamID,
Hosts: hosts,
}