Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

combine osquery + mdm information for connected_to_fleet #20230

Merged
merged 4 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/20057-connected-tweaks
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Improved the accuracy of the heuristic used to deterimine if a host is connected to Fleet via MDM by using osquery data for hosts that didn't send a Checkout message.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Improved the accuracy of the heuristic used to deterimine if a host is connected to Fleet via MDM by using osquery data for hosts that didn't send a Checkout message.
* Improved the accuracy of the heuristic used to determine if a host is connected to Fleet via MDM by using osquery data for hosts that didn't send a Checkout message.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you! these go through chatgpt (😨) before they land the release notes so I'll leave it just so I don't lose your ✅ and have to bother you again + fight CI

15 changes: 13 additions & 2 deletions server/datastore/mysql/apple_mdm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1752,6 +1752,17 @@ func createBuiltinLabels(t *testing.T, ds *Datastore) {
require.NoError(t, err)
}

func nanoEnrollAndSetHostMDMData(t *testing.T, ds *Datastore, host *fleet.Host, withUser bool) {
ctx := context.Background()
ac, err := ds.AppConfig(ctx)
require.NoError(t, err)
expectedMDMServerURL, err := apple_mdm.ResolveAppleEnrollMDMURL(ac.ServerSettings.ServerURL)
require.NoError(t, err)
nanoEnroll(t, ds, host, withUser)
err = ds.SetOrUpdateMDMData(ctx, host.ID, false, true, expectedMDMServerURL, true, fleet.WellKnownMDMFleet, "")
require.NoError(t, err)
}

func nanoEnroll(t *testing.T, ds *Datastore, host *fleet.Host, withUser bool) {
_, err := ds.writer(context.Background()).Exec(`INSERT INTO nano_devices (id, authenticate) VALUES (?, 'test')`, host.UUID)
require.NoError(t, err)
Expand Down Expand Up @@ -1852,7 +1863,7 @@ func testAggregateMacOSSettingsStatusWithFileVault(t *testing.T, ds *Datastore)
h := test.NewHost(t, ds, fmt.Sprintf("foo.local.%d", i), "1.1.1.1",
fmt.Sprintf("%d", i), fmt.Sprintf("%d", i), time.Now())
hosts = append(hosts, h)
nanoEnroll(t, ds, h, false)
nanoEnrollAndSetHostMDMData(t, ds, h, false)
}

// create somes config profiles for no team
Expand Down Expand Up @@ -2129,7 +2140,7 @@ func testMDMAppleHostsProfilesStatus(t *testing.T, ds *Datastore) {
h := test.NewHost(t, ds, fmt.Sprintf("foo.local.%d", i), "1.1.1.1",
fmt.Sprintf("%d", i), fmt.Sprintf("%d", i), time.Now())
hosts = append(hosts, h)
nanoEnroll(t, ds, h, false)
nanoEnrollAndSetHostMDMData(t, ds, h, false)
}

// create somes config profiles for no team
Expand Down
87 changes: 37 additions & 50 deletions server/datastore/mysql/hosts.go
Original file line number Diff line number Diff line change
Expand Up @@ -766,54 +766,9 @@ func queryStatsToScheduledQueryStats(queriesStats []fleet.QueryStats, packName s
return scheduledQueriesStats
}

func winHostConnectedToFleetCond(aliasedCols []string, lenPlaceholders int) string {
in := strings.Repeat("?,", lenPlaceholders)
in += strings.Join(aliasedCols, ",")
in = strings.Trim(in, ",")

return fmt.Sprintf(`
SELECT host_uuid
FROM mdm_windows_enrollments
WHERE host_uuid IN (%s)
AND device_state = '%s'`,
in,
microsoft_mdm.MDMDeviceStateEnrolled)
}

func appleHostConnectedToFleetCond(aliasedCols []string, lenPlaceholders int) string {
in := strings.Repeat("?,", lenPlaceholders)
in += strings.Join(aliasedCols, ",")
in = strings.Trim(in, ",")

return fmt.Sprintf(`
SELECT id
FROM nano_enrollments
WHERE id IN (%s)
AND enabled = 1
AND type = 'Device'`, in)
}

var caseConnectedToFleet = `
CASE
WHEN h.platform = 'windows' THEN (
SELECT CASE WHEN EXISTS (` + winHostConnectedToFleetCond([]string{"h.uuid"}, 0) + `)
THEN CAST(TRUE AS JSON)
ELSE CAST(FALSE AS JSON)
END
)
WHEN h.platform IN ('ios', 'ipados', 'darwin') THEN (
SELECT CASE WHEN EXISTS (` + appleHostConnectedToFleetCond([]string{"h.uuid"}, 0) + `)
THEN CAST(TRUE AS JSON)
ELSE CAST(FALSE AS JSON)
END
)
ELSE CAST(FALSE AS JSON)
END
`

