mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 00:45:19 +00:00
Reset query report when platform
/min_osquery_version
is changed (#17847)
#17018 - [X] Changes file added for user-visible changes in `changes/` or `orbit/changes/`. See [Changes files](https://fleetdm.com/docs/contributing/committing-changes#changes-files) for more information. - [X] Added/updated tests - [X] Manual QA for all new/changed functionality --------- Co-authored-by: RachelElysia <rachel@fleetdm.com> Co-authored-by: RachelElysia <71795832+RachelElysia@users.noreply.github.com>
This commit is contained in:
parent
bb0d031ea8
commit
1833e1fc5b
1
changes/17018-reset-query-report
Normal file
1
changes/17018-reset-query-report
Normal file
@ -0,0 +1 @@
|
||||
- Query report is reset when there is a change to the selected platform or selected minimum osquery version
|
@ -638,12 +638,32 @@ const EditQueryForm = ({
|
||||
"differential_ignore_removals",
|
||||
].includes(lastEditedQueryLoggingType);
|
||||
|
||||
// Note: The backend is not resetting the query reports with equivalent platform strings
|
||||
// so we are not showing a warning unless the platform combinations differ
|
||||
const formatPlatformEquivalences = (platforms?: string) => {
|
||||
// Remove white spaces allowed by API and format into a sorted string converted from a sorted array
|
||||
return platforms?.replace(/\s/g, "").split(",").sort().toString();
|
||||
};
|
||||
|
||||
const changedPlatforms =
|
||||
storedQuery &&
|
||||
formatPlatformEquivalences(lastEditedQueryPlatforms) !==
|
||||
formatPlatformEquivalences(storedQuery?.platform);
|
||||
|
||||
const changedMinOsqueryVersion =
|
||||
storedQuery &&
|
||||
lastEditedQueryMinOsqueryVersion !== storedQuery.min_osquery_version;
|
||||
|
||||
const enabledDiscardData =
|
||||
storedQuery && lastEditedQueryDiscardData && !storedQuery.discard_data;
|
||||
|
||||
const confirmChanges =
|
||||
currentlySavingQueryResults &&
|
||||
(changedSQL || changedLoggingToDifferential || enabledDiscardData);
|
||||
(changedSQL ||
|
||||
changedLoggingToDifferential ||
|
||||
enabledDiscardData ||
|
||||
changedPlatforms ||
|
||||
changedMinOsqueryVersion);
|
||||
|
||||
const showChangedSQLCopy =
|
||||
changedSQL && !changedLoggingToDifferential && !enabledDiscardData;
|
||||
@ -660,6 +680,7 @@ const EditQueryForm = ({
|
||||
const disableSaveFormErrors =
|
||||
(lastEditedQueryName === "" && !!lastEditedQueryId) || !!size(errors);
|
||||
|
||||
console.log("lastEditedQueryPlatforms", lastEditedQueryPlatforms);
|
||||
return (
|
||||
<>
|
||||
<form className={`${baseClass}`} autoComplete="off">
|
||||
|
@ -10017,13 +10017,149 @@ func (s *integrationTestSuite) TestQueryReports() {
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/queries/%d/report", osqueryInfoQuery.ID), getQueryReportRequest{}, http.StatusOK, &gqrr)
|
||||
require.Len(t, gqrr.Results, 2)
|
||||
|
||||
// now cause deletions and verify that results are deleted
|
||||
// now update the query and verify that results are deleted
|
||||
updatedQuery := "SELECT * FROM some_new_table;"
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/queries/%d", osqueryInfoQuery.ID), modifyQueryRequest{ID: osqueryInfoQuery.ID, QueryPayload: fleet.QueryPayload{Query: &updatedQuery}}, http.StatusOK, &modifyQueryResp)
|
||||
require.Equal(t, updatedQuery, modifyQueryResp.Query.Query)
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/queries/%d/report", osqueryInfoQuery.ID), getQueryReportRequest{}, http.StatusOK, &gqrr)
|
||||
require.Len(t, gqrr.Results, 0)
|
||||
|
||||
// Re-add results to our query and check that they're actually there
|
||||
s.DoJSON("POST", "/api/osquery/log", slreq, http.StatusOK, &slres)
|
||||
require.NoError(t, slres.Err)
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/queries/%d/report", osqueryInfoQuery.ID), getQueryReportRequest{}, http.StatusOK, &gqrr)
|
||||
require.Len(t, gqrr.Results, 1)
|
||||
|
||||
// now update the platform and verify that results are deleted
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/queries/%d", osqueryInfoQuery.ID), modifyQueryRequest{
|
||||
ID: osqueryInfoQuery.ID,
|
||||
QueryPayload: fleet.QueryPayload{
|
||||
Platform: ptr.String("linux"),
|
||||
},
|
||||
},
|
||||
http.StatusOK,
|
||||
&modifyQueryResp,
|
||||
)
|
||||
require.Equal(t, "linux", modifyQueryResp.Query.Platform)
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/queries/%d/report", osqueryInfoQuery.ID), getQueryReportRequest{}, http.StatusOK, &gqrr)
|
||||
require.Len(t, gqrr.Results, 0)
|
||||
|
||||
// Re-add results to our query and check that they're actually there
|
||||
s.DoJSON("POST", "/api/osquery/log", slreq, http.StatusOK, &slres)
|
||||
require.NoError(t, slres.Err)
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/queries/%d/report", osqueryInfoQuery.ID), getQueryReportRequest{}, http.StatusOK, &gqrr)
|
||||
require.Len(t, gqrr.Results, 1)
|
||||
|
||||
// now update the platform to the same value and verify that results are not deleted
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/queries/%d", osqueryInfoQuery.ID), modifyQueryRequest{
|
||||
ID: osqueryInfoQuery.ID,
|
||||
QueryPayload: fleet.QueryPayload{
|
||||
Platform: ptr.String("linux"),
|
||||
},
|
||||
},
|
||||
http.StatusOK,
|
||||
&modifyQueryResp,
|
||||
)
|
||||
require.Equal(t, "linux", modifyQueryResp.Query.Platform)
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/queries/%d/report", osqueryInfoQuery.ID), getQueryReportRequest{}, http.StatusOK, &gqrr)
|
||||
require.Len(t, gqrr.Results, 1)
|
||||
|
||||
// now update the min_osquery_version and verify that results are deleted
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/queries/%d", osqueryInfoQuery.ID), modifyQueryRequest{
|
||||
ID: osqueryInfoQuery.ID,
|
||||
QueryPayload: fleet.QueryPayload{
|
||||
MinOsqueryVersion: ptr.String("5.9.1"),
|
||||
},
|
||||
},
|
||||
http.StatusOK,
|
||||
&modifyQueryResp,
|
||||
)
|
||||
require.Equal(t, "5.9.1", modifyQueryResp.Query.MinOsqueryVersion)
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/queries/%d/report", osqueryInfoQuery.ID), getQueryReportRequest{}, http.StatusOK, &gqrr)
|
||||
require.Len(t, gqrr.Results, 0)
|
||||
|
||||
// Re-add results to our query and check that they're actually there
|
||||
s.DoJSON("POST", "/api/osquery/log", slreq, http.StatusOK, &slres)
|
||||
require.NoError(t, slres.Err)
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/queries/%d/report", osqueryInfoQuery.ID), getQueryReportRequest{}, http.StatusOK, &gqrr)
|
||||
require.Len(t, gqrr.Results, 1)
|
||||
|
||||
// now update the min_osquery_version to another value and verify that results are deleted
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/queries/%d", osqueryInfoQuery.ID), modifyQueryRequest{
|
||||
ID: osqueryInfoQuery.ID,
|
||||
QueryPayload: fleet.QueryPayload{
|
||||
MinOsqueryVersion: ptr.String("5.11.0"),
|
||||
},
|
||||
},
|
||||
http.StatusOK,
|
||||
&modifyQueryResp,
|
||||
)
|
||||
require.Equal(t, "5.11.0", modifyQueryResp.Query.MinOsqueryVersion)
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/queries/%d/report", osqueryInfoQuery.ID), getQueryReportRequest{}, http.StatusOK, &gqrr)
|
||||
require.Len(t, gqrr.Results, 0)
|
||||
|
||||
// Re-add results to our query and check that they're actually there
|
||||
s.DoJSON("POST", "/api/osquery/log", slreq, http.StatusOK, &slres)
|
||||
require.NoError(t, slres.Err)
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/queries/%d/report", osqueryInfoQuery.ID), getQueryReportRequest{}, http.StatusOK, &gqrr)
|
||||
require.Len(t, gqrr.Results, 1)
|
||||
|
||||
// now update the min_osquery_version to the same value and verify that results are not deleted
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/queries/%d", osqueryInfoQuery.ID), modifyQueryRequest{
|
||||
ID: osqueryInfoQuery.ID,
|
||||
QueryPayload: fleet.QueryPayload{
|
||||
MinOsqueryVersion: ptr.String("5.11.0"),
|
||||
},
|
||||
},
|
||||
http.StatusOK,
|
||||
&modifyQueryResp,
|
||||
)
|
||||
require.Equal(t, "5.11.0", modifyQueryResp.Query.MinOsqueryVersion)
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/queries/%d/report", osqueryInfoQuery.ID), getQueryReportRequest{}, http.StatusOK, &gqrr)
|
||||
require.Len(t, gqrr.Results, 1)
|
||||
|
||||
// now update the query via specs and change the min_osquery_version, results should be deleted.
|
||||
osqueryInfoQuerySpec := &fleet.QuerySpec{
|
||||
Name: osqueryInfoQuery.Name,
|
||||
Description: osqueryInfoQuery.Description,
|
||||
Query: osqueryInfoQuery.Query,
|
||||
Interval: osqueryInfoQuery.Interval,
|
||||
ObserverCanRun: osqueryInfoQuery.ObserverCanRun,
|
||||
Platform: osqueryInfoQuery.Platform,
|
||||
MinOsqueryVersion: osqueryInfoQuery.MinOsqueryVersion,
|
||||
AutomationsEnabled: osqueryInfoQuery.AutomationsEnabled,
|
||||
Logging: osqueryInfoQuery.Logging,
|
||||
DiscardData: osqueryInfoQuery.DiscardData,
|
||||
}
|
||||
osqueryInfoQuerySpec.MinOsqueryVersion = "5.12.0"
|
||||
var applyResp applyQuerySpecsResponse
|
||||
s.DoJSON("POST", "/api/latest/fleet/spec/queries", applyQuerySpecsRequest{
|
||||
Specs: []*fleet.QuerySpec{osqueryInfoQuerySpec},
|
||||
}, http.StatusOK, &applyResp)
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/queries/%d/report", osqueryInfoQuery.ID), getQueryReportRequest{}, http.StatusOK, &gqrr)
|
||||
require.Len(t, gqrr.Results, 0)
|
||||
|
||||
// Re-add results to our query and check that they're actually there
|
||||
s.DoJSON("POST", "/api/osquery/log", slreq, http.StatusOK, &slres)
|
||||
require.NoError(t, slres.Err)
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/queries/%d/report", osqueryInfoQuery.ID), getQueryReportRequest{}, http.StatusOK, &gqrr)
|
||||
require.Len(t, gqrr.Results, 1)
|
||||
|
||||
// don't change platform or min_osquery_version and results should not be deleted
|
||||
s.DoJSON("POST", "/api/latest/fleet/spec/queries", applyQuerySpecsRequest{
|
||||
Specs: []*fleet.QuerySpec{osqueryInfoQuerySpec},
|
||||
}, http.StatusOK, &applyResp)
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/queries/%d/report", osqueryInfoQuery.ID), getQueryReportRequest{}, http.StatusOK, &gqrr)
|
||||
require.Len(t, gqrr.Results, 1)
|
||||
|
||||
// now update the platform and results should be deleted.
|
||||
osqueryInfoQuerySpec.Platform = "darwin"
|
||||
s.DoJSON("POST", "/api/latest/fleet/spec/queries", applyQuerySpecsRequest{
|
||||
Specs: []*fleet.QuerySpec{osqueryInfoQuerySpec},
|
||||
}, http.StatusOK, &applyResp)
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/queries/%d/report", osqueryInfoQuery.ID), getQueryReportRequest{}, http.StatusOK, &gqrr)
|
||||
require.Len(t, gqrr.Results, 0)
|
||||
|
||||
// Update logging type, which should cause results deletion
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/queries/%d", usbDevicesQuery.ID), modifyQueryRequest{ID: usbDevicesQuery.ID, QueryPayload: fleet.QueryPayload{Logging: &fleet.LoggingDifferential}}, http.StatusOK, &modifyQueryResp)
|
||||
require.Equal(t, fleet.LoggingDifferential, modifyQueryResp.Query.Logging)
|
||||
|
@ -3,6 +3,8 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/authz"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
||||
@ -325,7 +327,6 @@ func modifyQueryEndpoint(ctx context.Context, request interface{}, svc fleet.Ser
|
||||
func (svc *Service) ModifyQuery(ctx context.Context, id uint, p fleet.QueryPayload) (*fleet.Query, error) {
|
||||
// Load query first to determine if the user can modify it.
|
||||
query, err := svc.ds.Query(ctx, id)
|
||||
shouldDiscardQueryResults, shouldDeleteStats := false, false
|
||||
if err != nil {
|
||||
setAuthCheckedOnPreAuthErr(ctx)
|
||||
return nil, err
|
||||
@ -344,6 +345,8 @@ func (svc *Service) ModifyQuery(ctx context.Context, id uint, p fleet.QueryPaylo
|
||||
})
|
||||
}
|
||||
|
||||
shouldDiscardQueryResults, shouldDeleteStats := false, false
|
||||
|
||||
if p.Name != nil {
|
||||
query.Name = *p.Name
|
||||
}
|
||||
@ -361,9 +364,15 @@ func (svc *Service) ModifyQuery(ctx context.Context, id uint, p fleet.QueryPaylo
|
||||
query.Interval = *p.Interval
|
||||
}
|
||||
if p.Platform != nil {
|
||||
if !comparePlatforms(query.Platform, *p.Platform) {
|
||||
shouldDiscardQueryResults = true
|
||||
}
|
||||
query.Platform = *p.Platform
|
||||
}
|
||||
if p.MinOsqueryVersion != nil {
|
||||
if query.MinOsqueryVersion != *p.MinOsqueryVersion {
|
||||
shouldDiscardQueryResults = true
|
||||
}
|
||||
query.MinOsqueryVersion = *p.MinOsqueryVersion
|
||||
}
|
||||
if p.AutomationsEnabled != nil {
|
||||
@ -405,6 +414,17 @@ func (svc *Service) ModifyQuery(ctx context.Context, id uint, p fleet.QueryPaylo
|
||||
return query, nil
|
||||
}
|
||||
|
||||
func comparePlatforms(platform1, platform2 string) bool {
|
||||
if platform1 == platform2 {
|
||||
return true
|
||||
}
|
||||
p1s := strings.Split(platform1, ",")
|
||||
slices.Sort(p1s)
|
||||
p2s := strings.Split(platform2, ",")
|
||||
slices.Sort(p2s)
|
||||
return slices.Compare(p1s, p2s) == 0
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Delete Query
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@ -627,7 +647,9 @@ func (svc *Service) ApplyQuerySpecs(ctx context.Context, specs []*fleet.QuerySpe
|
||||
|
||||
if (query.DiscardData && query.DiscardData != dbQuery.DiscardData) ||
|
||||
(query.Logging != dbQuery.Logging && query.Logging != fleet.LoggingSnapshot) ||
|
||||
query.Query != dbQuery.Query {
|
||||
query.Query != dbQuery.Query ||
|
||||
query.MinOsqueryVersion != dbQuery.MinOsqueryVersion ||
|
||||
!comparePlatforms(query.Platform, dbQuery.Platform) {
|
||||
queriesToDiscardResults[dbQuery.ID] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
@ -714,3 +714,66 @@ func TestQueryReportReturnsNilIfDiscardDataIsTrue(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, results)
|
||||
}
|
||||
|
||||
func TestComparePlatforms(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
p1 string
|
||||
p2 string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "equal single value",
|
||||
p1: "linux",
|
||||
p2: "linux",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "different single value",
|
||||
p1: "macos",
|
||||
p2: "linux",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "equal multiple values",
|
||||
p1: "linux,windows",
|
||||
p2: "linux,windows",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "equal multiple values out of order",
|
||||
p1: "linux,windows",
|
||||
p2: "windows,linux",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "different multiple values",
|
||||
p1: "linux,windows",
|
||||
p2: "linux,windows,darwin",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "no values set",
|
||||
p1: "",
|
||||
p2: "",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "no values set",
|
||||
p1: "",
|
||||
p2: "linux",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "single and multiple values",
|
||||
p1: "linux",
|
||||
p2: "windows,linux",
|
||||
expected: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
actual := comparePlatforms(tc.p1, tc.p2)
|
||||
require.Equal(t, tc.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user