Update backend and GitOps to handle AppleOSUpdateSettings.UpdateNewHosts (#37295)

**Related issue:** Resolves #36188 

Update backend and GitOps to handle AppleOSUpdateSettings.UpdateNewHosts
This commit is contained in:
Juan Fernandez
2025-12-16 14:38:28 -04:00
committed by GitHub
parent 62ee934ebe
commit 43181eb49c
22 changed files with 467 additions and 153 deletions
@@ -0,0 +1 @@
* Updated backend and GitOps to handle AppleOSUpdateSettings.UpdateNewHosts
+4
View File
@@ -248,6 +248,7 @@ spec:
macos_updates:
minimum_version: 12.3.1
deadline: 2011-03-01
update_new_hosts: true
`)
newAgentOpts := json.RawMessage(`{"config":{"views":{"foo":"bar"}}}`)
@@ -255,6 +256,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),
@@ -291,6 +293,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),
@@ -319,6 +322,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),
+24
View File
@@ -411,12 +411,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:
@@ -487,6 +492,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)
@@ -751,6 +766,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"
@@ -807,6 +825,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)
@@ -1489,6 +1512,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)
@@ -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",
@@ -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"
@@ -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",
@@ -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
@@ -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
+26 -1
View File
@@ -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 {
+7 -7
View File
@@ -6711,7 +6711,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
@@ -6728,7 +6728,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
@@ -6736,7 +6736,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":
@@ -6746,13 +6746,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":
@@ -6762,11 +6762,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
+8 -3
View File
@@ -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)
}
+2 -2
View File
@@ -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.
+2 -2
View File
@@ -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
@@ -7789,7 +7789,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()
+15
View File
@@ -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
}
+14 -14
View File
@@ -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{
+43 -9
View File
@@ -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
}
// 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
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
}
}
needsUpdate, err := apple_mdm.IsLessThanVersion(m.OSVersion, settings.MinimumVersion.Value)
// TODO: confirm what this check should do
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, 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
}
+189 -96
View File
@@ -5061,10 +5061,193 @@ 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, &notFoundError{}
}
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)
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,
Details: *tt.updateRequired,
}, sur)
} else {
require.Nil(t, sur)
}
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)
require.NoError(t, err)
// Ditto previous comment
if tt.updateRequired != nil {
require.Equal(t, &fleet.MDMAppleSoftwareUpdateRequired{
Code: fleet.MDMAppleSoftwareUpdateRequiredCode,
Details: *tt.updateRequired,
}, sur)
} else {
require.Nil(t, sur)
}
})
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, &notFoundError{}
}
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
}
@@ -5085,96 +5268,6 @@ func TestCheckMDMAppleEnrollmentWithMinimumOSVersion(t *testing.T) {
}
})
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"),
}, 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) (*fleet.AppleOSUpdateSettings, error) {
return &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) (*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, &notFoundError{}
}
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)
+13
View File
@@ -2191,6 +2191,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
@@ -2204,6 +2211,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
@@ -2217,6 +2227,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()
+97 -13
View File
@@ -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)
+1 -1
View File
@@ -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)
@@ -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))
+12
View File
@@ -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