From 3b32a526ee8f902e6157372936049bb8e3359b2d Mon Sep 17 00:00:00 2001 From: George Karr Date: Wed, 22 Jul 2026 09:18:57 -0500 Subject: [PATCH] Fix 500 on Apple MDM enroll when host has no DEP assignment (#47963) (#49623) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Related issue:** Resolves #47963 # Checklist for submitter - [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. ## Testing - [x] Added/updated automated tests - [ ] QA'd all new/changed functionality manually ## Summary Fixes a 500 seen via monitoring during `POST /api/mdm/apple/enroll`: ``` checking os updates settings serial [redacted]: getting team id for host: sql: no rows in result set ``` ### Root cause During DEP enrollment, `CheckMDMAppleEnrollmentWithMinimumOSVersion` → `shouldOSUpdateForDEPEnrollment` calls `GetMDMAppleOSUpdatesSettingsByHostSerial`, which joins `hosts` to `host_dep_assignments` by serial. When no matching row exists yet — e.g. the enrollment request arrives before the host / DEP assignment row is created or replicated (replica lag / ordering) — `sqlx.GetContext` returns `sql.ErrNoRows`. The service layer already handles this case gracefully (skip the OS-update check, allow enrollment to proceed) via `fleet.IsNotFound(err)`. But the datastore wrapped the raw `sql.ErrNoRows` with a plain `ctxerr.Wrap`, which does not implement the `IsNotFound()` interface, so the graceful path never triggered and the request 500'd. ### Fix Convert `sql.ErrNoRows` into a proper `notFound` error in the datastore method, matching the existing pattern used throughout `apple_mdm.go`. This lets the existing service-layer graceful-skip path take over so enrollment proceeds. ## Summary by CodeRabbit * **Bug Fixes** * Fixed Apple MDM enrollment to continue gracefully when OS update settings are missing because a host’s DEP assignment hasn’t been created yet or hasn’t replicated. * Prevented enrollment from failing with an unexpected 500 error by returning a clear “not found” outcome instead. --- changes/47963-enroll-500-os-updates-settings-not-found | 1 + server/datastore/mysql/apple_mdm.go | 7 +++++++ server/datastore/mysql/apple_mdm_test.go | 10 ++++++---- 3 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 changes/47963-enroll-500-os-updates-settings-not-found diff --git a/changes/47963-enroll-500-os-updates-settings-not-found b/changes/47963-enroll-500-os-updates-settings-not-found new file mode 100644 index 0000000000..e67a0a6489 --- /dev/null +++ b/changes/47963-enroll-500-os-updates-settings-not-found @@ -0,0 +1 @@ +- Fixed a 500 error during Apple MDM enrollment when a host had no DEP assignment yet (e.g. the enrollment request arrived before the host/DEP assignment row was created or replicated). The OS updates settings lookup now returns a not-found error so enrollment proceeds gracefully instead of failing. diff --git a/server/datastore/mysql/apple_mdm.go b/server/datastore/mysql/apple_mdm.go index 124cb90ae0..554f81f73f 100644 --- a/server/datastore/mysql/apple_mdm.go +++ b/server/datastore/mysql/apple_mdm.go @@ -6747,6 +6747,13 @@ LIMIT 1` Platform string `db:"platform"` } if err := sqlx.GetContext(ctx, ds.reader(ctx), &dest, stmt, serial); err != nil { + if errors.Is(err, sql.ErrNoRows) { + // The host may not have a DEP assignment yet (e.g. the enrollment + // request arrived before the host/DEP assignment row was created or + // replicated). Return a not-found error so callers can skip the OS + // updates check and allow enrollment to proceed. + return "", nil, ctxerr.Wrap(ctx, notFound("Host").WithName(serial), "getting team id for host") + } return "", nil, ctxerr.Wrap(ctx, err, "getting team id for host") } diff --git a/server/datastore/mysql/apple_mdm_test.go b/server/datastore/mysql/apple_mdm_test.go index c17f94cb87..5f2e162a30 100644 --- a/server/datastore/mysql/apple_mdm_test.go +++ b/server/datastore/mysql/apple_mdm_test.go @@ -9539,18 +9539,20 @@ func TestGetMDMAppleOSUpdatesSettingsByHostSerial(t *testing.T) { Platform: "macos", HardwareSerial: "non-dep-serial", }) + require.NoError(t, err) - // non-DEP host should return not found + // non-DEP host should return a not-found error (so callers can skip the + // OS updates check and allow enrollment to proceed) _, _, err = ds.GetMDMAppleOSUpdatesSettingsByHostSerial(context.Background(), "non-dep-serial") - require.ErrorIs(t, err, sql.ErrNoRows) + require.True(t, fleet.IsNotFound(err), "expected not found error, got %v", err) - // deleted DEP host should return not found + // deleted DEP host should return a not-found error ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { _, err := q.ExecContext(context.Background(), "UPDATE host_dep_assignments SET deleted_at = NOW() WHERE host_id = ?", hostIDsByKey["macos"]) return err }) _, _, err = ds.GetMDMAppleOSUpdatesSettingsByHostSerial(context.Background(), devicesByKey["macos"].SerialNumber) - require.ErrorIs(t, err, sql.ErrNoRows) + require.True(t, fleet.IsNotFound(err), "expected not found error, got %v", err) } func testMDMManagedSCEPCertificates(t *testing.T, ds *Datastore) {