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",
},