diff --git a/changes/15565-windows-automatic-enrollment b/changes/15565-windows-automatic-enrollment new file mode 100644 index 0000000000..a89e709468 --- /dev/null +++ b/changes/15565-windows-automatic-enrollment @@ -0,0 +1 @@ +- Fix a bug where all Windows MDM enrollments were detected as automatic diff --git a/cmd/osquery-perf/agent.go b/cmd/osquery-perf/agent.go index 719b59b23c..756f6f0cd5 100644 --- a/cmd/osquery-perf/agent.go +++ b/cmd/osquery-perf/agent.go @@ -1514,12 +1514,12 @@ func (a *agent) mdmWindows() []map[string]string { if !a.mdmEnrolled() { return []map[string]string{ // empty service url means not enrolled - {"is_federated": "0", "discovery_service_url": "", "provider_id": "", "installation_type": "Client"}, + {"aad_resource_id": "", "discovery_service_url": "", "provider_id": "", "installation_type": "Client"}, } } return []map[string]string{ { - "is_federated": "0", + "aad_resource_id": "", "discovery_service_url": a.serverAddress, "provider_id": fleet.WellKnownMDMFleet, "installation_type": "Client", diff --git a/docs/Contributing/windows-mdm-glossary-and-protocol.md b/docs/Contributing/windows-mdm-glossary-and-protocol.md index 03d5283a48..94dc6095db 100644 --- a/docs/Contributing/windows-mdm-glossary-and-protocol.md +++ b/docs/Contributing/windows-mdm-glossary-and-protocol.md @@ -57,6 +57,88 @@ The certificate created through the WSTEP process is used to authenticate mTLS b https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-mdm/33769a92-ac31-47ef-ae7b-dc8501f7104f +## MDM Device Registration Summary + +https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dvrd/296ebf70-bd4b-489e-a531-460d8ef7519b + +# Registry + +- `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Enrollments\` + Each enrollment gets its own subdirectory with a UUID as a key, + inside each directory is a set of keys associated with that enrollment + - `CurCryptoProvider` + Often `Microsoft Software Key Storage Provider` + Cryptographic Key storage provider + - `CurKeyContainer` + Key within key provider + - `DiscoveryServiceFullURL` + MDM Discovery service URL + - `DMPCertThumbPrint` + According to [this blog post](https://call4cloud.nl/2022/10/fullmetal-certificate-the-revenge-of-renewal/), this is the thumbprint of your MDM device certificate + - `EnrollmentFlags` + See [this link](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-xcep/cd22d3a0-f469-4a44-95ed-d10ce4dc2063) for details + + | Integer value | Meaning | + |---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + | 0x00000001 | Instructs the client and CA to include an S/MIME extension, as specified in [RFC4262]. | + | 0x00000008 | Instructs the CA to append the issued certificate to the userCertificate attribute, on the user object in Active Directory. | + | 0x00000010 | Instructs the CA to check the user's userCertificate attribute in Active Directory, as specified in [RFC4523], for valid certificates that match the template enrolled for. | + | 0x00000040 | This flag instructs clients to sign the renewal request using the private key of the existing certificate. For more information, see [MS-WCCE] section 3.2.2.6.2.1.4.5.6. This flag also instructs the CA to process the renewal requests as specified in [MS-WCCE] section 3.2.2.6.2.1.4.5.6. | + | 0x00000100 | Instructs the client to get a user's consent before attempting to enroll for a certificate based on the specified template. | + | 0x00000400 | Instructs the client to delete any expired, revoked, or renewed certificate from the user's certificate stores. | + | 0x00002000 | This flag instructs the client to reuse the private key for a smart card–based certificate renewal if it is unable to create a new private key on the card. | + - `EnrollmentState` + The best documentation we can find is [here](https://learn.microsoft.com/en-us/graph/api/resources/intune-shared-enrollmentstate?view=graph-rest-beta) + + | Member | Value | Description | + |--------------|-------|--------------------------------------------------------------------------------------------------------------------| + | unknown | 0 | Device enrollment state is unknown | + | enrolled | 1 | Device is Enrolled. | + | pendingReset | 2 | Enrolled but it's enrolled via enrollment profile and the enrolled profile is different from the assigned profile. | + | failed | 3 | Not enrolled and there is enrollment failure record. | + | notContacted | 4 | Device is imported but not enrolled. | + | blocked | 5 | Device is enrolled as userless, but is blocked from moving to user enrollment because the app failed to install. | + + - `EnrollmentType` + According to [this PDF](https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-MDE2/%5BMS-MDE2%5D.pdf) it can have three different values. + + Device, Full, and AppManaged + + From what I've seen, value 6 on AAD, 1 on manual + - `isFederated` + According to [this web page](https://learn.microsoft.com/en-us/windows/client-management/federated-authentication-device-enrollment), being federated means that the MDM + endpoints and details were fetched from a Discovery endpoint, + instead of being manually installed. The page does not make mention + of the specific registry key, but we are making an assumption that + it means the same thing. + - `ProviderID` + Set during enrollment. In our case it's the word "Fleet". + - `RenewalPeriod` + Set during enrollment. Period to renew WSTEP certificate. + - `RenewErrorCode` + Presumably set if there is an error renewing WSTEP certificate. + - `RenewROBOSupport` + According to [this post](https://call4cloud.nl/2022/10/fullmetal-certificate-the-revenge-of-renewal/) this means "Request On Behalf Of". + It seems to have to do with automatic certificate renewal + - `RenewStatus` + Status of the renewal + - `RenewTimestamp` + Presumably the timestamp of the last renewal + - `RootCertThumbPrint` + The thumbprint of the WSTEP root certificate + - `SID` + Security Identifier + - `UPN` + User Principal Name of the user that enrolled the device + - `AADResourceID` + Appears to be the domain of the server managing the enrollment, + always appears to be present on machines enrolled through Microsoft + Entra (Azure Active Directory) + - `AADTenantID` + Also related to Azure Active Directory, and also appears to be + present at the same time as AADResourceID. +- `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Provisioning\Diagnostics\AutoPilot` + Autopilot provisioning diagnostic data - \ No newline at end of file + diff --git a/docs/Using Fleet/Understanding-host-vitals.md b/docs/Using Fleet/Understanding-host-vitals.md index 10cc3f8548..47dca9f92c 100644 --- a/docs/Using Fleet/Understanding-host-vitals.md +++ b/docs/Using Fleet/Understanding-host-vitals.md @@ -176,10 +176,10 @@ WITH registry_keys AS ( 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, - MAX(CASE WHEN name = 'EnrollmentState' THEN data END) AS state + 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 ), @@ -190,7 +190,7 @@ WITH registry_keys AS ( LIMIT 1 ) SELECT - e.is_federated, + e.aad_resource_id, e.discovery_service_url, e.provider_id, i.installation_type @@ -374,7 +374,7 @@ SELECT * FROM os_version LIMIT 1 - Query: ```sql SELECT os.name, r.data as display_version, k.version - FROM + FROM registry r, os_version os, kernel_info k diff --git a/server/service/osquery_utils/queries.go b/server/service/osquery_utils/queries.go index 27ec3932db..030294f5ed 100644 --- a/server/service/osquery_utils/queries.go +++ b/server/service/osquery_utils/queries.go @@ -473,10 +473,10 @@ var extraDetailQueries = map[string]DetailQuery{ 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, - MAX(CASE WHEN name = 'EnrollmentState' THEN data END) AS state + 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 ), @@ -487,7 +487,7 @@ var extraDetailQueries = map[string]DetailQuery{ LIMIT 1 ) SELECT - e.is_federated, + e.aad_resource_id, e.discovery_service_url, e.provider_id, i.installation_type @@ -1612,7 +1612,7 @@ func directIngestMDMWindows(ctx context.Context, logger log.Logger, host *fleet. serverURL := data["discovery_service_url"] if serverURL != "" { enrolled = true - if isFederated := data["is_federated"]; isFederated == "1" { + if data["aad_resource_id"] != "" { // NOTE: We intentionally nest this condition to eliminate `enrolled == false && automatic == true` // as a possible status for Windows hosts (which would be otherwise be categorized as // "Pending"). Currently, the "Pending" status is supported only for macOS hosts. diff --git a/server/service/osquery_utils/queries_test.go b/server/service/osquery_utils/queries_test.go index 022c00aa7d..4a6da7a2ad 100644 --- a/server/service/osquery_utils/queries_test.go +++ b/server/service/osquery_utils/queries_test.go @@ -692,7 +692,7 @@ func TestDirectIngestMDMWindows(t *testing.T) { data: []map[string]string{ { "discovery_service_url": "", - "is_federated": "1", + "aad_resource_id": "https://example.com", "provider_id": "Some_ID", "installation_type": "Client", }, @@ -703,7 +703,7 @@ func TestDirectIngestMDMWindows(t *testing.T) { wantServerURL: "", }, { - name: "off missing is_federated and server url", + name: "off missing aad_resource_id and server url", data: []map[string]string{ { "provider_id": "Some_ID", @@ -728,7 +728,7 @@ func TestDirectIngestMDMWindows(t *testing.T) { data: []map[string]string{ { "discovery_service_url": "https://example.com", - "is_federated": "1", + "aad_resource_id": "https://example.com", "provider_id": "Some_ID", "installation_type": "Client", }, @@ -743,7 +743,7 @@ func TestDirectIngestMDMWindows(t *testing.T) { data: []map[string]string{ { "discovery_service_url": "https://example.com", - "is_federated": "0", + "aad_resource_id": "", "provider_id": "Local_Management", "installation_type": "Client", }, @@ -754,7 +754,7 @@ func TestDirectIngestMDMWindows(t *testing.T) { wantServerURL: "https://example.com", }, { - name: "on manual missing is_federated", + name: "on manual missing aad_resource_id", data: []map[string]string{ { "discovery_service_url": "https://example.com", @@ -772,7 +772,7 @@ func TestDirectIngestMDMWindows(t *testing.T) { data: []map[string]string{ { "discovery_service_url": "https://example.com", - "is_federated": "1", + "aad_resource_id": "https://example.com", "provider_id": "Some_ID", "installation_type": "Windows SeRvEr 99.9", }, @@ -790,7 +790,7 @@ func TestDirectIngestMDMWindows(t *testing.T) { data: []map[string]string{ { "discovery_service_url": "https://jumpcloud.com", - "is_federated": "0", + "aad_resource_id": "", "provider_id": "Local_Management", "installation_type": "Client", }, @@ -806,7 +806,7 @@ func TestDirectIngestMDMWindows(t *testing.T) { data: []map[string]string{ { "discovery_service_url": "https://airwatch.com", - "is_federated": "0", + "aad_resource_id": "", "provider_id": "Local_Management", "installation_type": "Client", }, @@ -822,7 +822,7 @@ func TestDirectIngestMDMWindows(t *testing.T) { data: []map[string]string{ { "discovery_service_url": "https://awmdm.com", - "is_federated": "0", + "aad_resource_id": "", "provider_id": "Local_Management", "installation_type": "Client", }, @@ -838,7 +838,7 @@ func TestDirectIngestMDMWindows(t *testing.T) { data: []map[string]string{ { "discovery_service_url": "https://microsoft.com", - "is_federated": "0", + "aad_resource_id": "", "provider_id": "Local_Management", "installation_type": "Client", }, @@ -854,7 +854,7 @@ func TestDirectIngestMDMWindows(t *testing.T) { data: []map[string]string{ { "discovery_service_url": "https://fleetdm.com", - "is_federated": "0", + "aad_resource_id": "", "provider_id": "Local_Management", "installation_type": "Client", }, @@ -871,7 +871,7 @@ func TestDirectIngestMDMWindows(t *testing.T) { data: []map[string]string{ { "discovery_service_url": "https://myinstall.local", - "is_federated": "0", + "aad_resource_id": "", "provider_id": "Fleet", "installation_type": "Client", },