clear enrollment from migration status on host when it is a new enrollment (#42553)

**Related issue:** Resolves #40076

This clears out the enrollment from migration status from the
`nano_enrollment` table if the device is going through a fresh
enrollment (aka not from an mdm migration)

# Checklist for submitter

- [ ] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
- [x] Added/updated automated tests
- [ ] QA'd all new/changed functionality manually

---------

Co-authored-by: Magnus Jensen <magnus@fleetdm.com>
This commit is contained in:
Gabriel Hernandez 2026-04-07 13:44:52 +01:00 committed by GitHub
parent 06b5f56870
commit 4f9c908102
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 46 additions and 0 deletions

View file

@ -0,0 +1 @@
- Fixed an issue where silent migration status would persist even after re-enrolling the device normally, causing SCEP renewal to fail.

View file

@ -5060,6 +5060,18 @@ func (ds *Datastore) MDMResetEnrollment(ctx context.Context, hostUUID string, sc
})
}
func (ds *Datastore) ClearHostEnrolledFromMigration(ctx context.Context, hostUUID string) error {
const stmt = `
UPDATE nano_enrollments
SET enrolled_from_migration = 0
WHERE id = ? AND enabled = 1 AND enrolled_from_migration = 1`
if _, err := ds.writer(ctx).ExecContext(ctx, stmt, hostUUID); err != nil {
return ctxerr.Wrap(ctx, err, "resetting enrolled_from_migration value")
}
return nil
}
// 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

View file

@ -1430,6 +1430,9 @@ type Datastore interface {
// information if a matching row for the host exists.
MDMResetEnrollment(ctx context.Context, hostUUID string, scepRenewalInProgress bool) error
// ClearHostEnrolledFromMigration clears the enrolled from migration status of a host
ClearHostEnrolledFromMigration(ctx context.Context, hostUUID string) error
// ListMDMAppleDEPSerialsInTeam returns a list of serial numbers of hosts
// that are enrolled or pending enrollment in Fleet's MDM via DEP for the
// specified team (or no team if teamID is nil).

View file

@ -989,6 +989,8 @@ type RestoreMDMApplePendingDEPHostFunc func(ctx context.Context, host *fleet.Hos
type MDMResetEnrollmentFunc func(ctx context.Context, hostUUID string, scepRenewalInProgress bool) error
type ClearHostEnrolledFromMigrationFunc func(ctx context.Context, hostUUID string) error
type ListMDMAppleDEPSerialsInTeamFunc func(ctx context.Context, teamID *uint) ([]string, error)
type ListMDMAppleDEPSerialsInHostIDsFunc func(ctx context.Context, hostIDs []uint) ([]string, error)
@ -3305,6 +3307,9 @@ type DataStore struct {
MDMResetEnrollmentFunc MDMResetEnrollmentFunc
MDMResetEnrollmentFuncInvoked bool
ClearHostEnrolledFromMigrationFunc ClearHostEnrolledFromMigrationFunc
ClearHostEnrolledFromMigrationFuncInvoked bool
ListMDMAppleDEPSerialsInTeamFunc ListMDMAppleDEPSerialsInTeamFunc
ListMDMAppleDEPSerialsInTeamFuncInvoked bool
@ -7988,6 +7993,13 @@ func (s *DataStore) MDMResetEnrollment(ctx context.Context, hostUUID string, sce
return s.MDMResetEnrollmentFunc(ctx, hostUUID, scepRenewalInProgress)
}
func (s *DataStore) ClearHostEnrolledFromMigration(ctx context.Context, hostUUID string) error {
s.mu.Lock()
s.ClearHostEnrolledFromMigrationFuncInvoked = true
s.mu.Unlock()
return s.ClearHostEnrolledFromMigrationFunc(ctx, hostUUID)
}
func (s *DataStore) ListMDMAppleDEPSerialsInTeam(ctx context.Context, teamID *uint) ([]string, error) {
s.mu.Lock()
s.ListMDMAppleDEPSerialsInTeamFuncInvoked = true

View file

@ -3590,6 +3590,11 @@ func (svc *MDMAppleCheckinAndCommandService) TokenUpdate(r *mdm.Request, m *mdm.
enqueueSetupExperienceItems := false
if m.AwaitingConfiguration {
// We are sure that the device is going through a new enrollment here, so we want to
// remove the enrolled from migration flag as it is no longer true.
if err := svc.ds.ClearHostEnrolledFromMigration(r.Context, r.ID); err != nil {
return ctxerr.Wrap(r.Context, err, "resetting enrolled from migration flag", "host_uuid", r.ID)
}
// Note that Setup Experience is only skipped for macOS during DEP migration. iOS and iPadOS will still get VPP apps
if info.MigrationInProgress && info.Platform == "darwin" {
svc.logger.InfoContext(r.Context, "skipping setup experience enqueueing because DEP migration is in progress", "host_uuid", r.ID)

View file

@ -1676,6 +1676,11 @@ func TestMDMTokenUpdate(t *testing.T) {
return true, nil
}
ds.ClearHostEnrolledFromMigrationFunc = func(ctx context.Context, hostUUID string) error {
require.Equal(t, uuid, hostUUID)
return nil
}
err = svc.TokenUpdate(
&mdm.Request{
Context: ctx,
@ -1693,6 +1698,7 @@ func TestMDMTokenUpdate(t *testing.T) {
)
require.NoError(t, err)
require.True(t, ds.EnqueueSetupExperienceItemsFuncInvoked)
require.True(t, ds.ClearHostEnrolledFromMigrationFuncInvoked)
ds.GetHostMDMCheckinInfoFunc = func(ct context.Context, hostUUID string) (*fleet.HostMDMCheckinInfo, error) {
require.Equal(t, uuid, hostUUID)
@ -1714,6 +1720,7 @@ func TestMDMTokenUpdate(t *testing.T) {
}
ds.EnqueueSetupExperienceItemsFuncInvoked = false
ds.ClearHostEnrolledFromMigrationFuncInvoked = false
err = svc.TokenUpdate(
&mdm.Request{
Context: ctx,
@ -1733,6 +1740,7 @@ func TestMDMTokenUpdate(t *testing.T) {
// Should NOT call the setup experience enqueue function but it should mark the migration complete
require.False(t, ds.EnqueueSetupExperienceItemsFuncInvoked)
require.True(t, ds.SetHostMDMMigrationCompletedFuncInvoked)
require.True(t, ds.ClearHostEnrolledFromMigrationFuncInvoked)
require.True(t, newActivityFuncInvoked)
ds.SetHostMDMMigrationCompletedFuncInvoked = false
@ -6622,6 +6630,10 @@ func TestMDMTokenUpdateSCEPRenewal(t *testing.T) {
ds.MDMResetEnrollmentFunc = func(ctx context.Context, hostUUID string, scepRenewalInProgress bool) error {
return nil
}
ds.ClearHostEnrolledFromMigrationFunc = func(ctx context.Context, hostUUID string) error {
require.Equal(t, uuid, hostUUID)
return nil
}
err := svc.TokenUpdate(
&mdm.Request{Context: ctx, EnrollID: &mdm.EnrollID{ID: uuid}},
@ -6640,6 +6652,7 @@ func TestMDMTokenUpdateSCEPRenewal(t *testing.T) {
require.True(t, ds.NewJobFuncInvoked)
require.True(t, newActivityFuncInvoked)
require.True(t, ds.MDMResetEnrollmentFuncInvoked)
require.True(t, ds.ClearHostEnrolledFromMigrationFuncInvoked)
})
t.Run("not awaiting configuration short-circuits", func(t *testing.T) {