From 149cd9dacac21d9a098c0d02fd9b698d08e997fc Mon Sep 17 00:00:00 2001 From: Jordan Montgomery Date: Thu, 22 May 2025 14:08:05 -0400 Subject: [PATCH] Tweak MDM detection query to return the proper enrollment when there are multiple entries (#29360) This change is deceptively simple but helps us choose the right one in cases like #29042 where there are multiple enrollments in the registry. In this case the customer seems to have been using something like co-management(though even using their MDM we have not repro'd internally) which leads to 2 registry keys in the registry with a UPN node. I believe the way some MDM services handle unenroll can also leave the registry keys in this state. Either way, because of this, and the fact that we have a LIMIT 1 in the query, we were, in 50% of the cases where we had multiple keys, returning the less useful of the nodes from the query and because no Server URL was coming back we were treating it as if the host was not MDM enrolled and thus, not unenrolling it, and leading to enrollment failing. With this change we'll return the proper registry key which should allow us to, in the case of migration, properly unenroll the host and even in the case where a customer isn't using Fleet MDM will allow us to display the correct information from the registry. # 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) - [x] Manual QA for all new/changed functionality --- changes/29042-windows-mdm-query-enhancement | 1 + .../understanding-host-vitals.md | 69 +++++++++--------- server/service/osquery_utils/queries.go | 71 ++++++++++--------- 3 files changed, 76 insertions(+), 65 deletions(-) create mode 100644 changes/29042-windows-mdm-query-enhancement diff --git a/changes/29042-windows-mdm-query-enhancement b/changes/29042-windows-mdm-query-enhancement new file mode 100644 index 0000000000..e70fd62419 --- /dev/null +++ b/changes/29042-windows-mdm-query-enhancement @@ -0,0 +1 @@ +* Modified the Windows MDM detection query to more accurately detect existing MDM enrollment details on hosts with multiple enrollments diff --git a/docs/Contributing/product-groups/orchestration/understanding-host-vitals.md b/docs/Contributing/product-groups/orchestration/understanding-host-vitals.md index d90028ddf1..7c9333d0ac 100644 --- a/docs/Contributing/product-groups/orchestration/understanding-host-vitals.md +++ b/docs/Contributing/product-groups/orchestration/understanding-host-vitals.md @@ -198,38 +198,43 @@ WITH - Query: ```sql WITH registry_keys AS ( - SELECT * - FROM registry - WHERE path LIKE 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Enrollments\%%' - ), - enrollment_info AS ( - SELECT - MAX(CASE WHEN name = 'UPN' THEN data END) AS upn, - MAX(CASE WHEN name = 'DiscoveryServiceFullURL' THEN data END) AS discovery_service_url, - MAX(CASE WHEN name = 'ProviderID' THEN data END) AS provider_id, - MAX(CASE WHEN name = 'EnrollmentState' THEN data END) AS state, - MAX(CASE WHEN name = 'AADResourceID' THEN data END) AS aad_resource_id - FROM registry_keys - GROUP BY key - ), - installation_info AS ( - SELECT data AS installation_type - FROM registry - WHERE path = 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\InstallationType' - LIMIT 1 - ) - SELECT - e.aad_resource_id, - e.discovery_service_url, - e.provider_id, - i.installation_type - FROM installation_info i - LEFT JOIN enrollment_info e ON e.upn IS NOT NULL - -- coalesce to 'unknown' and keep that state in the list - -- in order to account for hosts that might not have this - -- key, and servers - WHERE COALESCE(e.state, '0') IN ('0', '1', '2', '3') - LIMIT 1; + SELECT * + FROM registry + WHERE path LIKE 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Enrollments\%%' + ), + enrollment_info AS ( + SELECT + MAX(CASE WHEN name = 'UPN' THEN data END) AS upn, + MAX(CASE WHEN name = 'DiscoveryServiceFullURL' THEN data END) AS discovery_service_url, + MAX(CASE WHEN name = 'ProviderID' THEN data END) AS provider_id, + MAX(CASE WHEN name = 'EnrollmentState' THEN data END) AS state, + MAX(CASE WHEN name = 'AADResourceID' THEN data END) AS aad_resource_id + FROM registry_keys + GROUP BY key + ), + installation_info AS ( + SELECT data AS installation_type + FROM registry + WHERE path = 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\InstallationType' + LIMIT 1 + ) + SELECT + e.aad_resource_id, + e.discovery_service_url, + e.provider_id, + i.installation_type + FROM installation_info i + LEFT JOIN enrollment_info e ON e.upn IS NOT NULL + -- coalesce to 'unknown' and keep that state in the list + -- in order to account for hosts that might not have this + -- key, and servers + WHERE COALESCE(e.state, '0') IN ('0', '1', '2', '3') + -- old enrollments that aren't completely cleaned up may still be aronud + -- in the registry so we want to make sure we return the one with an actual + -- discovery URL set if there is one. LENGTH is used here to prefer those + -- with actual URLs over empty string/null if there are multiple + ORDER BY LENGTH(e.discovery_service_url) DESC + LIMIT 1; ``` ## munki_info diff --git a/server/service/osquery_utils/queries.go b/server/service/osquery_utils/queries.go index 990cf61a25..6a4eeff7e4 100644 --- a/server/service/osquery_utils/queries.go +++ b/server/service/osquery_utils/queries.go @@ -516,39 +516,44 @@ var extraDetailQueries = map[string]DetailQuery{ // // [1]: https://learn.microsoft.com/en-us/graph/api/resources/intune-shared-enrollmentstate Query: ` - WITH registry_keys AS ( - SELECT * - FROM registry - WHERE path LIKE 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Enrollments\%%' - ), - enrollment_info AS ( - SELECT - MAX(CASE WHEN name = 'UPN' THEN data END) AS upn, - MAX(CASE WHEN name = 'DiscoveryServiceFullURL' THEN data END) AS discovery_service_url, - MAX(CASE WHEN name = 'ProviderID' THEN data END) AS provider_id, - MAX(CASE WHEN name = 'EnrollmentState' THEN data END) AS state, - MAX(CASE WHEN name = 'AADResourceID' THEN data END) AS aad_resource_id - FROM registry_keys - GROUP BY key - ), - installation_info AS ( - SELECT data AS installation_type - FROM registry - WHERE path = 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\InstallationType' - LIMIT 1 - ) - SELECT - e.aad_resource_id, - e.discovery_service_url, - e.provider_id, - i.installation_type - FROM installation_info i - LEFT JOIN enrollment_info e ON e.upn IS NOT NULL - -- coalesce to 'unknown' and keep that state in the list - -- in order to account for hosts that might not have this - -- key, and servers - WHERE COALESCE(e.state, '0') IN ('0', '1', '2', '3') - LIMIT 1; + WITH registry_keys AS ( + SELECT * + FROM registry + WHERE path LIKE 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Enrollments\%%' + ), + enrollment_info AS ( + SELECT + MAX(CASE WHEN name = 'UPN' THEN data END) AS upn, + MAX(CASE WHEN name = 'DiscoveryServiceFullURL' THEN data END) AS discovery_service_url, + MAX(CASE WHEN name = 'ProviderID' THEN data END) AS provider_id, + MAX(CASE WHEN name = 'EnrollmentState' THEN data END) AS state, + MAX(CASE WHEN name = 'AADResourceID' THEN data END) AS aad_resource_id + FROM registry_keys + GROUP BY key + ), + installation_info AS ( + SELECT data AS installation_type + FROM registry + WHERE path = 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\InstallationType' + LIMIT 1 + ) + SELECT + e.aad_resource_id, + e.discovery_service_url, + e.provider_id, + i.installation_type + FROM installation_info i + LEFT JOIN enrollment_info e ON e.upn IS NOT NULL + -- coalesce to 'unknown' and keep that state in the list + -- in order to account for hosts that might not have this + -- key, and servers + WHERE COALESCE(e.state, '0') IN ('0', '1', '2', '3') + -- old enrollments that aren't completely cleaned up may still be aronud + -- in the registry so we want to make sure we return the one with an actual + -- discovery URL set if there is one. LENGTH is used here to prefer those + -- with actual URLs over empty string/null if there are multiple + ORDER BY LENGTH(e.discovery_service_url) DESC + LIMIT 1; `, DirectIngestFunc: directIngestMDMWindows, Platforms: []string{"windows"},