Update API endpoints that support os_setttings filter to include Windows MDM profiles status (#15188)

This commit is contained in:
Sarah Gillespie
2023-11-17 12:33:06 -06:00
committed by GitHub
parent 91ee14f573
commit 3399b31bfc
4 changed files with 163 additions and 35 deletions
@@ -0,0 +1 @@
- Updated API endpoints that support `os_setttings` filter to include Windows profiles status.
+111 -27
View File
@@ -1180,47 +1180,133 @@ func (ds *Datastore) filterHostsByOSSettingsStatus(sql string, opt fleet.HostLis
return sql, params
}
// TODO: Look into ways we can convert some of the LEFT JOINs in the main list hosts query
// to INNER JOINs if the OSSettingsFilter is set. This would allow us to use indices
// from the `host_mdm` table, for example, to cut down on the number of rows that need
// to be scanned. For now, this method assumes that LEFT JOINs are used in the main query
// and adds extra where clauses to filter out Windows hosts that are not enrolled to Fleet MDM
// or are servers. Similar logic could be applied to macOS hosts but is not included in this
// current implementation.
sqlFmt := ` AND h.platform IN('windows', 'darwin')`
if opt.TeamFilter == nil {
// macOS settings filter is not compatible with the "all teams" option so append the "no
// team" filter here (note that filterHostsByTeam applies the "no team" filter if TeamFilter == 0)
// OS settings filter is not compatible with the "all teams" option so append the "no team"
// filter here (note that filterHostsByTeam applies the "no team" filter if TeamFilter == 0)
sqlFmt += ` AND h.team_id IS NULL`
}
var whereMacOS, whereWindows string
sqlFmt += ` AND ((h.platform = 'windows' AND (%s)) OR (h.platform = 'darwin' AND (%s)))`
// construct the WHERE for macOS
var subqueryMacOS string
var subqueryParams []interface{}
whereWindows := "FALSE"
whereMacOS := "FALSE"
var paramsMacOS []interface{}
switch opt.OSSettingsFilter {
case fleet.OSSettingsFailed:
subqueryMacOS, subqueryParams = subqueryHostsMacOSSettingsStatusFailed()
if isDiskEncryptionEnabled {
whereWindows = ds.whereBitLockerStatus(fleet.DiskEncryptionFailed)
}
subqueryMacOS, paramsMacOS = subqueryHostsMacOSSettingsStatusFailed()
case fleet.OSSettingsPending:
subqueryMacOS, subqueryParams = subqueryHostsMacOSSettingsStatusPending()
if isDiskEncryptionEnabled {
whereWindows = ds.whereBitLockerStatus(fleet.DiskEncryptionEnforcing)
}
subqueryMacOS, paramsMacOS = subqueryHostsMacOSSettingsStatusPending()
case fleet.OSSettingsVerifying:
subqueryMacOS, subqueryParams = subqueryHostsMacOSSetttingsStatusVerifying()
if isDiskEncryptionEnabled {
whereWindows = ds.whereBitLockerStatus(fleet.DiskEncryptionVerifying)
}
subqueryMacOS, paramsMacOS = subqueryHostsMacOSSetttingsStatusVerifying()
case fleet.OSSettingsVerified:
subqueryMacOS, subqueryParams = subqueryHostsMacOSSetttingsStatusVerified()
if isDiskEncryptionEnabled {
whereWindows = ds.whereBitLockerStatus(fleet.DiskEncryptionVerified)
}
subqueryMacOS, paramsMacOS = subqueryHostsMacOSSetttingsStatusVerified()
}
if subqueryMacOS != "" {
whereMacOS = "EXISTS (" + subqueryMacOS + ")"
} else {
whereMacOS = "FALSE"
}
return sql + fmt.Sprintf(sqlFmt, whereWindows, whereMacOS), append(params, subqueryParams...)
// construct the WHERE for windows
whereWindows = `hmdm.name = ? AND hmdm.enrolled = 1 AND hmdm.is_server = 0`
paramsWindows := []interface{}{fleet.WellKnownMDMFleet}
subqueryFailed, paramsFailed := subqueryHostsMDMWindowsOSSettingsStatusFailed()
paramsWindows = append(paramsWindows, paramsFailed...)
subqueryPending, paramsPending := subqueryHostsMDMWindowsOSSettingsStatusPending()
paramsWindows = append(paramsWindows, paramsPending...)
subqueryVerifying, paramsVerifying := subqueryHostsMDMWindowsOSSettingsStatusVerifying()
paramsWindows = append(paramsWindows, paramsVerifying...)
subqueryVerified, paramsVerified := subqueryHostsMDMWindowsOSSettingsStatusVerified()
paramsWindows = append(paramsWindows, paramsVerified...)
profilesStatus := fmt.Sprintf(`
CASE WHEN EXISTS (%s) THEN
'profiles_failed'
WHEN EXISTS (%s) THEN
'profiles_pending'
WHEN EXISTS (%s) THEN
'profiles_verifying'
WHEN EXISTS (%s) THEN
'profiles_verified'
ELSE
''
END`,
subqueryFailed,
subqueryPending,
subqueryVerifying,
subqueryVerified,
)
bitlockerStatus := `''`
if isDiskEncryptionEnabled {
bitlockerStatus = fmt.Sprintf(`
CASE WHEN (%s) THEN
'bitlocker_verified'
WHEN (%s) THEN
'bitlocker_verifying'
WHEN (%s) THEN
'bitlocker_pending'
WHEN (%s) THEN
'bitlocker_failed'
ELSE
''
END`,
ds.whereBitLockerStatus(fleet.DiskEncryptionVerified),
ds.whereBitLockerStatus(fleet.DiskEncryptionVerifying),
ds.whereBitLockerStatus(fleet.DiskEncryptionEnforcing),
ds.whereBitLockerStatus(fleet.DiskEncryptionFailed),
)
}
whereWindows += fmt.Sprintf(` AND (
CASE (%s)
WHEN 'profiles_failed' THEN
'failed'
WHEN 'profiles_pending' THEN (
CASE (%s)
WHEN 'bitlocker_failed' THEN
'failed'
ELSE
'pending'
END)
WHEN 'profiles_verifying' THEN (
CASE (%s)
WHEN 'bitlocker_failed' THEN
'failed'
WHEN 'bitlocker_pending' THEN
'pending'
ELSE
'verifying'
END)
WHEN 'profiles_verified' THEN (
CASE (%s)
WHEN 'bitlocker_failed' THEN
'failed'
WHEN 'bitlocker_pending' THEN
'pending'
WHEN 'bitlocker_verifying' THEN
'verifying'
ELSE
'verified'
END)
ELSE
REPLACE((%s), 'bitlocker_', '')
END) = ?`, profilesStatus, bitlockerStatus, bitlockerStatus, bitlockerStatus, bitlockerStatus)
paramsWindows = append(paramsWindows, opt.OSSettingsFilter)
params = append(params, paramsWindows...)
params = append(params, paramsMacOS...)
return sql + fmt.Sprintf(sqlFmt, whereWindows, whereMacOS), params
}
func (ds *Datastore) filterHostsByOSSettingsDiskEncryptionStatus(sql string, opt fleet.HostListOptions, params []interface{}, enableDiskEncryption bool) (string, []interface{}) {
@@ -1229,10 +1315,8 @@ func (ds *Datastore) filterHostsByOSSettingsDiskEncryptionStatus(sql string, opt
}
sqlFmt := " AND h.platform IN('windows', 'darwin')"
// TODO: Should we add no team filter here? It isn't included for the FileVault filter but is
// for the general macOS settings filter.
if opt.TeamFilter == nil {
// macOS settings filter is not compatible with the "all teams" option so append the "no
// OS settings filter is not compatible with the "all teams" option so append the "no
// team" filter here (note that filterHostsByTeam applies the "no team" filter if TeamFilter == 0)
sqlFmt += ` AND h.team_id IS NULL`
}
+12
View File
@@ -879,6 +879,17 @@ SELECT
ELSE
'verifying'
END)
WHEN 'profiles_verified' THEN (
CASE (%s)
WHEN 'bitlocker_failed' THEN
'failed'
WHEN 'bitlocker_pending' THEN
'pending'
WHEN 'bitlocker_verifying' THEN
'verifying'
ELSE
'verified'
END)
ELSE
REPLACE((%s), 'bitlocker_', '')
END as status,
@@ -900,6 +911,7 @@ GROUP BY
bitlockerStatus,
bitlockerStatus,
bitlockerStatus,
bitlockerStatus,
bitlockerJoin,
fleet.WellKnownMDMFleet,
teamFilter,
+39 -8
View File
@@ -144,6 +144,10 @@ func testMDMWindowsDiskEncryption(t *testing.T, ds *Datastore) {
for _, h := range gotHosts {
require.Contains(t, expectedIDs, h.ID)
}
count, err := ds.CountHosts(ctx, fleet.TeamFilter{User: test.UserAdmin}, fleet.HostListOptions{TeamFilter: teamID, OSSettingsDiskEncryptionFilter: status})
require.NoError(t, err)
require.Equal(t, len(expectedIDs), count, fmt.Sprintf("status: %s", status))
}
checkHostBitLockerStatus := func(t *testing.T, expected fleet.DiskEncryptionStatus, hostIDs []uint) {
@@ -223,14 +227,10 @@ func testMDMWindowsDiskEncryption(t *testing.T, ds *Datastore) {
Verified: uint(len(ep[fleet.MDMDeliveryVerified])),
})
checkListHostsFilterOSSettings(t, teamID, fleet.OSSettingsVerified, expectedDE[fleet.DiskEncryptionVerified])
checkListHostsFilterOSSettings(t, teamID, fleet.OSSettingsVerifying, expectedDE[fleet.DiskEncryptionVerifying])
checkListHostsFilterOSSettings(t, teamID, fleet.OSSettingsFailed, expectedDE[fleet.DiskEncryptionFailed])
var expectedPending []uint
expectedPending = append(expectedPending, expectedDE[fleet.DiskEncryptionEnforcing]...)
expectedPending = append(expectedPending, expectedDE[fleet.DiskEncryptionRemovingEnforcement]...)
expectedPending = append(expectedPending, expectedDE[fleet.DiskEncryptionActionRequired]...)
checkListHostsFilterOSSettings(t, teamID, fleet.OSSettingsPending, expectedPending)
checkListHostsFilterOSSettings(t, teamID, fleet.OSSettingsVerified, ep[fleet.MDMDeliveryVerified])
checkListHostsFilterOSSettings(t, teamID, fleet.OSSettingsVerifying, ep[fleet.MDMDeliveryVerifying])
checkListHostsFilterOSSettings(t, teamID, fleet.OSSettingsFailed, ep[fleet.MDMDeliveryFailed])
checkListHostsFilterOSSettings(t, teamID, fleet.OSSettingsPending, ep[fleet.MDMDeliveryPending])
}
updateHostDisks := func(t *testing.T, hostID uint, encrypted bool, updated_at time.Time) {
@@ -292,8 +292,14 @@ func testMDMWindowsDiskEncryption(t *testing.T, ds *Datastore) {
t.Run("Disk encryption disabled", func(t *testing.T) {
ac, err := ds.AppConfig(ctx)
require.NoError(t, err)
ac.MDM.EnableDiskEncryption = optjson.SetBool(false)
require.NoError(t, ds.SaveAppConfig(ctx, ac))
ac, err = ds.AppConfig(ctx)
require.NoError(t, err)
require.False(t, ac.MDM.EnableDiskEncryption.Value)
cleanupHostProfiles(t)
checkExpected(t, nil, hostIDsByDEStatus{}) // no hosts are counted because disk encryption is not enabled
})
@@ -554,6 +560,26 @@ func testMDMWindowsProfilesSummary(t *testing.T, ds *Datastore) {
require.Equal(t, expected, *ps)
}
checkListHostsFilterOSSettings := func(t *testing.T, teamID *uint, status fleet.OSSettingsStatus, expectedIDs []uint) {
gotHosts, err := ds.ListHosts(ctx, fleet.TeamFilter{User: test.UserAdmin}, fleet.HostListOptions{TeamFilter: teamID, OSSettingsFilter: status})
require.NoError(t, err)
if len(expectedIDs) != len(gotHosts) {
gotIDs := make([]uint, len(gotHosts))
for _, h := range gotHosts {
gotIDs = append(gotIDs, h.ID)
}
require.Len(t, gotHosts, len(expectedIDs), fmt.Sprintf("status: %s expected: %v got: %v", status, expectedIDs, gotIDs))
}
for _, h := range gotHosts {
require.Contains(t, expectedIDs, h.ID)
}
count, err := ds.CountHosts(ctx, fleet.TeamFilter{User: test.UserAdmin}, fleet.HostListOptions{TeamFilter: teamID, OSSettingsFilter: status})
require.NoError(t, err)
require.Equal(t, len(expectedIDs), count, "status: %s", status)
}
type hostIDsByProfileStatus map[fleet.MDMDeliveryStatus][]uint
checkExpected := func(t *testing.T, teamID *uint, ep hostIDsByProfileStatus) {
@@ -563,6 +589,11 @@ func testMDMWindowsProfilesSummary(t *testing.T, ds *Datastore) {
Verifying: uint(len(ep[fleet.MDMDeliveryVerifying])),
Verified: uint(len(ep[fleet.MDMDeliveryVerified])),
})
checkListHostsFilterOSSettings(t, teamID, fleet.OSSettingsVerified, ep[fleet.MDMDeliveryVerified])
checkListHostsFilterOSSettings(t, teamID, fleet.OSSettingsVerifying, ep[fleet.MDMDeliveryVerifying])
checkListHostsFilterOSSettings(t, teamID, fleet.OSSettingsFailed, ep[fleet.MDMDeliveryFailed])
checkListHostsFilterOSSettings(t, teamID, fleet.OSSettingsPending, ep[fleet.MDMDeliveryPending])
}
upsertHostProfileStatus := func(t *testing.T, hostUUID string, profUUID string, status *fleet.MDMDeliveryStatus) {