diff --git a/changes/36188-back-end-add-update-new-hosts-config b/changes/36188-back-end-add-update-new-hosts-config new file mode 100644 index 00000000000..06c51dcd11b --- /dev/null +++ b/changes/36188-back-end-add-update-new-hosts-config @@ -0,0 +1 @@ +* Updated backend and GitOps to handle AppleOSUpdateSettings.UpdateNewHosts \ No newline at end of file diff --git a/cmd/fleetctl/fleetctl/apply_test.go b/cmd/fleetctl/fleetctl/apply_test.go index 40f373c5aef..5226e3914f0 100644 --- a/cmd/fleetctl/fleetctl/apply_test.go +++ b/cmd/fleetctl/fleetctl/apply_test.go @@ -239,6 +239,7 @@ spec: macos_updates: minimum_version: 12.3.1 deadline: 2011-03-01 + update_new_hosts: true `) newAgentOpts := json.RawMessage(`{"config":{"views":{"foo":"bar"}}}`) @@ -246,6 +247,7 @@ spec: MacOSUpdates: fleet.AppleOSUpdateSettings{ MinimumVersion: optjson.SetString("12.3.1"), Deadline: optjson.SetString("2011-03-01"), + UpdateNewHosts: optjson.SetBool(true), }, MacOSSetup: fleet.MacOSSetup{ EnableReleaseDeviceManually: optjson.SetBool(false), @@ -282,6 +284,7 @@ spec: MacOSUpdates: fleet.AppleOSUpdateSettings{ MinimumVersion: optjson.SetString("12.3.1"), Deadline: optjson.SetString("2011-03-01"), + UpdateNewHosts: optjson.SetBool(true), }, WindowsUpdates: fleet.WindowsUpdates{ DeadlineDays: optjson.SetInt(5), @@ -310,6 +313,7 @@ spec: MacOSUpdates: fleet.AppleOSUpdateSettings{ MinimumVersion: optjson.SetString("12.3.1"), Deadline: optjson.SetString("2011-03-01"), + UpdateNewHosts: optjson.SetBool(true), }, WindowsUpdates: fleet.WindowsUpdates{ DeadlineDays: optjson.SetInt(5), diff --git a/cmd/fleetctl/fleetctl/gitops_test.go b/cmd/fleetctl/fleetctl/gitops_test.go index 746aa316db3..63ca9c28835 100644 --- a/cmd/fleetctl/fleetctl/gitops_test.go +++ b/cmd/fleetctl/fleetctl/gitops_test.go @@ -398,12 +398,17 @@ func TestGitOpsBasicGlobalPremium(t *testing.T) { _, err = tmpFile.WriteString( ` controls: + macos_updates: + deadline: "2024-03-03" + minimum_version: "18.0" ios_updates: deadline: "2022-02-02" minimum_version: "17.6" + update_new_hosts: true ipados_updates: deadline: "2023-03-03" minimum_version: "18.0" + update_new_hosts: false enable_disk_encryption: true windows_require_bitlocker_pin: true queries: @@ -474,6 +479,16 @@ software: // Check MDM settings require.True(t, savedAppConfig.MDM.EnableDiskEncryption.Value) require.True(t, savedAppConfig.MDM.RequireBitLockerPIN.Value) + require.Equal(t, "18.0", savedAppConfig.MDM.MacOSUpdates.MinimumVersion.Value) + require.Equal(t, "2024-03-03", savedAppConfig.MDM.MacOSUpdates.Deadline.Value) + // To keep things backwards compatible if MinimumVersion & Deadline are set, then UpdateNewHosts should be set to true + require.Equal(t, optjson.SetBool(true), savedAppConfig.MDM.MacOSUpdates.UpdateNewHosts) + require.Equal(t, "2022-02-02", savedAppConfig.MDM.IOSUpdates.Deadline.Value) + require.Equal(t, "17.6", savedAppConfig.MDM.IOSUpdates.MinimumVersion.Value) + require.Equal(t, optjson.Bool{}, savedAppConfig.MDM.IOSUpdates.UpdateNewHosts) + require.Equal(t, "2023-03-03", savedAppConfig.MDM.IPadOSUpdates.Deadline.Value) + require.Equal(t, "18.0", savedAppConfig.MDM.IPadOSUpdates.MinimumVersion.Value) + require.Equal(t, optjson.Bool{}, savedAppConfig.MDM.IPadOSUpdates.UpdateNewHosts) // Check certificate authorities assert.True(t, ds.GetGroupedCertificateAuthoritiesFuncInvoked) @@ -732,6 +747,9 @@ func TestGitOpsBasicTeam(t *testing.T) { _, err = tmpFile.WriteString( ` controls: + macos_updates: + deadline: "2025-10-10" + minimum_version: "18.0" ios_updates: deadline: "2024-10-10" minimum_version: "18.0" @@ -788,6 +806,11 @@ software: assert.Empty(t, enrolledTeamSecrets) assert.True(t, savedTeam.Config.Features.EnableSoftwareInventory) + assert.Equal(t, "2025-10-10", savedTeam.Config.MDM.MacOSUpdates.Deadline.Value) + assert.Equal(t, "18.0", savedTeam.Config.MDM.MacOSUpdates.MinimumVersion.Value) + // To keep things backwards compatible if MinimumVersion & Deadline are set, then UpdateNewHosts should be set to true + assert.Equal(t, optjson.SetBool(true), savedTeam.Config.MDM.MacOSUpdates.UpdateNewHosts) + // The previous run created the team, so let's rerun with an existing team _ = RunAppForTest(t, []string{"gitops", "-f", tmpFile.Name()}) assert.Empty(t, enrolledTeamSecrets) @@ -1458,6 +1481,7 @@ software: assert.Empty(t, savedTeam.Config.MDM.WindowsSettings.CustomSettings.Value) assert.Empty(t, savedTeam.Config.MDM.MacOSUpdates.Deadline.Value) assert.Empty(t, savedTeam.Config.MDM.MacOSUpdates.MinimumVersion.Value) + assert.False(t, savedTeam.Config.MDM.MacOSUpdates.UpdateNewHosts.Value) assert.Empty(t, savedTeam.Config.MDM.MacOSSetup.BootstrapPackage.Value) assert.False(t, savedTeam.Config.MDM.EnableDiskEncryption) assert.Equal(t, filepath.Base(tmpFile.Name()), *savedTeam.Filename) diff --git a/cmd/fleetctl/fleetctl/testdata/generateGitops/appConfig.json b/cmd/fleetctl/fleetctl/testdata/generateGitops/appConfig.json index b011afadc32..10385b25b94 100644 --- a/cmd/fleetctl/fleetctl/testdata/generateGitops/appConfig.json +++ b/cmd/fleetctl/fleetctl/testdata/generateGitops/appConfig.json @@ -218,7 +218,8 @@ "enabled_and_configured": true, "macos_updates": { "minimum_version": "15.1", - "deadline": "2024-12-31" + "deadline": "2024-12-31", + "update_new_hosts": true }, "ios_updates": { "minimum_version": "18.1", diff --git a/cmd/fleetctl/fleetctl/testdata/generateGitops/expectedGlobalControls.yaml b/cmd/fleetctl/fleetctl/testdata/generateGitops/expectedGlobalControls.yaml index 514370507fb..ff2774f43da 100644 --- a/cmd/fleetctl/fleetctl/testdata/generateGitops/expectedGlobalControls.yaml +++ b/cmd/fleetctl/fleetctl/testdata/generateGitops/expectedGlobalControls.yaml @@ -18,7 +18,7 @@ android_settings: macos_updates: minimum_version: "15.1" deadline: "2024-12-31" - update_new_hosts: + update_new_hosts: true ios_updates: minimum_version: "18.1" deadline: "2025-12-31" diff --git a/cmd/fleetctl/fleetctl/testdata/generateGitops/teamConfig.json b/cmd/fleetctl/fleetctl/testdata/generateGitops/teamConfig.json index 89736aa27b5..4401fc9b0d3 100644 --- a/cmd/fleetctl/fleetctl/testdata/generateGitops/teamConfig.json +++ b/cmd/fleetctl/fleetctl/testdata/generateGitops/teamConfig.json @@ -107,7 +107,8 @@ "enable_disk_encryption": true, "macos_updates": { "minimum_version": "95.1", - "deadline": "2020-12-31" + "deadline": "2020-12-31", + "update_new_hosts": true }, "ios_updates": { "minimum_version": "98.1", diff --git a/cmd/fleetctl/fleetctl/testdata/generateGitops/test_dir_premium/teams/no-team.yml b/cmd/fleetctl/fleetctl/testdata/generateGitops/test_dir_premium/teams/no-team.yml index 09fc927acc6..b5dbe8ab230 100644 --- a/cmd/fleetctl/fleetctl/testdata/generateGitops/test_dir_premium/teams/no-team.yml +++ b/cmd/fleetctl/fleetctl/testdata/generateGitops/test_dir_premium/teams/no-team.yml @@ -18,7 +18,7 @@ controls: macos_updates: deadline: "2024-12-31" minimum_version: "15.1" - update_new_hosts: + update_new_hosts: true scripts: - path: ../lib/no-team/scripts/Script Z.ps1 windows_enabled_and_configured: true diff --git a/cmd/fleetctl/fleetctl/testdata/generateGitops/test_dir_premium/teams/team-a-thumbsup.yml b/cmd/fleetctl/fleetctl/testdata/generateGitops/test_dir_premium/teams/team-a-thumbsup.yml index 101f7c0ebdf..cadb0ac2d6d 100644 --- a/cmd/fleetctl/fleetctl/testdata/generateGitops/test_dir_premium/teams/team-a-thumbsup.yml +++ b/cmd/fleetctl/fleetctl/testdata/generateGitops/test_dir_premium/teams/team-a-thumbsup.yml @@ -38,7 +38,7 @@ controls: macos_updates: deadline: "2020-12-31" minimum_version: "95.1" - update_new_hosts: + update_new_hosts: true scripts: - path: "../lib/team-a-👍/scripts/Script B.ps1" windows_enabled_and_configured: true diff --git a/ee/server/service/teams.go b/ee/server/service/teams.go index 9a20f78adbe..2056edf952c 100644 --- a/ee/server/service/teams.go +++ b/ee/server/service/teams.go @@ -170,6 +170,7 @@ func (svc *Service) ModifyTeam(ctx context.Context, teamID uint, payload fleet.T var ( macOSMinVersionUpdated bool + updateNewHostsChanged bool iOSMinVersionUpdated bool iPadOSMinVersionUpdated bool windowsUpdatesUpdated bool @@ -182,9 +183,10 @@ func (svc *Service) ModifyTeam(ctx context.Context, teamID uint, payload fleet.T if err := payload.MDM.MacOSUpdates.Validate(); err != nil { return nil, fleet.NewInvalidArgumentError("macos_updates", err.Error()) } - if payload.MDM.MacOSUpdates.MinimumVersion.Set || payload.MDM.MacOSUpdates.Deadline.Set { + if payload.MDM.MacOSUpdates.MinimumVersion.Set || payload.MDM.MacOSUpdates.Deadline.Set || payload.MDM.MacOSUpdates.UpdateNewHosts.Set { macOSMinVersionUpdated = team.Config.MDM.MacOSUpdates.MinimumVersion.Value != payload.MDM.MacOSUpdates.MinimumVersion.Value || team.Config.MDM.MacOSUpdates.Deadline.Value != payload.MDM.MacOSUpdates.Deadline.Value + updateNewHostsChanged = team.Config.MDM.MacOSUpdates.UpdateNewHosts.Value != payload.MDM.MacOSUpdates.UpdateNewHosts.Value team.Config.MDM.MacOSUpdates = *payload.MDM.MacOSUpdates } } @@ -192,6 +194,7 @@ func (svc *Service) ModifyTeam(ctx context.Context, teamID uint, payload fleet.T if err := payload.MDM.IOSUpdates.Validate(); err != nil { return nil, fleet.NewInvalidArgumentError("ios_updates", err.Error()) } + if payload.MDM.IOSUpdates.MinimumVersion.Set || payload.MDM.IOSUpdates.Deadline.Set { iOSMinVersionUpdated = team.Config.MDM.IOSUpdates.MinimumVersion.Value != payload.MDM.IOSUpdates.MinimumVersion.Value || team.Config.MDM.IOSUpdates.Deadline.Value != payload.MDM.IOSUpdates.Deadline.Value @@ -376,6 +379,28 @@ func (svc *Service) ModifyTeam(ctx context.Context, teamID uint, payload fleet.T } } + if updateNewHostsChanged { + var activity fleet.ActivityDetails + activity = fleet.ActivityTypeEnabledMacosUpdateNewHosts{ + TeamID: &team.ID, + TeamName: &team.Name, + } + if payload.MDM != nil && payload.MDM.MacOSUpdates != nil && !payload.MDM.MacOSUpdates.UpdateNewHosts.Value { + activity = fleet.ActivityTypeDisabledMacosUpdateNewHosts{ + TeamID: &team.ID, + TeamName: &team.Name, + } + } + + if err := svc.NewActivity( + ctx, + authz.UserFromContext(ctx), + activity, + ); err != nil { + return nil, ctxerr.Wrap(ctx, err, "create activity for team macOS update new hosts edited") + } + } + if windowsUpdatesUpdated { var deadline, grace *int if team.Config.MDM.WindowsUpdates.DeadlineDays.Valid { diff --git a/server/datastore/mysql/apple_mdm.go b/server/datastore/mysql/apple_mdm.go index fd56131f318..751cab01ea1 100644 --- a/server/datastore/mysql/apple_mdm.go +++ b/server/datastore/mysql/apple_mdm.go @@ -6782,7 +6782,7 @@ WHERE ( return nil } -func (ds *Datastore) GetMDMAppleOSUpdatesSettingsByHostSerial(ctx context.Context, serial string) (*fleet.AppleOSUpdateSettings, error) { +func (ds *Datastore) GetMDMAppleOSUpdatesSettingsByHostSerial(ctx context.Context, serial string) (string, *fleet.AppleOSUpdateSettings, error) { stmt := ` SELECT team_id, platform @@ -6799,7 +6799,7 @@ LIMIT 1` Platform string `db:"platform"` } if err := sqlx.GetContext(ctx, ds.reader(ctx), &dest, stmt, serial); err != nil { - return nil, ctxerr.Wrap(ctx, err, "getting team id for host") + return "", nil, ctxerr.Wrap(ctx, err, "getting team id for host") } var settings fleet.AppleOSUpdateSettings @@ -6807,7 +6807,7 @@ LIMIT 1` // use the global settings ac, err := ds.AppConfig(ctx) if err != nil { - return nil, ctxerr.Wrap(ctx, err, "getting app config for os update settings") + return "", nil, ctxerr.Wrap(ctx, err, "getting app config for os update settings") } switch dest.Platform { case "ios": @@ -6817,13 +6817,13 @@ LIMIT 1` case "darwin": settings = ac.MDM.MacOSUpdates default: - return nil, ctxerr.New(ctx, fmt.Sprintf("unsupported platform %s", dest.Platform)) + return "", nil, ctxerr.New(ctx, fmt.Sprintf("unsupported platform %s", dest.Platform)) } } else { // use the team settings tm, err := ds.TeamLite(ctx, *dest.TeamID) if err != nil { - return nil, ctxerr.Wrap(ctx, err, "getting team os update settings") + return "", nil, ctxerr.Wrap(ctx, err, "getting team os update settings") } switch dest.Platform { case "ios": @@ -6833,11 +6833,11 @@ LIMIT 1` case "darwin": settings = tm.Config.MDM.MacOSUpdates default: - return nil, ctxerr.New(ctx, fmt.Sprintf("unsupported platform %s", dest.Platform)) + return "", nil, ctxerr.New(ctx, fmt.Sprintf("unsupported platform %s", dest.Platform)) } } - return &settings, nil + return dest.Platform, &settings, nil } // ClearMDMUpcomingActivitiesDB clears the upcoming activities of the host that diff --git a/server/datastore/mysql/apple_mdm_test.go b/server/datastore/mysql/apple_mdm_test.go index edb9c20bbf3..fc12d644df0 100644 --- a/server/datastore/mysql/apple_mdm_test.go +++ b/server/datastore/mysql/apple_mdm_test.go @@ -8011,9 +8011,14 @@ func TestGetMDMAppleOSUpdatesSettingsByHostSerial(t *testing.T) { checkDevice := func(t *testing.T, teamID uint, key string, wantVersion string) { checkExpectedVersion(t, getConfigSettings(teamID, key), wantVersion) - gotSettings, err := ds.GetMDMAppleOSUpdatesSettingsByHostSerial(context.Background(), devicesByKey[key].SerialNumber) + platform, gotSettings, err := ds.GetMDMAppleOSUpdatesSettingsByHostSerial(context.Background(), devicesByKey[key].SerialNumber) require.NoError(t, err) checkExpectedVersion(t, gotSettings, wantVersion) + if key == "macos" { + require.Equal(t, "darwin", platform) + } else { + require.Equal(t, platform, key) + } } // empty global settings to start @@ -8110,7 +8115,7 @@ func TestGetMDMAppleOSUpdatesSettingsByHostSerial(t *testing.T) { }) // non-DEP host should return not found - _, err = ds.GetMDMAppleOSUpdatesSettingsByHostSerial(context.Background(), "non-dep-serial") + _, _, err = ds.GetMDMAppleOSUpdatesSettingsByHostSerial(context.Background(), "non-dep-serial") require.ErrorIs(t, err, sql.ErrNoRows) // deleted DEP host should return not found @@ -8118,7 +8123,7 @@ func TestGetMDMAppleOSUpdatesSettingsByHostSerial(t *testing.T) { _, err := q.ExecContext(context.Background(), "UPDATE host_dep_assignments SET deleted_at = NOW() WHERE host_id = ?", hostIDsByKey["macos"]) return err }) - _, err = ds.GetMDMAppleOSUpdatesSettingsByHostSerial(context.Background(), devicesByKey["macos"].SerialNumber) + _, _, err = ds.GetMDMAppleOSUpdatesSettingsByHostSerial(context.Background(), devicesByKey["macos"].SerialNumber) require.ErrorIs(t, err, sql.ErrNoRows) } diff --git a/server/fleet/datastore.go b/server/fleet/datastore.go index 8286f24f6da..43635643f82 100644 --- a/server/fleet/datastore.go +++ b/server/fleet/datastore.go @@ -1566,8 +1566,8 @@ type Datastore interface { MDMAppleSetPendingDeclarationsAs(ctx context.Context, hostUUID string, status *MDMDeliveryStatus, detail string) error MDMAppleSetRemoveDeclarationsAsPending(ctx context.Context, hostUUID string, declarationUUIDs []string) error // GetMDMAppleOSUpdatesSettingsByHostSerial returns applicable Apple OS update settings (if any) - // for the host with the given serial number. The host must be DEP assigned to Fleet. - GetMDMAppleOSUpdatesSettingsByHostSerial(ctx context.Context, hostSerial string) (*AppleOSUpdateSettings, error) + // for the host with the given serial number alongside the host's platform. The host must be DEP assigned to Fleet. + GetMDMAppleOSUpdatesSettingsByHostSerial(ctx context.Context, hostSerial string) (string, *AppleOSUpdateSettings, error) // InsertMDMConfigAssets inserts MDM related config assets, such as SCEP and APNS certs and keys. // tx is optional and can be used to pass an existing transaction. diff --git a/server/mock/datastore_mock.go b/server/mock/datastore_mock.go index 86279868ded..75402ec0eff 100644 --- a/server/mock/datastore_mock.go +++ b/server/mock/datastore_mock.go @@ -1045,7 +1045,7 @@ type MDMAppleSetPendingDeclarationsAsFunc func(ctx context.Context, hostUUID str type MDMAppleSetRemoveDeclarationsAsPendingFunc func(ctx context.Context, hostUUID string, declarationUUIDs []string) error -type GetMDMAppleOSUpdatesSettingsByHostSerialFunc func(ctx context.Context, hostSerial string) (*fleet.AppleOSUpdateSettings, error) +type GetMDMAppleOSUpdatesSettingsByHostSerialFunc func(ctx context.Context, hostSerial string) (string, *fleet.AppleOSUpdateSettings, error) type InsertMDMConfigAssetsFunc func(ctx context.Context, assets []fleet.MDMConfigAsset, tx sqlx.ExtContext) error @@ -7739,7 +7739,7 @@ func (s *DataStore) MDMAppleSetRemoveDeclarationsAsPending(ctx context.Context, return s.MDMAppleSetRemoveDeclarationsAsPendingFunc(ctx, hostUUID, declarationUUIDs) } -func (s *DataStore) GetMDMAppleOSUpdatesSettingsByHostSerial(ctx context.Context, hostSerial string) (*fleet.AppleOSUpdateSettings, error) { +func (s *DataStore) GetMDMAppleOSUpdatesSettingsByHostSerial(ctx context.Context, hostSerial string) (string, *fleet.AppleOSUpdateSettings, error) { s.mu.Lock() s.GetMDMAppleOSUpdatesSettingsByHostSerialFuncInvoked = true s.mu.Unlock() diff --git a/server/service/appconfig.go b/server/service/appconfig.go index c13e21d4c3a..f45f579bb4b 100644 --- a/server/service/appconfig.go +++ b/server/service/appconfig.go @@ -390,6 +390,10 @@ func (svc *Service) ModifyAppConfig(ctx context.Context, p []byte, applyOpts fle return nil, ctxerr.Wrap(ctx, err) } + // AppleOSUpdateSettings.UpdateNewHosts only applies to macOS ... so just ignore w/e posted for iOS/iPadOS + appConfig.MDM.IOSUpdates.UpdateNewHosts = optjson.Bool{} + appConfig.MDM.IPadOSUpdates.UpdateNewHosts = optjson.Bool{} + // if turning off Windows MDM and Windows Migration is not explicitly set to // on in the same update, set it to off (otherwise, if it is explicitly set // to true, return an error that it can't be done when MDM is off, this is @@ -1131,6 +1135,17 @@ func (svc *Service) processAppleOSUpdateSettings( return ctxerr.Wrap(ctx, err, "create activity for app config apple min version modification") } } + + if oldOSUpdateSettings.UpdateNewHosts.Value != newOSUpdateSettings.UpdateNewHosts.Value && appleDevice == fleet.MacOS { + var activity fleet.ActivityDetails + activity = fleet.ActivityTypeEnabledMacosUpdateNewHosts{} + if !newOSUpdateSettings.UpdateNewHosts.Value { + activity = fleet.ActivityTypeDisabledMacosUpdateNewHosts{} + } + if err := svc.NewActivity(ctx, authz.UserFromContext(ctx), activity); err != nil { + return ctxerr.Wrap(ctx, err, "create activity for app config apple min version modification") + } + } return nil } diff --git a/server/service/appconfig_test.go b/server/service/appconfig_test.go index aec8ba3b34d..b43fcee896d 100644 --- a/server/service/appconfig_test.go +++ b/server/service/appconfig_test.go @@ -936,8 +936,8 @@ func TestMDMConfig(t *testing.T) { ManualAgentInstall: optjson.Bool{Set: true}, }, MacOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}, UpdateNewHosts: optjson.Bool{Set: true}}, - IOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}, UpdateNewHosts: optjson.Bool{Set: true}}, - IPadOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}, UpdateNewHosts: optjson.Bool{Set: true}}, + IOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}}, + IPadOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}}, VolumePurchasingProgram: optjson.Slice[fleet.MDMAppleVolumePurchasingProgramInfo]{Set: true, Value: []fleet.MDMAppleVolumePurchasingProgramInfo{}}, WindowsUpdates: fleet.WindowsUpdates{DeadlineDays: optjson.Int{Set: true}, GracePeriodDays: optjson.Int{Set: true}}, WindowsSettings: fleet.WindowsSettings{ @@ -986,8 +986,8 @@ func TestMDMConfig(t *testing.T) { ManualAgentInstall: optjson.Bool{Set: true}, }, MacOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}, UpdateNewHosts: optjson.Bool{Set: true}}, - IOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}, UpdateNewHosts: optjson.Bool{Set: true}}, - IPadOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}, UpdateNewHosts: optjson.Bool{Set: true}}, + IOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}}, + IPadOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}}, VolumePurchasingProgram: optjson.Slice[fleet.MDMAppleVolumePurchasingProgramInfo]{Set: true, Value: []fleet.MDMAppleVolumePurchasingProgramInfo{}}, WindowsUpdates: fleet.WindowsUpdates{DeadlineDays: optjson.Int{Set: true}, GracePeriodDays: optjson.Int{Set: true}}, WindowsSettings: fleet.WindowsSettings{ @@ -1018,8 +1018,8 @@ func TestMDMConfig(t *testing.T) { ManualAgentInstall: optjson.Bool{Set: true}, }, MacOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}, UpdateNewHosts: optjson.Bool{Set: true}}, - IOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}, UpdateNewHosts: optjson.Bool{Set: true}}, - IPadOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}, UpdateNewHosts: optjson.Bool{Set: true}}, + IOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}}, + IPadOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}}, VolumePurchasingProgram: optjson.Slice[fleet.MDMAppleVolumePurchasingProgramInfo]{Set: true, Value: []fleet.MDMAppleVolumePurchasingProgramInfo{}}, WindowsUpdates: fleet.WindowsUpdates{DeadlineDays: optjson.Int{Set: true}, GracePeriodDays: optjson.Int{Set: true}}, WindowsSettings: fleet.WindowsSettings{ @@ -1057,8 +1057,8 @@ func TestMDMConfig(t *testing.T) { ManualAgentInstall: optjson.Bool{Set: true}, }, MacOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}, UpdateNewHosts: optjson.Bool{Set: true}}, - IOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}, UpdateNewHosts: optjson.Bool{Set: true}}, - IPadOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}, UpdateNewHosts: optjson.Bool{Set: true}}, + IOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}}, + IPadOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}}, VolumePurchasingProgram: optjson.Slice[fleet.MDMAppleVolumePurchasingProgramInfo]{Set: true, Value: []fleet.MDMAppleVolumePurchasingProgramInfo{}}, WindowsUpdates: fleet.WindowsUpdates{DeadlineDays: optjson.Int{Set: true}, GracePeriodDays: optjson.Int{Set: true}}, WindowsSettings: fleet.WindowsSettings{ @@ -1096,8 +1096,8 @@ func TestMDMConfig(t *testing.T) { ManualAgentInstall: optjson.Bool{Set: true}, }, MacOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}, UpdateNewHosts: optjson.Bool{Set: true}}, - IOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}, UpdateNewHosts: optjson.Bool{Set: true}}, - IPadOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}, UpdateNewHosts: optjson.Bool{Set: true}}, + IOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}}, + IPadOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}}, VolumePurchasingProgram: optjson.Slice[fleet.MDMAppleVolumePurchasingProgramInfo]{Set: true, Value: []fleet.MDMAppleVolumePurchasingProgramInfo{}}, WindowsUpdates: fleet.WindowsUpdates{DeadlineDays: optjson.Int{Set: true}, GracePeriodDays: optjson.Int{Set: true}}, WindowsSettings: fleet.WindowsSettings{ @@ -1135,8 +1135,8 @@ func TestMDMConfig(t *testing.T) { ManualAgentInstall: optjson.Bool{Set: true}, }, MacOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}, UpdateNewHosts: optjson.Bool{Set: true}}, - IOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}, UpdateNewHosts: optjson.Bool{Set: true}}, - IPadOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}, UpdateNewHosts: optjson.Bool{Set: true}}, + IOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}}, + IPadOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}}, VolumePurchasingProgram: optjson.Slice[fleet.MDMAppleVolumePurchasingProgramInfo]{Set: true, Value: []fleet.MDMAppleVolumePurchasingProgramInfo{}}, WindowsUpdates: fleet.WindowsUpdates{DeadlineDays: optjson.Int{Set: true}, GracePeriodDays: optjson.Int{Set: true}}, WindowsSettings: fleet.WindowsSettings{ @@ -1199,8 +1199,8 @@ func TestMDMConfig(t *testing.T) { ManualAgentInstall: optjson.Bool{Set: true}, }, MacOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}, UpdateNewHosts: optjson.Bool{Set: true}}, - IOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}, UpdateNewHosts: optjson.Bool{Set: true}}, - IPadOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}, UpdateNewHosts: optjson.Bool{Set: true}}, + IOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}}, + IPadOSUpdates: fleet.AppleOSUpdateSettings{MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}}, VolumePurchasingProgram: optjson.Slice[fleet.MDMAppleVolumePurchasingProgramInfo]{Set: true, Value: []fleet.MDMAppleVolumePurchasingProgramInfo{}}, WindowsUpdates: fleet.WindowsUpdates{DeadlineDays: optjson.Int{Set: true}, GracePeriodDays: optjson.Int{Set: true}}, WindowsSettings: fleet.WindowsSettings{ diff --git a/server/service/apple_mdm.go b/server/service/apple_mdm.go index a06a455905f..fcba1bd6178 100644 --- a/server/service/apple_mdm.go +++ b/server/service/apple_mdm.go @@ -2099,7 +2099,7 @@ func (svc *Service) CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx context.Cont } if !needsUpdate { - level.Debug(svc.logger).Log("msg", "device is above minimum, skipping os version check", "serial", m.Serial) + level.Debug(svc.logger).Log("msg", "device is above minimum or update new host not checked, skipping os version check", "serial", m.Serial) return nil, nil } @@ -2124,23 +2124,57 @@ func (svc *Service) needsOSUpdateForDEPEnrollment(ctx context.Context, m fleet.M // some cross-check against the machine info to ensure that the platform of the host aligns with // what we expect from the machine info. But that would involve work to derive the platform from // the machine info (presumably from the product name, but that's not a 1:1 mapping). - settings, err := svc.ds.GetMDMAppleOSUpdatesSettingsByHostSerial(ctx, m.Serial) + platform, settings, err := svc.ds.GetMDMAppleOSUpdatesSettingsByHostSerial(ctx, m.Serial) if err != nil { if fleet.IsNotFound(err) { - level.Info(svc.logger).Log("msg", "checking os updates settings, settings not found", "serial", m.Serial) + level.Info(svc.logger).Log( + "msg", "checking os updates settings, settings not found", + "serial", m.Serial, + ) return false, nil } return false, err } + + minVersion := settings.MinimumVersion.Value + hasMinVersion := settings.MinimumVersion.Set && settings.MinimumVersion.Valid && minVersion != "" + updateNewHosts := settings.UpdateNewHosts.Set && settings.UpdateNewHosts.Valid && settings.UpdateNewHosts.Value + isMacOS := platform == "darwin" + + if isMacOS { + // If "Update New Hosts" is unchecked on macOS, we never update. + if !updateNewHosts { + return false, nil + } + + // If "Update New Hosts" is checked but no version is set, we force an update + if !hasMinVersion { + level.Info(svc.logger).Log( + "msg", "checking os updates settings, minimum version not set, forcing macos update", + "serial", m.Serial, + ) + return true, nil + } + } + // TODO: confirm what this check should do - if !settings.MinimumVersion.Set || !settings.MinimumVersion.Valid || settings.MinimumVersion.Value == "" { - level.Info(svc.logger).Log("msg", "checking os updates settings, minimum version not set", "serial", m.Serial, "current_version", m.OSVersion, "minimum_version", settings.MinimumVersion.Value) - return false, nil + if !hasMinVersion { + level.Info(svc.logger).Log( + "msg", "checking os updates settings, minimum version not set", + "serial", m.Serial, + "current_version", m.OSVersion, + "minimum_version", minVersion, + ) } - needsUpdate, err := apple_mdm.IsLessThanVersion(m.OSVersion, settings.MinimumVersion.Value) + needsUpdate, err := apple_mdm.IsLessThanVersion(m.OSVersion, minVersion) if err != nil { - level.Info(svc.logger).Log("msg", "checking os updates settings, cannot compare versions", "serial", m.Serial, "current_version", m.OSVersion, "minimum_version", settings.MinimumVersion.Value) + level.Info(svc.logger).Log( + "msg", "checking os updates settings, cannot compare versions", + "serial", m.Serial, + "current_version", m.OSVersion, + "minimum_version", minVersion, + ) return false, nil } diff --git a/server/service/apple_mdm_test.go b/server/service/apple_mdm_test.go index 3c734008ad4..1c364a11284 100644 --- a/server/service/apple_mdm_test.go +++ b/server/service/apple_mdm_test.go @@ -5061,20 +5061,151 @@ func TestCheckMDMAppleEnrollmentWithMinimumOSVersion(t *testing.T) { } for _, tt := range testCases { - t.Run(tt.name, func(t *testing.T) { - t.Run("settings minimum equal to latest", func(t *testing.T) { - ds.GetMDMAppleOSUpdatesSettingsByHostSerialFunc = func(ctx context.Context, serial string) (*fleet.AppleOSUpdateSettings, error) { - return &fleet.AppleOSUpdateSettings{ + // Non-macOS platforms + for _, platform := range []string{"ios", "ipados"} { + t.Run(fmt.Sprintf("%s: %s", platform, tt.name), func(t *testing.T) { + t.Run("settings minimum equal to latest", func(t *testing.T) { + ds.GetMDMAppleOSUpdatesSettingsByHostSerialFunc = func(ctx context.Context, serial string) (string, *fleet.AppleOSUpdateSettings, error) { + return "ios", &fleet.AppleOSUpdateSettings{ + MinimumVersion: optjson.SetString(latestMacOSVersion), + }, nil + } + sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, tt.machineInfo) + if tt.err != "" { + require.Error(t, err) + require.Contains(t, err.Error(), tt.err) + } else { + require.NoError(t, err) + } + if tt.updateRequired != nil { + require.Equal(t, &fleet.MDMAppleSoftwareUpdateRequired{ + Code: fleet.MDMAppleSoftwareUpdateRequiredCode, + Details: *tt.updateRequired, + }, sur) + } else { + require.Nil(t, sur) + } + }) + + t.Run("settings minimum below latest", func(t *testing.T) { + ds.GetMDMAppleOSUpdatesSettingsByHostSerialFunc = func(ctx context.Context, serial string) (string, *fleet.AppleOSUpdateSettings, error) { + return "ios", &fleet.AppleOSUpdateSettings{ + MinimumVersion: optjson.SetString("14.5"), + }, nil + } + sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, tt.machineInfo) + if tt.err != "" { + require.Error(t, err) + require.Contains(t, err.Error(), tt.err) + } else { + require.NoError(t, err) + } + if tt.updateRequired != nil { + require.Equal(t, &fleet.MDMAppleSoftwareUpdateRequired{ + Code: fleet.MDMAppleSoftwareUpdateRequiredCode, + Details: *tt.updateRequired, + }, sur) + } else { + require.Nil(t, sur) + } + }) + + t.Run("settings minimum above latest", func(t *testing.T) { + // edge case, but in practice it would get treated as if minimum was equal to latest + ds.GetMDMAppleOSUpdatesSettingsByHostSerialFunc = func(ctx context.Context, serial string) (string, *fleet.AppleOSUpdateSettings, error) { + return "ios", &fleet.AppleOSUpdateSettings{ + MinimumVersion: optjson.SetString("14.7"), + }, nil + } + sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, tt.machineInfo) + if tt.err != "" { + require.Error(t, err) + require.Contains(t, err.Error(), tt.err) + } else { + require.NoError(t, err) + } + if tt.updateRequired != nil { + require.Equal(t, &fleet.MDMAppleSoftwareUpdateRequired{ + Code: fleet.MDMAppleSoftwareUpdateRequiredCode, + Details: *tt.updateRequired, + }, sur) + } else { + require.Nil(t, sur) + } + }) + + t.Run("device above settings minimum", func(t *testing.T) { + ds.GetMDMAppleOSUpdatesSettingsByHostSerialFunc = func(ctx context.Context, serial string) (string, *fleet.AppleOSUpdateSettings, error) { + return "ios", &fleet.AppleOSUpdateSettings{ + MinimumVersion: optjson.SetString("14.1"), + }, nil + } + sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, tt.machineInfo) + if tt.err != "" { + require.Error(t, err) + require.Contains(t, err.Error(), tt.err) + } else { + require.NoError(t, err) + } + + require.Nil(t, sur) + }) + + t.Run("minimum not set", func(t *testing.T) { + ds.GetMDMAppleOSUpdatesSettingsByHostSerialFunc = func(ctx context.Context, serial string) (string, *fleet.AppleOSUpdateSettings, error) { + return "ios", &fleet.AppleOSUpdateSettings{}, nil + } + sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, tt.machineInfo) + require.NoError(t, err) + require.Nil(t, sur) + + ds.GetMDMAppleOSUpdatesSettingsByHostSerialFunc = func(ctx context.Context, serial string) (string, *fleet.AppleOSUpdateSettings, error) { + return "ios", &fleet.AppleOSUpdateSettings{ + MinimumVersion: optjson.SetString(""), + }, nil + } + sur, err = svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, tt.machineInfo) + require.NoError(t, err) + require.Nil(t, sur) + }) + + t.Run("minimum not found", func(t *testing.T) { + ds.GetMDMAppleOSUpdatesSettingsByHostSerialFunc = func(ctx context.Context, serial string) (string, *fleet.AppleOSUpdateSettings, error) { + return "ios", nil, ¬FoundError{} + } + sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, tt.machineInfo) + require.NoError(t, err) + require.Nil(t, sur) + }) + }) + } + } + + for _, tt := range testCases { + t.Run(fmt.Sprintf("%s for macOS", tt.name), func(t *testing.T) { + t.Run("when UpdateNewHosts is not set should never update", func(t *testing.T) { + ds.GetMDMAppleOSUpdatesSettingsByHostSerialFunc = func(ctx context.Context, serial string) (string, *fleet.AppleOSUpdateSettings, error) { + return "darwin", &fleet.AppleOSUpdateSettings{ + UpdateNewHosts: optjson.SetBool(false), MinimumVersion: optjson.SetString(latestMacOSVersion), }, nil } sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, tt.machineInfo) - if tt.err != "" { - require.Error(t, err) - require.Contains(t, err.Error(), tt.err) - } else { - require.NoError(t, err) + require.NoError(t, err) + require.Nil(t, sur) + }) + + t.Run("when UpdateNewHosts is set and minimum is not set", func(t *testing.T) { + ds.GetMDMAppleOSUpdatesSettingsByHostSerialFunc = func(ctx context.Context, serial string) (string, *fleet.AppleOSUpdateSettings, error) { + return "darwin", &fleet.AppleOSUpdateSettings{ + UpdateNewHosts: optjson.SetBool(true), + }, nil } + sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, tt.machineInfo) + require.NoError(t, err) + + // min version is not important for determining whether an update is required so the logic is based on + // the installed version only if tt.updateRequired != nil { require.Equal(t, &fleet.MDMAppleSoftwareUpdateRequired{ Code: fleet.MDMAppleSoftwareUpdateRequiredCode, @@ -5083,21 +5214,17 @@ func TestCheckMDMAppleEnrollmentWithMinimumOSVersion(t *testing.T) { } else { require.Nil(t, sur) } - }) - t.Run("settings minimum below latest", func(t *testing.T) { - ds.GetMDMAppleOSUpdatesSettingsByHostSerialFunc = func(ctx context.Context, serial string) (*fleet.AppleOSUpdateSettings, error) { - return &fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("14.5"), + ds.GetMDMAppleOSUpdatesSettingsByHostSerialFunc = func(ctx context.Context, serial string) (string, *fleet.AppleOSUpdateSettings, error) { + return "darwin", &fleet.AppleOSUpdateSettings{ + UpdateNewHosts: optjson.SetBool(true), + MinimumVersion: optjson.SetString(""), }, nil } - sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, tt.machineInfo) - if tt.err != "" { - require.Error(t, err) - require.Contains(t, err.Error(), tt.err) - } else { - require.NoError(t, err) - } + sur, err = svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, tt.machineInfo) + require.NoError(t, err) + + // Ditto previous comment if tt.updateRequired != nil { require.Equal(t, &fleet.MDMAppleSoftwareUpdateRequired{ Code: fleet.MDMAppleSoftwareUpdateRequiredCode, @@ -5108,11 +5235,20 @@ func TestCheckMDMAppleEnrollmentWithMinimumOSVersion(t *testing.T) { } }) - t.Run("settings minimum above latest", func(t *testing.T) { - // edge case, but in practice it would get treated as if minimum was equal to latest - ds.GetMDMAppleOSUpdatesSettingsByHostSerialFunc = func(ctx context.Context, serial string) (*fleet.AppleOSUpdateSettings, error) { - return &fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("14.7"), + t.Run("when apple OS settings not found", func(t *testing.T) { + ds.GetMDMAppleOSUpdatesSettingsByHostSerialFunc = func(ctx context.Context, serial string) (string, *fleet.AppleOSUpdateSettings, error) { + return "darwin", nil, ¬FoundError{} + } + sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, tt.machineInfo) + require.NoError(t, err) + require.Nil(t, sur) + }) + + t.Run("when UpdateNewHosts is set and required minimum is equal to latest", func(t *testing.T) { + ds.GetMDMAppleOSUpdatesSettingsByHostSerialFunc = func(ctx context.Context, serial string) (string, *fleet.AppleOSUpdateSettings, error) { + return "darwin", &fleet.AppleOSUpdateSettings{ + UpdateNewHosts: optjson.SetBool(true), + MinimumVersion: optjson.SetString(latestMacOSVersion), }, nil } sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, tt.machineInfo) @@ -5132,49 +5268,6 @@ func TestCheckMDMAppleEnrollmentWithMinimumOSVersion(t *testing.T) { } }) - t.Run("device above settings minimum", func(t *testing.T) { - ds.GetMDMAppleOSUpdatesSettingsByHostSerialFunc = func(ctx context.Context, serial string) (*fleet.AppleOSUpdateSettings, error) { - return &fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("14.1"), - }, nil - } - sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, tt.machineInfo) - if tt.err != "" { - require.Error(t, err) - require.Contains(t, err.Error(), tt.err) - } else { - require.NoError(t, err) - } - - require.Nil(t, sur) - }) - - t.Run("minimum not set", func(t *testing.T) { - ds.GetMDMAppleOSUpdatesSettingsByHostSerialFunc = func(ctx context.Context, serial string) (*fleet.AppleOSUpdateSettings, error) { - return &fleet.AppleOSUpdateSettings{}, nil - } - sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, tt.machineInfo) - require.NoError(t, err) - require.Nil(t, sur) - - ds.GetMDMAppleOSUpdatesSettingsByHostSerialFunc = func(ctx context.Context, serial string) (*fleet.AppleOSUpdateSettings, error) { - return &fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString(""), - }, nil - } - sur, err = svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, tt.machineInfo) - require.NoError(t, err) - require.Nil(t, sur) - }) - - t.Run("minimum not found", func(t *testing.T) { - ds.GetMDMAppleOSUpdatesSettingsByHostSerialFunc = func(ctx context.Context, serial string) (*fleet.AppleOSUpdateSettings, error) { - return nil, ¬FoundError{} - } - sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, tt.machineInfo) - require.NoError(t, err) - require.Nil(t, sur) - }) }) } @@ -5183,8 +5276,8 @@ func TestCheckMDMAppleEnrollmentWithMinimumOSVersion(t *testing.T) { for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { - ds.GetMDMAppleOSUpdatesSettingsByHostSerialFunc = func(ctx context.Context, serial string) (*fleet.AppleOSUpdateSettings, error) { - return &fleet.AppleOSUpdateSettings{MinimumVersion: optjson.SetString(latestMacOSVersion)}, nil + ds.GetMDMAppleOSUpdatesSettingsByHostSerialFunc = func(ctx context.Context, serial string) (string, *fleet.AppleOSUpdateSettings, error) { + return "ios", &fleet.AppleOSUpdateSettings{MinimumVersion: optjson.SetString(latestMacOSVersion)}, nil } sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, tt.machineInfo) diff --git a/server/service/client.go b/server/service/client.go index 0663aa400e7..e373478cb5f 100644 --- a/server/service/client.go +++ b/server/service/client.go @@ -2158,6 +2158,13 @@ func (c *Client) DoGitOps( if deadline, ok := macOSUpdates["deadline"]; !ok || deadline == nil { macOSUpdates["deadline"] = "" } + + // To keep things backward compatible, if a minimum_version and deadline are both set, + // then we also set update_new_hosts + if macOSUpdates["minimum_version"] != "" && macOSUpdates["deadline"] != "" { + macOSUpdates["update_new_hosts"] = true + } + // Put in default values for ios_updates if incoming.Controls.IOSUpdates != nil { mdmAppConfig["ios_updates"] = incoming.Controls.IOSUpdates @@ -2171,6 +2178,9 @@ func (c *Client) DoGitOps( if deadline, ok := iOSUpdates["deadline"]; !ok || deadline == nil { iOSUpdates["deadline"] = "" } + // update_new_hosts is only used for macOS so ignore any values posted for iOS + iOSUpdates["update_new_hosts"] = nil + // Put in default values for ipados_updates if incoming.Controls.IPadOSUpdates != nil { mdmAppConfig["ipados_updates"] = incoming.Controls.IPadOSUpdates @@ -2184,6 +2194,9 @@ func (c *Client) DoGitOps( if deadline, ok := iPadOSUpdates["deadline"]; !ok || deadline == nil { iPadOSUpdates["deadline"] = "" } + // update_new_hosts is only used for macOS so ignore any values posted for iPadOS + iPadOSUpdates["update_new_hosts"] = nil + // Put in default values for macos_setup if incoming.Controls.MacOSSetup != nil { incoming.Controls.MacOSSetup.SetDefaultsIfNeeded() diff --git a/server/service/integration_enterprise_test.go b/server/service/integration_enterprise_test.go index 606bc136b18..4c2db175e8c 100644 --- a/server/service/integration_enterprise_test.go +++ b/server/service/integration_enterprise_test.go @@ -219,8 +219,9 @@ func (s *integrationEnterpriseTestSuite) TestTeamSpecs() { "features": &features, "mdm": map[string]any{ "macos_updates": map[string]any{ - "minimum_version": "10.15.0", - "deadline": "2021-01-01", + "minimum_version": "10.15.0", + "deadline": "2021-01-01", + "update_new_hosts": true, }, "ios_updates": map[string]any{ "minimum_version": "17.5.1", @@ -252,7 +253,7 @@ func (s *integrationEnterpriseTestSuite) TestTeamSpecs() { MacOSUpdates: fleet.AppleOSUpdateSettings{ MinimumVersion: optjson.SetString("10.15.0"), Deadline: optjson.SetString("2021-01-01"), - UpdateNewHosts: optjson.Bool{Set: true}, + UpdateNewHosts: optjson.SetBool(true), }, IOSUpdates: fleet.AppleOSUpdateSettings{ MinimumVersion: optjson.SetString("17.5.1"), @@ -367,7 +368,7 @@ func (s *integrationEnterpriseTestSuite) TestTeamSpecs() { MacOSUpdates: fleet.AppleOSUpdateSettings{ MinimumVersion: optjson.SetString("10.15.0"), Deadline: optjson.SetString("2021-01-01"), - UpdateNewHosts: optjson.Bool{Set: true}, + UpdateNewHosts: optjson.SetBool(true), }, IOSUpdates: fleet.AppleOSUpdateSettings{ MinimumVersion: optjson.SetString("17.5.1"), @@ -407,7 +408,7 @@ func (s *integrationEnterpriseTestSuite) TestTeamSpecs() { MacOSUpdates: fleet.AppleOSUpdateSettings{ MinimumVersion: optjson.SetString("10.15.0"), Deadline: optjson.SetString("2021-01-01"), - UpdateNewHosts: optjson.Bool{Set: true}, + UpdateNewHosts: optjson.SetBool(true), }, IOSUpdates: fleet.AppleOSUpdateSettings{ MinimumVersion: optjson.SetString("17.5.1"), @@ -449,7 +450,7 @@ func (s *integrationEnterpriseTestSuite) TestTeamSpecs() { MacOSUpdates: fleet.AppleOSUpdateSettings{ MinimumVersion: optjson.SetString("10.15.0"), Deadline: optjson.SetString("2021-01-01"), - UpdateNewHosts: optjson.Bool{Set: true}, + UpdateNewHosts: optjson.SetBool(true), }, IOSUpdates: fleet.AppleOSUpdateSettings{ MinimumVersion: optjson.SetString("17.5.1"), @@ -2684,7 +2685,7 @@ func (s *integrationEnterpriseTestSuite) TestWindowsUpdatesTeamConfig() { IPadOSUpdates: fleet.AppleOSUpdateSettings{ MinimumVersion: optjson.String{Set: true}, Deadline: optjson.String{Set: true}, - UpdateNewHosts: optjson.Bool{Set: true}, + UpdateNewHosts: optjson.Bool{Set: true, Valid: false, Value: false}, }, WindowsUpdates: fleet.WindowsUpdates{ DeadlineDays: optjson.SetInt(5), @@ -2920,6 +2921,7 @@ func (s *integrationEnterpriseTestSuite) TestAppleOSUpdatesTeamConfig() { macOSUpdates := &fleet.AppleOSUpdateSettings{ MinimumVersion: optjson.SetString("10.15.0"), Deadline: optjson.SetString("2021-01-01"), + UpdateNewHosts: optjson.SetBool(true), } s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{ "mdm": map[string]any{ @@ -2928,7 +2930,18 @@ func (s *integrationEnterpriseTestSuite) TestAppleOSUpdatesTeamConfig() { }, http.StatusOK, &tmResp) require.Equal(t, "10.15.0", tmResp.Team.Config.MDM.MacOSUpdates.MinimumVersion.Value) require.Equal(t, "2021-01-01", tmResp.Team.Config.MDM.MacOSUpdates.Deadline.Value) - s.lastActivityMatches(fleet.ActivityTypeEditedMacOSMinVersion{}.ActivityName(), fmt.Sprintf(`{"team_id": %d, "team_name": %q, "minimum_version": "10.15.0", "deadline": "2021-01-01"}`, team.ID, team.Name), 0) + require.Equal(t, true, tmResp.Team.Config.MDM.MacOSUpdates.UpdateNewHosts.Value) + + s.lastActivityOfTypeMatches( + fleet.ActivityTypeEditedMacOSMinVersion{}.ActivityName(), + fmt.Sprintf(`{"team_id": %d, "team_name": %q, "minimum_version": "10.15.0", "deadline": "2021-01-01"}`, team.ID, team.Name), 0) + + s.lastActivityOfTypeMatches( + fleet.ActivityTypeEnabledMacosUpdateNewHosts{}.ActivityName(), + fmt.Sprintf(`{"team_id": %d, "team_name": %q}`, + team.ID, + team.Name, + ), 0) s.assertAppleOSUpdatesDeclaration(&team.ID, mdm.FleetMacOSUpdatesProfileName, macOSUpdates) s.assertAppleOSUpdatesDeclaration(&team.ID, mdm.FleetIOSUpdatesProfileName, nil) @@ -2938,11 +2951,14 @@ func (s *integrationEnterpriseTestSuite) TestAppleOSUpdatesTeamConfig() { iOSUpdates := &fleet.AppleOSUpdateSettings{ MinimumVersion: optjson.SetString("11.11.11"), Deadline: optjson.SetString("2022-02-02"), + UpdateNewHosts: optjson.SetBool(true), } iPadOSUpdates := &fleet.AppleOSUpdateSettings{ MinimumVersion: optjson.SetString("12.12.12"), Deadline: optjson.SetString("2023-03-03"), + UpdateNewHosts: optjson.SetBool(true), } + fmt.Printf("%+v\n", iOSUpdates) s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{ "mdm": map[string]any{ "ios_updates": iOSUpdates, @@ -2951,10 +2967,18 @@ func (s *integrationEnterpriseTestSuite) TestAppleOSUpdatesTeamConfig() { }, http.StatusOK, &tmResp) require.Equal(t, "10.15.0", tmResp.Team.Config.MDM.MacOSUpdates.MinimumVersion.Value) require.Equal(t, "2021-01-01", tmResp.Team.Config.MDM.MacOSUpdates.Deadline.Value) + require.Equal(t, optjson.SetBool(true), tmResp.Team.Config.MDM.MacOSUpdates.UpdateNewHosts) + require.Equal(t, "11.11.11", tmResp.Team.Config.MDM.IOSUpdates.MinimumVersion.Value) require.Equal(t, "2022-02-02", tmResp.Team.Config.MDM.IOSUpdates.Deadline.Value) + // UpdateNewHosts values are ignored for iOS + require.Equal(t, optjson.Bool{Set: true}, tmResp.Team.Config.MDM.IOSUpdates.UpdateNewHosts) + require.Equal(t, "12.12.12", tmResp.Team.Config.MDM.IPadOSUpdates.MinimumVersion.Value) require.Equal(t, "2023-03-03", tmResp.Team.Config.MDM.IPadOSUpdates.Deadline.Value) + // UpdateNewHosts values are ignored for iPadOS + require.Equal(t, optjson.Bool{Set: true}, tmResp.Team.Config.MDM.IPadOSUpdates.UpdateNewHosts) + s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedMacOSMinVersion{}.ActivityName(), fmt.Sprintf(`{"team_id": %d, "team_name": %q, "minimum_version": "10.15.0", "deadline": "2021-01-01"}`, team.ID, team.Name), 0) s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIOSMinVersion{}.ActivityName(), fmt.Sprintf(`{"team_id": %d, "team_name": %q, "minimum_version": "11.11.11", "deadline": "2022-02-02"}`, team.ID, team.Name), 0) s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIPadOSMinVersion{}.ActivityName(), fmt.Sprintf(`{"team_id": %d, "team_name": %q, "minimum_version": "12.12.12", "deadline": "2023-03-03"}`, team.ID, team.Name), 0) @@ -2967,6 +2991,7 @@ func (s *integrationEnterpriseTestSuite) TestAppleOSUpdatesTeamConfig() { macOSUpdates = &fleet.AppleOSUpdateSettings{ MinimumVersion: optjson.SetString("10.15.0"), Deadline: optjson.SetString("2025-10-01"), + UpdateNewHosts: optjson.SetBool(true), } iOSUpdates = &fleet.AppleOSUpdateSettings{ MinimumVersion: optjson.SetString("11.11.11"), @@ -2985,10 +3010,16 @@ func (s *integrationEnterpriseTestSuite) TestAppleOSUpdatesTeamConfig() { }, http.StatusOK, &tmResp) require.Equal(t, "10.15.0", tmResp.Team.Config.MDM.MacOSUpdates.MinimumVersion.Value) require.Equal(t, "2025-10-01", tmResp.Team.Config.MDM.MacOSUpdates.Deadline.Value) + require.Equal(t, optjson.SetBool(true), tmResp.Team.Config.MDM.MacOSUpdates.UpdateNewHosts) + require.Equal(t, "11.11.11", tmResp.Team.Config.MDM.IOSUpdates.MinimumVersion.Value) require.Equal(t, "2024-02-02", tmResp.Team.Config.MDM.IOSUpdates.Deadline.Value) + require.Equal(t, optjson.Bool{Set: true}, tmResp.Team.Config.MDM.IOSUpdates.UpdateNewHosts) + require.Equal(t, "12.12.12", tmResp.Team.Config.MDM.IPadOSUpdates.MinimumVersion.Value) require.Equal(t, "2024-03-03", tmResp.Team.Config.MDM.IPadOSUpdates.Deadline.Value) + require.Equal(t, optjson.Bool{Set: true}, tmResp.Team.Config.MDM.IPadOSUpdates.UpdateNewHosts) + macOSLastActivity := s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedMacOSMinVersion{}.ActivityName(), fmt.Sprintf(`{"team_id": %d, "team_name": %q, "minimum_version": "10.15.0", "deadline": "2025-10-01"}`, team.ID, team.Name), 0) iOSLastActivity := s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIOSMinVersion{}.ActivityName(), fmt.Sprintf(`{"team_id": %d, "team_name": %q, "minimum_version": "11.11.11", "deadline": "2024-02-02"}`, team.ID, team.Name), 0) iPadOSLastActivity := s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIPadOSMinVersion{}.ActivityName(), fmt.Sprintf(`{"team_id": %d, "team_name": %q, "minimum_version": "12.12.12", "deadline": "2024-03-03"}`, team.ID, team.Name), 0) @@ -2997,6 +3028,35 @@ func (s *integrationEnterpriseTestSuite) TestAppleOSUpdatesTeamConfig() { s.assertAppleOSUpdatesDeclaration(&team.ID, mdm.FleetIOSUpdatesProfileName, iOSUpdates) s.assertAppleOSUpdatesDeclaration(&team.ID, mdm.FleetIPadOSUpdatesProfileName, iPadOSUpdates) + // Unchecking the UpdateNewHosts flag should register as an activity + macOSUpdates = &fleet.AppleOSUpdateSettings{ + MinimumVersion: optjson.SetString("10.15.0"), + Deadline: optjson.SetString("2025-10-01"), + UpdateNewHosts: optjson.SetBool(false), + } + iOSUpdates = &fleet.AppleOSUpdateSettings{ + MinimumVersion: optjson.SetString("11.11.11"), + Deadline: optjson.SetString("2024-02-02"), + } + iPadOSUpdates = &fleet.AppleOSUpdateSettings{ + MinimumVersion: optjson.SetString("12.12.12"), + Deadline: optjson.SetString("2024-03-03"), + } + s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{ + "mdm": map[string]any{ + "macos_updates": macOSUpdates, + "ios_updates": iOSUpdates, + "ipados_updates": iPadOSUpdates, + }, + }, http.StatusOK, &tmResp) + require.Equal(t, false, tmResp.Team.Config.MDM.MacOSUpdates.UpdateNewHosts.Value) + s.lastActivityOfTypeMatches( + fleet.ActivityTypeDisabledMacosUpdateNewHosts{}.ActivityName(), + fmt.Sprintf(`{"team_id": %d, "team_name": %q}`, + team.ID, + team.Name, + ), 0) + // setting the windows updates doesn't alter the apple updates tmResp = teamResponse{} s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{ @@ -3907,29 +3967,41 @@ func (s *integrationEnterpriseTestSuite) TestMDMAppleOSUpdates() { "mdm": { "macos_updates": { "minimum_version": "12.3.1", - "deadline": "2022-01-01" + "deadline": "2022-01-01", + "update_new_hosts": true }, "ios_updates": { "minimum_version": "13.13.13", - "deadline": "2023-03-03" + "deadline": "2023-03-03", + "update_new_hosts": true }, "ipados_updates": { "minimum_version": "14.14.14", - "deadline": "2024-04-04" + "deadline": "2024-04-04", + "update_new_hosts": true } } }`), http.StatusOK, &acResp) require.Equal(t, "12.3.1", acResp.MDM.MacOSUpdates.MinimumVersion.Value) require.Equal(t, "2022-01-01", acResp.MDM.MacOSUpdates.Deadline.Value) + require.Equal(t, optjson.SetBool(true), acResp.MDM.MacOSUpdates.UpdateNewHosts) + require.Equal(t, "13.13.13", acResp.MDM.IOSUpdates.MinimumVersion.Value) require.Equal(t, "2023-03-03", acResp.MDM.IOSUpdates.Deadline.Value) + require.Equal(t, optjson.Bool{Set: true}, acResp.MDM.IOSUpdates.UpdateNewHosts) // posted value is ignored for iOS + require.Equal(t, "14.14.14", acResp.MDM.IPadOSUpdates.MinimumVersion.Value) require.Equal(t, "2024-04-04", acResp.MDM.IPadOSUpdates.Deadline.Value) + require.Equal(t, optjson.Bool{Set: true}, acResp.MDM.IPadOSUpdates.UpdateNewHosts) // posted value is ignored for iOS // edited macos min version activity got created s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedMacOSMinVersion{}.ActivityName(), `{"deadline":"2022-01-01", "minimum_version":"12.3.1", "team_id": null, "team_name": null}`, 0) s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIOSMinVersion{}.ActivityName(), `{"deadline":"2023-03-03", "minimum_version":"13.13.13", "team_id": null, "team_name": null}`, 0) s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIPadOSMinVersion{}.ActivityName(), `{"deadline":"2024-04-04", "minimum_version":"14.14.14", "team_id": null, "team_name": null}`, 0) + + // Activity for 'Update New Hosts checked' got created + s.lastActivityOfTypeMatches(fleet.ActivityTypeEnabledMacosUpdateNewHosts{}.ActivityName(), "", 0) + s.assertAppleOSUpdatesDeclaration(nil, mdm.FleetMacOSUpdatesProfileName, &fleet.AppleOSUpdateSettings{ MinimumVersion: optjson.SetString("12.3.1"), Deadline: optjson.SetString("2022-01-01"), }) @@ -3945,18 +4017,24 @@ func (s *integrationEnterpriseTestSuite) TestMDMAppleOSUpdates() { s.DoJSON("GET", "/api/latest/fleet/config", nil, http.StatusOK, &acResp) require.Equal(t, "12.3.1", acResp.MDM.MacOSUpdates.MinimumVersion.Value) require.Equal(t, "2022-01-01", acResp.MDM.MacOSUpdates.Deadline.Value) + require.Equal(t, optjson.SetBool(true), acResp.MDM.MacOSUpdates.UpdateNewHosts) + require.Equal(t, "13.13.13", acResp.MDM.IOSUpdates.MinimumVersion.Value) require.Equal(t, "2023-03-03", acResp.MDM.IOSUpdates.Deadline.Value) + require.Equal(t, optjson.Bool{Set: true}, acResp.MDM.IOSUpdates.UpdateNewHosts) + require.Equal(t, "14.14.14", acResp.MDM.IPadOSUpdates.MinimumVersion.Value) require.Equal(t, "2024-04-04", acResp.MDM.IPadOSUpdates.Deadline.Value) + require.Equal(t, optjson.Bool{Set: true}, acResp.MDM.IOSUpdates.UpdateNewHosts) - // update the deadline + // update the deadline and the update_new_hosts flag acResp = appConfigResponse{} s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{ "mdm": { "macos_updates": { "minimum_version": "12.3.1", - "deadline": "2024-01-01" + "deadline": "2024-01-01", + "update_new_hosts": false }, "ios_updates": { "minimum_version": "13.13.13", @@ -3970,12 +4048,18 @@ func (s *integrationEnterpriseTestSuite) TestMDMAppleOSUpdates() { }`), http.StatusOK, &acResp) require.Equal(t, "12.3.1", acResp.MDM.MacOSUpdates.MinimumVersion.Value) require.Equal(t, "2024-01-01", acResp.MDM.MacOSUpdates.Deadline.Value) + require.Equal(t, optjson.SetBool(false), acResp.MDM.MacOSUpdates.UpdateNewHosts) + require.Equal(t, "13.13.13", acResp.MDM.IOSUpdates.MinimumVersion.Value) require.Equal(t, "2025-05-05", acResp.MDM.IOSUpdates.Deadline.Value) + require.Equal(t, optjson.Bool{Set: true}, acResp.MDM.IOSUpdates.UpdateNewHosts) + require.Equal(t, "14.14.14", acResp.MDM.IPadOSUpdates.MinimumVersion.Value) require.Equal(t, "2026-06-06", acResp.MDM.IPadOSUpdates.Deadline.Value) + require.Equal(t, optjson.Bool{Set: true}, acResp.MDM.IPadOSUpdates.UpdateNewHosts) // another edited macos min version activity got created + s.lastActivityOfTypeMatches(fleet.ActivityTypeDisabledMacosUpdateNewHosts{}.ActivityName(), "", 0) s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedMacOSMinVersion{}.ActivityName(), `{"deadline":"2024-01-01", "minimum_version":"12.3.1", "team_id": null, "team_name": null}`, 0) s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIOSMinVersion{}.ActivityName(), `{"deadline":"2025-05-05", "minimum_version":"13.13.13", "team_id": null, "team_name": null}`, 0) lastActivity = s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIPadOSMinVersion{}.ActivityName(), `{"deadline":"2026-06-06", "minimum_version":"14.14.14", "team_id": null, "team_name": null}`, 0) diff --git a/server/service/integration_mdm_dep_test.go b/server/service/integration_mdm_dep_test.go index 6d7615abb8c..f26fbccc215 100644 --- a/server/service/integration_mdm_dep_test.go +++ b/server/service/integration_mdm_dep_test.go @@ -2458,7 +2458,7 @@ func (s *integrationMDMTestSuite) TestEnforceMiniumOSVersion() { // this helper function sets the minimum OS version for the team or no team setMinOSVersion := func(minVersion string, deadline string, teamID *uint) { - raw := json.RawMessage(fmt.Sprintf(`{ "mdm": { "macos_updates": { "minimum_version": "%s", "deadline": "%s" } } }`, minVersion, deadline)) + raw := json.RawMessage(fmt.Sprintf(`{ "mdm": { "macos_updates": { "minimum_version": "%s", "deadline": "%s", "update_new_hosts": true } } }`, minVersion, deadline)) if teamID == nil { acResp := appConfigResponse{} s.DoJSON("PATCH", "/api/latest/fleet/config", raw, http.StatusOK, &acResp) diff --git a/server/service/integration_mdm_profiles_test.go b/server/service/integration_mdm_profiles_test.go index 7d85a57e4b3..4a5d54bfd3f 100644 --- a/server/service/integration_mdm_profiles_test.go +++ b/server/service/integration_mdm_profiles_test.go @@ -5343,6 +5343,7 @@ func (s *integrationMDMTestSuite) TestMDMBatchSetProfilesKeepsReservedNames() { MacOSUpdates: &fleet.AppleOSUpdateSettings{ Deadline: optjson.SetString("2023-12-31"), MinimumVersion: optjson.SetString("13.3.8"), + UpdateNewHosts: optjson.SetBool(true), }, }, }, @@ -5354,6 +5355,7 @@ func (s *integrationMDMTestSuite) TestMDMBatchSetProfilesKeepsReservedNames() { require.Equal(t, 1, tmResp.Team.Config.MDM.WindowsUpdates.GracePeriodDays.Value) require.Equal(t, "2023-12-31", tmResp.Team.Config.MDM.MacOSUpdates.Deadline.Value) require.Equal(t, "13.3.8", tmResp.Team.Config.MDM.MacOSUpdates.MinimumVersion.Value) + require.Equal(t, true, tmResp.Team.Config.MDM.MacOSUpdates.UpdateNewHosts.Value) require.NoError(t, ReconcileAppleProfiles(ctx, s.ds, s.mdmCommander, s.logger)) diff --git a/server/service/teams.go b/server/service/teams.go index c5270e4c6ee..51567919974 100644 --- a/server/service/teams.go +++ b/server/service/teams.go @@ -9,6 +9,7 @@ import ( "net/http" "net/url" + "github.com/fleetdm/fleet/v4/pkg/optjson" "golang.org/x/text/unicode/norm" "github.com/fleetdm/fleet/v4/server/contexts/ctxerr" @@ -155,6 +156,17 @@ type modifyTeamRequest struct { func modifyTeamEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (fleet.Errorer, error) { req := request.(*modifyTeamRequest) + + // AppleOSUpdateSettings.UpdateNewHosts is only used in macOS ... so ignore any values sent for iOS/iPadOS + if req.TeamPayload.MDM != nil { + if req.TeamPayload.MDM.IOSUpdates != nil { + req.TeamPayload.MDM.IOSUpdates.UpdateNewHosts = optjson.Bool{} + } + if req.TeamPayload.MDM.IPadOSUpdates != nil { + req.TeamPayload.MDM.IPadOSUpdates.UpdateNewHosts = optjson.Bool{} + } + } + team, err := svc.ModifyTeam(ctx, req.ID, req.TeamPayload) if err != nil { return teamResponse{Err: err}, nil