// hostMDMSelect is the SQL fragment used to construct the JSON object
// of MDM host data. It assumes that hostMDMJoin is included in the query.
var hostMDMSelect = `,
const hostMDMSelect = `,
JSON_OBJECT(
'enrollment_status',
CASE
Expand Down Expand Up @@ -850,7 +805,39 @@ var hostMDMSelect = `,
ELSE hdek.decryptable
END,
'connected_to_fleet',
` + caseConnectedToFleet + `,
CASE
WHEN h.platform = 'windows' THEN (` +
// NOTE: if you change any of the conditions in this
// query, please update the AreHostsConnectedToFleetMDM
// datastore method and any relevant filters.
`SELECT CASE WHEN EXISTS (
SELECT mwe.host_uuid
FROM mdm_windows_enrollments mwe
WHERE mwe.host_uuid = h.uuid
AND mwe.device_state = '` + microsoft_mdm.MDMDeviceStateEnrolled + `'
AND hmdm.enrolled = 1
)
THEN CAST(TRUE AS JSON)
ELSE CAST(FALSE AS JSON)
END
)
WHEN h.platform IN ('ios', 'ipados', 'darwin') THEN (` +
// NOTE: if you change any of the conditions in this
// query, please update the AreHostsConnectedToFleetMDM
// datastore method and any relevant filters.
`SELECT CASE WHEN EXISTS (
SELECT ne.id FROM nano_enrollments ne
WHERE ne.id = h.uuid
AND ne.enabled = 1
AND ne.type = 'Device'
AND hmdm.enrolled = 1
)
THEN CAST(TRUE AS JSON)
ELSE CAST(FALSE AS JSON)
END
)
ELSE CAST(FALSE AS JSON)
END,
'name', hmdm.name
) mdm_host_data
`
Expand Down Expand Up @@ -1293,7 +1280,7 @@ func filterHostsByMacOSSettingsStatus(sql string, opt fleet.HostListOptions, par
}

// ensure the host has MDM turned on
whereStatus := " AND ne.id IS NOT NULL"
whereStatus := " AND ne.id IS NOT NULL AND hmdm.enrolled = 1"
// 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)
if opt.TeamFilter == nil {
Expand Down Expand Up @@ -1333,7 +1320,7 @@ func filterHostsByMacOSDiskEncryptionStatus(sql string, opt fleet.HostListOption
subquery, subqueryParams = subqueryFileVaultRemovingEnforcement()
}

return sql + fmt.Sprintf(` AND EXISTS (%s) AND ne.id IS NOT NULL`, subquery), append(params, subqueryParams...)
return sql + fmt.Sprintf(` AND EXISTS (%s) AND ne.id IS NOT NULL AND hmdm.enrolled = 1`, subquery), append(params, subqueryParams...)
}

func (ds *Datastore) filterHostsByOSSettingsStatus(sql string, opt fleet.HostListOptions, params []interface{}, isDiskEncryptionEnabled bool) (string, []interface{}, error) {
Expand All @@ -1349,7 +1336,7 @@ func (ds *Datastore) filterHostsByOSSettingsStatus(sql string, opt fleet.HostLis
// 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', 'ios', 'ipados') AND (ne.id IS NOT NULL OR mwe.host_uuid IS NOT NULL) `
sqlFmt := ` AND h.platform IN('windows', 'darwin', 'ios', 'ipados') AND (ne.id IS NOT NULL OR mwe.host_uuid IS NOT NULL) AND hmdm.enrolled = 1`
if opt.TeamFilter == nil {
// 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)
Expand Down
4 changes: 2 additions & 2 deletions server/datastore/mysql/hosts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -778,7 +778,7 @@ func testHostListOptionsTeamFilter(t *testing.T, ds *Datastore) {
h := test.NewHost(t, ds, fmt.Sprintf("foo.local.%d", i), "1.1.1.1",
fmt.Sprintf("%d", i), fmt.Sprintf("%d", i), time.Now(), opts...)
hosts = append(hosts, h)
nanoEnroll(t, ds, h, false)
nanoEnrollAndSetHostMDMData(t, ds, h, false)
}
userFilter := fleet.TeamFilter{User: test.UserAdmin}

Expand Down Expand Up @@ -3298,7 +3298,7 @@ func testHostsListMacOSSettingsDiskEncryptionStatus(t *testing.T, ds *Datastore)
})
require.NoError(t, err)
hosts = append(hosts, h)
nanoEnroll(t, ds, h, false)
nanoEnrollAndSetHostMDMData(t, ds, h, false)
}

// set up data
Expand Down
10 changes: 5 additions & 5 deletions server/datastore/mysql/labels_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,8 @@ func testLabelsListHostsInLabelAndTeamFilter(deferred bool, t *testing.T, db *Da
listHostsInLabelCheckCount(t, db, userFilter, l1.ID, fleet.HostListOptions{TeamFilter: teamIDFilterNil}, 2) // h1 and h2

// test team filter in combination with macos settings filter
nanoEnroll(t, db, h1, false)
nanoEnrollAndSetHostMDMData(t, db, h1, false)
require.NoError(t, err)
require.NoError(t, db.BulkUpsertMDMAppleHostProfiles(context.Background(), []*fleet.MDMAppleBulkUpsertHostProfilePayload{
{
ProfileUUID: "a" + uuid.NewString(),
Expand All @@ -515,7 +516,7 @@ func testLabelsListHostsInLabelAndTeamFilter(deferred bool, t *testing.T, db *Da
listHostsInLabelCheckCount(t, db, userFilter, l1.ID, fleet.HostListOptions{TeamFilter: teamIDFilterNil, MacOSSettingsFilter: fleet.OSSettingsVerifying}, 0) // no team
listHostsInLabelCheckCount(t, db, userFilter, l1.ID, fleet.HostListOptions{MacOSSettingsFilter: fleet.OSSettingsVerifying}, 0) // no team

nanoEnroll(t, db, h2, false)
nanoEnrollAndSetHostMDMData(t, db, h2, false)
require.NoError(t, db.BulkUpsertMDMAppleHostProfiles(context.Background(), []*fleet.MDMAppleBulkUpsertHostProfilePayload{
{
ProfileUUID: "a" + uuid.NewString(),
Expand Down Expand Up @@ -1145,7 +1146,7 @@ func testListHostsInLabelDiskEncryptionStatus(t *testing.T, ds *Datastore) {
})
require.NoError(t, err)
hosts = append(hosts, h)
nanoEnroll(t, ds, h, false)
nanoEnrollAndSetHostMDMData(t, ds, h, false)
}

// set up data
Expand Down Expand Up @@ -1506,8 +1507,7 @@ func testLabelsListHostsInLabelOSSettings(t *testing.T, db *Datastore) {

// add two hosts to MDM to enforce disk encryption, fleet doesn't enforce settings on centos so h3 is not included
for _, h := range []*fleet.Host{h1, h2} {
require.NoError(t, db.SetOrUpdateMDMData(context.Background(), h.ID, false, true, "https://example.com", false, fleet.WellKnownMDMFleet, ""))
nanoEnroll(t, db, h, false)
nanoEnrollAndSetHostMDMData(t, db, h, false)
}
// add disk encryption key for h1
require.NoError(t, db.SetOrUpdateHostDiskEncryptionKey(context.Background(), h1.ID, "test-key", "", ptr.Bool(true)))
Expand Down
38 changes: 34 additions & 4 deletions server/datastore/mysql/mdm.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/mdm"
"github.com/fleetdm/fleet/v4/server/mdm/apple/mobileconfig"
microsoft_mdm "github.com/fleetdm/fleet/v4/server/mdm/microsoft"
"github.com/go-kit/log/level"
"github.com/jmoiron/sqlx"
)
Expand Down Expand Up @@ -1316,11 +1317,15 @@ func (ds *Datastore) AreHostsConnectedToFleetMDM(ctx context.Context, hosts []*f
res[h.UUID] = false
}

setConnectedUUIDs := func(stmtFn func(aliasedCols []string, lenPlaceholders int) string, uuids []any, mp map[string]bool) error {
setConnectedUUIDs := func(stmt string, uuids []any, mp map[string]bool) error {
var res []string

if len(uuids) > 0 {
err := sqlx.SelectContext(ctx, ds.reader(ctx), &res, stmtFn(nil, len(uuids)), uuids...)
stmt, args, err := sqlx.In(stmt, uuids)
if err != nil {
return ctxerr.Wrap(ctx, err, "building sqlx.In statement")
}
err = sqlx.SelectContext(ctx, ds.reader(ctx), &res, stmt, args...)
if err != nil {
return ctxerr.Wrap(ctx, err, "retrieving hosts connected to fleet")
}
Expand All @@ -1333,11 +1338,36 @@ func (ds *Datastore) AreHostsConnectedToFleetMDM(ctx context.Context, hosts []*f
return nil
}

if err := setConnectedUUIDs(appleHostConnectedToFleetCond, appleUUIDs, res); err != nil {
// NOTE: if you change any of the conditions in this query, please
// update the `hostMDMSelect` constant too, which has a
// `connected_to_fleet` condition, and any relevant filters.
const appleStmt = `
SELECT ne.id
FROM nano_enrollments ne
JOIN hosts h ON h.uuid = ne.id
JOIN host_mdm hm ON hm.host_id = h.id
WHERE ne.id IN (?)
AND ne.enabled = 1
AND ne.type = 'Device'
AND hm.enrolled = 1
`
if err := setConnectedUUIDs(appleStmt, appleUUIDs, res); err != nil {
return nil, err
}

if err := setConnectedUUIDs(winHostConnectedToFleetCond, winUUIDs, res); err != nil {
// NOTE: if you change any of the conditions in this query, please
// update the `hostMDMSelect` constant too, which has a
// `connected_to_fleet` condition, and any relevant filters.
const winStmt = `
SELECT mwe.host_uuid
FROM mdm_windows_enrollments mwe
JOIN hosts h ON h.uuid = mwe.host_uuid
JOIN host_mdm hm ON hm.host_id = h.id
WHERE mwe.host_uuid IN (?)
AND mwe.device_state = '` + microsoft_mdm.MDMDeviceStateEnrolled + `'
AND hm.enrolled = 1
`
if err := setConnectedUUIDs(winStmt, winUUIDs, res); err != nil {
return nil, err
}

Expand Down
Loading
Loading