mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #42799 When a macOS device acknowledges a lock command it can immediately send a trailing Idle check-in. CleanAppleMDMLock now requires that unlock_ref to be set at least 5 minutes ago before clearing the lock state, preventing that trailing Idle to prematurely clearing the MDM lock state.
This commit is contained in:
parent
8bc84d9a2d
commit
569d85340d
6 changed files with 48 additions and 6 deletions
1
changes/42799-option-to-unlock-not-available-afler-lock
Normal file
1
changes/42799-option-to-unlock-not-available-afler-lock
Normal file
|
|
@ -0,0 +1 @@
|
|||
* Fixed bug that cleared the MDM lock state if an "idle" message was received right after the lock ACK.
|
||||
|
|
@ -5038,17 +5038,25 @@ func (ds *Datastore) MDMResetEnrollment(ctx context.Context, hostUUID string, sc
|
|||
})
|
||||
}
|
||||
|
||||
// MDMLockCleanupMinutes is the minimum number of minutes that must have elapsed
|
||||
// since unlock_ref was set before CleanAppleMDMLock will clear the lock state.
|
||||
// This prevents the trailing Idle check-in (sent by the device right after
|
||||
// acknowledging the lock command) from prematurely clearing the lock state.
|
||||
const MDMLockCleanupMinutes = 5
|
||||
|
||||
func (ds *Datastore) CleanAppleMDMLock(ctx context.Context, hostUUID string) error {
|
||||
const stmt = `
|
||||
stmt := fmt.Sprintf(`
|
||||
UPDATE host_mdm_actions hma
|
||||
JOIN hosts h ON hma.host_id = h.id
|
||||
SET hma.unlock_ref = NULL,
|
||||
hma.lock_ref = NULL,
|
||||
hma.unlock_pin = NULL
|
||||
WHERE h.uuid = ? AND (
|
||||
(hma.unlock_ref IS NOT NULL AND hma.unlock_pin IS NOT NULL AND h.platform = 'darwin')
|
||||
(hma.unlock_ref IS NOT NULL AND hma.unlock_pin IS NOT NULL AND h.platform = 'darwin'
|
||||
AND (STR_TO_DATE(hma.unlock_ref, '%%Y-%%m-%%d %%H:%%i:%%s') IS NULL
|
||||
OR STR_TO_DATE(hma.unlock_ref, '%%Y-%%m-%%d %%H:%%i:%%s') <= UTC_TIMESTAMP() - INTERVAL %d MINUTE))
|
||||
OR (hma.unlock_ref IS NOT NULL AND (h.platform = 'ios' OR h.platform = 'ipados'))
|
||||
)`
|
||||
)`, MDMLockCleanupMinutes)
|
||||
|
||||
if _, err := ds.writer(ctx).ExecContext(ctx, stmt, hostUUID); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "cleaning up macOS lock")
|
||||
|
|
|
|||
|
|
@ -5577,6 +5577,21 @@ func testLockUnlockWipeMacOS(t *testing.T, ds *Datastore) {
|
|||
require.NoError(t, err)
|
||||
checkLockWipeState(t, status, false, true, false, false, false, false)
|
||||
|
||||
err = ds.CleanAppleMDMLock(ctx, host.UUID)
|
||||
require.NoError(t, err)
|
||||
status, err = ds.GetHostLockWipeStatus(ctx, host)
|
||||
require.NoError(t, err)
|
||||
checkLockWipeState(t, status, false, true, false, false, false, false)
|
||||
|
||||
// backdate unlock_ref to simulate the device having been locked for more than 5 minutes
|
||||
ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
|
||||
_, err := q.ExecContext(ctx,
|
||||
fmt.Sprintf(`UPDATE host_mdm_actions hma JOIN hosts h ON hma.host_id = h.id
|
||||
SET hma.unlock_ref = DATE_FORMAT(UTC_TIMESTAMP() - INTERVAL %d MINUTE, '%%Y-%%m-%%d %%H:%%i:%%s')
|
||||
WHERE h.uuid = ?`, MDMLockCleanupMinutes+1), host.UUID)
|
||||
return err
|
||||
})
|
||||
|
||||
// execute CleanAppleMDMLock to simulate successful unlock
|
||||
err = ds.CleanAppleMDMLock(ctx, host.UUID)
|
||||
require.NoError(t, err)
|
||||
|
|
|
|||
|
|
@ -2228,6 +2228,8 @@ func (ds *Datastore) WipeHostViaScript(ctx context.Context, request *fleet.HostS
|
|||
})
|
||||
}
|
||||
|
||||
// UnlockHostManually records a manual unlock request for the given host.
|
||||
// ts must be in UTC to ensure consistency with the STR_TO_DATE comparison in CleanAppleMDMLock.
|
||||
func (ds *Datastore) UnlockHostManually(ctx context.Context, hostID uint, hostFleetPlatform string, ts time.Time) error {
|
||||
const stmt = `
|
||||
INSERT INTO host_mdm_actions
|
||||
|
|
@ -2249,7 +2251,7 @@ func (ds *Datastore) UnlockHostManually(ctx context.Context, hostID uint, hostFl
|
|||
// from then on, the host is marked as "pending unlock" until the device is
|
||||
// actually unlocked with the PIN. The actual unlocking happens when the
|
||||
// device sends an Idle MDM request.
|
||||
unlockRef := ts.Format(time.DateTime)
|
||||
unlockRef := ts.UTC().Format(time.DateTime)
|
||||
_, err := ds.writer(ctx).ExecContext(ctx, stmt, hostID, unlockRef, hostFleetPlatform)
|
||||
return ctxerr.Wrap(ctx, err, "record manual unlock host request")
|
||||
}
|
||||
|
|
@ -2276,7 +2278,8 @@ func buildHostLockWipeStatusUpdateStmt(refCol string, succeeded bool, joinPart s
|
|||
// Currently only used for Apple MDM devices.
|
||||
// We set the unlock_ref to current time since the device can be unlocked any time after the lock.
|
||||
// Apple MDM does not have a concept of unlock pending.
|
||||
stmt += fmt.Sprintf("%sunlock_ref = '%s', %[1]swipe_ref = NULL", alias, time.Now().Format(time.DateTime))
|
||||
// UTC_TIMESTAMP() is used to ensure timezone consistency with the comparison in CleanAppleMDMLock.
|
||||
stmt += fmt.Sprintf("%sunlock_ref = UTC_TIMESTAMP(), %[1]swipe_ref = NULL", alias)
|
||||
}
|
||||
case "unlock_ref":
|
||||
// a successful unlock clears itself as well as the lock ref, because
|
||||
|
|
|
|||
|
|
@ -2124,7 +2124,10 @@ type Datastore interface {
|
|||
UnlockHostManually(ctx context.Context, hostID uint, hostFleetPlatform string, ts time.Time) error
|
||||
|
||||
// CleanAppleMDMLock cleans the lock status and pin for a macOS device
|
||||
// after it has been unlocked.
|
||||
// after it has been unlocked. CleanAppleMDMLock will be a no-op when
|
||||
// unlock_ref was set within the last 5 minutes, to prevent the trailing
|
||||
// Idle (sent right after the device acknowledges the lock command)
|
||||
// from prematurely clearing the lock state.
|
||||
CleanAppleMDMLock(ctx context.Context, hostUUID string) error
|
||||
|
||||
InsertHostLocationData(ctx context.Context, locData HostLocationData) error
|
||||
|
|
|
|||
|
|
@ -11,11 +11,13 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/pkg/mdm/mdmtest"
|
||||
"github.com/fleetdm/fleet/v4/server/datastore/mysql"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/mdm/nanodep/godep"
|
||||
mdmtesting "github.com/fleetdm/fleet/v4/server/mdm/testing_utils"
|
||||
"github.com/fleetdm/fleet/v4/server/ptr"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
|
@ -110,6 +112,16 @@ func (s *integrationMDMTestSuite) TestLockUnlockWipeMacOS() {
|
|||
fmt.Sprintf(`{"host_id": %d, "host_display_name": %q, "host_platform": %q}`, host.ID, host.DisplayName(), host.FleetPlatform()), 0)
|
||||
require.NotEqual(t, unlockActID, newUnlockActID)
|
||||
|
||||
// simulate passage of time: backdate unlock_ref so that CleanAppleMDMLock's
|
||||
// 5-minute guard doesn't block the upcoming Idle from clearing the lock state.
|
||||
mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error {
|
||||
_, err := q.ExecContext(context.Background(),
|
||||
fmt.Sprintf(`UPDATE host_mdm_actions hma JOIN hosts h ON hma.host_id = h.id
|
||||
SET hma.unlock_ref = DATE_FORMAT(UTC_TIMESTAMP() - INTERVAL %d MINUTE, '%%Y-%%m-%%d %%H:%%i:%%s')
|
||||
WHERE h.uuid = ?`, mysql.MDMLockCleanupMinutes+1), host.UUID)
|
||||
return err
|
||||
})
|
||||
|
||||
// as soon as the host sends an Idle MDM request, it is marked as unlocked
|
||||
cmd, err = mdmClient.Idle()
|
||||
require.NoError(t, err)
|
||||
|
|
|
|||
Loading…
Reference in a new issue