Add patch policies to software filtered policies (#41839)
Bug found while developing a policy related feature. <img width="1872" height="426" alt="Screenshot 2026-03-17 at 12 30 55 PM" src="https://github.com/user-attachments/assets/a97c52fe-5a60-4acf-920f-07622d54f56f" />
This commit is contained in:
@@ -2685,7 +2685,7 @@ func (ds *Datastore) createAutomationClause(ctx context.Context, automationFilte
|
||||
|
||||
switch automationFilter {
|
||||
case "software":
|
||||
return " AND (p.software_installer_id IS NOT NULL OR p.vpp_apps_teams_id IS NOT NULL)", nil, nil
|
||||
return " AND (p.software_installer_id IS NOT NULL OR p.vpp_apps_teams_id IS NOT NULL OR p.type = 'patch')", nil, nil
|
||||
case "scripts":
|
||||
return " AND p.script_id IS NOT NULL", nil, nil
|
||||
case "calendar":
|
||||
|
||||
@@ -7662,6 +7662,29 @@ func testTeamPolicyAutomationFilter(t *testing.T, ds *Datastore) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Add an FMA.
|
||||
fma, err := ds.UpsertMaintainedApp(ctx, &fleet.MaintainedApp{ID: 1})
|
||||
require.NoError(t, err)
|
||||
|
||||
payload := &fleet.UploadSoftwareInstallerPayload{
|
||||
InstallScript: "hello",
|
||||
PreInstallQuery: "SELECT 1",
|
||||
PostInstallScript: "world",
|
||||
StorageID: "storage1",
|
||||
Filename: "maintained1",
|
||||
Title: "Maintained1",
|
||||
Version: "1.0",
|
||||
Source: "apps",
|
||||
Platform: "darwin",
|
||||
BundleIdentifier: "fleet.maintained1",
|
||||
UserID: user1.ID,
|
||||
TeamID: nil,
|
||||
ValidatedLabels: &fleet.LabelIdentsWithScope{},
|
||||
FleetMaintainedAppID: &fma.ID,
|
||||
}
|
||||
_, titleID2, err := ds.MatchOrCreateSoftwareInstaller(context.Background(), payload)
|
||||
require.NoError(t, err)
|
||||
|
||||
test.CreateInsertGlobalVPPToken(t, ds)
|
||||
|
||||
// create team1 app
|
||||
@@ -7721,6 +7744,14 @@ func testTeamPolicyAutomationFilter(t *testing.T, ds *Datastore) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
teamPatchPolicy, err := ds.NewTeamPolicy(ctx, 0, nil, fleet.PolicyPayload{
|
||||
Name: "query 8",
|
||||
Query: "SELECT 1;",
|
||||
Type: fleet.PolicyTypePatch,
|
||||
PatchSoftwareTitleID: &titleID2,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// TODO: test ticket integration policies?
|
||||
|
||||
config := fleet.TeamConfig{}
|
||||
@@ -7735,7 +7766,7 @@ func testTeamPolicyAutomationFilter(t *testing.T, ds *Datastore) {
|
||||
}, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, merged, 7)
|
||||
require.Len(t, merged, 8)
|
||||
assert.Equal(t, gpol.ID, merged[0].ID)
|
||||
assert.Equal(t, teamInstallerPolicy.ID, merged[1].ID)
|
||||
assert.Equal(t, teamAppStorePolicy.ID, merged[2].ID)
|
||||
@@ -7743,6 +7774,7 @@ func testTeamPolicyAutomationFilter(t *testing.T, ds *Datastore) {
|
||||
assert.Equal(t, teamCalendarPolicy.ID, merged[4].ID)
|
||||
assert.Equal(t, teamConditionalPolicy.ID, merged[5].ID)
|
||||
assert.Equal(t, teamWebhookPolicy.ID, merged[6].ID)
|
||||
assert.Equal(t, teamPatchPolicy.ID, merged[7].ID)
|
||||
|
||||
// Test filters
|
||||
merged, err = ds.ListMergedTeamPolicies(ctx, 0, fleet.ListOptions{
|
||||
@@ -7750,9 +7782,10 @@ func testTeamPolicyAutomationFilter(t *testing.T, ds *Datastore) {
|
||||
OrderDirection: fleet.OrderAscending,
|
||||
}, "software")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, merged, 2)
|
||||
require.Len(t, merged, 3)
|
||||
assert.Equal(t, teamInstallerPolicy.ID, merged[0].ID)
|
||||
assert.Equal(t, teamAppStorePolicy.ID, merged[1].ID)
|
||||
assert.Equal(t, teamPatchPolicy.ID, merged[2].ID)
|
||||
|
||||
merged, err = ds.ListMergedTeamPolicies(ctx, 0, fleet.ListOptions{
|
||||
OrderKey: "name",
|
||||
@@ -7792,7 +7825,8 @@ func testTeamPolicyAutomationFilter(t *testing.T, ds *Datastore) {
|
||||
OrderDirection: fleet.OrderAscending,
|
||||
}, fleet.ListOptions{}, "software")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, policies, 2)
|
||||
require.Len(t, policies, 3)
|
||||
assert.Equal(t, teamInstallerPolicy.ID, policies[0].ID)
|
||||
assert.Equal(t, teamAppStorePolicy.ID, policies[1].ID)
|
||||
assert.Equal(t, teamPatchPolicy.ID, policies[2].ID)
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ type PolicyPayload struct {
|
||||
|
||||
// Type is the policy type. It is 'dynamic' by default and 'patch' for patch policies.
|
||||
Type string
|
||||
// PatchSoftwareTitleID is the title id of the Fleet maintained app chcked by a patch policy.
|
||||
// PatchSoftwareTitleID is the title id of the Fleet maintained app checked by a patch policy.
|
||||
//
|
||||
// Only applies to team policies with the patch type.
|
||||
PatchSoftwareTitleID *uint
|
||||
|
||||
@@ -1343,6 +1343,92 @@ func (s *integrationEnterpriseTestSuite) TestTeamPolicies() {
|
||||
require.Len(t, ts.Policies, 0)
|
||||
}
|
||||
|
||||
func (s *integrationEnterpriseTestSuite) TestListTeamPoliciesAutomationTypeSoftware() {
|
||||
t := s.T()
|
||||
ctx := context.Background()
|
||||
|
||||
// Create a team
|
||||
team, err := s.ds.NewTeam(ctx, &fleet.Team{
|
||||
Name: "team_automation_type_" + t.Name(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Upload a software installer for the software install policy
|
||||
payload := &fleet.UploadSoftwareInstallerPayload{
|
||||
InstallScript: "some install script",
|
||||
Filename: "ruby.deb",
|
||||
TeamID: &team.ID,
|
||||
}
|
||||
s.uploadSoftwareInstaller(t, payload, http.StatusOK, "")
|
||||
rubyTitleID := getSoftwareTitleID(t, s.ds, "ruby", "deb_packages")
|
||||
|
||||
// Upload a second software installer for the patch policy (supported for FMAs)
|
||||
payload2 := &fleet.UploadSoftwareInstallerPayload{
|
||||
InstallScript: "some install script",
|
||||
Filename: "dummy_installer.pkg",
|
||||
TeamID: &team.ID,
|
||||
}
|
||||
s.uploadSoftwareInstaller(t, payload2, http.StatusOK, "")
|
||||
dummyTitleID := getSoftwareTitleID(t, s.ds, "DummyApp", "apps")
|
||||
// Create a Fleet Maintained App and associate it with the dummy installer
|
||||
mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error {
|
||||
res, err := q.ExecContext(ctx, `INSERT INTO fleet_maintained_apps (name, slug, platform, unique_identifier)
|
||||
VALUES ('DummyApp', 'dummy-autofilter/darwin', 'darwin', 'com.example.dummy-autofilter')`)
|
||||
require.NoError(t, err)
|
||||
id, err := res.LastInsertId()
|
||||
require.NoError(t, err)
|
||||
_, err = q.ExecContext(ctx, `UPDATE software_installers SET fleet_maintained_app_id = ? WHERE global_or_team_id = ? AND title_id = ?`, id, team.ID, dummyTitleID)
|
||||
require.NoError(t, err)
|
||||
return nil
|
||||
})
|
||||
|
||||
// Create a regular policy (no automation)
|
||||
regularPolicy := teamPolicyResponse{}
|
||||
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/fleets/%d/policies", team.ID), teamPolicyRequest{
|
||||
Name: "regular policy",
|
||||
Query: "SELECT 1;",
|
||||
}, http.StatusOK, ®ularPolicy)
|
||||
|
||||
// Create a software install policy
|
||||
installPolicy := teamPolicyResponse{}
|
||||
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/fleets/%d/policies", team.ID), teamPolicyRequest{
|
||||
Name: "install software policy",
|
||||
Query: "SELECT 1 FROM some_table;",
|
||||
SoftwareTitleID: &rubyTitleID,
|
||||
}, http.StatusOK, &installPolicy)
|
||||
require.NotNil(t, installPolicy.Policy.InstallSoftware)
|
||||
|
||||
// Create a patch policy
|
||||
patchPolicy := teamPolicyResponse{}
|
||||
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/fleets/%d/policies", team.ID), teamPolicyRequest{
|
||||
Type: ptr.String("patch"),
|
||||
PatchSoftwareTitleID: &dummyTitleID,
|
||||
}, http.StatusOK, &patchPolicy)
|
||||
require.NotNil(t, patchPolicy.Policy.PatchSoftware)
|
||||
require.Equal(t, fleet.PolicyTypePatch, patchPolicy.Policy.Type)
|
||||
|
||||
// List all policies (no filter) - should return all 3
|
||||
listResp := listTeamPoliciesResponse{}
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/fleets/%d/policies", team.ID), nil, http.StatusOK, &listResp)
|
||||
require.Len(t, listResp.Policies, 3)
|
||||
|
||||
// List with automation_type=software - should return install policy and patch policy only
|
||||
listResp = listTeamPoliciesResponse{}
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/fleets/%d/policies", team.ID), nil, http.StatusOK, &listResp, "automation_type", "software")
|
||||
require.Len(t, listResp.Policies, 2)
|
||||
policyIDs := []uint{listResp.Policies[0].ID, listResp.Policies[1].ID}
|
||||
assert.Contains(t, policyIDs, installPolicy.Policy.ID)
|
||||
assert.Contains(t, policyIDs, patchPolicy.Policy.ID)
|
||||
|
||||
// List with merge_inherited and automation_type=software
|
||||
listResp = listTeamPoliciesResponse{}
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/fleets/%d/policies", team.ID), nil, http.StatusOK, &listResp, "merge_inherited", "true", "automation_type", "software")
|
||||
require.Len(t, listResp.Policies, 2)
|
||||
policyIDs = []uint{listResp.Policies[0].ID, listResp.Policies[1].ID}
|
||||
assert.Contains(t, policyIDs, installPolicy.Policy.ID)
|
||||
assert.Contains(t, policyIDs, patchPolicy.Policy.ID)
|
||||
}
|
||||
|
||||
func (s *integrationEnterpriseTestSuite) TestNoTeamPolicies() {
|
||||
t := s.T()
|
||||
ctx := context.Background()
|
||||
|
||||
Reference in New Issue
Block a user