mirror of
https://github.com/fleetdm/fleet
synced 2026-05-23 08:58:41 +00:00
Prevent installing on pending host+installer (#21722)
#21428 Figma: https://www.figma.com/design/4pfUOYy7IyMIrjMH2fuCdU/%2319551-Policy-automations%3A-install-software?node-id=5871-12100&t=pKh926u8a30iYFBA-4 - [X] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Committing-Changes.md#changes-files) for more information. - [X] Added/updated tests - [X] Manual QA for all new/changed functionality
This commit is contained in:
parent
ee7b05cb26
commit
5f2eaefabd
3 changed files with 61 additions and 5 deletions
1
changes/21428-prevent-install-when-already-pending
Normal file
1
changes/21428-prevent-install-when-already-pending
Normal file
|
|
@ -0,0 +1 @@
|
|||
* Added validation to `POST /api/_version_/fleet/hosts/{host_id}/software/install/{software_title_id}` to prevent installing on a host that already has a pending installation for that software title.
|
||||
|
|
@ -385,6 +385,24 @@ func (svc *Service) InstallSoftwareTitle(ctx context.Context, hostID uint, softw
|
|||
|
||||
// if we found an installer, use that
|
||||
if installer != nil {
|
||||
lastInstallRequest, err := svc.ds.GetHostLastInstallData(ctx, host.ID, installer.InstallerID)
|
||||
if err != nil {
|
||||
return ctxerr.Wrapf(ctx, err, "getting last install data for host %d and installer %d", host.ID, installer.InstallerID)
|
||||
}
|
||||
if lastInstallRequest != nil && lastInstallRequest.Status != nil && *lastInstallRequest.Status == fleet.SoftwareInstallerPending {
|
||||
return &fleet.BadRequestError{
|
||||
Message: "Couldn't install software. Host has a pending install request.",
|
||||
InternalErr: ctxerr.WrapWithData(
|
||||
ctx, err, "host already has a pending install for this installer",
|
||||
map[string]any{
|
||||
"host_id": host.ID,
|
||||
"software_installer_id": installer.InstallerID,
|
||||
"team_id": host.TeamID,
|
||||
"title_id": softwareTitleID,
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
return svc.installSoftwareTitleUsingInstaller(ctx, host, installer)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11117,7 +11117,7 @@ func (s *integrationEnterpriseTestSuite) TestHostSoftwareInstallResult() {
|
|||
|
||||
host := createOrbitEnrolledHost(t, "linux", "", s.ds)
|
||||
|
||||
// create a software installer and some host install requests
|
||||
// Create software installers and corresponding host install requests.
|
||||
payload := &fleet.UploadSoftwareInstallerPayload{
|
||||
InstallScript: "install script",
|
||||
PreInstallQuery: "pre install query",
|
||||
|
|
@ -11127,6 +11127,24 @@ func (s *integrationEnterpriseTestSuite) TestHostSoftwareInstallResult() {
|
|||
}
|
||||
s.uploadSoftwareInstaller(payload, http.StatusOK, "")
|
||||
titleID := getSoftwareTitleID(t, s.ds, payload.Title, "deb_packages")
|
||||
payload2 := &fleet.UploadSoftwareInstallerPayload{
|
||||
InstallScript: "install script 2",
|
||||
PreInstallQuery: "pre install query 2",
|
||||
PostInstallScript: "post install script 2",
|
||||
Filename: "vim.deb",
|
||||
Title: "vim",
|
||||
}
|
||||
s.uploadSoftwareInstaller(payload2, http.StatusOK, "")
|
||||
titleID2 := getSoftwareTitleID(t, s.ds, payload2.Title, "deb_packages")
|
||||
payload3 := &fleet.UploadSoftwareInstallerPayload{
|
||||
InstallScript: "install script 3",
|
||||
PreInstallQuery: "pre install query 3",
|
||||
PostInstallScript: "post install script 3",
|
||||
Filename: "emacs.deb",
|
||||
Title: "emacs",
|
||||
}
|
||||
s.uploadSoftwareInstaller(payload3, http.StatusOK, "")
|
||||
titleID3 := getSoftwareTitleID(t, s.ds, payload3.Title, "deb_packages")
|
||||
|
||||
latestInstallUUID := func() string {
|
||||
var id string
|
||||
|
|
@ -11138,9 +11156,10 @@ func (s *integrationEnterpriseTestSuite) TestHostSoftwareInstallResult() {
|
|||
|
||||
// create some install requests for the host
|
||||
installUUIDs := make([]string, 3)
|
||||
titleIDs := []uint{titleID, titleID2, titleID3}
|
||||
for i := 0; i < len(installUUIDs); i++ {
|
||||
resp := installSoftwareResponse{}
|
||||
s.DoJSON("POST", fmt.Sprintf("/api/v1/fleet/hosts/%d/software/install/%d", host.ID, titleID), nil, http.StatusAccepted, &resp)
|
||||
s.DoJSON("POST", fmt.Sprintf("/api/v1/fleet/hosts/%d/software/install/%d", host.ID, titleIDs[i]), nil, http.StatusAccepted, &resp)
|
||||
installUUIDs[i] = latestInstallUUID()
|
||||
}
|
||||
|
||||
|
|
@ -11203,7 +11222,14 @@ func (s *integrationEnterpriseTestSuite) TestHostSoftwareInstallResult() {
|
|||
Status: fleet.SoftwareInstallerFailed,
|
||||
PreInstallQueryOutput: ptr.String(fleet.SoftwareInstallerQueryFailCopy),
|
||||
})
|
||||
wantAct.InstallUUID = installUUIDs[1]
|
||||
wantAct = fleet.ActivityTypeInstalledSoftware{
|
||||
HostID: host.ID,
|
||||
HostDisplayName: host.DisplayName(),
|
||||
SoftwareTitle: payload2.Title,
|
||||
SoftwarePackage: payload2.Filename,
|
||||
InstallUUID: installUUIDs[1],
|
||||
Status: string(fleet.SoftwareInstallerFailed),
|
||||
}
|
||||
s.lastActivityOfTypeMatches(wantAct.ActivityName(), string(jsonMustMarshal(t, wantAct)), 0)
|
||||
|
||||
s.Do("POST", "/api/fleet/orbit/software_install/result",
|
||||
|
|
@ -11225,8 +11251,14 @@ func (s *integrationEnterpriseTestSuite) TestHostSoftwareInstallResult() {
|
|||
Output: ptr.String(fmt.Sprintf(fleet.SoftwareInstallerInstallSuccessCopy, "success")),
|
||||
PostInstallScriptOutput: ptr.String(fmt.Sprintf(fleet.SoftwareInstallerPostInstallSuccessCopy, "ok")),
|
||||
})
|
||||
wantAct.InstallUUID = installUUIDs[2]
|
||||
wantAct.Status = string(fleet.SoftwareInstallerInstalled)
|
||||
wantAct = fleet.ActivityTypeInstalledSoftware{
|
||||
HostID: host.ID,
|
||||
HostDisplayName: host.DisplayName(),
|
||||
SoftwareTitle: payload3.Title,
|
||||
SoftwarePackage: payload3.Filename,
|
||||
InstallUUID: installUUIDs[2],
|
||||
Status: string(fleet.SoftwareInstallerInstalled),
|
||||
}
|
||||
lastActID := s.lastActivityOfTypeMatches(wantAct.ActivityName(), string(jsonMustMarshal(t, wantAct)), 0)
|
||||
|
||||
// non-existing installation uuid
|
||||
|
|
@ -13073,6 +13105,11 @@ func (s *integrationEnterpriseTestSuite) TestPolicyAutomationsSoftwareInstallers
|
|||
require.Equal(t, fleet.SoftwareInstallerPending, *host1LastInstall.Status)
|
||||
prevExecutionID := host1LastInstall.ExecutionID
|
||||
|
||||
// Request a manual installation on the host for the same installer, which should fail.
|
||||
var installResp installSoftwareResponse
|
||||
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/software/install/%d",
|
||||
host1Team1.ID, dummyInstallerPkgTitleID), nil, http.StatusBadRequest, &installResp)
|
||||
|
||||
// Submit same results as before, which should not trigger a installation because the policy is already failing.
|
||||
distributedResp = submitDistributedQueryResultsResponse{}
|
||||
s.DoJSONWithoutAuth("POST", "/api/osquery/distributed/write", genDistributedReqWithPolicyResults(
|
||||
|
|
|
|||
Loading…
Reference in a new issue