diff --git a/changes/25273-hde-windows-verifying b/changes/25273-hde-windows-verifying new file mode 100644 index 000000000000..c45ed02e8df1 --- /dev/null +++ b/changes/25273-hde-windows-verifying @@ -0,0 +1,2 @@ +- Fixed issue where Windows disk encryption where status updates from "Verifying" to "Verified" were + sometimes stuck in the "Verifying" state. diff --git a/server/datastore/mysql/hosts.go b/server/datastore/mysql/hosts.go index 35318813b68a..6d56a0dd4d17 100644 --- a/server/datastore/mysql/hosts.go +++ b/server/datastore/mysql/hosts.go @@ -3829,7 +3829,7 @@ func (ds *Datastore) SetOrUpdateHostDisksSpace(ctx context.Context, hostID uint, func (ds *Datastore) SetOrUpdateHostDisksEncryption(ctx context.Context, hostID uint, encrypted bool) error { return ds.updateOrInsert( ctx, - `UPDATE host_disks SET encrypted = ? WHERE host_id = ?`, + `UPDATE host_disks SET encrypted = ?, updated_at = CURRENT_TIMESTAMP(6) WHERE host_id = ?`, `INSERT INTO host_disks (encrypted, host_id) VALUES (?, ?)`, encrypted, hostID, ) diff --git a/server/datastore/mysql/microsoft_mdm_test.go b/server/datastore/mysql/microsoft_mdm_test.go index 6bad061f14cd..b03920c6fa0c 100644 --- a/server/datastore/mysql/microsoft_mdm_test.go +++ b/server/datastore/mysql/microsoft_mdm_test.go @@ -562,6 +562,52 @@ func testMDMWindowsDiskEncryption(t *testing.T, ds *Datastore) { // Check that filtered lists do include macOS hosts checkListHostsFilterDiskEncryption(t, nil, fleet.DiskEncryptionFailed, []uint{hosts[1].ID, hosts[5].ID}) checkListHostsFilterOSSettings(t, nil, fleet.OSSettingsFailed, []uint{hosts[1].ID, hosts[5].ID}) + + // delete the macOS host profile + ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error { + _, err := q.ExecContext(ctx, `DELETE FROM host_mdm_apple_profiles WHERE host_uuid = ? AND profile_identifier = ?`, hosts[5].UUID, mobileconfig.FleetFileVaultPayloadIdentifier) + return err + }) + }) + + t.Run("BitLocker host disks must update to transition from Verifying to Verified", func(t *testing.T) { + // we'll use hosts[4] as the target for this test + targetHost := hosts[4] + + // confirm our initial state is as expected from previous tests + // hosts[2] is was transferred to a team and is not counted + // hosts[3] is a Windows server and is not counted + checkExpected(t, nil, hostIDsByDEStatus{ + fleet.DiskEncryptionVerified: []uint{hosts[0].ID}, + fleet.DiskEncryptionFailed: []uint{hosts[1].ID}, + fleet.DiskEncryptionEnforcing: []uint{targetHost.ID}, // targetHost is initially enforcing + }) + + // simulate targetHost previously reported encrypted for disk encryption detail query + // results + require.NoError(t, ds.SetOrUpdateHostDisksEncryption(ctx, targetHost.ID, true)) + // manualy update host_disks for targetHost to encrypted and ensure updated_at + // timestamp is in the past + updateHostDisks(t, targetHost.ID, true, time.Now().Add(-3*time.Hour)) + + // simulate targetHost reporting disk encryption key + require.NoError(t, ds.SetOrUpdateHostDiskEncryptionKey(ctx, targetHost, "test-key", "", ptr.Bool(true))) + + // check that targetHost is now counted as verifying (not verified because host_disks still needs to be updated) + checkExpected(t, nil, hostIDsByDEStatus{ + fleet.DiskEncryptionVerified: []uint{hosts[0].ID}, + fleet.DiskEncryptionFailed: []uint{hosts[1].ID}, + fleet.DiskEncryptionVerifying: []uint{targetHost.ID}, + }) + + // simulate targetHost reporting detail query results for disk encryption + require.NoError(t, ds.SetOrUpdateHostDisksEncryption(ctx, targetHost.ID, true)) + // status for targetHost now verified because SetOrUpdateHostDisksEncryption always sets host_disks.updated_at + // to the current timestamp even if the `encrypted` value hasn't changed + checkExpected(t, nil, hostIDsByDEStatus{ + fleet.DiskEncryptionVerified: []uint{hosts[0].ID, targetHost.ID}, + fleet.DiskEncryptionFailed: []uint{hosts[1].ID}, + }) }) }) } @@ -863,6 +909,36 @@ func testMDMWindowsProfilesSummary(t *testing.T, ds *Datastore) { cleanupTables(t) }) + t.Run("BitLocker host disks must update to transition from Verifying to Verified", func(t *testing.T) { + // all hosts are pending because no profiles and disk encryption is enabled + checkExpected(t, nil, hostIDsByProfileStatus{ + fleet.MDMDeliveryPending: []uint{hosts[0].ID, hosts[1].ID, hosts[2].ID, hosts[3].ID, hosts[4].ID}, + }) + + // simulate host already has encrypted disks + require.NoError(t, ds.SetOrUpdateHostDisksEncryption(ctx, hosts[0].ID, true)) + // manualy update host_disks for hosts[0] to encrypted and ensure updated_at + // timestamp is in the past + updateHostDisks(t, hosts[0].ID, true, time.Now().Add(-2*time.Hour)) + + require.NoError(t, ds.SetOrUpdateHostDiskEncryptionKey(ctx, hosts[0], "test-key", "", ptr.Bool(true))) + // status is verifying because hosts_disks hasn't been updated again + checkExpected(t, nil, hostIDsByProfileStatus{ + fleet.MDMDeliveryVerifying: []uint{hosts[0].ID}, + fleet.MDMDeliveryPending: []uint{hosts[1].ID, hosts[2].ID, hosts[3].ID, hosts[4].ID}, + }) + + require.NoError(t, ds.SetOrUpdateHostDisksEncryption(ctx, hosts[0].ID, true)) + // status for hosts[0] now verified because SetOrUpdateHostDisksEncryption always sets host_disks.updated_at + // to the current timestamp even if the `encrypted` value hasn't changed + checkExpected(t, nil, hostIDsByProfileStatus{ + fleet.MDMDeliveryVerified: []uint{hosts[0].ID}, + fleet.MDMDeliveryPending: []uint{hosts[1].ID, hosts[2].ID, hosts[3].ID, hosts[4].ID}, + }) + + cleanupTables(t) + }) + t.Run("bitlocker failed", func(t *testing.T) { expected := hostIDsByProfileStatus{ fleet.MDMDeliveryPending: []uint{hosts[0].ID, hosts[1].ID, hosts[2].ID, hosts[3].ID, hosts[4].ID}, @@ -2460,11 +2536,11 @@ VALUES (?, 'pending', 'install', ?, 'disable-onedrive', ?)`, enrolledDevice2.Hos atomicCommandUUID) }) assert.Empty(t, count, "All devices have responded, so the command should be completely removed from the queue") - } func createResponseAsEnrichedSyncML(t *testing.T, enrolledDevice *fleet.MDMWindowsEnrolledDevice, atomicCommandUUID string, - replaceCommandUUID string) fleet.EnrichedSyncML { + replaceCommandUUID string, +) fleet.EnrichedSyncML { rawResponse := fmt.Sprintf(`