Remote Wipe: implement transition of "wiped" back to "unlocked" after re-enrollment (#17217)

This commit is contained in:
Martin Angers 2024-02-28 09:48:26 -05:00 committed by GitHub
parent b692d7fa05
commit 1710e1c8ef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 75 additions and 2 deletions

View file

@ -114,6 +114,8 @@ func (svc *Service) LockHost(ctx context.Context, hostID uint) error {
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Host has pending unlock request. Host cannot be locked again until unlock is complete."))
case lockWipe.IsPendingWipe():
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Host has pending wipe request. Cannot process lock requests once host is wiped."))
case lockWipe.IsWiped():
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Host is wiped. Cannot process lock requests once host is wiped."))
case lockWipe.IsLocked():
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Host is already locked.").WithStatus(http.StatusConflict))
}
@ -186,6 +188,8 @@ func (svc *Service) UnlockHost(ctx context.Context, hostID uint) (string, error)
return "", ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Host has pending unlock request. The host will unlock when it comes online."))
case lockWipe.IsPendingWipe():
return "", ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Host has pending wipe request. Cannot process unlock requests once host is wiped."))
case lockWipe.IsWiped():
return "", ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Host is wiped. Cannot process unlock requests once host is wiped."))
case lockWipe.IsUnlocked():
return "", ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Host is already unlocked.").WithStatus(http.StatusConflict))
}
@ -275,6 +279,8 @@ func (svc *Service) WipeHost(ctx context.Context, hostID uint) error {
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Host has pending unlock request. Host cannot be wiped until unlock is complete."))
case lockWipe.IsPendingWipe():
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Host has pending wipe request. The host will be wiped when it comes online."))
case lockWipe.IsLocked():
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Host is locked. Host cannot be wiped when it is locked."))
case lockWipe.IsWiped():
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("host_id", "Host is already wiped.").WithStatus(http.StatusConflict))
}

View file

@ -653,6 +653,11 @@ func updateMDMAppleHostDB(
return ctxerr.Wrap(ctx, err, "update mdm apple host")
}
// clear any host_mdm_actions following re-enrollment here
if _, err := tx.ExecContext(ctx, `DELETE FROM host_mdm_actions WHERE host_id = ?`, hostID); err != nil {
return ctxerr.Wrap(ctx, err, "error clearing mdm apple host_mdm_actions")
}
if err := upsertMDMAppleHostMDMInfoDB(ctx, tx, appCfg.ServerSettings, false, hostID); err != nil {
return ctxerr.Wrap(ctx, err, "ingest mdm apple host upsert MDM info")
}

View file

@ -1506,9 +1506,9 @@ func filterHostsByVulnerability(sqlstmt string, opt fleet.HostListOptions, param
SELECT hs.host_id FROM host_software hs
JOIN software_cve sc ON sc.software_id = hs.software_id
WHERE sc.cve = ?
UNION
SELECT hos.host_id FROM host_operating_system hos
JOIN operating_system_vulnerabilities osv ON osv.operating_system_id = hos.os_id
WHERE osv.cve = ?)`
@ -1778,6 +1778,11 @@ func (ds *Datastore) EnrollOrbit(ctx context.Context, isMDMEnabled bool, hostInf
}
host.ID = hostID
// clear any host_mdm_actions following re-enrollment here
if _, err := tx.ExecContext(ctx, `DELETE FROM host_mdm_actions WHERE host_id = ?`, hostID); err != nil {
return ctxerr.Wrap(ctx, err, "orbit enroll error clearing host_mdm_actions")
}
case errors.Is(err, sql.ErrNoRows):
zeroTime := time.Unix(0, 0).Add(24 * time.Hour)
// Create new host record. We always create newly enrolled hosts with refetch_requested = true
@ -1900,6 +1905,11 @@ func (ds *Datastore) EnrollHost(ctx context.Context, isMDMEnabled bool, osqueryH
return ctxerr.Wrap(ctx, err, "cleanup policy membership on re-enroll")
}
// clear any host_mdm_actions following re-enrollment here
if _, err := tx.ExecContext(ctx, `DELETE FROM host_mdm_actions WHERE host_id = ?`, matchedID); err != nil {
return ctxerr.Wrap(ctx, err, "error clearing host_mdm_actions")
}
// Update existing host record
sqlUpdate := `
UPDATE hosts

View file

