mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
iOS/iPadOS as platforms/labels (#20126)
#19963 - [X] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://fleetdm.com/docs/contributing/committing-changes#changes-files) for more information. - [X] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [X] Added/updated tests - [X] If database migrations are included, checked table schema to confirm autoupdate - For database migrations: - [X] Checked schema for all modified table for columns that will auto-update timestamps during migration. - [X] Confirmed that updating the timestamps is acceptable, and will not cause unwanted side effects. - [X] Ensured the correct collation is explicitly set for character columns (`COLLATE utf8mb4_unicode_ci`). - [X] Manual QA for all new/changed functionality --- # API changes for dashboard UI changes ## Main dashboard page `GET /api/latest/fleet/host_summary?low_disk_space=32` (see `ios`/`ipados` platforms and `iOS`/`iPadOS` labels) ```json { "totals_hosts_count": 9, "online_count": 0, "offline_count": 9, "mia_count": 0, "missing_30_days_count": 0, "new_count": 0, "all_linux_count": 2, "low_disk_space_count": 3, "builtin_labels": [ { "id": 1, "name": "macOS 14+ (Sonoma+)", "description": "macOS hosts with version 14 and above", "label_type": "builtin" }, { "id": 7, "name": "All Hosts", "description": "All hosts which have enrolled in Fleet", "label_type": "builtin" }, { "id": 8, "name": "macOS", "description": "All macOS hosts", "label_type": "builtin" }, { "id": 9, "name": "Ubuntu Linux", "description": "All Ubuntu hosts", "label_type": "builtin" }, { "id": 10, "name": "CentOS Linux", "description": "All CentOS hosts", "label_type": "builtin" }, { "id": 11, "name": "MS Windows", "description": "All Windows hosts", "label_type": "builtin" }, { "id": 12, "name": "Red Hat Linux", "description": "All Red Hat Enterprise Linux hosts", "label_type": "builtin" }, { "id": 13, "name": "All Linux", "description": "All Linux distributions", "label_type": "builtin" }, { "id": 14, "name": "chrome", "description": "All Chrome hosts", "label_type": "builtin" }, { "id": 15, "name": "iOS", "description": "All iOS hosts", "label_type": "builtin" }, { "id": 16, "name": "iPadOS", "description": "All iPadOS hosts", "label_type": "builtin" } ], "platforms": [ { "platform": "darwin", "hosts_count": 3 }, { "platform": "ios", "hosts_count": 1 }, { "platform": "ipados", "hosts_count": 1 }, { "platform": "rhel", "hosts_count": 1 }, { "platform": "ubuntu", "hosts_count": 1 }, { "platform": "windows", "hosts_count": 2 } ] } ``` ## After selecting a platform `GET /api/latest/fleet/host_summary?platform=ios&low_disk_space=100` (similar with `ipados`) ```json { "totals_hosts_count": 1, "online_count": 0, "offline_count": 1, "mia_count": 0, "missing_30_days_count": 0, "new_count": 0, "all_linux_count": 0, "low_disk_space_count": 1, "builtin_labels": [ { "id": 1, "name": "macOS 14+ (Sonoma+)", "description": "macOS hosts with version 14 and above", "label_type": "builtin" }, { "id": 7, "name": "All Hosts", "description": "All hosts which have enrolled in Fleet", "label_type": "builtin" }, { "id": 8, "name": "macOS", "description": "All macOS hosts", "label_type": "builtin" }, { "id": 9, "name": "Ubuntu Linux", "description": "All Ubuntu hosts", "label_type": "builtin" }, { "id": 10, "name": "CentOS Linux", "description": "All CentOS hosts", "label_type": "builtin" }, { "id": 11, "name": "MS Windows", "description": "All Windows hosts", "label_type": "builtin" }, { "id": 12, "name": "Red Hat Linux", "description": "All Red Hat Enterprise Linux hosts", "label_type": "builtin" }, { "id": 13, "name": "All Linux", "description": "All Linux distributions", "label_type": "builtin" }, { "id": 14, "name": "chrome", "description": "All Chrome hosts", "label_type": "builtin" }, { "id": 15, "name": "iOS", "description": "All iOS hosts", "label_type": "builtin" }, { "id": 16, "name": "iPadOS", "description": "All iPadOS hosts", "label_type": "builtin" } ], "platforms": [ { "platform": "ios", "hosts_count": 1 } ] } ``` ### To populate list of MDM solutions of a selected platform `GET /api/latest/fleet/hosts/summary/mdm\?platform=ios` (similar with `ipados`) ```json { "counts_updated_at": "2024-06-27T21:56:45Z", "mobile_device_management_enrollment_status": { "enrolled_manual_hosts_count": 0, "enrolled_automated_hosts_count": 1, "pending_hosts_count": 0, "unenrolled_hosts_count": 0, "hosts_count": 1 }, "mobile_device_management_solution": [ { "id": 1, "name": "Fleet", "server_url": "https://lucas-fleet.ngrok.app/mdm/apple/mdm", "hosts_count": 1 } ] } ``` ### To populate OS versions of a selected platform `GET /api/latest/fleet/os_versions?platform=ipados` (similar with `ios`) ```json { "meta": { "has_next_results": false, "has_previous_results": false }, "count": 1, "counts_updated_at": "2024-06-27T21:36:12Z", "os_versions": [ { "os_version_id": 7, "hosts_count": 1, "name": "iPadOS 17.5.1", "name_only": "iPadOS", "version": "17.5.1", "platform": "ipados", "vulnerabilities": [] } ] } ``` ## Filtering hosts by the two new `iOS`/`iPadOS` labels Works the same as with other labels.
This commit is contained in:
parent
ed3417de9d
commit
28ca463d13
16 changed files with 605 additions and 55 deletions
3
changes/19963-ios-ipados-as-platforms
Normal file
3
changes/19963-ios-ipados-as-platforms
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
- Added iOS/iPadOS builtin manual labels. IMPORTANT: Before migrating to this version, make sure to delete any labels with name "iOS" or "iPadOS".
|
||||
- Added aggregation of iOS/iPadOS OS versions.
|
||||
- Added change to custom profiles for iOS/iPadOS to go from 'pending' straight to 'verified' (skip 'verifying').
|
||||
|
|
@ -5,12 +5,11 @@ services:
|
|||
# officially supported).
|
||||
# To run in macOS M1, set FLEET_MYSQL_IMAGE=arm64v8/mysql:oracle FLEET_MYSQL_PLATFORM=linux/arm64/v8
|
||||
mysql:
|
||||
image: ${FLEET_MYSQL_IMAGE:-mysql:5.7}
|
||||
image: ${FLEET_MYSQL_IMAGE:-mysql:5.7.21}
|
||||
platform: ${FLEET_MYSQL_PLATFORM:-linux/x86_64}
|
||||
volumes:
|
||||
- mysql-persistent-volume:/tmp
|
||||
command:
|
||||
[
|
||||
command: [
|
||||
"mysqld",
|
||||
"--datadir=/tmp/mysqldata",
|
||||
# These 3 keys run MySQL with GTID consistency enforced to avoid issues with production deployments that use it.
|
||||
|
|
@ -18,7 +17,7 @@ services:
|
|||
"--log-bin=bin.log",
|
||||
"--server-id=master-01",
|
||||
# Required for storage of Apple MDM bootstrap packages.
|
||||
"--max_allowed_packet=536870912"
|
||||
"--max_allowed_packet=536870912",
|
||||
]
|
||||
environment: &mysql-default-environment
|
||||
MYSQL_ROOT_PASSWORD: toor
|
||||
|
|
@ -31,11 +30,10 @@ services:
|
|||
- "3306:3306"
|
||||
|
||||
mysql_test:
|
||||
image: ${FLEET_MYSQL_IMAGE:-mysql:5.7}
|
||||
image: ${FLEET_MYSQL_IMAGE:-mysql:5.7.21}
|
||||
platform: ${FLEET_MYSQL_PLATFORM:-linux/x86_64}
|
||||
# innodb-file-per-table=OFF gives ~20% speedup for test runs.
|
||||
command:
|
||||
[
|
||||
command: [
|
||||
"mysqld",
|
||||
"--datadir=/tmpfs",
|
||||
"--slow_query_log=1",
|
||||
|
|
@ -48,7 +46,7 @@ services:
|
|||
"--log-bin=bin.log",
|
||||
"--server-id=1",
|
||||
# Required for storage of Apple MDM bootstrap packages.
|
||||
"--max_allowed_packet=536870912"
|
||||
"--max_allowed_packet=536870912",
|
||||
]
|
||||
environment: *mysql-default-environment
|
||||
ports:
|
||||
|
|
@ -58,11 +56,10 @@ services:
|
|||
- /tmpfs
|
||||
|
||||
mysql_replica_test:
|
||||
image: ${FLEET_MYSQL_IMAGE:-mysql:5.7}
|
||||
image: ${FLEET_MYSQL_IMAGE:-mysql:5.7.21}
|
||||
platform: ${FLEET_MYSQL_PLATFORM:-linux/x86_64}
|
||||
# innodb-file-per-table=OFF gives ~20% speedup for test runs.
|
||||
command:
|
||||
[
|
||||
command: [
|
||||
"mysqld",
|
||||
"--datadir=/tmpfs",
|
||||
"--slow_query_log=1",
|
||||
|
|
@ -75,7 +72,7 @@ services:
|
|||
"--log-bin=bin.log",
|
||||
"--server-id=2",
|
||||
# Required for storage of Apple MDM bootstrap packages.
|
||||
"--max_allowed_packet=536870912"
|
||||
"--max_allowed_packet=536870912",
|
||||
]
|
||||
environment: *mysql-default-environment
|
||||
ports:
|
||||
|
|
@ -100,11 +97,7 @@ services:
|
|||
- "1026:1025"
|
||||
volumes:
|
||||
- ./tools/mailpit/auth.txt:/auth.txt
|
||||
command:
|
||||
[
|
||||
"--smtp-auth-file=/auth.txt",
|
||||
"--smtp-auth-allow-insecure=true"
|
||||
]
|
||||
command: ["--smtp-auth-file=/auth.txt", "--smtp-auth-allow-insecure=true"]
|
||||
|
||||
# SMTP server with TLS
|
||||
smtp4dev_test:
|
||||
|
|
@ -183,5 +176,3 @@ services:
|
|||
volumes:
|
||||
mysql-persistent-volume:
|
||||
data-minio:
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1143,38 +1143,53 @@ func upsertMDMAppleHostLabelMembershipDB(ctx context.Context, tx sqlx.ExtContext
|
|||
ID uint `db:"id"`
|
||||
Name string `db:"name"`
|
||||
}{}
|
||||
err := sqlx.SelectContext(ctx, tx, &labels, `SELECT id, name FROM labels WHERE label_type = 1 AND (name = 'All Hosts' OR name = 'macOS')`)
|
||||
err := sqlx.SelectContext(ctx, tx, &labels, `SELECT id, name FROM labels WHERE label_type = 1 AND (name = 'All Hosts' OR name = 'macOS' OR name = 'iOS' OR name = 'iPadOS')`)
|
||||
switch {
|
||||
case err != nil:
|
||||
return ctxerr.Wrap(ctx, err, "get builtin labels")
|
||||
case len(labels) != 2:
|
||||
case len(labels) != 4:
|
||||
// Builtin labels can get deleted so it is important that we check that
|
||||
// they still exist before we continue.
|
||||
level.Error(logger).Log("err", fmt.Sprintf("expected 2 builtin labels but got %d", len(labels)))
|
||||
level.Error(logger).Log("err", fmt.Sprintf("expected 4 builtin labels but got %d", len(labels)))
|
||||
return nil
|
||||
default:
|
||||
// continue
|
||||
}
|
||||
|
||||
// Put "All Hosts" label first (we don't want to make assumptions around ids of builtin labels).
|
||||
labelIDs := make([]uint, 0, 2)
|
||||
if labels[0].Name == "All Hosts" {
|
||||
labelIDs = append(labelIDs, labels[0].ID, labels[1].ID)
|
||||
} else {
|
||||
labelIDs = append(labelIDs, labels[1].ID, labels[0].ID)
|
||||
// We cannot assume IDs on labels, thus we look by name.
|
||||
var (
|
||||
allHostsLabelID uint
|
||||
macOSLabelID uint
|
||||
iOSLabelID uint
|
||||
iPadOSLabelID uint
|
||||
)
|
||||
for _, label := range labels {
|
||||
switch label.Name {
|
||||
case "All Hosts":
|
||||
allHostsLabelID = label.ID
|
||||
case "macOS":
|
||||
macOSLabelID = label.ID
|
||||
case "iOS":
|
||||
iOSLabelID = label.ID
|
||||
case "iPadOS":
|
||||
iPadOSLabelID = label.ID
|
||||
}
|
||||
}
|
||||
|
||||
parts := []string{}
|
||||
args := []interface{}{}
|
||||
for _, h := range hosts {
|
||||
// iOS/iPadOS devices only get the "All Hosts" label.
|
||||
if h.Platform == "ios" || h.Platform == "ipados" {
|
||||
parts = append(parts, "(?,?)")
|
||||
args = append(args, h.ID, labelIDs[0])
|
||||
} else { // macOS devices get both labels, "All Hosts" and "macOS".
|
||||
parts = append(parts, "(?,?),(?,?)")
|
||||
args = append(args, h.ID, labelIDs[0], h.ID, labelIDs[1])
|
||||
var osLabelID uint
|
||||
switch h.Platform {
|
||||
case "ios":
|
||||
osLabelID = iOSLabelID
|
||||
case "ipados":
|
||||
osLabelID = iPadOSLabelID
|
||||
default: // at this point, assume "darwin"
|
||||
osLabelID = macOSLabelID
|
||||
}
|
||||
parts = append(parts, "(?,?),(?,?)")
|
||||
args = append(args, h.ID, allHostsLabelID, h.ID, osLabelID)
|
||||
}
|
||||
_, err = tx.ExecContext(ctx, fmt.Sprintf(`
|
||||
INSERT INTO label_membership (host_id, label_id) VALUES %s
|
||||
|
|
@ -2269,11 +2284,29 @@ func (ds *Datastore) UpdateOrDeleteHostMDMAppleProfile(ctx context.Context, prof
|
|||
detail = fmt.Sprintf("Failed to remove: %s", detail)
|
||||
}
|
||||
|
||||
// Check whether we want to set a install operation as 'verifying' for an iOS/iPadOS device.
|
||||
var isIOSIPadOSInstallVerifiying bool
|
||||
if profile.OperationType == fleet.MDMOperationTypeInstall && profile.Status != nil && *profile.Status == fleet.MDMDeliveryVerifying {
|
||||
if err := ds.writer(ctx).GetContext(ctx, &isIOSIPadOSInstallVerifiying, `
|
||||
SELECT platform = 'ios' OR platform = 'ipados' FROM hosts WHERE uuid = ?`,
|
||||
profile.HostUUID,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
status := profile.Status
|
||||
if isIOSIPadOSInstallVerifiying {
|
||||
// iOS/iPadOS devices do not have osquery,
|
||||
// thus they go from 'pending' straight to 'verified'
|
||||
status = &fleet.MDMDeliveryVerified
|
||||
}
|
||||
|
||||
_, err := ds.writer(ctx).ExecContext(ctx, `
|
||||
UPDATE host_mdm_apple_profiles
|
||||
SET status = ?, operation_type = ?, detail = ?
|
||||
WHERE host_uuid = ? AND command_uuid = ?
|
||||
`, profile.Status, profile.OperationType, detail, profile.HostUUID, profile.CommandUUID)
|
||||
`, status, profile.OperationType, detail, profile.HostUUID, profile.CommandUUID)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ func TestMDMApple(t *testing.T) {
|
|||
{"TestDeleteMDMAppleConfigProfileByTeamAndIdentifier", testDeleteMDMAppleConfigProfileByTeamAndIdentifier},
|
||||
{"TestListMDMAppleConfigProfiles", testListMDMAppleConfigProfiles},
|
||||
{"TestHostDetailsMDMProfiles", testHostDetailsMDMProfiles},
|
||||
{"TestHostDetailsMDMProfilesIOSIPadOS", testHostDetailsMDMProfilesIOSIPadOS},
|
||||
{"TestBatchSetMDMAppleProfiles", testBatchSetMDMAppleProfiles},
|
||||
{"TestMDMAppleProfileManagement", testMDMAppleProfileManagement},
|
||||
{"TestMDMAppleProfileManagementBatch2", testMDMAppleProfileManagementBatch2},
|
||||
|
|
@ -1730,14 +1731,22 @@ func testGetMDMAppleProfilesContents(t *testing.T, ds *Datastore) {
|
|||
// createBuiltinLabels creates entries for "All Hosts" and "macOS" labels, which are assumed to be
|
||||
// extant for MDM flows
|
||||
func createBuiltinLabels(t *testing.T, ds *Datastore) {
|
||||
// Labels are deleted when truncating tables in between tests.
|
||||
// We need to delete the iOS/iPadOS labels because these two are created on a table migration,
|
||||
// and also we want to keep their indexes higher than "All Hosts" and "macOS" (to not break existing tests).
|
||||
_, err := ds.writer(context.Background()).Exec(`
|
||||
DELETE FROM labels WHERE name = 'iOS' OR name = 'iPadOS'`,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = ds.writer(context.Background()).Exec(`
|
||||
INSERT INTO labels (
|
||||
name,
|
||||
description,
|
||||
query,
|
||||
platform,
|
||||
label_type
|
||||
) VALUES (?, ?, ?, ?, ?), (?, ?, ?, ?, ?)`,
|
||||
) VALUES (?, ?, ?, ?, ?), (?, ?, ?, ?, ?), (?, ?, ?, ?, ?), (?, ?, ?, ?, ?)`,
|
||||
"All Hosts",
|
||||
"",
|
||||
"",
|
||||
|
|
@ -1748,6 +1757,16 @@ func createBuiltinLabels(t *testing.T, ds *Datastore) {
|
|||
"",
|
||||
"",
|
||||
fleet.LabelTypeBuiltIn,
|
||||
"iOS",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
fleet.LabelTypeBuiltIn,
|
||||
"iPadOS",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
fleet.LabelTypeBuiltIn,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
|
@ -5735,8 +5754,16 @@ func testMDMAppleUpsertHostIOSIPadOS(t *testing.T, ds *Datastore) {
|
|||
|
||||
labels, err := ds.ListLabelsForHost(ctx, h.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, labels, 1)
|
||||
require.Len(t, labels, 2)
|
||||
sort.Slice(labels, func(i, j int) bool {
|
||||
return labels[i].ID < labels[j].ID
|
||||
})
|
||||
require.Equal(t, "All Hosts", labels[0].Name)
|
||||
if i == 0 {
|
||||
require.Equal(t, "iOS", labels[1].Name)
|
||||
} else {
|
||||
require.Equal(t, "iPadOS", labels[1].Name)
|
||||
}
|
||||
|
||||
// Insert again to test updateMDMAppleHostDB.
|
||||
err = ds.MDMAppleUpsertHost(ctx, &fleet.Host{
|
||||
|
|
@ -5754,8 +5781,16 @@ func testMDMAppleUpsertHostIOSIPadOS(t *testing.T, ds *Datastore) {
|
|||
|
||||
labels, err = ds.ListLabelsForHost(ctx, h.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, labels, 1)
|
||||
require.Len(t, labels, 2)
|
||||
sort.Slice(labels, func(i, j int) bool {
|
||||
return labels[i].ID < labels[j].ID
|
||||
})
|
||||
require.Equal(t, "All Hosts", labels[0].Name)
|
||||
if i == 0 {
|
||||
require.Equal(t, "iOS", labels[1].Name)
|
||||
} else {
|
||||
require.Equal(t, "iPadOS", labels[1].Name)
|
||||
}
|
||||
}
|
||||
|
||||
err := ds.MDMAppleUpsertHost(ctx, &fleet.Host{
|
||||
|
|
@ -5870,3 +5905,146 @@ func testMDMAppleProfilesOnIOSIPadOS(t *testing.T, ds *Datastore) {
|
|||
require.Len(t, profiles, 1)
|
||||
require.Equal(t, someProfile.Name, profiles[0].Name)
|
||||
}
|
||||
|
||||
func testHostDetailsMDMProfilesIOSIPadOS(t *testing.T, ds *Datastore) {
|
||||
ctx := context.Background()
|
||||
|
||||
p0, err := ds.NewMDMAppleConfigProfile(ctx, fleet.MDMAppleConfigProfile{
|
||||
Name: "Name0",
|
||||
Identifier: "Identifier0",
|
||||
Mobileconfig: []byte("profile0-bytes"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
profiles, err := ds.ListMDMAppleConfigProfiles(ctx, ptr.Uint(0))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, profiles, 1)
|
||||
|
||||
iOS, err := ds.NewHost(ctx, &fleet.Host{
|
||||
DetailUpdatedAt: time.Now(),
|
||||
LabelUpdatedAt: time.Now(),
|
||||
PolicyUpdatedAt: time.Now(),
|
||||
SeenTime: time.Now(),
|
||||
OsqueryHostID: ptr.String("host0-osquery-id"),
|
||||
NodeKey: ptr.String("host0-node-key"),
|
||||
UUID: "host0-test-mdm-profiles",
|
||||
Hostname: "hostname0",
|
||||
Platform: "ios",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
iPadOS, err := ds.NewHost(ctx, &fleet.Host{
|
||||
DetailUpdatedAt: time.Now(),
|
||||
LabelUpdatedAt: time.Now(),
|
||||
PolicyUpdatedAt: time.Now(),
|
||||
SeenTime: time.Now(),
|
||||
OsqueryHostID: ptr.String("host0-osquery-id-2"),
|
||||
NodeKey: ptr.String("host0-node-key-2"),
|
||||
UUID: "host0-test-mdm-profiles-2",
|
||||
Hostname: "hostname0-2",
|
||||
Platform: "ipados",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
gotHost, err := ds.Host(ctx, iOS.ID)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, gotHost.MDM.Profiles)
|
||||
gotProfs, err := ds.GetHostMDMAppleProfiles(ctx, iOS.UUID)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, gotProfs)
|
||||
gotHost, err = ds.Host(ctx, iPadOS.ID)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, gotHost.MDM.Profiles)
|
||||
gotProfs, err = ds.GetHostMDMAppleProfiles(ctx, iPadOS.UUID)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, gotProfs)
|
||||
|
||||
expectedProfilesIOS := map[string]fleet.HostMDMAppleProfile{
|
||||
p0.ProfileUUID: {
|
||||
HostUUID: iOS.UUID,
|
||||
Name: p0.Name,
|
||||
ProfileUUID: p0.ProfileUUID,
|
||||
CommandUUID: "cmd0-uuid",
|
||||
Status: &fleet.MDMDeliveryPending,
|
||||
OperationType: fleet.MDMOperationTypeInstall,
|
||||
Detail: "",
|
||||
},
|
||||
}
|
||||
expectedProfilesIPadOS := map[string]fleet.HostMDMAppleProfile{
|
||||
p0.ProfileUUID: {
|
||||
HostUUID: iPadOS.UUID,
|
||||
Name: p0.Name,
|
||||
ProfileUUID: p0.ProfileUUID,
|
||||
CommandUUID: "cmd0-uuid",
|
||||
Status: &fleet.MDMDeliveryPending,
|
||||
OperationType: fleet.MDMOperationTypeInstall,
|
||||
Detail: "",
|
||||
},
|
||||
}
|
||||
|
||||
var args []interface{}
|
||||
for _, p := range expectedProfilesIOS {
|
||||
args = append(args, p.HostUUID, p.ProfileUUID, p.CommandUUID, *p.Status, p.OperationType, p.Detail, p.Name)
|
||||
}
|
||||
for _, p := range expectedProfilesIPadOS {
|
||||
args = append(args, p.HostUUID, p.ProfileUUID, p.CommandUUID, *p.Status, p.OperationType, p.Detail, p.Name)
|
||||
}
|
||||
|
||||
ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
|
||||
_, err := q.ExecContext(ctx, `
|
||||
INSERT INTO host_mdm_apple_profiles (
|
||||
host_uuid, profile_uuid, command_uuid, status, operation_type, detail, profile_name)
|
||||
VALUES (?,?,?,?,?,?,?),(?,?,?,?,?,?,?)
|
||||
`, args...,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
for _, tc := range []struct {
|
||||
host *fleet.Host
|
||||
expectedProfiles map[string]fleet.HostMDMAppleProfile
|
||||
}{
|
||||
{
|
||||
host: iOS,
|
||||
expectedProfiles: expectedProfilesIOS,
|
||||
},
|
||||
{
|
||||
host: iPadOS,
|
||||
expectedProfiles: expectedProfilesIPadOS,
|
||||
},
|
||||
} {
|
||||
gotProfs, err = ds.GetHostMDMAppleProfiles(ctx, tc.host.UUID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, gotProfs, 1)
|
||||
for _, gp := range gotProfs {
|
||||
ep, ok := expectedProfilesIOS[gp.ProfileUUID]
|
||||
require.True(t, ok)
|
||||
require.Equal(t, ep.Name, gp.Name)
|
||||
require.Equal(t, *ep.Status, *gp.Status)
|
||||
require.Equal(t, ep.OperationType, gp.OperationType)
|
||||
require.Equal(t, ep.Detail, gp.Detail)
|
||||
}
|
||||
|
||||
// mark pending profile to 'verifying', which should instead set it as 'verified'.
|
||||
installPendingProfile := expectedProfilesIOS[p0.ProfileUUID]
|
||||
err = ds.UpdateOrDeleteHostMDMAppleProfile(ctx, &fleet.HostMDMAppleProfile{
|
||||
HostUUID: installPendingProfile.HostUUID,
|
||||
CommandUUID: installPendingProfile.CommandUUID,
|
||||
ProfileUUID: installPendingProfile.ProfileUUID,
|
||||
Name: installPendingProfile.Name,
|
||||
Status: &fleet.MDMDeliveryVerifying,
|
||||
OperationType: fleet.MDMOperationTypeInstall,
|
||||
Detail: "",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check that the profile is the 'verified' state.
|
||||
gotProfs, err = ds.GetHostMDMAppleProfiles(ctx, iOS.UUID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, gotProfs, 1)
|
||||
require.NotNil(t, gotProfs[0].Status)
|
||||
require.Equal(t, fleet.MDMDeliveryVerified, *gotProfs[0].Status)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,8 +26,10 @@ import (
|
|||
|
||||
// Since many hosts may have issues, we need to batch the inserts of host issues.
|
||||
// This is a variable, so it can be adjusted during unit testing.
|
||||
var hostIssuesInsertBatchSize = 10000
|
||||
var hostIssuesUpdateFailingPoliciesBatchSize = 10000
|
||||
var (
|
||||
hostIssuesInsertBatchSize = 10000
|
||||
hostIssuesUpdateFailingPoliciesBatchSize = 10000
|
||||
)
|
||||
|
||||
// A large number of hosts could be changing teams at once, so we need to batch this operation to prevent excessive locks
|
||||
var addHostsToTeamBatchSize = 10000
|
||||
|
|
@ -4082,7 +4084,7 @@ func (ds *Datastore) AggregatedMDMSolutions(ctx context.Context, teamID *uint, p
|
|||
|
||||
func (ds *Datastore) GenerateAggregatedMunkiAndMDM(ctx context.Context) error {
|
||||
var (
|
||||
platforms = []string{"", "darwin", "windows"}
|
||||
platforms = []string{"", "darwin", "windows", "ios", "ipados"}
|
||||
teamIDs []uint
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -5724,15 +5724,21 @@ func testAggregatedHostMDMAndMunki(t *testing.T, ds *Datastore) {
|
|||
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"))
|
||||
h4 := test.NewHost(t, ds, "h4"+t.Name(), "192.168.1.11", "4", "4", time.Now(), test.WithPlatform("windows"))
|
||||
h5 := test.NewHost(t, ds, "h5"+t.Name(), "192.168.1.12", "5", "5", time.Now(), test.WithPlatform("ios"))
|
||||
h6 := test.NewHost(t, ds, "h6"+t.Name(), "192.168.1.12", "6", "6", time.Now(), test.WithPlatform("ipados"))
|
||||
|
||||
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.AddHostsToTeam(context.Background(), &team1.ID, []uint{h4.ID}))
|
||||
require.NoError(t, ds.AddHostsToTeam(context.Background(), &team1.ID, []uint{h6.ID}))
|
||||
|
||||
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), h1.ID, false, true, "https://simplemdm.com", false, fleet.WellKnownMDMSimpleMDM, ""))
|
||||
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), h2.ID, false, true, "url", false, "", ""))
|
||||
|
||||
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), h5.ID, false, true, "https://fleet.example.com", true, fleet.WellKnownMDMFleet, ""))
|
||||
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), h6.ID, false, true, "https://fleet.example.com", true, fleet.WellKnownMDMFleet, ""))
|
||||
|
||||
// Add a server, this will be ignored in lists and aggregated data.
|
||||
require.NoError(t, ds.SetOrUpdateMDMData(context.Background(), h4.ID, true, true, "https://simplemdm.com", false, fleet.WellKnownMDMSimpleMDM, ""))
|
||||
|
||||
|
|
@ -5782,20 +5788,39 @@ func testAggregatedHostMDMAndMunki(t *testing.T, ds *Datastore) {
|
|||
})
|
||||
require.True(t, updatedAt.After(firstUpdatedAt))
|
||||
|
||||
status, _, err = ds.AggregatedMDMStatus(context.Background(), nil, "")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 11, status.HostsCount)
|
||||
assert.Equal(t, 1, status.UnenrolledHostsCount)
|
||||
assert.Equal(t, 5, status.EnrolledManualHostsCount)
|
||||
assert.Equal(t, 3, status.EnrolledAutomatedHostsCount)
|
||||
|
||||
status, _, err = ds.AggregatedMDMStatus(context.Background(), &team1.ID, "")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, status.HostsCount)
|
||||
assert.Equal(t, 2, status.HostsCount)
|
||||
assert.Equal(t, 0, status.UnenrolledHostsCount)
|
||||
assert.Equal(t, 1, status.EnrolledManualHostsCount)
|
||||
assert.Equal(t, 0, status.EnrolledAutomatedHostsCount)
|
||||
assert.Equal(t, 1, status.EnrolledAutomatedHostsCount)
|
||||
|
||||
solutions, updatedAt, err = ds.AggregatedMDMSolutions(context.Background(), nil, "")
|
||||
require.True(t, updatedAt.After(firstUpdatedAt))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, solutions, 5)
|
||||
// Check the new MDM solution used by the iOS/iPadOS
|
||||
assert.Equal(t, "https://fleet.example.com", solutions[4].ServerURL)
|
||||
assert.Equal(t, fleet.WellKnownMDMFleet, solutions[4].Name)
|
||||
assert.Equal(t, 2, solutions[4].HostsCount)
|
||||
|
||||
solutions, updatedAt, err = ds.AggregatedMDMSolutions(context.Background(), &team1.ID, "")
|
||||
require.True(t, updatedAt.After(firstUpdatedAt))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, solutions, 1)
|
||||
require.Len(t, solutions, 2)
|
||||
assert.Equal(t, "https://simplemdm.com", solutions[0].ServerURL)
|
||||
assert.Equal(t, fleet.WellKnownMDMSimpleMDM, solutions[0].Name)
|
||||
assert.Equal(t, 1, solutions[0].HostsCount)
|
||||
assert.Equal(t, "https://fleet.example.com", solutions[1].ServerURL)
|
||||
assert.Equal(t, fleet.WellKnownMDMFleet, solutions[1].Name)
|
||||
assert.Equal(t, 1, solutions[1].HostsCount)
|
||||
|
||||
status, _, err = ds.AggregatedMDMStatus(context.Background(), &team1.ID, "darwin")
|
||||
require.NoError(t, err)
|
||||
|
|
@ -5816,6 +5841,34 @@ func testAggregatedHostMDMAndMunki(t *testing.T, ds *Datastore) {
|
|||
assert.Equal(t, 1, status.EnrolledManualHostsCount)
|
||||
assert.Equal(t, 0, status.EnrolledAutomatedHostsCount)
|
||||
|
||||
status, _, err = ds.AggregatedMDMStatus(context.Background(), &team1.ID, "ios")
|
||||
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)
|
||||
|
||||
status, _, err = ds.AggregatedMDMStatus(context.Background(), nil, "ios")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, status.HostsCount)
|
||||
assert.Equal(t, 0, status.UnenrolledHostsCount)
|
||||
assert.Equal(t, 0, status.EnrolledManualHostsCount)
|
||||
assert.Equal(t, 1, status.EnrolledAutomatedHostsCount)
|
||||
|
||||
status, _, err = ds.AggregatedMDMStatus(context.Background(), &team1.ID, "ipados")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, status.HostsCount)
|
||||
assert.Equal(t, 0, status.UnenrolledHostsCount)
|
||||
assert.Equal(t, 0, status.EnrolledManualHostsCount)
|
||||
assert.Equal(t, 1, status.EnrolledAutomatedHostsCount)
|
||||
|
||||
status, _, err = ds.AggregatedMDMStatus(context.Background(), nil, "ipados")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, status.HostsCount)
|
||||
assert.Equal(t, 0, status.UnenrolledHostsCount)
|
||||
assert.Equal(t, 0, status.EnrolledManualHostsCount)
|
||||
assert.Equal(t, 1, status.EnrolledAutomatedHostsCount)
|
||||
|
||||
solutions, updatedAt, err = ds.AggregatedMDMSolutions(context.Background(), &team1.ID, "windows")
|
||||
require.True(t, updatedAt.After(firstUpdatedAt))
|
||||
require.NoError(t, err)
|
||||
|
|
@ -5823,6 +5876,35 @@ func testAggregatedHostMDMAndMunki(t *testing.T, ds *Datastore) {
|
|||
assert.Equal(t, "https://simplemdm.com", solutions[0].ServerURL)
|
||||
assert.Equal(t, fleet.WellKnownMDMSimpleMDM, solutions[0].Name)
|
||||
assert.Equal(t, 1, solutions[0].HostsCount)
|
||||
|
||||
solutions, updatedAt, err = ds.AggregatedMDMSolutions(context.Background(), nil, "ios")
|
||||
require.True(t, updatedAt.After(firstUpdatedAt))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, solutions, 1)
|
||||
assert.Equal(t, "https://fleet.example.com", solutions[0].ServerURL)
|
||||
assert.Equal(t, fleet.WellKnownMDMFleet, solutions[0].Name)
|
||||
assert.Equal(t, 1, solutions[0].HostsCount)
|
||||
|
||||
solutions, updatedAt, err = ds.AggregatedMDMSolutions(context.Background(), &team1.ID, "ios")
|
||||
require.True(t, updatedAt.After(firstUpdatedAt))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, solutions, 0)
|
||||
|
||||
solutions, updatedAt, err = ds.AggregatedMDMSolutions(context.Background(), nil, "ipados")
|
||||
require.True(t, updatedAt.After(firstUpdatedAt))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, solutions, 1)
|
||||
assert.Equal(t, "https://fleet.example.com", solutions[0].ServerURL)
|
||||
assert.Equal(t, fleet.WellKnownMDMFleet, solutions[0].Name)
|
||||
assert.Equal(t, 1, solutions[0].HostsCount)
|
||||
|
||||
solutions, updatedAt, err = ds.AggregatedMDMSolutions(context.Background(), &team1.ID, "ipados")
|
||||
require.True(t, updatedAt.After(firstUpdatedAt))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, solutions, 1)
|
||||
assert.Equal(t, "https://fleet.example.com", solutions[0].ServerURL)
|
||||
assert.Equal(t, fleet.WellKnownMDMFleet, solutions[0].Name)
|
||||
assert.Equal(t, 1, solutions[0].HostsCount)
|
||||
}
|
||||
|
||||
func testHostsLite(t *testing.T, ds *Datastore) {
|
||||
|
|
|
|||
|
|
@ -1342,7 +1342,6 @@ func (ds *Datastore) AreHostsConnectedToFleetMDM(ctx context.Context, hosts []*f
|
|||
}
|
||||
|
||||
return res, nil
|
||||
|
||||
}
|
||||
|
||||
func (ds *Datastore) IsHostConnectedToFleetMDM(ctx context.Context, host *fleet.Host) (bool, error) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,114 @@
|
|||
package tables
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/VividCortex/mysqlerr"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
func init() {
|
||||
MigrationClient.AddMigration(Up_20240707134036, Down_20240707134036)
|
||||
}
|
||||
|
||||
func Up_20240707134036(tx *sql.Tx) error {
|
||||
// Create new builtin+manual labels for iOS/iPadOS
|
||||
iOSLabelID, iPadOSLabelID, err := createBuiltinManualIOSAndIPadOSLabels(tx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create iOS/iPadOS labels: %w", err)
|
||||
}
|
||||
|
||||
// Add label membership to existing iOS/iPadOS devices.
|
||||
if _, err := tx.Exec(`
|
||||
INSERT INTO label_membership (host_id, label_id)
|
||||
SELECT id AS host_id, IF(platform = 'ios', ?, ?) AS label_id
|
||||
FROM hosts WHERE platform = 'ios' OR platform = 'ipados';`,
|
||||
iOSLabelID, iPadOSLabelID,
|
||||
); err != nil {
|
||||
return fmt.Errorf("failed to insert label membership: %w", err)
|
||||
}
|
||||
|
||||
// Move existing iOS/iPadOS profiles from "Verifying" to "Verified"
|
||||
// (there's no osquery in these devices).
|
||||
if _, err := tx.Exec(`
|
||||
UPDATE host_mdm_apple_profiles hmap
|
||||
JOIN hosts h ON hmap.host_uuid = h.uuid AND
|
||||
(h.platform = 'ios' OR h.platform = 'ipados') AND hmap.status = 'verifying'
|
||||
SET hmap.status = 'verified';`,
|
||||
); err != nil {
|
||||
return fmt.Errorf("failed to update host_mdm_apple_profiles: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createBuiltinManualIOSAndIPadOSLabels(tx *sql.Tx) (iOSLabelID uint, iPadOSLabelID uint, err error) {
|
||||
// hard-coded timestamps are used so that schema.sql is stable
|
||||
stableTS := time.Date(2024, 6, 28, 0, 0, 0, 0, time.UTC)
|
||||
for _, label := range []struct {
|
||||
name string
|
||||
description string
|
||||
platform string
|
||||
}{
|
||||
{
|
||||
fleet.BuiltinLabelIOS,
|
||||
"All iOS hosts",
|
||||
"ios",
|
||||
},
|
||||
{
|
||||
fleet.BuiltinLabelIPadOS,
|
||||
"All iPadOS hosts",
|
||||
"ipados",
|
||||
},
|
||||
} {
|
||||
res, err := tx.Exec(`
|
||||
INSERT INTO labels (
|
||||
name,
|
||||
description,
|
||||
query,
|
||||
platform,
|
||||
label_type,
|
||||
label_membership_type,
|
||||
created_at,
|
||||
updated_at
|
||||
) VALUES (?, ?, '', ?, ?, ?, ?, ?);`,
|
||||
label.name,
|
||||
label.description,
|
||||
label.platform,
|
||||
fleet.LabelTypeBuiltIn,
|
||||
fleet.LabelMembershipTypeManual,
|
||||
stableTS,
|
||||
stableTS,
|
||||
)
|
||||
if err != nil {
|
||||
if driverErr, ok := err.(*mysql.MySQLError); ok {
|
||||
if driverErr.Number == mysqlerr.ER_DUP_ENTRY {
|
||||
// All label names need to be unique across built-in and regular.
|
||||
// Thus we return an error and instruct the user how to solve the issue.
|
||||
//
|
||||
// NOTE(lucas): This is using the same approach we used when creating the Sonoma builtin label.
|
||||
return 0, 0, fmt.Errorf(
|
||||
"label with the name %q already exists, please rename it before applying this migration: %w",
|
||||
label.name,
|
||||
err,
|
||||
)
|
||||
}
|
||||
}
|
||||
return 0, 0, fmt.Errorf("failed to insert label: %w", err)
|
||||
}
|
||||
labelID, _ := res.LastInsertId()
|
||||
if label.name == fleet.BuiltinLabelIOS {
|
||||
iOSLabelID = uint(labelID)
|
||||
} else {
|
||||
iPadOSLabelID = uint(labelID)
|
||||
}
|
||||
}
|
||||
return iOSLabelID, iPadOSLabelID, nil
|
||||
}
|
||||
|
||||
func Down_20240707134036(tx *sql.Tx) error {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
package tables
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestUp_20240707134036(t *testing.T) {
|
||||
db := applyUpToPrev(t)
|
||||
|
||||
// Insert existing hosts before migration.
|
||||
hostID := 1
|
||||
newHost := func(platform, uuid string) uint {
|
||||
id := fmt.Sprintf("%d", hostID)
|
||||
hostID++
|
||||
return uint(execNoErrLastID(t, db,
|
||||
`INSERT INTO hosts (osquery_host_id, node_key, uuid, platform) VALUES (?, ?, ?, ?);`,
|
||||
id, id, uuid, platform,
|
||||
))
|
||||
}
|
||||
iOSID := newHost("ios", "iOS_UUID")
|
||||
iPadOSID := newHost("ipados", "iPadOS_UUID")
|
||||
newHost("darwin", "macOS_UUID")
|
||||
|
||||
// Insert existing profiles and host profiles before migration.
|
||||
stmt := `
|
||||
INSERT INTO
|
||||
mdm_apple_configuration_profiles (team_id, identifier, name, mobileconfig, checksum, profile_uuid)
|
||||
VALUES (?, ?, ?, ?, '', ?)`
|
||||
|
||||
_, err := db.Exec(stmt, 0, "profileID0", "TestPayloadName0", `<?xml version="1.0"`, "profileID0")
|
||||
require.NoError(t, err)
|
||||
_, err = db.Exec(stmt, 0, "profileID1", "TestPayloadName1", `<?xml version="1.0"`, "profileID1")
|
||||
require.NoError(t, err)
|
||||
|
||||
stmt = `
|
||||
INSERT INTO host_mdm_apple_profiles
|
||||
(profile_uuid, profile_identifier, host_uuid, command_uuid, status, operation_type, detail, checksum)
|
||||
VALUES
|
||||
(?, 'com.foo.bar', ?, 'command-uuid', ?, ?, 'detail', '')`
|
||||
|
||||
execNoErr(t, db, stmt, "profileID0", "iOS_UUID", "verifying", "install")
|
||||
execNoErr(t, db, stmt, "profileID0", "iPadOS_UUID", "verifying", "install")
|
||||
execNoErr(t, db, stmt, "profileID0", "macOS_UUID", "verifying", "install")
|
||||
execNoErr(t, db, stmt, "profileID1", "iOS_UUID", "pending", "install")
|
||||
execNoErr(t, db, stmt, "profileID1", "iPadOS_UUID", "pending", "install")
|
||||
|
||||
// Apply current migration.
|
||||
applyNext(t, db)
|
||||
|
||||
var labelIDs []uint
|
||||
err = db.Select(&labelIDs, `SELECT id FROM labels WHERE name = 'iOS' OR name = 'iPadOS';`)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, labelIDs, 2)
|
||||
iOSLabelID := labelIDs[0]
|
||||
iPadOSLabelID := labelIDs[1]
|
||||
|
||||
type hostAndLabel struct {
|
||||
HostID uint `db:"host_id"`
|
||||
LabelID uint `db:"label_id"`
|
||||
}
|
||||
var labelMemberships []hostAndLabel
|
||||
err = db.Select(&labelMemberships, `SELECT host_id, label_id FROM label_membership;`)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, labelMemberships, 2)
|
||||
sort.Slice(labelMemberships, func(i, j int) bool {
|
||||
return labelMemberships[i].HostID < labelMemberships[j].HostID
|
||||
})
|
||||
require.Equal(t, iOSID, labelMemberships[0].HostID)
|
||||
require.Equal(t, iOSLabelID, labelMemberships[0].LabelID)
|
||||
require.Equal(t, iPadOSID, labelMemberships[1].HostID)
|
||||
require.Equal(t, iPadOSLabelID, labelMemberships[1].LabelID)
|
||||
|
||||
type hostPlusProfile struct {
|
||||
HostUUID string `db:"host_uuid"`
|
||||
ProfileUUID string `db:"profile_uuid"`
|
||||
Status string `db:"status"`
|
||||
}
|
||||
var hostProfiles []hostPlusProfile
|
||||
err = db.Select(&hostProfiles, `SELECT host_uuid, profile_uuid, status FROM host_mdm_apple_profiles;`)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, hostProfiles, 5)
|
||||
for _, hostProfile := range hostProfiles {
|
||||
switch {
|
||||
case hostProfile.HostUUID == "iOS_UUID" && hostProfile.ProfileUUID == "profileID0":
|
||||
require.Equal(t, "verified", hostProfile.Status) // should now be verified
|
||||
case hostProfile.HostUUID == "iPadOS_UUID" && hostProfile.ProfileUUID == "profileID0":
|
||||
require.Equal(t, "verified", hostProfile.Status) // should now be verified
|
||||
case hostProfile.HostUUID == "macOS_UUID" && hostProfile.ProfileUUID == "profileID0":
|
||||
require.Equal(t, "verifying", hostProfile.Status) // should remain unchanged
|
||||
case hostProfile.HostUUID == "iOS_UUID" && hostProfile.ProfileUUID == "profileID1":
|
||||
require.Equal(t, "pending", hostProfile.Status) // should remain unchanged because it's pending
|
||||
case hostProfile.HostUUID == "iPadOS_UUID" && hostProfile.ProfileUUID == "profileID1":
|
||||
require.Equal(t, "pending", hostProfile.Status) // should remain unchanged because it's pending
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -156,6 +156,8 @@ const (
|
|||
BuiltinLabelNameAllLinux = "All Linux"
|
||||
BuiltinLabelNameChrome = "chrome"
|
||||
BuiltinLabelMacOS14Plus = "macOS 14+ (Sonoma+)"
|
||||
BuiltinLabelIOS = "iOS"
|
||||
BuiltinLabelIPadOS = "iPadOS"
|
||||
)
|
||||
|
||||
// ReservedLabelNames returns a map of label name strings
|
||||
|
|
@ -171,5 +173,7 @@ func ReservedLabelNames() map[string]struct{} {
|
|||
BuiltinLabelNameAllLinux: {},
|
||||
BuiltinLabelNameChrome: {},
|
||||
BuiltinLabelMacOS14Plus: {},
|
||||
BuiltinLabelIOS: {},
|
||||
BuiltinLabelIPadOS: {},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,8 +99,11 @@ func VerifyHostMDMProfiles(ctx context.Context, ds fleet.ProfileVerificationStor
|
|||
// the MDM protocol and updates the verification status in the datastore. It is intended to be
|
||||
// called by the Fleet MDM checkin and command service install profile request handler.
|
||||
func HandleHostMDMProfileInstallResult(ctx context.Context, ds fleet.ProfileVerificationStore, hostUUID string, cmdUUID string, status *fleet.MDMDeliveryStatus, detail string) error {
|
||||
host := &fleet.Host{UUID: hostUUID, Platform: "darwin"}
|
||||
if status != nil && *status == fleet.MDMDeliveryFailed {
|
||||
// Here we set the host.Platform to "darwin" but it applies to iOS/iPadOS too.
|
||||
// The logic in GetHostMDMProfileRetryCountByCommandUUID and UpdateHostMDMProfilesVerification
|
||||
// is the exact same when platform is "darwin", "ios" or "ipados".
|
||||
host := &fleet.Host{UUID: hostUUID, Platform: "darwin"}
|
||||
m, err := ds.GetHostMDMProfileRetryCountByCommandUUID(ctx, host, cmdUUID)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -2737,13 +2737,18 @@ func (svc *MDMAppleCheckinAndCommandService) CommandAndReportResults(r *mdm.Requ
|
|||
host.Hostname = deviceName
|
||||
host.GigsDiskSpaceAvailable = availableDeviceCapacity
|
||||
host.GigsTotalDiskSpace = deviceCapacity
|
||||
var osVersionPrefix string
|
||||
var (
|
||||
osVersionPrefix string
|
||||
platform string
|
||||
)
|
||||
if strings.HasPrefix(productName, "iPhone") {
|
||||
osVersionPrefix = "iOS "
|
||||
osVersionPrefix = "iOS"
|
||||
platform = "ios"
|
||||
} else { // iPad
|
||||
osVersionPrefix = "iPadOS "
|
||||
osVersionPrefix = "iPadOS"
|
||||
platform = "ipados"
|
||||
}
|
||||
host.OSVersion = osVersionPrefix + osVersion
|
||||
host.OSVersion = osVersionPrefix + " " + osVersion
|
||||
host.PrimaryMac = wifiMac
|
||||
host.HardwareModel = productName
|
||||
host.DetailUpdatedAt = time.Now()
|
||||
|
|
@ -2753,6 +2758,13 @@ func (svc *MDMAppleCheckinAndCommandService) CommandAndReportResults(r *mdm.Requ
|
|||
if err := svc.ds.SetOrUpdateHostDisksSpace(r.Context, host.ID, availableDeviceCapacity, 100*availableDeviceCapacity/deviceCapacity, deviceCapacity); err != nil {
|
||||
return nil, ctxerr.Wrap(r.Context, err, "failed to update host storage")
|
||||
}
|
||||
if err := svc.ds.UpdateHostOperatingSystem(r.Context, host.ID, fleet.OperatingSystem{
|
||||
Name: osVersionPrefix,
|
||||
Version: osVersion,
|
||||
Platform: platform,
|
||||
}); err != nil {
|
||||
return nil, ctxerr.Wrap(r.Context, err, "failed to update host operating system")
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3237,6 +3237,13 @@ func TestMDMCommandAndReportResultsIOSIPadOSRefetch(t *testing.T) {
|
|||
require.NotZero(t, 64, int64(gigsTotal))
|
||||
return nil
|
||||
}
|
||||
ds.UpdateHostOperatingSystemFunc = func(ctx context.Context, hostID uint, hostOS fleet.OperatingSystem) error {
|
||||
require.Equal(t, hostID, hostID)
|
||||
require.Equal(t, "iPadOS", hostOS.Name)
|
||||
require.Equal(t, "17.5.1", hostOS.Version)
|
||||
require.Equal(t, "ipados", hostOS.Platform)
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := svc.CommandAndReportResults(
|
||||
&mdm.Request{Context: ctx},
|
||||
|
|
@ -3277,4 +3284,5 @@ func TestMDMCommandAndReportResultsIOSIPadOSRefetch(t *testing.T) {
|
|||
require.True(t, ds.UpdateHostFuncInvoked)
|
||||
require.True(t, ds.HostByIdentifierFuncInvoked)
|
||||
require.True(t, ds.SetOrUpdateHostDisksSpaceFuncInvoked)
|
||||
require.True(t, ds.UpdateHostOperatingSystemFuncInvoked)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -162,6 +162,20 @@ func AddBuiltinLabels(t *testing.T, ds fleet.Datastore) {
|
|||
LabelType: fleet.LabelTypeBuiltIn,
|
||||
LabelMembershipType: fleet.LabelMembershipTypeDynamic,
|
||||
},
|
||||
{
|
||||
Name: "iOS",
|
||||
Platform: "ios",
|
||||
Query: "",
|
||||
LabelType: fleet.LabelTypeBuiltIn,
|
||||
LabelMembershipType: fleet.LabelMembershipTypeManual,
|
||||
},
|
||||
{
|
||||
Name: "iPadOS",
|
||||
Platform: "ipados",
|
||||
Query: "",
|
||||
LabelType: fleet.LabelTypeBuiltIn,
|
||||
LabelMembershipType: fleet.LabelMembershipTypeManual,
|
||||
},
|
||||
}
|
||||
|
||||
names := fleet.ReservedLabelNames()
|
||||
|
|
|
|||
|
|
@ -38,6 +38,13 @@ func main() {
|
|||
log.Fatal("only one of -profile-uuid or -serial-number must be provided")
|
||||
}
|
||||
|
||||
if len(*serverPrivateKey) > 32 {
|
||||
// We truncate to 32 bytes because AES-256 requires a 32 byte (256 bit) PK, but some
|
||||
// infra setups generate keys that are longer than 32 bytes.
|
||||
truncatedServerPrivateKey := (*serverPrivateKey)[:32]
|
||||
serverPrivateKey = &truncatedServerPrivateKey
|
||||
}
|
||||
|
||||
cfg := config.MysqlConfig{
|
||||
Protocol: "tcp",
|
||||
Address: *mysqlAddr,
|
||||
|
|
|
|||
Loading…
Reference in a new issue