fix: retrigger automatic installations after label scope changes (#25163)
> Related issue: #25071 # Checklist for submitter If some of the following don't apply, delete the relevant line. <!-- Note that API documentation changes are now addressed by the product design team. --> - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [x] Added/updated automated tests - [x] A detailed QA plan exists on the associated ticket (if it isn't there, work with the product group's QA engineer to add it) - [x] Manual QA for all new/changed functionality
This commit is contained in:
@@ -416,6 +416,34 @@ func (ds *Datastore) RecordPolicyQueryExecutions(ctx context.Context, host *flee
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) ClearAutoInstallPolicyStatusForHosts(ctx context.Context, installerID uint, hostIDs []uint) error {
|
||||
if len(hostIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
stmt := `
|
||||
UPDATE
|
||||
policies p
|
||||
JOIN policy_membership pm ON pm.policy_id = p.id
|
||||
SET
|
||||
passes = NULL
|
||||
WHERE
|
||||
p.software_installer_id = ?
|
||||
AND pm.host_id IN (?)
|
||||
`
|
||||
|
||||
stmt, args, err := sqlx.In(stmt, installerID, hostIDs)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "building in statement for clearing auto install policy status")
|
||||
}
|
||||
|
||||
if _, err := ds.writer(ctx).ExecContext(ctx, stmt, args...); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "clearing auto install policy status")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) ListGlobalPolicies(ctx context.Context, opts fleet.ListOptions) ([]*fleet.Policy, error) {
|
||||
return listPoliciesDB(ctx, ds.reader(ctx), nil, opts)
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ func TestPolicies(t *testing.T) {
|
||||
{"TestPoliciesTeamPoliciesWithScript", testTeamPoliciesWithScript},
|
||||
{"TeamPoliciesNoTeam", testTeamPoliciesNoTeam},
|
||||
{"TestPoliciesBySoftwareTitleID", testPoliciesBySoftwareTitleID},
|
||||
{"TestClearAutoInstallPolicyStatusForHost", testClearAutoInstallPolicyStatusForHost},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
@@ -5371,3 +5372,83 @@ func testPoliciesBySoftwareTitleID(t *testing.T, ds *Datastore) {
|
||||
require.NoError(t, err)
|
||||
require.Len(t, policies, 0)
|
||||
}
|
||||
|
||||
func testClearAutoInstallPolicyStatusForHost(t *testing.T, ds *Datastore) {
|
||||
ctx := context.Background()
|
||||
|
||||
user1 := test.NewUser(t, ds, "Alice", "alice@example.com", true)
|
||||
team1, err := ds.NewTeam(ctx, &fleet.Team{Name: "team1" + t.Name()})
|
||||
require.NoError(t, err)
|
||||
|
||||
// create a regular policy
|
||||
policy1 := newTestPolicy(t, ds, user1, "policy 1"+t.Name(), "darwin", &team1.ID)
|
||||
|
||||
// create an automatic install policy
|
||||
policy2 := newTestPolicy(t, ds, user1, "policy 2"+t.Name(), "darwin", &team1.ID)
|
||||
installer, err := fleet.NewTempFileReader(strings.NewReader("hello"), t.TempDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
installer1ID, _, err := ds.MatchOrCreateSoftwareInstaller(context.Background(), &fleet.UploadSoftwareInstallerPayload{
|
||||
InstallScript: "hello",
|
||||
PreInstallQuery: "SELECT 1",
|
||||
PostInstallScript: "world",
|
||||
InstallerFile: installer,
|
||||
StorageID: "storage1",
|
||||
Filename: "file1",
|
||||
Title: "file1",
|
||||
Version: "1.0",
|
||||
Source: "apps",
|
||||
UserID: user1.ID,
|
||||
TeamID: &team1.ID,
|
||||
ValidatedLabels: &fleet.LabelIdentsWithScope{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
policy2.SoftwareInstallerID = ptr.Uint(installer1ID)
|
||||
err = ds.SavePolicy(context.Background(), policy2, false, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
// create a host
|
||||
host, err := ds.NewHost(ctx, &fleet.Host{
|
||||
OsqueryHostID: ptr.String(uuid.New().String()),
|
||||
DetailUpdatedAt: time.Now(),
|
||||
LabelUpdatedAt: time.Now(),
|
||||
PolicyUpdatedAt: time.Now(),
|
||||
SeenTime: time.Now(),
|
||||
NodeKey: ptr.String(uuid.New().String()),
|
||||
UUID: uuid.New().String(),
|
||||
Hostname: "host" + t.Name(),
|
||||
TeamID: &team1.ID,
|
||||
Platform: "darwin",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// record a policy run for both policies
|
||||
err = ds.RecordPolicyQueryExecutions(ctx, host, map[uint]*bool{
|
||||
policy1.ID: ptr.Bool(true),
|
||||
policy2.ID: ptr.Bool(false), // software isn't installed on host, so Fleet should install it
|
||||
}, time.Now(), false)
|
||||
require.NoError(t, err)
|
||||
|
||||
hostPolicies, err := ds.ListPoliciesForHost(ctx, host)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, hostPolicies, 2)
|
||||
sort.Slice(hostPolicies, func(i, j int) bool {
|
||||
return hostPolicies[i].ID < hostPolicies[j].ID
|
||||
})
|
||||
require.Equal(t, hostPolicies[0].Response, "pass")
|
||||
require.Equal(t, hostPolicies[1].Response, "fail")
|
||||
|
||||
// clear status for automatic install policy
|
||||
err = ds.ClearAutoInstallPolicyStatusForHosts(ctx, installer1ID, []uint{host.ID})
|
||||
require.NoError(t, err)
|
||||
|
||||
// the status should be NULL for the automatic install policy but not the "regular" one
|
||||
hostPolicies, err = ds.ListPoliciesForHost(ctx, host)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, hostPolicies, 2)
|
||||
sort.Slice(hostPolicies, func(i, j int) bool {
|
||||
return hostPolicies[i].ID < hostPolicies[j].ID
|
||||
})
|
||||
require.Equal(t, hostPolicies[0].Response, "pass")
|
||||
require.Empty(t, hostPolicies[1].Response)
|
||||
}
|
||||
|
||||
@@ -1755,3 +1755,109 @@ func (ds *Datastore) IsSoftwareInstallerLabelScoped(ctx context.Context, install
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
const labelScopedFilter = `
|
||||
SELECT
|
||||
1
|
||||
FROM (
|
||||
-- no labels
|
||||
SELECT
|
||||
0 AS count_installer_labels,
|
||||
0 AS count_host_labels,
|
||||
0 AS count_host_updated_after_labels
|
||||
WHERE NOT EXISTS ( SELECT 1 FROM software_installer_labels sil WHERE sil.software_installer_id = ?)
|
||||
|
||||
UNION
|
||||
|
||||
-- include any
|
||||
SELECT
|
||||
COUNT(*) AS count_installer_labels,
|
||||
COUNT(lm.label_id) AS count_host_labels,
|
||||
0 AS count_host_updated_after_labels
|
||||
FROM
|
||||
software_installer_labels sil
|
||||
LEFT OUTER JOIN label_membership lm ON lm.label_id = sil.label_id
|
||||
AND lm.host_id = h.id
|
||||
WHERE
|
||||
sil.software_installer_id = ?
|
||||
AND sil.exclude = 0
|
||||
HAVING
|
||||
count_installer_labels > 0
|
||||
AND count_host_labels > 0
|
||||
|
||||
UNION
|
||||
|
||||
-- exclude any, ignore software that depends on labels created
|
||||
-- _after_ the label_updated_at timestamp of the host (because
|
||||
-- we don't have results for that label yet, the host may or may
|
||||
-- not be a member).
|
||||
SELECT
|
||||
COUNT(*) AS count_installer_labels,
|
||||
COUNT(lm.label_id) AS count_host_labels,
|
||||
SUM(
|
||||
CASE WHEN lbl.created_at IS NOT NULL
|
||||
AND(
|
||||
SELECT
|
||||
label_updated_at FROM hosts
|
||||
WHERE
|
||||
id = 1) >= lbl.created_at THEN
|
||||
1
|
||||
ELSE
|
||||
0
|
||||
END) AS count_host_updated_after_labels
|
||||
FROM
|
||||
software_installer_labels sil
|
||||
LEFT OUTER JOIN labels lbl ON lbl.id = sil.label_id
|
||||
LEFT OUTER JOIN label_membership lm ON lm.label_id = sil.label_id
|
||||
AND lm.host_id = h.id
|
||||
WHERE
|
||||
sil.software_installer_id = ?
|
||||
AND sil.exclude = 1
|
||||
HAVING
|
||||
count_installer_labels > 0
|
||||
AND count_installer_labels = count_host_updated_after_labels
|
||||
AND count_host_labels = 0) t`
|
||||
|
||||
func (ds *Datastore) GetIncludedHostIDMapForSoftwareInstaller(ctx context.Context, installerID uint) (map[uint]struct{}, error) {
|
||||
stmt := fmt.Sprintf(`SELECT
|
||||
h.id
|
||||
FROM
|
||||
hosts h
|
||||
WHERE
|
||||
EXISTS (%s)
|
||||
`, labelScopedFilter)
|
||||
|
||||
var hostIDs []uint
|
||||
if err := sqlx.SelectContext(ctx, ds.reader(ctx), &hostIDs, stmt, installerID, installerID, installerID); err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "listing hosts included in software installer scope")
|
||||
}
|
||||
|
||||
res := make(map[uint]struct{}, len(hostIDs))
|
||||
for _, id := range hostIDs {
|
||||
res[id] = struct{}{}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) GetExcludedHostIDMapForSoftwareInstaller(ctx context.Context, installerID uint) (map[uint]struct{}, error) {
|
||||
stmt := fmt.Sprintf(`SELECT
|
||||
h.id
|
||||
FROM
|
||||
hosts h
|
||||
WHERE
|
||||
NOT EXISTS (%s)
|
||||
`, labelScopedFilter)
|
||||
|
||||
var hostIDs []uint
|
||||
if err := sqlx.SelectContext(ctx, ds.reader(ctx), &hostIDs, stmt, installerID, installerID, installerID); err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "listing hosts excluded from software installer scope")
|
||||
}
|
||||
|
||||
res := make(map[uint]struct{}, len(hostIDs))
|
||||
for _, id := range hostIDs {
|
||||
res[id] = struct{}{}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
@@ -5321,6 +5321,10 @@ func testListHostSoftwareWithLabelScoping(t *testing.T, ds *Datastore) {
|
||||
require.NoError(t, err)
|
||||
require.True(t, scoped)
|
||||
|
||||
hostsInScope, err := ds.GetIncludedHostIDMapForSoftwareInstaller(ctx, installerID1)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, hostsInScope, host.ID)
|
||||
|
||||
label1, err := ds.NewLabel(ctx, &fleet.Label{Name: "label1" + t.Name()})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -5343,6 +5347,10 @@ func testListHostSoftwareWithLabelScoping(t *testing.T, ds *Datastore) {
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, software)
|
||||
|
||||
hostsNotInScope, err := ds.GetExcludedHostIDMapForSoftwareInstaller(ctx, installerID1)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, hostsNotInScope, host.ID)
|
||||
|
||||
// installer1 should be out of scope since the label is "exclude any"
|
||||
scoped, err = ds.IsSoftwareInstallerLabelScoped(ctx, installerID1, host.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1706,6 +1706,18 @@ type Datastore interface {
|
||||
// Software installers
|
||||
//
|
||||
|
||||
// GetIncludedHostIDMapForSoftwareInstaller gets the set of hosts that are targeted/in scope for the
|
||||
// given software installer, based label membership.
|
||||
GetIncludedHostIDMapForSoftwareInstaller(ctx context.Context, installerID uint) (map[uint]struct{}, error)
|
||||
|
||||
// GetExcludedHostIDMapForSoftwareInstaller gets the set of hosts that are NOT targeted/in scope for the
|
||||
// given software installer, based label membership.
|
||||
GetExcludedHostIDMapForSoftwareInstaller(ctx context.Context, installerID uint) (map[uint]struct{}, error)
|
||||
|
||||
// ClearAutoInstallPolicyStatusForHosts clears out the status of the policy related to the given
|
||||
// software installer for all the given hosts.
|
||||
ClearAutoInstallPolicyStatusForHosts(ctx context.Context, installerID uint, hostIDs []uint) error
|
||||
|
||||
// GetSoftwareInstallDetails returns details required to fetch and
|
||||
// run software installers
|
||||
GetSoftwareInstallDetails(ctx context.Context, executionId string) (*SoftwareInstallDetails, error)
|
||||
|
||||
@@ -1077,6 +1077,12 @@ type WipeHostViaWindowsMDMFunc func(ctx context.Context, host *fleet.Host, cmd *
|
||||
|
||||
type UpdateHostLockWipeStatusFromAppleMDMResultFunc func(ctx context.Context, hostUUID string, cmdUUID string, requestType string, succeeded bool) error
|
||||
|
||||
type GetIncludedHostIDMapForSoftwareInstallerFunc func(ctx context.Context, installerID uint) (map[uint]struct{}, error)
|
||||
|
||||
type GetExcludedHostIDMapForSoftwareInstallerFunc func(ctx context.Context, installerID uint) (map[uint]struct{}, error)
|
||||
|
||||
type ClearAutoInstallPolicyStatusForHostsFunc func(ctx context.Context, installerID uint, hostIDs []uint) error
|
||||
|
||||
type GetSoftwareInstallDetailsFunc func(ctx context.Context, executionId string) (*fleet.SoftwareInstallDetails, error)
|
||||
|
||||
type ListPendingSoftwareInstallsFunc func(ctx context.Context, hostID uint) ([]string, error)
|
||||
@@ -2772,6 +2778,15 @@ type DataStore struct {
|
||||
UpdateHostLockWipeStatusFromAppleMDMResultFunc UpdateHostLockWipeStatusFromAppleMDMResultFunc
|
||||
UpdateHostLockWipeStatusFromAppleMDMResultFuncInvoked bool
|
||||
|
||||
GetIncludedHostIDMapForSoftwareInstallerFunc GetIncludedHostIDMapForSoftwareInstallerFunc
|
||||
GetIncludedHostIDMapForSoftwareInstallerFuncInvoked bool
|
||||
|
||||
GetExcludedHostIDMapForSoftwareInstallerFunc GetExcludedHostIDMapForSoftwareInstallerFunc
|
||||
GetExcludedHostIDMapForSoftwareInstallerFuncInvoked bool
|
||||
|
||||
ClearAutoInstallPolicyStatusForHostsFunc ClearAutoInstallPolicyStatusForHostsFunc
|
||||
ClearAutoInstallPolicyStatusForHostsFuncInvoked bool
|
||||
|
||||
GetSoftwareInstallDetailsFunc GetSoftwareInstallDetailsFunc
|
||||
GetSoftwareInstallDetailsFuncInvoked bool
|
||||
|
||||
@@ -6636,6 +6651,27 @@ func (s *DataStore) UpdateHostLockWipeStatusFromAppleMDMResult(ctx context.Conte
|
||||
return s.UpdateHostLockWipeStatusFromAppleMDMResultFunc(ctx, hostUUID, cmdUUID, requestType, succeeded)
|
||||
}
|
||||
|
||||
func (s *DataStore) GetIncludedHostIDMapForSoftwareInstaller(ctx context.Context, installerID uint) (map[uint]struct{}, error) {
|
||||
s.mu.Lock()
|
||||
s.GetIncludedHostIDMapForSoftwareInstallerFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.GetIncludedHostIDMapForSoftwareInstallerFunc(ctx, installerID)
|
||||
}
|
||||
|
||||
func (s *DataStore) GetExcludedHostIDMapForSoftwareInstaller(ctx context.Context, installerID uint) (map[uint]struct{}, error) {
|
||||
s.mu.Lock()
|
||||
s.GetExcludedHostIDMapForSoftwareInstallerFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.GetExcludedHostIDMapForSoftwareInstallerFunc(ctx, installerID)
|
||||
}
|
||||
|
||||
func (s *DataStore) ClearAutoInstallPolicyStatusForHosts(ctx context.Context, installerID uint, hostIDs []uint) error {
|
||||
s.mu.Lock()
|
||||
s.ClearAutoInstallPolicyStatusForHostsFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.ClearAutoInstallPolicyStatusForHostsFunc(ctx, installerID, hostIDs)
|
||||
}
|
||||
|
||||
func (s *DataStore) GetSoftwareInstallDetails(ctx context.Context, executionId string) (*fleet.SoftwareInstallDetails, error) {
|
||||
s.mu.Lock()
|
||||
s.GetSoftwareInstallDetailsFuncInvoked = true
|
||||
|
||||
@@ -15306,6 +15306,215 @@ func (s *integrationEnterpriseTestSuite) TestPolicyAutomationsSoftwareInstallers
|
||||
require.NotNil(t, host1LastInstall)
|
||||
}
|
||||
|
||||
func (s *integrationEnterpriseTestSuite) TestPolicyAutomationLabelScopingRetrigger() {
|
||||
t := s.T()
|
||||
ctx := context.Background()
|
||||
host, err := s.ds.NewHost(context.Background(), &fleet.Host{
|
||||
DetailUpdatedAt: time.Now(),
|
||||
LabelUpdatedAt: time.Now(),
|
||||
PolicyUpdatedAt: time.Now(),
|
||||
SeenTime: time.Now().Add(-1 * time.Minute),
|
||||
OsqueryHostID: ptr.String(t.Name()),
|
||||
NodeKey: ptr.String(t.Name()),
|
||||
UUID: uuid.New().String(),
|
||||
Hostname: fmt.Sprintf("%sfoo.local", t.Name()),
|
||||
Platform: "linux",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
orbitKey := setOrbitEnrollment(t, host, s.ds)
|
||||
host.OrbitNodeKey = &orbitKey
|
||||
|
||||
// Create a few labels
|
||||
var newLabelResp createLabelResponse
|
||||
s.DoJSON("POST", "/api/v1/fleet/labels", fleet.LabelPayload{
|
||||
Name: uuid.NewString(),
|
||||
Query: "SELECT 1",
|
||||
}, http.StatusOK, &newLabelResp)
|
||||
lbl1 := newLabelResp.Label
|
||||
|
||||
newLabelResp = createLabelResponse{}
|
||||
s.DoJSON("POST", "/api/v1/fleet/labels", fleet.LabelPayload{
|
||||
Name: uuid.NewString(),
|
||||
Query: "SELECT 2",
|
||||
}, http.StatusOK, &newLabelResp)
|
||||
lbl2 := newLabelResp.Label
|
||||
|
||||
newLabelResp = createLabelResponse{}
|
||||
s.DoJSON("POST", "/api/v1/fleet/labels", fleet.LabelPayload{
|
||||
Name: uuid.NewString(),
|
||||
Query: "SELECT 3",
|
||||
}, http.StatusOK, &newLabelResp)
|
||||
lbl3 := newLabelResp.Label
|
||||
|
||||
// Add label1 and label2 to the host
|
||||
err = s.ds.RecordLabelQueryExecutions(context.Background(), host, map[uint]*bool{lbl1.ID: ptr.Bool(true), lbl2.ID: ptr.Bool(true)}, time.Now(), false)
|
||||
require.NoError(t, err)
|
||||
|
||||
// upload software. Add label1 and label3 as "exclude any" labels.
|
||||
rubyPayload := &fleet.UploadSoftwareInstallerPayload{
|
||||
InstallScript: "some deb install script",
|
||||
Filename: "ruby.deb",
|
||||
TeamID: nil,
|
||||
LabelsIncludeAny: []string{lbl1.Name, lbl3.Name},
|
||||
Platform: "linux",
|
||||
}
|
||||
s.uploadSoftwareInstaller(t, rubyPayload, http.StatusOK, "")
|
||||
|
||||
// Get software title ID of the uploaded installer.
|
||||
resp := listSoftwareTitlesResponse{}
|
||||
s.DoJSON(
|
||||
"GET", "/api/latest/fleet/software/titles",
|
||||
listSoftwareTitlesRequest{},
|
||||
http.StatusOK, &resp,
|
||||
"query", "ruby",
|
||||
"team_id", "0",
|
||||
)
|
||||
require.Len(t, resp.SoftwareTitles, 1)
|
||||
require.NotNil(t, resp.SoftwareTitles[0].SoftwarePackage)
|
||||
rubyDebTitleID := resp.SoftwareTitles[0].ID
|
||||
|
||||
var rubyDetail getSoftwareTitleResponse
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/software/titles/%d", rubyDebTitleID), nil, http.StatusOK, &rubyDetail)
|
||||
require.NotNil(t, rubyDetail.SoftwareTitle)
|
||||
require.NotNil(t, rubyDetail.SoftwareTitle.SoftwarePackage)
|
||||
rubyInstallerID := rubyDetail.SoftwareTitle.SoftwarePackage.InstallerID
|
||||
|
||||
policy1, err := s.ds.NewTeamPolicy(ctx, 0, nil, fleet.PolicyPayload{
|
||||
Name: "policy1",
|
||||
Query: "SELECT 1;",
|
||||
Platform: "linux",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
mtplr := modifyTeamPolicyResponse{}
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/0/policies/%d", policy1.ID), modifyTeamPolicyRequest{
|
||||
ModifyPolicyPayload: fleet.ModifyPolicyPayload{
|
||||
SoftwareTitleID: optjson.Any[uint]{Set: true, Valid: true, Value: rubyDebTitleID},
|
||||
},
|
||||
}, http.StatusOK, &mtplr)
|
||||
|
||||
// No install attempt yet
|
||||
host1LastInstall, err := s.ds.GetHostLastInstallData(ctx, host.ID, rubyInstallerID)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, host1LastInstall)
|
||||
|
||||
// Send back a failed result for the policy.
|
||||
distributedResp := submitDistributedQueryResultsResponse{}
|
||||
s.DoJSONWithoutAuth("POST", "/api/osquery/distributed/write", genDistributedReqWithPolicyResults(
|
||||
host,
|
||||
map[uint]*bool{
|
||||
policy1.ID: ptr.Bool(false),
|
||||
},
|
||||
), http.StatusOK, &distributedResp)
|
||||
err = s.ds.UpdateHostPolicyCounts(ctx)
|
||||
require.NoError(t, err)
|
||||
policy1, err = s.ds.Policy(ctx, policy1.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Because the installer is in scope, the policy is failing
|
||||
require.Equal(t, uint(0), policy1.PassingHostCount)
|
||||
require.Equal(t, uint(1), policy1.FailingHostCount)
|
||||
|
||||
// We've triggered an installation attempt
|
||||
host1LastInstall, err = s.ds.GetHostLastInstallData(ctx, host.ID, rubyInstallerID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, host1LastInstall)
|
||||
|
||||
// Update the installer's labels to "exclude any". This de-scopes the software.
|
||||
s.updateSoftwareInstaller(t, &fleet.UpdateSoftwareInstallerPayload{
|
||||
InstallScript: ptr.String("some install script"),
|
||||
PreInstallQuery: ptr.String("some pre install query"),
|
||||
PostInstallScript: ptr.String("some post install script"),
|
||||
Filename: "ruby.deb",
|
||||
TitleID: rubyDebTitleID,
|
||||
TeamID: nil,
|
||||
LabelsExcludeAny: []string{lbl1.Name, lbl3.Name},
|
||||
}, http.StatusOK, "")
|
||||
|
||||
// The update should clear out the installation attempt
|
||||
host1LastInstall, err = s.ds.GetHostLastInstallData(ctx, host.ID, rubyInstallerID)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, host1LastInstall)
|
||||
|
||||
// Update the installer's labels to be "include any" again. The software is now back in scope.
|
||||
s.updateSoftwareInstaller(t, &fleet.UpdateSoftwareInstallerPayload{
|
||||
InstallScript: ptr.String("some install script"),
|
||||
PreInstallQuery: ptr.String("some pre install query"),
|
||||
PostInstallScript: ptr.String("some post install script"),
|
||||
Filename: "ruby.deb",
|
||||
TitleID: rubyDebTitleID,
|
||||
TeamID: nil,
|
||||
LabelsIncludeAny: []string{lbl1.Name, lbl3.Name},
|
||||
}, http.StatusOK, "")
|
||||
|
||||
// Simulate a failure of the policy
|
||||
s.DoJSONWithoutAuth("POST", "/api/osquery/distributed/write", genDistributedReqWithPolicyResults(
|
||||
host,
|
||||
map[uint]*bool{
|
||||
policy1.ID: ptr.Bool(false),
|
||||
},
|
||||
), http.StatusOK, &distributedResp)
|
||||
err = s.ds.UpdateHostPolicyCounts(ctx)
|
||||
require.NoError(t, err)
|
||||
policy1, err = s.ds.Policy(ctx, policy1.ID)
|
||||
require.NoError(t, err)
|
||||
// Because the installer is in scope, the policy should be failing again
|
||||
require.Equal(t, uint(0), policy1.PassingHostCount)
|
||||
require.Equal(t, uint(1), policy1.FailingHostCount)
|
||||
|
||||
// We have an installation attempt again; the policy automation has been re-triggered
|
||||
host1LastInstall, err = s.ds.GetHostLastInstallData(ctx, host.ID, rubyInstallerID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, host1LastInstall)
|
||||
|
||||
// Update the include any labels. The host doesn't have label2, so this means that the software
|
||||
// moved out of scope.
|
||||
s.updateSoftwareInstaller(t, &fleet.UpdateSoftwareInstallerPayload{
|
||||
InstallScript: ptr.String("some install script"),
|
||||
PreInstallQuery: ptr.String("some pre install query"),
|
||||
PostInstallScript: ptr.String("some post install script"),
|
||||
Filename: "ruby.deb",
|
||||
TitleID: rubyDebTitleID,
|
||||
TeamID: nil,
|
||||
LabelsIncludeAny: []string{lbl2.Name},
|
||||
}, http.StatusOK, "")
|
||||
|
||||
host1LastInstall, err = s.ds.GetHostLastInstallData(ctx, host.ID, rubyInstallerID)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, host1LastInstall)
|
||||
|
||||
// Update to exclude any with label 2. This moves the software back into scope. The policy
|
||||
// automation should re-trigger.
|
||||
s.updateSoftwareInstaller(t, &fleet.UpdateSoftwareInstallerPayload{
|
||||
InstallScript: ptr.String("some install script"),
|
||||
PreInstallQuery: ptr.String("some pre install query"),
|
||||
PostInstallScript: ptr.String("some post install script"),
|
||||
Filename: "ruby.deb",
|
||||
TitleID: rubyDebTitleID,
|
||||
TeamID: nil,
|
||||
LabelsExcludeAny: []string{lbl2.Name},
|
||||
}, http.StatusOK, "")
|
||||
|
||||
s.DoJSONWithoutAuth("POST", "/api/osquery/distributed/write", genDistributedReqWithPolicyResults(
|
||||
host,
|
||||
map[uint]*bool{
|
||||
policy1.ID: ptr.Bool(false),
|
||||
},
|
||||
), http.StatusOK, &distributedResp)
|
||||
err = s.ds.UpdateHostPolicyCounts(ctx)
|
||||
require.NoError(t, err)
|
||||
policy1, err = s.ds.Policy(ctx, policy1.ID)
|
||||
require.NoError(t, err)
|
||||
// Because the installer is in scope, the policy should be failing again.
|
||||
require.Equal(t, uint(0), policy1.PassingHostCount)
|
||||
require.Equal(t, uint(1), policy1.FailingHostCount)
|
||||
|
||||
// We have an installation attempt again.
|
||||
host1LastInstall, err = s.ds.GetHostLastInstallData(ctx, host.ID, rubyInstallerID)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, host1LastInstall)
|
||||
}
|
||||
|
||||
func (s *integrationEnterpriseTestSuite) TestPolicyAutomationsScripts() {
|
||||
t := s.T()
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -692,6 +692,16 @@ func (ts *withServer) updateSoftwareInstaller(
|
||||
require.NoError(t, w.WriteField("self_service", "false"))
|
||||
}
|
||||
}
|
||||
if payload.LabelsIncludeAny != nil {
|
||||
for _, l := range payload.LabelsIncludeAny {
|
||||
require.NoError(t, w.WriteField("labels_include_any", l))
|
||||
}
|
||||
}
|
||||
if payload.LabelsExcludeAny != nil {
|
||||
for _, l := range payload.LabelsExcludeAny {
|
||||
require.NoError(t, w.WriteField("labels_exclude_any", l))
|
||||
}
|
||||
}
|
||||
|
||||
w.Close()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user