Add MDM detection for windows and mdm endpoints (#8479)

This commit is contained in:
Frank Sievertsen 2022-11-01 18:22:07 +01:00 committed by GitHub
parent 1aaee2b5de
commit baa1ddc0f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 475 additions and 138 deletions

1
changes/7860-mdm-windows Normal file
View File

@ -0,0 +1 @@
* Detect Windows MDM solutions and add mdm endpoints.

View File

@ -1675,8 +1675,10 @@ None.
- [Transfer hosts to a team by filter](#transfer-hosts-to-a-team-by-filter)
- [Bulk delete hosts by filter or ids](#bulk-delete-hosts-by-filter-or-ids)
- [Get host's Google Chrome profiles](#get-hosts-google-chrome-profiles)
- [Get host's mobile device management (MDM) and Munki information](#get-hosts-mobile-device-management-mdm-and-munki-information)
- [Get aggregated host's mobile device management (MDM) and Munki information](#get-aggregated-hosts-mobile-device-management-mdm-and-munki-information)
- [Get host's mobile device management (MDM) information](#get-hosts-mobile-device-management-mdm-information)
- [Get mobile device management (MDM) summary](#get-mobile-device-management-mdm-summary)
- [Get host's macadmin mobile device management (MDM) and Munki information](#get-hosts-macadmin-mobile-device-management-mdm-and-munki-information)
- [Get aggregated host's mobile device management (MDM) and Munki information](#get-aggregated-hosts-macadmin-mobile-device-management-mdm-and-munki-information)
- [Get host OS versions](#get-host-os-versions)
- [Get hosts report in CSV](#get-hosts-report-in-csv)
@ -2538,7 +2540,94 @@ user by email.
---
### Get host's mobile device management (MDM) and Munki information
### Get host's mobile device management (MDM) information
Currently supports Windows and MacOS. On MacOS this requires the [macadmins osquery
extension](https://github.com/macadmins/osquery-extension) which comes bundled
in [Fleet's osquery installers](https://fleetdm.com/docs/using-fleet/adding-hosts#osquery-installer).
Retrieves a host's MDM enrollment status and MDM server URL.
`GET /api/v1/fleet/hosts/{id}/mdm`
#### Parameters
| Name | Type | In | Description |
| ------- | ------- | ---- | -------------------------------------------------------------------------------- |
| id | integer | path | **Required** The id of the host to get the details for |
#### Example
`GET /api/v1/fleet/hosts/32/mdm`
##### Default response
`Status: 200`
```json
{
"enrollment_status": "Enrolled (automated)",
"server_url": "some.mdm.com",
"name": "Some MDM",
"id": 3
}
```
---
### Get mobile device management (MDM) summary
Currently supports Windows and MacOS. On MacOS this requires the [macadmins osquery
extension](https://github.com/macadmins/osquery-extension) which comes bundled
in [Fleet's osquery installers](https://fleetdm.com/docs/using-fleet/adding-hosts#osquery-installer).
Retrieves MDM enrollment summary.
`GET /api/v1/fleet/hosts/summary/mdm`
#### Parameters
| Name | Type | In | Description |
| -------- | ------- | ----- | -------------------------------------------------------------------------------- |
| team_id | integer | query | Filter by team |
| platform | string | query | Filter by platform ("windows" or "darwin") |
#### Example
`GET /api/v1/fleet/hosts/summary/mdm?team_id=1&platform=windows`
##### Default response
`Status: 200`
```json
{
"mobile_device_management_enrollment_status": {
"enrolled_manual_hosts_count": 0,
"enrolled_automated_hosts_count": 2,
"unenrolled_hosts_count": 0,
"hosts_count": 2
},
"mobile_device_management_solution": [
{
"id": 2,
"name": "Solution1",
"server_url": "solution1.com",
"hosts_count": 1
},
{
"id": 3,
"name": "Solution2",
"server_url": "solution2.com",
"hosts_count": 1
}
]
}
```
---
### Get host's macadmin mobile device management (MDM) and Munki information
Requires the [macadmins osquery
extension](https://github.com/macadmins/osquery-extension) which comes bundled
@ -2596,7 +2685,7 @@ Retrieves a host's MDM enrollment status, MDM server URL, and Munki version.
---
### Get aggregated host's mobile device management (MDM) and Munki information
### Get aggregated host's macadmin mobile device management (MDM) and Munki information
Requires the [macadmins osquery
extension](https://github.com/macadmins/osquery-extension) which comes bundled

View File

@ -2113,8 +2113,8 @@ func (ds *Datastore) getOrInsertMunkiIssues(ctx context.Context, errors, warning
return msgToID, nil
}
func (ds *Datastore) SetOrUpdateMDMData(ctx context.Context, hostID uint, enrolled bool, serverURL string, installedFromDep bool) error {
mdmID, err := ds.getOrInsertMDMSolution(ctx, serverURL)
func (ds *Datastore) SetOrUpdateMDMData(ctx context.Context, hostID uint, enrolled bool, serverURL string, installedFromDep bool, name string) error {
mdmID, err := ds.getOrInsertMDMSolution(ctx, serverURL, name)
if err != nil {
return err
}
@ -2147,9 +2147,10 @@ func (ds *Datastore) SetOrUpdateHostOrbitInfo(ctx context.Context, hostID uint,
)
}
func (ds *Datastore) getOrInsertMDMSolution(ctx context.Context, serverURL string) (mdmID uint, err error) {
mdmName := fleet.MDMNameFromServerURL(serverURL)
func (ds *Datastore) getOrInsertMDMSolution(ctx context.Context, serverURL string, mdmName string) (mdmID uint, err error) {
if mdmName == "" {
mdmName = fleet.MDMNameFromServerURL(serverURL)
}
readStmt := &parameterizedStmt{
Statement: `SELECT id FROM mobile_device_management_solutions WHERE name = ? AND server_url = ?`,
Args: []interface{}{mdmName, serverURL},
@ -2313,7 +2314,7 @@ func (ds *Datastore) AggregatedMunkiIssues(ctx context.Context, teamID *uint) ([
return result, resultJSON.UpdatedAt, nil
}
func (ds *Datastore) AggregatedMDMStatus(ctx context.Context, teamID *uint) (fleet.AggregatedMDMStatus, time.Time, error) {
func (ds *Datastore) AggregatedMDMStatus(ctx context.Context, teamID *uint, platform string) (fleet.AggregatedMDMStatus, time.Time, error) {
id := uint(0)
if teamID != nil {
@ -2327,8 +2328,8 @@ func (ds *Datastore) AggregatedMDMStatus(ctx context.Context, teamID *uint) (fle
}
err := sqlx.GetContext(
ctx, ds.reader, &statusJson,
`select json_value, updated_at from aggregated_stats where id = ? and type = 'mdm_status'`,
id,
`select json_value, updated_at from aggregated_stats where id = ? and type = ?`,
id, platformKey("mdm_status", platform),
)
if err != nil {
if err == sql.ErrNoRows {
@ -2343,7 +2344,14 @@ func (ds *Datastore) AggregatedMDMStatus(ctx context.Context, teamID *uint) (fle
return status, statusJson.UpdatedAt, nil
}
func (ds *Datastore) AggregatedMDMSolutions(ctx context.Context, teamID *uint) ([]fleet.AggregatedMDMSolutions, time.Time, error) {
func platformKey(key string, platform string) string {
if platform == "" {
return key
}
return key + "_" + platform
}
func (ds *Datastore) AggregatedMDMSolutions(ctx context.Context, teamID *uint, platform string) ([]fleet.AggregatedMDMSolutions, time.Time, error) {
id := uint(0)
if teamID != nil {
@ -2357,8 +2365,8 @@ func (ds *Datastore) AggregatedMDMSolutions(ctx context.Context, teamID *uint) (
}
err := sqlx.GetContext(
ctx, ds.reader, &resultJSON,
`SELECT json_value, updated_at FROM aggregated_stats WHERE id = ? AND type = 'mdm_solutions'`,
id,
`SELECT json_value, updated_at FROM aggregated_stats WHERE id = ? AND type = ?`,
id, platformKey("mdm_solutions", platform),
)
if err != nil {
if err == sql.ErrNoRows {
@ -2374,7 +2382,11 @@ func (ds *Datastore) AggregatedMDMSolutions(ctx context.Context, teamID *uint) (
}
func (ds *Datastore) GenerateAggregatedMunkiAndMDM(ctx context.Context) error {
var ids []uint
var (
platforms = []string{"", "darwin", "windows"}
ids []uint
)
if err := sqlx.SelectContext(ctx, ds.reader, &ids, `SELECT id FROM teams`); err != nil {
return ctxerr.Wrap(ctx, err, "list teams")
}
@ -2386,11 +2398,13 @@ func (ds *Datastore) GenerateAggregatedMunkiAndMDM(ctx context.Context) error {
if err := ds.generateAggregatedMunkiIssues(ctx, &id); err != nil {
return ctxerr.Wrap(ctx, err, "generating aggregated munki issues")
}
if err := ds.generateAggregatedMDMStatus(ctx, &id); err != nil {
return ctxerr.Wrap(ctx, err, "generating aggregated mdm status")
}
if err := ds.generateAggregatedMDMSolutions(ctx, &id); err != nil {
return ctxerr.Wrap(ctx, err, "generating aggregated mdm solutions")
for _, platform := range platforms {
if err := ds.generateAggregatedMDMStatus(ctx, &id, platform); err != nil {
return ctxerr.Wrap(ctx, err, "generating aggregated mdm status")
}
if err := ds.generateAggregatedMDMSolutions(ctx, &id, platform); err != nil {
return ctxerr.Wrap(ctx, err, "generating aggregated mdm solutions")
}
}
}
@ -2400,11 +2414,13 @@ func (ds *Datastore) GenerateAggregatedMunkiAndMDM(ctx context.Context) error {
if err := ds.generateAggregatedMunkiIssues(ctx, nil); err != nil {
return ctxerr.Wrap(ctx, err, "generating aggregated munki issues")
}
if err := ds.generateAggregatedMDMStatus(ctx, nil); err != nil {
return ctxerr.Wrap(ctx, err, "generating aggregated mdm status")
}
if err := ds.generateAggregatedMDMSolutions(ctx, nil); err != nil {
return ctxerr.Wrap(ctx, err, "generating aggregated mdm solutions")
for _, platform := range platforms {
if err := ds.generateAggregatedMDMStatus(ctx, nil, platform); err != nil {
return ctxerr.Wrap(ctx, err, "generating aggregated mdm status")
}
if err := ds.generateAggregatedMDMSolutions(ctx, nil, platform); err != nil {
return ctxerr.Wrap(ctx, err, "generating aggregated mdm solutions")
}
}
return nil
}
@ -2496,10 +2512,12 @@ ON DUPLICATE KEY UPDATE
return nil
}
func (ds *Datastore) generateAggregatedMDMStatus(ctx context.Context, teamID *uint) error {
id := uint(0)
func (ds *Datastore) generateAggregatedMDMStatus(ctx context.Context, teamID *uint, platform string) error {
var (
id = uint(0)
status fleet.AggregatedMDMStatus
)
var status fleet.AggregatedMDMStatus
query := `SELECT
COUNT(DISTINCT host_id) as hosts_count,
COALESCE(SUM(CASE WHEN NOT enrolled THEN 1 ELSE 0 END), 0) as unenrolled_hosts_count,
@ -2508,10 +2526,19 @@ func (ds *Datastore) generateAggregatedMDMStatus(ctx context.Context, teamID *ui
FROM host_mdm hm
`
args := []interface{}{}
if teamID != nil || platform != "" {
query += ` JOIN hosts h ON (h.id = hm.host_id) `
}
whereAnd := "WHERE"
if teamID != nil {
args = append(args, *teamID)
query += ` JOIN hosts h ON (h.id = hm.host_id) WHERE h.team_id = ?`
query += ` WHERE h.team_id = ? `
id = *teamID
whereAnd = "AND"
}
if platform != "" {
args = append(args, platform)
query += whereAnd + " h.platform = ? "
}
err := sqlx.GetContext(ctx, ds.reader, &status, query, args...)
if err != nil {
@ -2531,7 +2558,7 @@ ON DUPLICATE KEY UPDATE
json_value = VALUES(json_value),
updated_at = CURRENT_TIMESTAMP
`,
id, "mdm_status", statusJson,
id, platformKey("mdm_status", platform), statusJson,
)
if err != nil {
return ctxerr.Wrapf(ctx, err, "inserting stats for mdm_status id %d", id)
@ -2539,10 +2566,12 @@ ON DUPLICATE KEY UPDATE
return nil
}
func (ds *Datastore) generateAggregatedMDMSolutions(ctx context.Context, teamID *uint) error {
id := uint(0)
var results []fleet.AggregatedMDMSolutions
func (ds *Datastore) generateAggregatedMDMSolutions(ctx context.Context, teamID *uint, platform string) error {
var (
id = uint(0)
results []fleet.AggregatedMDMSolutions
whereAnd = "WHERE"
)
query := `SELECT
mdms.id,
mdms.server_url,
@ -2553,10 +2582,18 @@ func (ds *Datastore) generateAggregatedMDMSolutions(ctx context.Context, teamID
ON hm.mdm_id = mdms.id
`
args := []interface{}{}
if teamID != nil || platform != "" {
query += ` JOIN hosts h ON (h.id = hm.host_id) `
}
if teamID != nil {
args = append(args, *teamID)
query += ` JOIN hosts h ON (h.id = hm.host_id) WHERE h.team_id = ?`
query += ` WHERE h.team_id = ? `
id = *teamID
whereAnd = "AND"
}
if platform != "" {
args = append(args, platform)
query += whereAnd + ` h.platform = ? `
}
query += ` GROUP BY id, server_url, name`
err := sqlx.SelectContext(ctx, ds.reader, &results, query, args...)
@ -2577,7 +2614,7 @@ ON DUPLICATE KEY UPDATE
json_value = VALUES(json_value),
updated_at = CURRENT_TIMESTAMP
`,
id, "mdm_solutions", resultsJSON,
id, platformKey("mdm_solutions", platform), resultsJSON,
)
if err != nil {
return ctxerr.Wrapf(ctx, err, "inserting stats for mdm_solutions id %d", id)

View File

@ -890,13 +890,13 @@ func testHostsListMDM(t *testing.T, ds *Datastore) {
}
const simpleMDM, kandji, unknown = "https://simplemdm.com", "https://kandji.io", "https://url.com"
err := ds.SetOrUpdateMDMData(ctx, hostIDs[0], true, simpleMDM, true) // enrollment: automatic
err := ds.SetOrUpdateMDMData(ctx, hostIDs[0], true, simpleMDM, true, "") // enrollment: automatic
require.NoError(t, err)
err = ds.SetOrUpdateMDMData(ctx, hostIDs[1], true, kandji, true) // enrollment: automatic
err = ds.SetOrUpdateMDMData(ctx, hostIDs[1], true, kandji, true, "") // enrollment: automatic
require.NoError(t, err)
err = ds.SetOrUpdateMDMData(ctx, hostIDs[2], true, unknown, false) // enrollment: manual
err = ds.SetOrUpdateMDMData(ctx, hostIDs[2], true, unknown, false, "") // enrollment: manual
require.NoError(t, err)
err = ds.SetOrUpdateMDMData(ctx, hostIDs[3], false, simpleMDM, false) // enrollment: unenrolled
err = ds.SetOrUpdateMDMData(ctx, hostIDs[3], false, simpleMDM, false, "") // enrollment: unenrolled
require.NoError(t, err)
var simpleMDMID uint
@ -3907,7 +3907,7 @@ func testHostMDMAndMunki(t *testing.T, ds *Datastore) {
_, err = ds.GetHostMDM(context.Background(), 432)
require.True(t, fleet.IsNotFound(err), err)
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), 432, true, "url", false))
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), 432, true, "url", false, ""))
hmdm, err := ds.GetHostMDM(context.Background(), 432)
require.NoError(t, err)
@ -3919,8 +3919,8 @@ func testHostMDMAndMunki(t *testing.T, ds *Datastore) {
urlMDMID := *hmdm.MDMID
assert.Equal(t, fleet.UnknownMDMName, hmdm.Name)
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), 455, true, "https://kandji.io", true)) // kandji mdm name
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), 432, false, "url3", true))
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), 455, true, "https://kandji.io", true, "")) // kandji mdm name
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), 432, false, "url3", true, ""))
hmdm, err = ds.GetHostMDM(context.Background(), 432)
require.NoError(t, err)
@ -3954,7 +3954,7 @@ func testHostMDMAndMunki(t *testing.T, ds *Datastore) {
require.ErrorIs(t, err, sql.ErrNoRows)
// switch to simplemdm in an update
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), 455, true, "https://simplemdm.com", false)) // now simplemdm name
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), 455, true, "https://simplemdm.com", false, "")) // now simplemdm name
hmdm, err = ds.GetHostMDM(context.Background(), 455)
require.NoError(t, err)
@ -3966,7 +3966,7 @@ func testHostMDMAndMunki(t *testing.T, ds *Datastore) {
assert.Equal(t, fleet.WellKnownMDMSimpleMDM, hmdm.Name)
// switch back to "url"
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), 455, false, "url", false))
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), 455, false, "url", false, ""))
hmdm, err = ds.GetHostMDM(context.Background(), 455)
require.NoError(t, err)
@ -3979,7 +3979,7 @@ func testHostMDMAndMunki(t *testing.T, ds *Datastore) {
// switch to a different Kandji server URL, will have a different MDM ID as
// even though this is another Kandji, the URL is different.
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), 455, true, "https://kandji.io/2", false))
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), 455, true, "https://kandji.io/2", false, ""))
hmdm, err = ds.GetHostMDM(context.Background(), 455)
require.NoError(t, err)
@ -4065,11 +4065,19 @@ func testAggregatedHostMDMAndMunki(t *testing.T, ds *Datastore) {
require.NoError(t, err)
require.Len(t, issues, 0)
require.Zero(t, updatedAt)
status, updatedAt, err := ds.AggregatedMDMStatus(context.Background(), nil)
status, updatedAt, err := ds.AggregatedMDMStatus(context.Background(), nil, "")
require.NoError(t, err)
require.Empty(t, status)
require.Zero(t, updatedAt)
solutions, updatedAt, err := ds.AggregatedMDMSolutions(context.Background(), nil)
solutions, updatedAt, err := ds.AggregatedMDMSolutions(context.Background(), nil, "")
require.NoError(t, err)
require.Len(t, solutions, 0)
require.Zero(t, updatedAt)
status, updatedAt, err = ds.AggregatedMDMStatus(context.Background(), nil, "windows")
require.NoError(t, err)
require.Empty(t, status)
require.Zero(t, updatedAt)
solutions, updatedAt, err = ds.AggregatedMDMSolutions(context.Background(), nil, "windows")
require.NoError(t, err)
require.Len(t, solutions, 0)
require.Zero(t, updatedAt)
@ -4088,11 +4096,19 @@ func testAggregatedHostMDMAndMunki(t *testing.T, ds *Datastore) {
require.NoError(t, err)
require.Empty(t, issues)
require.NotZero(t, updatedAt)
status, updatedAt, err = ds.AggregatedMDMStatus(context.Background(), nil)
status, updatedAt, err = ds.AggregatedMDMStatus(context.Background(), nil, "")
require.NoError(t, err)
require.Empty(t, status)
require.NotZero(t, updatedAt)
solutions, updatedAt, err = ds.AggregatedMDMSolutions(context.Background(), nil)
solutions, updatedAt, err = ds.AggregatedMDMSolutions(context.Background(), nil, "")
require.NoError(t, err)
require.Len(t, solutions, 0)
require.NotZero(t, updatedAt)
status, updatedAt, err = ds.AggregatedMDMStatus(context.Background(), nil, "windows")
require.NoError(t, err)
require.Empty(t, status)
require.NotZero(t, updatedAt)
solutions, updatedAt, err = ds.AggregatedMDMSolutions(context.Background(), nil, "windows")
require.NoError(t, err)
require.Len(t, solutions, 0)
require.NotZero(t, updatedAt)
@ -4149,23 +4165,23 @@ func testAggregatedHostMDMAndMunki(t *testing.T, ds *Datastore) {
},
})
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), 432, true, "url", false))
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), 123, true, "url", false))
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), 124, true, "url", false))
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), 455, true, "https://simplemdm.com", true))
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), 999, false, "https://kandji.io", true))
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), 875, false, "https://kandji.io", true))
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), 432, true, "url", false, ""))
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), 123, true, "url", false, ""))
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), 124, true, "url", false, ""))
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), 455, true, "https://simplemdm.com", true, ""))
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), 999, false, "https://kandji.io", true, ""))
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), 875, false, "https://kandji.io", true, ""))
require.NoError(t, ds.GenerateAggregatedMunkiAndMDM(context.Background()))
status, _, err = ds.AggregatedMDMStatus(context.Background(), nil)
status, _, err = ds.AggregatedMDMStatus(context.Background(), nil, "")
require.NoError(t, err)
assert.Equal(t, 6, status.HostsCount)
assert.Equal(t, 2, status.UnenrolledHostsCount)
assert.Equal(t, 3, status.EnrolledManualHostsCount)
assert.Equal(t, 1, status.EnrolledAutomatedHostsCount)
solutions, _, err = ds.AggregatedMDMSolutions(context.Background(), nil)
solutions, _, err = ds.AggregatedMDMSolutions(context.Background(), nil, "")
require.NoError(t, err)
require.Len(t, solutions, 3) // 3 different urls
for _, sol := range solutions {
@ -4196,16 +4212,16 @@ func testAggregatedHostMDMAndMunki(t *testing.T, ds *Datastore) {
})
require.NoError(t, err)
h1 := test.NewHost(t, ds, "h1"+t.Name(), "192.168.1.10", "1", "1", time.Now())
h2 := test.NewHost(t, ds, "h2"+t.Name(), "192.168.1.11", "2", "2", time.Now())
h3 := test.NewHost(t, ds, "h3"+t.Name(), "192.168.1.11", "3", "3", time.Now())
h1 := test.NewHost(t, ds, "h1"+t.Name(), "192.168.1.10", "1", "1", time.Now(), test.WithPlatform("windows"))
h2 := test.NewHost(t, ds, "h2"+t.Name(), "192.168.1.11", "2", "2", time.Now(), test.WithPlatform("darwin"))
h3 := test.NewHost(t, ds, "h3"+t.Name(), "192.168.1.11", "3", "3", time.Now(), test.WithPlatform("darwin"))
require.NoError(t, ds.AddHostsToTeam(context.Background(), &team1.ID, []uint{h1.ID}))
require.NoError(t, ds.AddHostsToTeam(context.Background(), &team2.ID, []uint{h2.ID}))
require.NoError(t, ds.AddHostsToTeam(context.Background(), &team1.ID, []uint{h3.ID}))
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), h1.ID, true, "https://simplemdm.com", false))
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), h2.ID, true, "url", false))
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), h1.ID, true, "https://simplemdm.com", false, ""))
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), h2.ID, true, "url", false, ""))
require.NoError(t, ds.SetOrUpdateMunkiInfo(context.Background(), h1.ID, "1.2.3", []string{"d"}, nil))
require.NoError(t, ds.SetOrUpdateMunkiInfo(context.Background(), h2.ID, "1.2.3", []string{"d"}, []string{"e"}))
@ -4252,14 +4268,41 @@ func testAggregatedHostMDMAndMunki(t *testing.T, ds *Datastore) {
})
require.True(t, updatedAt.After(firstUpdatedAt))
status, _, err = ds.AggregatedMDMStatus(context.Background(), &team1.ID)
status, _, err = ds.AggregatedMDMStatus(context.Background(), &team1.ID, "")
require.NoError(t, err)
assert.Equal(t, 1, status.HostsCount)
assert.Equal(t, 0, status.UnenrolledHostsCount)
assert.Equal(t, 1, status.EnrolledManualHostsCount)
assert.Equal(t, 0, status.EnrolledAutomatedHostsCount)
solutions, updatedAt, err = ds.AggregatedMDMSolutions(context.Background(), &team1.ID)
solutions, updatedAt, err = ds.AggregatedMDMSolutions(context.Background(), &team1.ID, "")
require.True(t, updatedAt.After(firstUpdatedAt))
require.NoError(t, err)
require.Len(t, solutions, 1)
assert.Equal(t, "https://simplemdm.com", solutions[0].ServerURL)
assert.Equal(t, fleet.WellKnownMDMSimpleMDM, solutions[0].Name)
assert.Equal(t, 1, solutions[0].HostsCount)
status, _, err = ds.AggregatedMDMStatus(context.Background(), &team1.ID, "darwin")
require.NoError(t, err)
assert.Equal(t, 0, status.HostsCount)
assert.Equal(t, 0, status.UnenrolledHostsCount)
assert.Equal(t, 0, status.EnrolledManualHostsCount)
assert.Equal(t, 0, status.EnrolledAutomatedHostsCount)
solutions, updatedAt, err = ds.AggregatedMDMSolutions(context.Background(), &team1.ID, "darwin")
require.True(t, updatedAt.After(firstUpdatedAt))
require.NoError(t, err)
require.Len(t, solutions, 0)
status, _, err = ds.AggregatedMDMStatus(context.Background(), &team1.ID, "windows")
require.NoError(t, err)
assert.Equal(t, 1, status.HostsCount)
assert.Equal(t, 0, status.UnenrolledHostsCount)
assert.Equal(t, 1, status.EnrolledManualHostsCount)
assert.Equal(t, 0, status.EnrolledAutomatedHostsCount)
solutions, updatedAt, err = ds.AggregatedMDMSolutions(context.Background(), &team1.ID, "windows")
require.True(t, updatedAt.After(firstUpdatedAt))
require.NoError(t, err)
require.Len(t, solutions, 1)
@ -4871,7 +4914,7 @@ func testHostsDeleteHosts(t *testing.T, ds *Datastore) {
require.NoError(t, err)
require.NoError(t, ds.RecordPolicyQueryExecutions(context.Background(), host, map[uint]*bool{policy.ID: ptr.Bool(true)}, time.Now(), false))
// Update host_mdm.
err = ds.SetOrUpdateMDMData(context.Background(), host.ID, false, "", false)
err = ds.SetOrUpdateMDMData(context.Background(), host.ID, false, "", false, "")
require.NoError(t, err)
// Update host_munki_info.
err = ds.SetOrUpdateMunkiInfo(context.Background(), host.ID, "42", []string{"a"}, []string{"b"})

View File

@ -254,8 +254,8 @@ type Datastore interface {
AggregatedMunkiVersion(ctx context.Context, teamID *uint) ([]AggregatedMunkiVersion, time.Time, error)
AggregatedMunkiIssues(ctx context.Context, teamID *uint) ([]AggregatedMunkiIssue, time.Time, error)
AggregatedMDMStatus(ctx context.Context, teamID *uint) (AggregatedMDMStatus, time.Time, error)
AggregatedMDMSolutions(ctx context.Context, teamID *uint) ([]AggregatedMDMSolutions, time.Time, error)
AggregatedMDMStatus(ctx context.Context, teamID *uint, platform string) (AggregatedMDMStatus, time.Time, error)
AggregatedMDMSolutions(ctx context.Context, teamID *uint, platform string) ([]AggregatedMDMSolutions, time.Time, error)
GenerateAggregatedMunkiAndMDM(ctx context.Context) error
GetMunkiIssue(ctx context.Context, munkiIssueID uint) (*MunkiIssue, error)
@ -605,7 +605,7 @@ type Datastore interface {
SaveHostAdditional(ctx context.Context, hostID uint, additional *json.RawMessage) error
SetOrUpdateMunkiInfo(ctx context.Context, hostID uint, version string, errors, warnings []string) error
SetOrUpdateMDMData(ctx context.Context, hostID uint, enrolled bool, serverURL string, installedFromDep bool) error
SetOrUpdateMDMData(ctx context.Context, hostID uint, enrolled bool, serverURL string, installedFromDep bool, name string) error
SetOrUpdateHostDisksSpace(ctx context.Context, hostID uint, gigsAvailable, percentAvailable float64) error
// SetOrUpdateHostOrbitInfo inserts of updates the orbit info for a host
SetOrUpdateHostOrbitInfo(ctx context.Context, hostID uint, version string) error

View File

@ -486,6 +486,12 @@ type AggregatedMDMStatus struct {
HostsCount int `json:"hosts_count" db:"hosts_count"`
}
// AggregatedMDMData contains aggregated data from mdm installations.
type AggregatedMDMData struct {
MDMStatus AggregatedMDMStatus `json:"mobile_device_management_enrollment_status"`
MDMSolutions []AggregatedMDMSolutions `json:"mobile_device_management_solution"`
}
// MDMSolution represents a single MDM solution, as returned by the list hosts
// endpoint when an MDM Solution ID is provided as filter.
type MDMSolution struct {

View File

@ -315,7 +315,9 @@ type Service interface {
DisableAuthForPing(ctx context.Context)
MacadminsData(ctx context.Context, id uint) (*MacadminsData, error)
MDMData(ctx context.Context, id uint) (*HostMDM, error)
AggregatedMacadminsData(ctx context.Context, teamID *uint) (*AggregatedMacadminsData, error)
AggregatedMDMData(ctx context.Context, id *uint, platform string) (AggregatedMDMData, error)
GetMDMSolution(ctx context.Context, mdmID uint) (*MDMSolution, error)
GetMunkiIssue(ctx context.Context, munkiIssueID uint) (*MunkiIssue, error)

View File

@ -203,9 +203,9 @@ type AggregatedMunkiVersionFunc func(ctx context.Context, teamID *uint) ([]fleet
type AggregatedMunkiIssuesFunc func(ctx context.Context, teamID *uint) ([]fleet.AggregatedMunkiIssue, time.Time, error)
type AggregatedMDMStatusFunc func(ctx context.Context, teamID *uint) (fleet.AggregatedMDMStatus, time.Time, error)
type AggregatedMDMStatusFunc func(ctx context.Context, teamID *uint, platform string) (fleet.AggregatedMDMStatus, time.Time, error)
type AggregatedMDMSolutionsFunc func(ctx context.Context, teamID *uint) ([]fleet.AggregatedMDMSolutions, time.Time, error)
type AggregatedMDMSolutionsFunc func(ctx context.Context, teamID *uint, platform string) ([]fleet.AggregatedMDMSolutions, time.Time, error)
type GenerateAggregatedMunkiAndMDMFunc func(ctx context.Context) error
@ -437,7 +437,7 @@ type SaveHostAdditionalFunc func(ctx context.Context, hostID uint, additional *j
type SetOrUpdateMunkiInfoFunc func(ctx context.Context, hostID uint, version string, errors []string, warnings []string) error
type SetOrUpdateMDMDataFunc func(ctx context.Context, hostID uint, enrolled bool, serverURL string, installedFromDep bool) error
type SetOrUpdateMDMDataFunc func(ctx context.Context, hostID uint, enrolled bool, serverURL string, installedFromDep bool, name string) error
type SetOrUpdateHostDisksSpaceFunc func(ctx context.Context, hostID uint, gigsAvailable float64, percentAvailable float64) error
@ -1700,14 +1700,14 @@ func (s *DataStore) AggregatedMunkiIssues(ctx context.Context, teamID *uint) ([]
return s.AggregatedMunkiIssuesFunc(ctx, teamID)
}
func (s *DataStore) AggregatedMDMStatus(ctx context.Context, teamID *uint) (fleet.AggregatedMDMStatus, time.Time, error) {
func (s *DataStore) AggregatedMDMStatus(ctx context.Context, teamID *uint, platform string) (fleet.AggregatedMDMStatus, time.Time, error) {
s.AggregatedMDMStatusFuncInvoked = true
return s.AggregatedMDMStatusFunc(ctx, teamID)
return s.AggregatedMDMStatusFunc(ctx, teamID, platform)
}
func (s *DataStore) AggregatedMDMSolutions(ctx context.Context, teamID *uint) ([]fleet.AggregatedMDMSolutions, time.Time, error) {
func (s *DataStore) AggregatedMDMSolutions(ctx context.Context, teamID *uint, platform string) ([]fleet.AggregatedMDMSolutions, time.Time, error) {
s.AggregatedMDMSolutionsFuncInvoked = true
return s.AggregatedMDMSolutionsFunc(ctx, teamID)
return s.AggregatedMDMSolutionsFunc(ctx, teamID, platform)
}
func (s *DataStore) GenerateAggregatedMunkiAndMDM(ctx context.Context) error {
@ -2285,9 +2285,9 @@ func (s *DataStore) SetOrUpdateMunkiInfo(ctx context.Context, hostID uint, versi
return s.SetOrUpdateMunkiInfoFunc(ctx, hostID, version, errors, warnings)
}
func (s *DataStore) SetOrUpdateMDMData(ctx context.Context, hostID uint, enrolled bool, serverURL string, installedFromDep bool) error {
func (s *DataStore) SetOrUpdateMDMData(ctx context.Context, hostID uint, enrolled bool, serverURL string, installedFromDep bool, name string) error {
s.SetOrUpdateMDMDataFuncInvoked = true
return s.SetOrUpdateMDMDataFunc(ctx, hostID, enrolled, serverURL, installedFromDep)
return s.SetOrUpdateMDMDataFunc(ctx, hostID, enrolled, serverURL, installedFromDep, name)
}
func (s *DataStore) SetOrUpdateHostDisksSpace(ctx context.Context, hostID uint, gigsAvailable float64, percentAvailable float64) error {

View File

@ -343,6 +343,9 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
ue.GET("/api/_version_/fleet/hosts/report", hostsReportEndpoint, hostsReportRequest{})
ue.GET("/api/_version_/fleet/os_versions", osVersionsEndpoint, osVersionsRequest{})
ue.GET("/api/_version_/fleet/hosts/summary/mdm", getHostMDMSummary, getHostMDMSummaryRequest{})
ue.GET("/api/_version_/fleet/hosts/{id:[0-9]+}/mdm", getHostMDM, getHostMDMRequest{})
ue.POST("/api/_version_/fleet/labels", createLabelEndpoint, createLabelRequest{})
ue.PATCH("/api/_version_/fleet/labels/{id:[0-9]+}", modifyLabelEndpoint, modifyLabelRequest{})
ue.GET("/api/_version_/fleet/labels/{id:[0-9]+}", getLabelEndpoint, getLabelRequest{})

View File

@ -889,6 +889,50 @@ func (svc *Service) ListHostDeviceMapping(ctx context.Context, id uint) ([]*flee
return svc.ds.ListHostDeviceMapping(ctx, id)
}
////////////////////////////////////////////////////////////////////////////////
// MDM
////////////////////////////////////////////////////////////////////////////////
type getHostMDMRequest struct {
ID uint `url:"id"`
}
type getHostMDMResponse struct {
Err error `json:"error,omitempty"`
*fleet.HostMDM
}
func getHostMDM(ctx context.Context, request interface{}, svc fleet.Service) (interface{}, error) {
req := request.(*getHostMDMRequest)
mdm, err := svc.MDMData(ctx, req.ID)
if err != nil {
return getHostMDMResponse{Err: err}, nil
}
return getHostMDMResponse{HostMDM: mdm}, nil
}
type getHostMDMSummaryResponse struct {
fleet.AggregatedMDMData
Err error `json:"error,omitempty"`
}
type getHostMDMSummaryRequest struct {
TeamID *uint `query:"team_id,optional"`
Platform string `query:"platform,optional"`
}
func getHostMDMSummary(ctx context.Context, request interface{}, svc fleet.Service) (interface{}, error) {
req := request.(*getHostMDMSummaryRequest)
resp := getHostMDMSummaryResponse{}
var err error
resp.AggregatedMDMData, err = svc.AggregatedMDMData(ctx, req.TeamID, req.Platform)
if err != nil {
return getHostMDMSummaryResponse{Err: err}, nil
}
return resp, nil
}
////////////////////////////////////////////////////////////////////////////////
// Macadmins
////////////////////////////////////////////////////////////////////////////////
@ -1016,18 +1060,15 @@ func (svc *Service) AggregatedMacadminsData(ctx context.Context, teamID *uint) (
}
agg.MunkiIssues = issues
status, mdmUpdatedAt, err := svc.ds.AggregatedMDMStatus(ctx, teamID)
var mdmUpdatedAt, mdmSolutionsUpdatedAt time.Time
agg.MDMStatus, mdmUpdatedAt, err = svc.ds.AggregatedMDMStatus(ctx, teamID, "darwin")
if err != nil {
return nil, err
}
agg.MDMStatus = status
solutions, mdmSolutionsUpdatedAt, err := svc.ds.AggregatedMDMSolutions(ctx, teamID)
agg.MDMSolutions, mdmSolutionsUpdatedAt, err = svc.ds.AggregatedMDMSolutions(ctx, teamID, "darwin")
if err != nil {
return nil, err
}
agg.MDMSolutions = solutions
agg.CountsUpdatedAt = munkiUpdatedAt
if munkiIssUpdatedAt.After(agg.CountsUpdatedAt) {
agg.CountsUpdatedAt = munkiIssUpdatedAt
@ -1042,6 +1083,48 @@ func (svc *Service) AggregatedMacadminsData(ctx context.Context, teamID *uint) (
return agg, nil
}
func (svc *Service) MDMData(ctx context.Context, id uint) (*fleet.HostMDM, error) {
if err := svc.authz.Authorize(ctx, &fleet.Host{}, fleet.ActionList); err != nil {
return nil, err
}
host, err := svc.ds.HostLite(ctx, id)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "find host for MDMData")
}
if err := svc.authz.Authorize(ctx, host, fleet.ActionRead); err != nil {
return nil, err
}
var mdm *fleet.HostMDM
switch hmdm, err := svc.ds.GetHostMDM(ctx, id); {
case err != nil && !fleet.IsNotFound(err):
return nil, err
case err == nil:
mdm = hmdm
}
return mdm, nil
}
func (svc *Service) AggregatedMDMData(ctx context.Context, teamID *uint, platform string) (fleet.AggregatedMDMData, error) {
if err := svc.authz.Authorize(ctx, &fleet.Host{TeamID: teamID}, fleet.ActionList); err != nil {
return fleet.AggregatedMDMData{}, err
}
var err error
data := fleet.AggregatedMDMData{}
data.MDMStatus, _, err = svc.ds.AggregatedMDMStatus(ctx, teamID, platform)
if err != nil {
return fleet.AggregatedMDMData{}, err
}
data.MDMSolutions, _, err = svc.ds.AggregatedMDMSolutions(ctx, teamID, platform)
if err != nil {
return fleet.AggregatedMDMData{}, err
}
return data, nil
}
////////////////////////////////////////////////////////////////////////////////
// Hosts Report in CSV downloadable file
////////////////////////////////////////////////////////////////////////////////

View File

@ -932,7 +932,7 @@ func (s *integrationTestSuite) TestHostsCount() {
require.Equal(t, 0, resp.Count)
// set MDM information on a host
require.NoError(t, s.ds.SetOrUpdateMDMData(context.Background(), hosts[1].ID, true, "https://simplemdm.com", false))
require.NoError(t, s.ds.SetOrUpdateMDMData(context.Background(), hosts[1].ID, true, "https://simplemdm.com", false, ""))
var mdmID uint
mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error {
return sqlx.GetContext(context.Background(), q, &mdmID,
@ -1119,7 +1119,7 @@ func (s *integrationTestSuite) TestListHosts() {
assert.Nil(t, resp.MunkiIssue)
// set MDM information on a host
require.NoError(t, s.ds.SetOrUpdateMDMData(context.Background(), host.ID, true, "https://simplemdm.com", false))
require.NoError(t, s.ds.SetOrUpdateMDMData(context.Background(), host.ID, true, "https://simplemdm.com", false, ""))
var mdmID uint
mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error {
return sqlx.GetContext(context.Background(), q, &mdmID,
@ -2542,6 +2542,7 @@ func (s *integrationTestSuite) TestGetMacadminsData() {
PrimaryIP: "192.168.1.1",
PrimaryMac: "30-65-EC-6F-C4-58",
OsqueryHostID: "1",
Platform: "darwin",
})
require.NoError(t, err)
require.NotNil(t, hostAll)
@ -2557,6 +2558,7 @@ func (s *integrationTestSuite) TestGetMacadminsData() {
PrimaryIP: "192.168.1.2",
PrimaryMac: "30-65-EC-6F-C4-59",
OsqueryHostID: "2",
Platform: "darwin",
})
require.NoError(t, err)
require.NotNil(t, hostNothing)
@ -2572,6 +2574,7 @@ func (s *integrationTestSuite) TestGetMacadminsData() {
PrimaryIP: "192.168.1.3",
PrimaryMac: "30-65-EC-6F-C4-5F",
OsqueryHostID: "3",
Platform: "darwin",
})
require.NoError(t, err)
require.NotNil(t, hostOnlyMunki)
@ -2587,6 +2590,7 @@ func (s *integrationTestSuite) TestGetMacadminsData() {
PrimaryIP: "192.168.1.4",
PrimaryMac: "30-65-EC-6F-C4-5A",
OsqueryHostID: "4",
Platform: "darwin",
})
require.NoError(t, err)
require.NotNil(t, hostOnlyMDM)
@ -2602,6 +2606,7 @@ func (s *integrationTestSuite) TestGetMacadminsData() {
PrimaryIP: "192.168.1.5",
PrimaryMac: "30-65-EC-6F-D5-5A",
OsqueryHostID: "5",
Platform: "darwin",
})
require.NoError(t, err)
require.NotNil(t, hostMDMNoID)
@ -2614,7 +2619,7 @@ func (s *integrationTestSuite) TestGetMacadminsData() {
return err
})
require.NoError(t, s.ds.SetOrUpdateMDMData(ctx, hostAll.ID, true, "url", false))
require.NoError(t, s.ds.SetOrUpdateMDMData(ctx, hostAll.ID, true, "url", false, ""))
require.NoError(t, s.ds.SetOrUpdateMunkiInfo(ctx, hostAll.ID, "1.3.0", []string{"error1"}, []string{"warning1"}))
macadminsData := macadminsDataResponse{}
@ -2641,7 +2646,7 @@ func (s *integrationTestSuite) TestGetMacadminsData() {
assert.False(t, macadminsData.Macadmins.MunkiIssues[1].HostIssueCreatedAt.IsZero())
assert.Equal(t, "warning", macadminsData.Macadmins.MunkiIssues[1].IssueType)
require.NoError(t, s.ds.SetOrUpdateMDMData(ctx, hostAll.ID, true, "https://simplemdm.com", true))
require.NoError(t, s.ds.SetOrUpdateMDMData(ctx, hostAll.ID, true, "https://simplemdm.com", true, ""))
require.NoError(t, s.ds.SetOrUpdateMunkiInfo(ctx, hostAll.ID, "1.5.0", []string{"error1"}, nil))
macadminsData = macadminsDataResponse{}
@ -2657,7 +2662,7 @@ func (s *integrationTestSuite) TestGetMacadminsData() {
require.Len(t, macadminsData.Macadmins.MunkiIssues, 1)
assert.Equal(t, "error1", macadminsData.Macadmins.MunkiIssues[0].Name)
require.NoError(t, s.ds.SetOrUpdateMDMData(ctx, hostAll.ID, false, "url2", false))
require.NoError(t, s.ds.SetOrUpdateMDMData(ctx, hostAll.ID, false, "url2", false, ""))
macadminsData = macadminsDataResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/macadmins", hostAll.ID), nil, http.StatusOK, &macadminsData)
@ -2685,7 +2690,7 @@ func (s *integrationTestSuite) TestGetMacadminsData() {
assert.Equal(t, "warning1", macadminsData.Macadmins.MunkiIssues[0].Name)
// only mdm returns null on munki info
require.NoError(t, s.ds.SetOrUpdateMDMData(ctx, hostOnlyMDM.ID, true, "https://kandji.io", true))
require.NoError(t, s.ds.SetOrUpdateMDMData(ctx, hostOnlyMDM.ID, true, "https://kandji.io", true, ""))
macadminsData = macadminsDataResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/hosts/%d/macadmins", hostOnlyMDM.ID), nil, http.StatusOK, &macadminsData)
require.NotNil(t, macadminsData.Macadmins)

View File

@ -29,7 +29,7 @@ func (s *integrationTestSuite) TestDeviceAuthenticatedEndpoints() {
{HostID: hosts[0].ID, Email: "a@b.c", Source: "google_chrome_profiles"},
{HostID: hosts[0].ID, Email: "b@b.c", Source: "google_chrome_profiles"},
})
require.NoError(t, s.ds.SetOrUpdateMDMData(context.Background(), hosts[0].ID, true, "url", false))
require.NoError(t, s.ds.SetOrUpdateMDMData(context.Background(), hosts[0].ID, true, "url", false, ""))
require.NoError(t, s.ds.SetOrUpdateMunkiInfo(context.Background(), hosts[0].ID, "1.3.0", nil, nil))
// create a battery for hosts[0]
require.NoError(t, s.ds.ReplaceHostBatteries(context.Background(), hosts[0].ID, []*fleet.HostBattery{

View File

@ -560,11 +560,11 @@ func TestHostDetailQueries(t *testing.T) {
queries, discovery, err = svc.detailQueriesForHost(context.Background(), &host)
require.NoError(t, err)
// 2 - 4 = -2
// 2 - 5 = -3
// └───┼────┼─► additional queries: bim, foobar
// └────┼─► windows specific queries: os_windows, os_version_windows, network_interface_windows, disk_space_windows
// └─► net difference
require.Equal(t, len(expectedDetailQueries)-2, len(queries), distQueriesMapKeys(queries))
require.Equal(t, len(expectedDetailQueries)-3, len(queries), distQueriesMapKeys(queries))
verifyDiscovery(t, queries, discovery)
for name := range queries {
assert.True(t,
@ -744,8 +744,8 @@ func TestLabelQueries(t *testing.T) {
// should be turned on so that we can quickly fill labels)
queries, discovery, acc, err := svc.GetDistributedQueries(ctx)
require.NoError(t, err)
// -4 windows specific queries: disk_space_windows, network_interface_windows, os_windows, os_version_windows
require.Equal(t, len(expectedDetailQueries)-4, len(queries), distQueriesMapKeys(queries))
// -5 windows specific queries: disk_space_windows, network_interface_windows, os_windows, os_version_windows
require.Equal(t, len(expectedDetailQueries)-5, len(queries), distQueriesMapKeys(queries))
verifyDiscovery(t, queries, discovery)
assert.NotZero(t, acc)
@ -835,8 +835,8 @@ func TestLabelQueries(t *testing.T) {
ctx = hostctx.NewContext(ctx, host)
queries, discovery, acc, err = svc.GetDistributedQueries(ctx)
require.NoError(t, err)
// +3 for label queries, but -4 windows specific queries
require.Equal(t, len(expectedDetailQueries)-1, len(queries), distQueriesMapKeys(queries))
// +3 for label queries, but -5 windows specific queries
require.Equal(t, len(expectedDetailQueries)-2, len(queries), distQueriesMapKeys(queries))
verifyDiscovery(t, queries, discovery)
assert.Zero(t, acc)
@ -1051,8 +1051,8 @@ func TestDetailQueriesWithEmptyStrings(t *testing.T) {
require.NoError(t, err)
// somehow confusingly, the query response above changed the host's platform
// from windows to darwin
// -4 windows queries
require.Equal(t, len(expectedDetailQueries)-4, len(queries), distQueriesMapKeys(queries))
// -5 windows queries
require.Equal(t, len(expectedDetailQueries)-5, len(queries), distQueriesMapKeys(queries))
verifyDiscovery(t, queries, discovery)
assert.Zero(t, acc)
}
@ -1080,7 +1080,7 @@ func TestDetailQueries(t *testing.T) {
ds.PolicyQueriesForHostFunc = func(ctx context.Context, host *fleet.Host) (map[string]string, error) {
return map[string]string{}, nil
}
ds.SetOrUpdateMDMDataFunc = func(ctx context.Context, hostID uint, enrolled bool, serverURL string, installedFromDep bool) error {
ds.SetOrUpdateMDMDataFunc = func(ctx context.Context, hostID uint, enrolled bool, serverURL string, installedFromDep bool, name string) error {
require.True(t, enrolled)
require.False(t, installedFromDep)
require.Equal(t, "hi.com", serverURL)
@ -1115,8 +1115,8 @@ func TestDetailQueries(t *testing.T) {
// queries)
queries, discovery, acc, err := svc.GetDistributedQueries(ctx)
require.NoError(t, err)
// -3 macos queries, -4 windows quries, +1 for software inventory
if !assert.Equal(t, len(expectedDetailQueries)-6, len(queries)) {
// -3 macos queries, -5 windows quries, +1 for software inventory
if !assert.Equal(t, len(expectedDetailQueries)-3-5+1, len(queries)) {
// this is just to print the diff between the expected and actual query
// keys when the count assertion fails, to help debugging - they are not
// expected to match.
@ -1373,8 +1373,8 @@ func TestDetailQueries(t *testing.T) {
queries, discovery, acc, err = svc.GetDistributedQueries(ctx)
require.NoError(t, err)
// -4 windows queries, +1 software inventory
require.Equal(t, len(expectedDetailQueries)-3, len(queries), distQueriesMapKeys(queries))
// -5 windows queries, +1 software inventory
require.Equal(t, len(expectedDetailQueries)-4, len(queries), distQueriesMapKeys(queries))
verifyDiscovery(t, queries, discovery)
assert.Zero(t, acc)
}
@ -2339,8 +2339,8 @@ func TestPolicyQueries(t *testing.T) {
queries, discovery, _, err := svc.GetDistributedQueries(ctx)
require.NoError(t, err)
// all queries -4 windows only queries +2 policy queries
require.Equal(t, len(expectedDetailQueries)-2, len(queries), distQueriesMapKeys(queries))
// all queries -5 windows only queries +2 policy queries
require.Equal(t, len(expectedDetailQueries)-3, len(queries), distQueriesMapKeys(queries))
verifyDiscovery(t, queries, discovery)
checkPolicyResults := func(queries map[string]string) {
@ -2396,8 +2396,8 @@ func TestPolicyQueries(t *testing.T) {
ctx = hostctx.NewContext(context.Background(), host)
queries, discovery, _, err = svc.GetDistributedQueries(ctx)
require.NoError(t, err)
// all standard queries -4 windows only queries
require.Equal(t, len(expectedDetailQueries)-4, len(queries), distQueriesMapKeys(queries))
// all standard queries -5 windows only queries
require.Equal(t, len(expectedDetailQueries)-5, len(queries), distQueriesMapKeys(queries))
verifyDiscovery(t, queries, discovery)
noPolicyResults(queries)
@ -2406,8 +2406,8 @@ func TestPolicyQueries(t *testing.T) {
queries, discovery, _, err = svc.GetDistributedQueries(ctx)
require.NoError(t, err)
// all standard queries -4 windows only queries +2 policy queries
require.Equal(t, len(expectedDetailQueries)-2, len(queries), distQueriesMapKeys(queries))
// all standard queries -5 windows only queries +2 policy queries
require.Equal(t, len(expectedDetailQueries)-3, len(queries), distQueriesMapKeys(queries))
verifyDiscovery(t, queries, discovery)
checkPolicyResults(queries)
@ -2435,8 +2435,8 @@ func TestPolicyQueries(t *testing.T) {
ctx = hostctx.NewContext(context.Background(), host)
queries, discovery, _, err = svc.GetDistributedQueries(ctx)
require.NoError(t, err)
// all standard queries -4 windows only queries
require.Equal(t, len(expectedDetailQueries)-4, len(queries), distQueriesMapKeys(queries))
// all standard queries -5 windows only queries
require.Equal(t, len(expectedDetailQueries)-5, len(queries), distQueriesMapKeys(queries))
verifyDiscovery(t, queries, discovery)
noPolicyResults(queries)
@ -2445,8 +2445,8 @@ func TestPolicyQueries(t *testing.T) {
ctx = hostctx.NewContext(context.Background(), host)
queries, discovery, _, err = svc.GetDistributedQueries(ctx)
require.NoError(t, err)
// all standard queries -4 windows only queries +2 policy queries
require.Equal(t, len(expectedDetailQueries)-2, len(queries), distQueriesMapKeys(queries))
// all standard queries -5 windows only queries +2 policy queries
require.Equal(t, len(expectedDetailQueries)-3, len(queries), distQueriesMapKeys(queries))
verifyDiscovery(t, queries, discovery)
checkPolicyResults(queries)
@ -2476,8 +2476,8 @@ func TestPolicyQueries(t *testing.T) {
ctx = hostctx.NewContext(context.Background(), host)
queries, discovery, _, err = svc.GetDistributedQueries(ctx)
require.NoError(t, err)
// all standard queries -4 windows only queries
require.Equal(t, len(expectedDetailQueries)-4, len(queries), distQueriesMapKeys(queries))
// all standard queries -5 windows only queries
require.Equal(t, len(expectedDetailQueries)-5, len(queries), distQueriesMapKeys(queries))
verifyDiscovery(t, queries, discovery)
noPolicyResults(queries)
}
@ -2542,8 +2542,8 @@ func TestPolicyWebhooks(t *testing.T) {
queries, discovery, _, err := svc.GetDistributedQueries(ctx)
require.NoError(t, err)
// all queries -4 windows only, +3 for policies
require.Equal(t, len(expectedDetailQueries)-1, len(queries), distQueriesMapKeys(queries))
// all queries -5 windows only, +3 for policies
require.Equal(t, len(expectedDetailQueries)-2, len(queries), distQueriesMapKeys(queries))
verifyDiscovery(t, queries, discovery)
checkPolicyResults := func(queries map[string]string) {
@ -2656,8 +2656,8 @@ func TestPolicyWebhooks(t *testing.T) {
ctx = hostctx.NewContext(context.Background(), host)
queries, discovery, _, err = svc.GetDistributedQueries(ctx)
require.NoError(t, err)
// all queries -4 windows only queries
require.Equal(t, len(expectedDetailQueries)-4, len(queries), distQueriesMapKeys(queries))
// all queries -5 windows only queries
require.Equal(t, len(expectedDetailQueries)-5, len(queries), distQueriesMapKeys(queries))
verifyDiscovery(t, queries, discovery)
noPolicyResults(queries)
@ -2666,8 +2666,8 @@ func TestPolicyWebhooks(t *testing.T) {
queries, discovery, _, err = svc.GetDistributedQueries(ctx)
require.NoError(t, err)
// all queries -4 windows only queries, +3 for policies
require.Equal(t, len(expectedDetailQueries)-1, len(queries), distQueriesMapKeys(queries))
// all queries -5 windows only queries, +3 for policies
require.Equal(t, len(expectedDetailQueries)-2, len(queries), distQueriesMapKeys(queries))
verifyDiscovery(t, queries, discovery)
checkPolicyResults(queries)
@ -2791,7 +2791,7 @@ func TestLiveQueriesFailing(t *testing.T) {
queries, discovery, _, err := svc.GetDistributedQueries(ctx)
require.NoError(t, err)
// all standard queries minus windows only queries
require.Equal(t, len(expectedDetailQueries)-4, len(queries), distQueriesMapKeys(queries))
require.Equal(t, len(expectedDetailQueries)-5, len(queries), distQueriesMapKeys(queries))
verifyDiscovery(t, queries, discovery)
logs, err := io.ReadAll(buf)

View File

@ -440,10 +440,24 @@ func ingestKubequeryInfo(ctx context.Context, logger log.Logger, host *fleet.Hos
var extraDetailQueries = map[string]DetailQuery{
"mdm": {
Query: `select enrolled, server_url, installed_from_dep from mdm;`,
DirectIngestFunc: directIngestMDM,
DirectIngestFunc: directIngestMDMMac,
Platforms: []string{"darwin"},
Discovery: discoveryTable("mdm"),
},
"mdm_windows": {
Query: `
SELECT provid.providerID, url.discoveryServiceURL, autopilot.autopilot FROM (
SELECT data providerID FROM registry WHERE path LIKE 'HKEY_LOCAL_MACHINE\Software\Microsoft\Enrollments\%\ProviderID'
) provid, (
SELECT data discoveryServiceURL FROM registry WHERE path LIKE 'HKEY_LOCAL_MACHINE\Software\Microsoft\Enrollments\%\DiscoveryServiceFullURL'
) url, ( SELECT EXISTS (
SELECT 1 FROM registry WHERE KEY LIKE 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Provisioning\AutopilotPolicyCache'
) autopilot
) autopilot;
`,
DirectIngestFunc: directIngestMDMWindows,
Platforms: []string{"windows"},
},
"munki_info": {
Query: `select version, errors, warnings from munki_info;`,
DirectIngestFunc: directIngestMunkiInfo,
@ -1179,7 +1193,7 @@ func directIngestUsers(ctx context.Context, logger log.Logger, host *fleet.Host,
return nil
}
func directIngestMDM(ctx context.Context, logger log.Logger, host *fleet.Host, ds fleet.Datastore, rows []map[string]string, failed bool) error {
func directIngestMDMMac(ctx context.Context, logger log.Logger, host *fleet.Host, ds fleet.Datastore, rows []map[string]string, failed bool) error {
if len(rows) == 0 || failed {
// assume the extension is not there
return nil
@ -1205,7 +1219,27 @@ func directIngestMDM(ctx context.Context, logger log.Logger, host *fleet.Host, d
}
}
return ds.SetOrUpdateMDMData(ctx, host.ID, enrolled, rows[0]["server_url"], installedFromDep)
return ds.SetOrUpdateMDMData(ctx, host.ID, enrolled, rows[0]["server_url"], installedFromDep, "")
}
func directIngestMDMWindows(ctx context.Context, logger log.Logger, host *fleet.Host, ds fleet.Datastore, rows []map[string]string, failed bool) error {
if failed {
level.Error(logger).Log("op", "directIngestMDMWindows", "err", "failed")
return nil
}
if len(rows) > 1 {
logger.Log("component", "service", "method", "ingestMDMWindows", "warn",
fmt.Sprintf("mdm_windows expected single result got %d", len(rows)))
}
if len(rows) == 0 {
return ds.SetOrUpdateMDMData(ctx, host.ID, false, "", false, "")
}
autoPilot, err := strconv.ParseBool(rows[0]["autopilot"])
if err != nil {
return ctxerr.Wrap(ctx, err, "parsing autopilot")
}
return ds.SetOrUpdateMDMData(ctx, host.ID, true, rows[0]["discoveryServiceURL"], autoPilot, rows[0]["providerID"])
}
func directIngestMunkiInfo(ctx context.Context, logger log.Logger, host *fleet.Host, ds fleet.Datastore, rows []map[string]string, failed bool) error {

View File

@ -235,7 +235,6 @@ func sortedKeysCompare(t *testing.T, m map[string]DetailQuery, expectedKeys []st
func TestGetDetailQueries(t *testing.T) {
queriesNoConfig := GetDetailQueries(config.FleetConfig{}, nil)
require.Len(t, queriesNoConfig, 19)
baseQueries := []string{
"network_interface_unix",
@ -249,6 +248,7 @@ func TestGetDetailQueries(t *testing.T) {
"disk_space_unix",
"disk_space_windows",
"mdm",
"mdm_windows",
"munki_info",
"google_chrome_profiles",
"battery",
@ -258,19 +258,22 @@ func TestGetDetailQueries(t *testing.T) {
"kubequery_info",
"orbit_info",
}
require.Len(t, queriesNoConfig, len(baseQueries))
sortedKeysCompare(t, queriesNoConfig, baseQueries)
queriesWithoutWinOSVuln := GetDetailQueries(config.FleetConfig{Vulnerabilities: config.VulnerabilitiesConfig{DisableWinOSVulnerabilities: true}}, nil)
require.Len(t, queriesWithoutWinOSVuln, 18)
require.Len(t, queriesWithoutWinOSVuln, 19)
queriesWithUsers := GetDetailQueries(config.FleetConfig{App: config.AppConfig{EnableScheduledQueryStats: true}}, &fleet.Features{EnableHostUsers: true})
require.Len(t, queriesWithUsers, 21)
sortedKeysCompare(t, queriesWithUsers, append(baseQueries, "users", "scheduled_query_stats"))
qs := append(baseQueries, "users", "scheduled_query_stats")
require.Len(t, queriesWithUsers, len(qs))
sortedKeysCompare(t, queriesWithUsers, qs)
queriesWithUsersAndSoftware := GetDetailQueries(config.FleetConfig{App: config.AppConfig{EnableScheduledQueryStats: true}}, &fleet.Features{EnableHostUsers: true, EnableSoftwareInventory: true})
require.Len(t, queriesWithUsersAndSoftware, 24)
sortedKeysCompare(t, queriesWithUsersAndSoftware,
append(baseQueries, "users", "software_macos", "software_linux", "software_windows", "scheduled_query_stats"))
qs = append(baseQueries, "users", "software_macos", "software_linux", "software_windows", "scheduled_query_stats")
require.Len(t, queriesWithUsersAndSoftware, len(qs))
sortedKeysCompare(t, queriesWithUsersAndSoftware, qs)
}
func TestDetailQueriesOSVersionUnixLike(t *testing.T) {
@ -402,9 +405,9 @@ func TestDetailQueriesOSVersionWindows(t *testing.T) {
assert.Equal(t, "Windows 10 Enterprise LTSC 1809", host.OSVersion)
}
func TestDirectIngestMDM(t *testing.T) {
func TestDirectIngestMDMMac(t *testing.T) {
ds := new(mock.Store)
ds.SetOrUpdateMDMDataFunc = func(ctx context.Context, hostID uint, enrolled bool, serverURL string, installedFromDep bool) error {
ds.SetOrUpdateMDMDataFunc = func(ctx context.Context, hostID uint, enrolled bool, serverURL string, installedFromDep bool, name string) error {
require.False(t, enrolled)
require.False(t, installedFromDep)
require.Empty(t, serverURL)
@ -413,11 +416,11 @@ func TestDirectIngestMDM(t *testing.T) {
var host fleet.Host
err := directIngestMDM(context.Background(), log.NewNopLogger(), &host, ds, []map[string]string{}, true)
err := directIngestMDMMac(context.Background(), log.NewNopLogger(), &host, ds, []map[string]string{}, true)
require.NoError(t, err)
require.False(t, ds.SetOrUpdateMDMDataFuncInvoked)
err = directIngestMDM(context.Background(), log.NewNopLogger(), &host, ds, []map[string]string{
err = directIngestMDMMac(context.Background(), log.NewNopLogger(), &host, ds, []map[string]string{
{
"enrolled": "false",
"installed_from_dep": "",
@ -428,6 +431,31 @@ func TestDirectIngestMDM(t *testing.T) {
require.True(t, ds.SetOrUpdateMDMDataFuncInvoked)
}
func TestDirectIngestMDMWindows(t *testing.T) {
ds := new(mock.Store)
ds.SetOrUpdateMDMDataFunc = func(ctx context.Context, hostID uint, enrolled bool, serverURL string, installedFromDep bool, name string) error {
require.True(t, enrolled)
require.True(t, installedFromDep)
require.NotEmpty(t, serverURL)
return nil
}
var host fleet.Host
err := directIngestMDMWindows(context.Background(), log.NewNopLogger(), &host, ds, []map[string]string{}, true)
require.NoError(t, err)
require.False(t, ds.SetOrUpdateMDMDataFuncInvoked)
err = directIngestMDMWindows(context.Background(), log.NewNopLogger(), &host, ds, []map[string]string{
{
"discoveryServiceURL": "some url",
"autopilot": "true",
"providerID": "1337",
},
}, false)
require.NoError(t, err)
require.True(t, ds.SetOrUpdateMDMDataFuncInvoked)
}
func TestDirectIngestChromeProfiles(t *testing.T) {
ds := new(mock.Store)
ds.ReplaceHostDeviceMappingFunc = func(ctx context.Context, hostID uint, mapping []*fleet.HostDeviceMapping) error {

View File

@ -108,6 +108,12 @@ func WithComputerName(s string) NewHostOption {
}
}
func WithPlatform(s string) NewHostOption {
return func(h *fleet.Host) {
h.Platform = s
}
}
func NewHost(tb testing.TB, ds fleet.Datastore, name, ip, key, uuid string, now time.Time, options ...NewHostOption) *fleet.Host {
osqueryHostID, _ := server.GenerateRandomText(10)
h := &fleet.Host{