diff --git a/changes/15362-windows-mdm-query b/changes/15362-windows-mdm-query new file mode 100644 index 0000000000..86a5c269ed --- /dev/null +++ b/changes/15362-windows-mdm-query @@ -0,0 +1 @@ +* Improved the query used to get MDM details for Windows hosts to account for multiple registry entries. diff --git a/server/service/osquery_utils/queries.go b/server/service/osquery_utils/queries.go index 9dfc02aa4d..b213cf0421 100644 --- a/server/service/osquery_utils/queries.go +++ b/server/service/osquery_utils/queries.go @@ -438,31 +438,48 @@ var extraDetailQueries = map[string]DetailQuery{ Discovery: discoveryTable("mdm"), }, "mdm_windows": { + // we get most of the MDM information for Windows from the + // `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Enrollments\%%` + // registry keys. A computer might many different folders under + // that path, for different enrollments, so we need to group by + // enrollment (key in this case) and try to grab the most + // likely candiate to be an MDM solution. + // + // The best way I have found, is to filter by groups of entries + // with an UPN value, and pick the first one. + // + // An example of a host having more than one entry: when + // the `mdm_bridge` table is used, the `mdmlocalmanagement.dll` + // registers an MDM with ProviderID = `Local_Management` + // + // For more information, refer to issue #15362 Query: ` - SELECT * FROM ( - SELECT "provider_id" AS "key", data as "value" FROM registry - WHERE path LIKE 'HKEY_LOCAL_MACHINE\Software\Microsoft\Enrollments\%\ProviderID' - 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 = 'IsFederated' THEN data END) AS is_federated, + MAX(CASE WHEN name = 'DiscoveryServiceFullURL' THEN data END) AS discovery_service_url, + MAX(CASE WHEN name = 'ProviderID' THEN data END) AS provider_id + FROM registry_keys + GROUP BY key ) - UNION ALL - SELECT * FROM ( - SELECT "discovery_service_url" AS "key", data as "value" FROM registry - WHERE path LIKE 'HKEY_LOCAL_MACHINE\Software\Microsoft\Enrollments\%\DiscoveryServiceFullURL' - LIMIT 1 - ) - UNION ALL - SELECT * FROM ( - SELECT "is_federated" AS "key", data as "value" FROM registry - WHERE path LIKE 'HKEY_LOCAL_MACHINE\Software\Microsoft\Enrollments\%\IsFederated' - LIMIT 1 - ) - UNION ALL - SELECT * FROM ( - SELECT "installation_type" AS "key", data as "value" FROM registry - WHERE path = 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\InstallationType' - LIMIT 1 - ) - ; + SELECT + e.is_federated, + e.discovery_service_url, + e.provider_id, + ( + SELECT data + FROM registry + WHERE path = 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\InstallationType' + ) AS installation_type + FROM enrollment_info e + WHERE e.upn IS NOT NULL + LIMIT 1; `, DirectIngestFunc: directIngestMDMWindows, Platforms: []string{"windows"}, @@ -1458,10 +1475,19 @@ func deduceMDMNameWindows(data map[string]string) string { } func directIngestMDMWindows(ctx context.Context, logger log.Logger, host *fleet.Host, ds fleet.Datastore, rows []map[string]string) error { - data := make(map[string]string, len(rows)) - for _, r := range rows { - data[r["key"]] = r["value"] + if len(rows) != 1 { + logger.Log("component", "service", "method", "directIngestMDMWindows", "warn", + fmt.Sprintf("mdm expected single result got %d", len(rows))) + // assume the extension is not there + return nil } + + if len(rows) > 1 { + logger.Log("component", "service", "method", "directIngestMDMWindows", "warn", + fmt.Sprintf("mdm expected single result got %d", len(rows))) + } + + data := rows[0] var enrolled bool var automatic bool serverURL := data["discovery_service_url"] diff --git a/server/service/osquery_utils/queries_test.go b/server/service/osquery_utils/queries_test.go index 1e32c22bb3..6d42d4dae6 100644 --- a/server/service/osquery_utils/queries_test.go +++ b/server/service/osquery_utils/queries_test.go @@ -588,10 +588,12 @@ func TestDirectIngestMDMWindows(t *testing.T) { { name: "off empty server URL", data: []map[string]string{ - {"key": "discovery_service_url", "value": ""}, - {"key": "is_federated", "value": "1"}, - {"key": "provider_id", "value": "Some_ID"}, - {"key": "installation_type", "value": "Client"}, + { + "discovery_service_url": "", + "is_federated": "1", + "provider_id": "Some_ID", + "installation_type": "Client", + }, }, wantEnrolled: false, wantInstalledFromDep: false, @@ -601,8 +603,10 @@ func TestDirectIngestMDMWindows(t *testing.T) { { name: "off missing is_federated and server url", data: []map[string]string{ - {"key": "provider_id", "value": "Some_ID"}, - {"key": "installation_type", "value": "Client"}, + { + "provider_id": "Some_ID", + "installation_type": "Client", + }, }, wantEnrolled: false, wantInstalledFromDep: false, @@ -612,10 +616,12 @@ func TestDirectIngestMDMWindows(t *testing.T) { { name: "on automatic", data: []map[string]string{ - {"key": "discovery_service_url", "value": "https://example.com"}, - {"key": "is_federated", "value": "1"}, - {"key": "provider_id", "value": "Some_ID"}, - {"key": "installation_type", "value": "Client"}, + { + "discovery_service_url": "https://example.com", + "is_federated": "1", + "provider_id": "Some_ID", + "installation_type": "Client", + }, }, wantEnrolled: true, wantInstalledFromDep: true, @@ -625,10 +631,12 @@ func TestDirectIngestMDMWindows(t *testing.T) { { name: "on manual", data: []map[string]string{ - {"key": "discovery_service_url", "value": "https://example.com"}, - {"key": "is_federated", "value": "0"}, - {"key": "provider_id", "value": "Local_Management"}, - {"key": "installation_type", "value": "Client"}, + { + "discovery_service_url": "https://example.com", + "is_federated": "0", + "provider_id": "Local_Management", + "installation_type": "Client", + }, }, wantEnrolled: true, wantInstalledFromDep: false, @@ -638,9 +646,11 @@ func TestDirectIngestMDMWindows(t *testing.T) { { name: "on manual missing is_federated", data: []map[string]string{ - {"key": "discovery_service_url", "value": "https://example.com"}, - {"key": "provider_id", "value": "Some_ID"}, - {"key": "installation_type", "value": "Client"}, + { + "discovery_service_url": "https://example.com", + "provider_id": "Some_ID", + "installation_type": "Client", + }, }, wantEnrolled: true, wantInstalledFromDep: false, @@ -650,10 +660,11 @@ func TestDirectIngestMDMWindows(t *testing.T) { { name: "is_server", data: []map[string]string{ - {"key": "discovery_service_url", "value": "https://example.com"}, - {"key": "is_federated", "value": "1"}, - {"key": "provider_id", "value": "Some_ID"}, - {"key": "installation_type", "value": "Windows SeRvEr 99.9"}, + { + "discovery_service_url": "https://example.com", + "is_federated": "1", + "provider_id": "Some_ID", + "installation_type": "Windows SeRvEr 99.9"}, }, wantEnrolled: true, wantInstalledFromDep: true,