mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 08:55:24 +00:00
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:
parent
e68535d468
commit
a228dcb170
@ -1 +1 @@
|
||||
* Add ability to mark policy as critical.
|
||||
* Add ability to mark policy as critical with Fleet Premium.
|
@ -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
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user