@ -11067,6 +11067,10 @@ func (s *integrationMDMTestSuite) TestLockUnlockWipeWindowsLinux() {
// try to lock the host again
s.Do("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/lock", host.ID), nil, http.StatusConflict)
// try to wipe a locked host
res = s.Do("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/wipe", host.ID), nil, http.StatusUnprocessableEntity)
errMsg = extractServerErrorText(res.Body)
require.Contains(t, errMsg, "Host cannot be wiped when it is locked.")
// unlock the host
s.Do("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/unlock", host.ID), nil, http.StatusNoContent)
@ -11176,10 +11180,35 @@ func (s *integrationMDMTestSuite) TestLockUnlockWipeWindowsLinux() {
require.NotNil(t, getHostResp.Host.MDM.PendingAction)
require.Equal(t, "", *getHostResp.Host.MDM.PendingAction)
// try to lock/unlock the host fails
res = s.Do("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/lock", host.ID), nil, http.StatusUnprocessableEntity)
errMsg = extractServerErrorText(res.Body)
require.Contains(t, errMsg, "Cannot process lock requests once host is wiped.")
res = s.Do("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/unlock", host.ID), nil, http.StatusUnprocessableEntity)
errMsg = extractServerErrorText(res.Body)
require.Contains(t, errMsg, "Cannot process unlock requests once host is wiped.")
// try to wipe the host again, conflict (already wiped)
s.Do("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/wipe", host.ID), nil, http.StatusConflict)
// no activity created
s.lastActivityOfTypeMatches(fleet.ActivityTypeWipedHost{}.ActivityName(), fmt.Sprintf(`{"host_id": %d, "host_display_name": %q}`, host.ID, host.DisplayName()), wipeActID)
// re-enroll the host, simulating that another user received the wiped host
newOrbitKey := uuid.New().String()
newHost, err := s.ds.EnrollOrbit(ctx, true, fleet.OrbitHostInfo{
HardwareUUID: *host.OsqueryHostID,
HardwareSerial: host.HardwareSerial,
}, newOrbitKey, nil)
require.NoError(t, err)
// it re-enrolled using the same host record
require.Equal(t, host.ID, newHost.ID)
// refresh the host's status, it is back to unlocked
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d", host.ID), nil, http.StatusOK, &getHostResp)
require.NotNil(t, getHostResp.Host.MDM.DeviceStatus)
require.Equal(t, "unlocked", *getHostResp.Host.MDM.DeviceStatus)
require.NotNil(t, getHostResp.Host.MDM.PendingAction)
require.Equal(t, "", *getHostResp.Host.MDM.PendingAction)
})
}
}
@ -11232,6 +11261,10 @@ func (s *integrationMDMTestSuite) TestLockUnlockWipeMacOS() {
// try to lock the host again
s.Do("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/lock", host.ID), nil, http.StatusConflict)
// try to wipe a locked host
res = s.Do("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/wipe", host.ID), nil, http.StatusUnprocessableEntity)
errMsg = extractServerErrorText(res.Body)
require.Contains(t, errMsg, "Host cannot be wiped when it is locked.")
// unlock the host
unlockResp = unlockHostResponse{}
@ -11304,10 +11337,29 @@ func (s *integrationMDMTestSuite) TestLockUnlockWipeMacOS() {
require.NotNil(t, getHostResp.Host.MDM.PendingAction)
require.Equal(t, "", *getHostResp.Host.MDM.PendingAction)
// try to lock/unlock the host fails
res = s.Do("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/lock", host.ID), nil, http.StatusUnprocessableEntity)
errMsg = extractServerErrorText(res.Body)
require.Contains(t, errMsg, "Cannot process lock requests once host is wiped.")
res = s.Do("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/unlock", host.ID), nil, http.StatusUnprocessableEntity)
errMsg = extractServerErrorText(res.Body)
require.Contains(t, errMsg, "Cannot process unlock requests once host is wiped.")
// try to wipe the host again, conflict (already wiped)
s.Do("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/wipe", host.ID), nil, http.StatusConflict)
// no activity created
s.lastActivityOfTypeMatches(fleet.ActivityTypeWipedHost{}.ActivityName(), fmt.Sprintf(`{"host_id": %d, "host_display_name": %q}`, host.ID, host.DisplayName()), wipeActID)
// re-enroll the host, simulating that another user received the wiped host
err = mdmClient.Enroll()
require.NoError(t, err)
// refresh the host's status, it is back to unlocked
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d", host.ID), nil, http.StatusOK, &getHostResp)
require.NotNil(t, getHostResp.Host.MDM.DeviceStatus)
require.Equal(t, "unlocked", *getHostResp.Host.MDM.DeviceStatus)
require.NotNil(t, getHostResp.Host.MDM.PendingAction)
require.Equal(t, "", *getHostResp.Host.MDM.PendingAction)
}
func (s *integrationMDMTestSuite) TestZCustomConfigurationWebURL() {