From a7be0be9e9517a23a8d80b5fd8b810e7e59b54a8 Mon Sep 17 00:00:00 2001 From: Roberto Dip Date: Mon, 4 Dec 2023 11:03:05 -0300 Subject: [PATCH] improve mdm_windows query to account for multiple registry entries (#15391) for #15362, this adjusts the query we use to get MDM details for windows to account for hosts that might have more than one matching value in the registry for any of the items we query. --- changes/15362-windows-mdm-query | 1 + server/service/osquery_utils/queries.go | 78 +++++++++++++------- server/service/osquery_utils/queries_test.go | 53 +++++++------ 3 files changed, 85 insertions(+), 47 deletions(-) create mode 100644 changes/15362-windows-mdm-query 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,