Fix 500 on Apple MDM enroll when host has no DEP assignment (#47963) (#49623)

**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.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## 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.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
George Karr
2026-07-22 09:18:57 -05:00
committed by GitHub
parent ba0b1c3bea
commit 3b32a526ee
3 changed files with 14 additions and 4 deletions
@@ -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.
+7
View File
@@ -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")
}
+6 -4
View File
@@ -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) {