From 5f95d683ff6d2d119429d6135422609be4ada3de Mon Sep 17 00:00:00 2001 From: Jordan Montgomery Date: Wed, 15 Jul 2026 17:52:57 -0700 Subject: [PATCH] Speculative fix for iOS/iPadOS enrollment w/ required sw update (#49280) **Related issue:** Resolves #49260 This fix is based on direct discussion with Apple and consultation of the Apple docs. We are not currently sure if this is actually issue the customer is running into but we have identified it as an issue with our enrollment process. As far as testing my iPad running 18.7 gets prompted to update every time now when I enroll it with updates turned on. If I back up and don't take the upgrade and try to enroll again I get an error but I believe this is an Apple bug which we've been told was fixed in 26+ # Checklist for submitter If some of the following don't apply, delete the relevant line. - [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/guides/committing-changes.md#changes-files) for more information. - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters. - [x] Timeouts are implemented and retries are limited to avoid infinite loops - [x] If paths of existing endpoints are modified without backwards compatibility, checked the frontend/CLI for any necessary changes ## Testing - [x] Added/updated automated tests - [x] Where appropriate, [automated tests simulate multiple hosts and test for host isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing) (updates to one hosts's records do not affect another) - [x] QA'd all new/changed functionality manually ## Summary by CodeRabbit * **Bug Fixes** * Fixed an issue during Android Device Enrollment where devices assigned a mandatory software update could incorrectly show a failure status. * Resolved cases where the required software update was not displayed correctly. * Improved Apple device enrollment update details by reporting the operating system version consistently. --- changes/49260-swupdate | 1 + server/fleet/apple_mdm.go | 18 +++++++++--------- server/service/apple_mdm.go | 5 +---- server/service/apple_mdm_test.go | 9 ++------- server/service/integration_mdm_dep_test.go | 4 +--- 5 files changed, 14 insertions(+), 23 deletions(-) create mode 100644 changes/49260-swupdate diff --git a/changes/49260-swupdate b/changes/49260-swupdate new file mode 100644 index 0000000000..e10293cbce --- /dev/null +++ b/changes/49260-swupdate @@ -0,0 +1 @@ +* Fixed an issue where devices given a mandatory update during ADE enrollment might display a failure or fail to display the update diff --git a/server/fleet/apple_mdm.go b/server/fleet/apple_mdm.go index dd4e809801..b185b705f6 100644 --- a/server/fleet/apple_mdm.go +++ b/server/fleet/apple_mdm.go @@ -1379,10 +1379,15 @@ const MDMAppleSoftwareUpdateRequiredCode = "com.apple.softwareupdate.required" // MDMAppleSoftwareUpdateRequiredDetails is the [details][1] specified by Apple for the // required software update. // +// Apple's schema also defines an optional BuildVersion key, but we deliberately omit it: +// GDMF often publishes multiple concurrent builds of the same OS version that all list the +// same device, and pinning a build the device's software update client won't resolve makes +// the mandatory update fail during Setup Assistant. Sending only OSVersion lets the device +// pick the right build. +// // [1]: https://developer.apple.com/documentation/devicemanagement/errorcodesoftwareupdaterequired/details type MDMAppleSoftwareUpdateRequiredDetails struct { - OSVersion string `json:"OSVersion"` - BuildVersion string `json:"BuildVersion"` + OSVersion string `json:"OSVersion"` } // MDMAppleSoftwareUpdateRequired is the [error response][1] specified by Apple to indicate that the device @@ -1394,18 +1399,13 @@ type MDMAppleSoftwareUpdateRequired struct { Details MDMAppleSoftwareUpdateRequiredDetails `json:"details"` } -func NewMDMAppleSoftwareUpdateRequired(asset MDMAppleSoftwareUpdateAsset) *MDMAppleSoftwareUpdateRequired { +func NewMDMAppleSoftwareUpdateRequired(osVersion string) *MDMAppleSoftwareUpdateRequired { return &MDMAppleSoftwareUpdateRequired{ Code: MDMAppleSoftwareUpdateRequiredCode, - Details: MDMAppleSoftwareUpdateRequiredDetails{OSVersion: asset.ProductVersion, BuildVersion: asset.Build}, + Details: MDMAppleSoftwareUpdateRequiredDetails{OSVersion: osVersion}, } } -type MDMAppleSoftwareUpdateAsset struct { - ProductVersion string `json:"ProductVersion"` - Build string `json:"Build"` -} - type MDMManagedCertificate struct { ProfileUUID string `db:"profile_uuid"` HostUUID string `db:"host_uuid"` diff --git a/server/service/apple_mdm.go b/server/service/apple_mdm.go index 483330eb67..75ff32efa7 100644 --- a/server/service/apple_mdm.go +++ b/server/service/apple_mdm.go @@ -2736,10 +2736,7 @@ func (svc *Service) getAppleSoftwareUpdateRequiredForDEPEnrollment(m fleet.MDMAp return nil, nil } - return fleet.NewMDMAppleSoftwareUpdateRequired(fleet.MDMAppleSoftwareUpdateAsset{ - ProductVersion: latest.ProductVersion, - Build: latest.Build, - }), nil + return fleet.NewMDMAppleSoftwareUpdateRequired(latest.ProductVersion), nil } // enqueueMDMAppleCommandRemoveEnrollmentProfile enqueues a RemoveProfile MDM command for the given host. diff --git a/server/service/apple_mdm_test.go b/server/service/apple_mdm_test.go index 925121823b..089070053b 100644 --- a/server/service/apple_mdm_test.go +++ b/server/service/apple_mdm_test.go @@ -6712,10 +6712,7 @@ func TestCheckMDMAppleEnrollmentWithMinimumOSVersion(t *testing.T) { dev_mode.SetOverride("FLEET_DEV_GDMF_URL", gdmf.URL, t) latestMacOSVersion := "14.6.1" - latestMacOSBuild := "23G93" - latestIOSVersion := "17.6.1" - latestIOSBuild := "21G93" testCases := []struct { name string @@ -6755,8 +6752,7 @@ func TestCheckMDMAppleEnrollmentWithMinimumOSVersion(t *testing.T) { SoftwareUpdateDeviceID: "J516sAP", }, updateRequired: &fleet.MDMAppleSoftwareUpdateRequiredDetails{ - OSVersion: latestMacOSVersion, - BuildVersion: latestMacOSBuild, + OSVersion: latestMacOSVersion, }, }, { @@ -6846,8 +6842,7 @@ func TestCheckMDMAppleEnrollmentWithMinimumOSVersion(t *testing.T) { var details *fleet.MDMAppleSoftwareUpdateRequiredDetails if tt.updateRequired != nil { details = &fleet.MDMAppleSoftwareUpdateRequiredDetails{ - OSVersion: latestIOSVersion, - BuildVersion: latestIOSBuild, + OSVersion: latestIOSVersion, } } diff --git a/server/service/integration_mdm_dep_test.go b/server/service/integration_mdm_dep_test.go index 7a864803a6..ec4ddcc92d 100644 --- a/server/service/integration_mdm_dep_test.go +++ b/server/service/integration_mdm_dep_test.go @@ -2335,7 +2335,6 @@ func (s *integrationMDMTestSuite) TestEnforceMiniumOSVersion() { s.enableABM(t.Name()) latestMacOSVersion := "14.6.1" // this is the latest version in our test data (see ../mdm/apple/gdmf/testdata/gdmf.json) - latestMacOSBuild := "23G93" // this is the latest version in our test data (see ../mdm/apple/gdmf/testdata/gdmf.json) deadline := "2023-12-31" scepChallenge := "scepcha/>