diff --git a/changes/39713-min-os-dep b/changes/39713-min-os-dep new file mode 100644 index 0000000000..cbaf8d0983 --- /dev/null +++ b/changes/39713-min-os-dep @@ -0,0 +1 @@ +- Updated DEP enrollment flow to apply minimum macOS version check when specified. \ No newline at end of file diff --git a/cmd/fleetctl/fleetctl/apply_test.go b/cmd/fleetctl/fleetctl/apply_test.go index c8516e9942..21e8f63f82 100644 --- a/cmd/fleetctl/fleetctl/apply_test.go +++ b/cmd/fleetctl/fleetctl/apply_test.go @@ -27,6 +27,7 @@ import ( apple_mdm "github.com/fleetdm/fleet/v4/server/mdm/apple" nanodep_client "github.com/fleetdm/fleet/v4/server/mdm/nanodep/client" "github.com/fleetdm/fleet/v4/server/mdm/nanodep/tokenpki" + mdmtest "github.com/fleetdm/fleet/v4/server/mdm/testing_utils" "github.com/fleetdm/fleet/v4/server/mock" mdmmock "github.com/fleetdm/fleet/v4/server/mock/mdm" nanodep_mock "github.com/fleetdm/fleet/v4/server/mock/nanodep" @@ -206,6 +207,9 @@ func TestApplyTeamSpecs(t *testing.T) { license := &fleet.LicenseInfo{Tier: fleet.TierPremium, Expiration: time.Now().Add(24 * time.Hour)} _, ds := testing_utils.RunServerWithMockedDS(t, &service.TestServerOpts{License: license}) + // Mock Apple GDMF API (required for validating OS update minimum version settings) + mdmtest.StartNewAppleGDMFTestServer(t) + teamsByName := map[string]*fleet.Team{ "team1": { ID: 42, @@ -321,7 +325,7 @@ spec: - secret: AAA mdm: macos_updates: - minimum_version: 12.3.1 + minimum_version: 14.6.1 deadline: 2011-03-01 update_new_hosts: true `) @@ -329,7 +333,7 @@ spec: newAgentOpts := json.RawMessage(`{"config":{"views":{"foo":"bar"}}}`) newMDMSettings := fleet.TeamMDM{ MacOSUpdates: fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("12.3.1"), + MinimumVersion: optjson.SetString("14.6.1"), Deadline: optjson.SetString("2011-03-01"), UpdateNewHosts: optjson.SetBool(true), }, @@ -368,7 +372,7 @@ spec: require.Equal(t, "[+] applied 1 fleet\n", RunAppForTest(t, []string{"apply", "-f", filename})) newMDMSettings = fleet.TeamMDM{ MacOSUpdates: fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("12.3.1"), + MinimumVersion: optjson.SetString("14.6.1"), Deadline: optjson.SetString("2011-03-01"), UpdateNewHosts: optjson.SetBool(true), }, @@ -398,7 +402,7 @@ spec: newMDMSettings = fleet.TeamMDM{ MacOSUpdates: fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("12.3.1"), + MinimumVersion: optjson.SetString("14.6.1"), Deadline: optjson.SetString("2011-03-01"), UpdateNewHosts: optjson.SetBool(true), }, @@ -436,13 +440,13 @@ spec: name: team1 mdm: macos_updates: - minimum_version: 10.10.10 + minimum_version: 14.6.1 deadline: 1992-03-01 ios_updates: - minimum_version: 11.11.11 + minimum_version: 17.6.1 deadline: 1993-04-02 ipados_updates: - minimum_version: 12.12.12 + minimum_version: 17.6.1 deadline: 1994-05-03 secrets: - secret: BBB @@ -450,15 +454,15 @@ spec: newMDMSettings = fleet.TeamMDM{ MacOSUpdates: fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("10.10.10"), + MinimumVersion: optjson.SetString("14.6.1"), Deadline: optjson.SetString("1992-03-01"), }, IOSUpdates: fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("11.11.11"), + MinimumVersion: optjson.SetString("17.6.1"), Deadline: optjson.SetString("1993-04-02"), }, IPadOSUpdates: fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("12.12.12"), + MinimumVersion: optjson.SetString("17.6.1"), Deadline: optjson.SetString("1994-05-03"), }, WindowsUpdates: fleet.WindowsUpdates{ @@ -690,6 +694,9 @@ func TestApplyAppConfig(t *testing.T) { license := &fleet.LicenseInfo{Tier: fleet.TierPremium, Expiration: time.Now().Add(24 * time.Hour)} _, ds := testing_utils.RunServerWithMockedDS(t, &service.TestServerOpts{License: license}) + // Mock Apple GDMF API (required for validating OS update minimum version settings) + mdmtest.StartNewAppleGDMFTestServer(t) + ds.ListUsersFunc = func(ctx context.Context, opt fleet.UserListOptions) ([]*fleet.User, error) { return userRoleSpecList, nil } @@ -776,7 +783,7 @@ spec: mdm: apple_bm_default_team: "team1" macos_updates: - minimum_version: 12.1.1 + minimum_version: 14.6.1 deadline: 2011-02-01 windows_updates: deadline_days: 5 @@ -787,7 +794,7 @@ spec: DeprecatedAppleBMDefaultTeam: "team1", AppleBMTermsExpired: false, MacOSUpdates: fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("12.1.1"), + MinimumVersion: optjson.SetString("14.6.1"), Deadline: optjson.SetString("2011-02-01"), }, MacOSSetup: fleet.MacOSSetup{ @@ -870,7 +877,7 @@ spec: DeprecatedAppleBMDefaultTeam: "team1", AppleBMTermsExpired: false, MacOSUpdates: fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("12.1.1"), + MinimumVersion: optjson.SetString("14.6.1"), Deadline: optjson.SetString("2011-02-01"), }, MacOSSetup: fleet.MacOSSetup{ @@ -1343,6 +1350,9 @@ func TestApplyAsGitOps(t *testing.T) { // Mock Apple DEP API depStorage := SetupMockDEPStorageAndMockDEPServer(t) + // Mock Apple GDMF API (required for validating OS update minimum version settings) + mdmtest.StartNewAppleGDMFTestServer(t) + config.SetTestMDMConfig(t, &fleetCfg, testCertPEM, testKeyPEM, "../../../server/service/testdata") _, ds := testing_utils.RunServerWithMockedDS(t, &service.TestServerOpts{ @@ -1541,7 +1551,7 @@ kind: config spec: mdm: macos_updates: - minimum_version: 10.10.10 + minimum_version: 14.6.1 deadline: 2020-02-02 windows_updates: deadline_days: 1 @@ -1570,7 +1580,7 @@ spec: LockEndUserInfo: optjson.SetBool(false), }, MacOSUpdates: fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("10.10.10"), + MinimumVersion: optjson.SetString("14.6.1"), Deadline: optjson.SetString("2020-02-02"), }, WindowsUpdates: fleet.WindowsUpdates{ @@ -1614,7 +1624,7 @@ spec: LockEndUserInfo: optjson.SetBool(false), }, MacOSUpdates: fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("10.10.10"), + MinimumVersion: optjson.SetString("14.6.1"), Deadline: optjson.SetString("2020-02-02"), }, WindowsUpdates: fleet.WindowsUpdates{ @@ -1641,7 +1651,7 @@ spec: mdm: enable_disk_encryption: false macos_updates: - minimum_version: 10.10.10 + minimum_version: 14.6.1 deadline: 1992-03-01 windows_updates: deadline_days: 0 @@ -1665,7 +1675,7 @@ spec: CustomSettings: []fleet.MDMProfileSpec{{Path: mobileConfigPath}}, }, MacOSUpdates: fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("10.10.10"), + MinimumVersion: optjson.SetString("14.6.1"), Deadline: optjson.SetString("1992-03-01"), }, MacOSSetup: fleet.MacOSSetup{ @@ -1708,7 +1718,7 @@ spec: CustomSettings: []fleet.MDMProfileSpec{{Path: mobileConfigPath}}, }, MacOSUpdates: fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("10.10.10"), + MinimumVersion: optjson.SetString("14.6.1"), Deadline: optjson.SetString("1992-03-01"), }, WindowsUpdates: fleet.WindowsUpdates{ @@ -1746,7 +1756,7 @@ spec: CustomSettings: []fleet.MDMProfileSpec{{Path: mobileConfigPath}}, }, MacOSUpdates: fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("10.10.10"), + MinimumVersion: optjson.SetString("14.6.1"), Deadline: optjson.SetString("1992-03-01"), }, WindowsUpdates: fleet.WindowsUpdates{ @@ -3457,7 +3467,7 @@ spec: minimum_version: "12.2" deadline: "1892-01-01T00:00:00Z" `, - wantErr: `422 Validation Failed: deadline accepts YYYY-MM-DD format only (E.g., "2023-06-01.")`, + wantErr: fmt.Sprintf(`422 Validation Failed: %s`, fleet.AppleOSVersionDeadlineInvalidMessage), }, { desc: "macos_updates.deadline with invalid date", @@ -3472,7 +3482,7 @@ spec: minimum_version: "12.2" deadline: "18-01-01" `, - wantErr: `422 Validation Failed: deadline accepts YYYY-MM-DD format only (E.g., "2023-06-01.")`, + wantErr: fmt.Sprintf(`422 Validation Failed: %s`, fleet.AppleOSVersionDeadlineInvalidMessage), }, { desc: "macos_updates.deadline with incomplete date", @@ -3487,7 +3497,7 @@ spec: minimum_version: "12.2" deadline: "2022-01" `, - wantErr: `422 Validation Failed: deadline accepts YYYY-MM-DD format only (E.g., "2023-06-01.")`, + wantErr: fmt.Sprintf(`422 Validation Failed: %s`, fleet.AppleOSVersionDeadlineInvalidMessage), }, { desc: "windows_updates.deadline_days but grace period empty", @@ -3782,7 +3792,7 @@ spec: minimum_version: "12.2" deadline: "1892-01-01T00:00:00Z" `, - wantErr: `422 Validation Failed: deadline accepts YYYY-MM-DD format only (E.g., "2023-06-01.")`, + wantErr: fmt.Sprintf(`422 Validation Failed: %s`, fleet.AppleOSVersionDeadlineInvalidMessage), }, { desc: "app config macos_updates.deadline with invalid date", @@ -3795,7 +3805,7 @@ spec: minimum_version: "12.2" deadline: "18-01-01" `, - wantErr: `422 Validation Failed: deadline accepts YYYY-MM-DD format only (E.g., "2023-06-01.")`, + wantErr: fmt.Sprintf(`422 Validation Failed: %s`, fleet.AppleOSVersionDeadlineInvalidMessage), }, { desc: "app config macos_updates.deadline with incomplete date", @@ -3808,7 +3818,7 @@ spec: minimum_version: "12.2" deadline: "2022-01" `, - wantErr: `422 Validation Failed: deadline accepts YYYY-MM-DD format only (E.g., "2023-06-01.")`, + wantErr: fmt.Sprintf(`422 Validation Failed: %s`, fleet.AppleOSVersionDeadlineInvalidMessage), }, { desc: "app config windows_updates.deadline_days but grace period empty", diff --git a/cmd/fleetctl/fleetctl/gitops_test.go b/cmd/fleetctl/fleetctl/gitops_test.go index 014f218be9..01c577324a 100644 --- a/cmd/fleetctl/fleetctl/gitops_test.go +++ b/cmd/fleetctl/fleetctl/gitops_test.go @@ -23,6 +23,7 @@ import ( "github.com/fleetdm/fleet/v4/server/mdm" apple_mdm "github.com/fleetdm/fleet/v4/server/mdm/apple" "github.com/fleetdm/fleet/v4/server/mdm/nanodep/tokenpki" + mdmtest "github.com/fleetdm/fleet/v4/server/mdm/testing_utils" mdmtesting "github.com/fleetdm/fleet/v4/server/mdm/testing_utils" "github.com/fleetdm/fleet/v4/server/mock" digicert_mock "github.com/fleetdm/fleet/v4/server/mock/digicert" @@ -335,6 +336,9 @@ func TestGitOpsBasicGlobalPremium(t *testing.T) { }, ) + // Mock Apple GDMF API (required for validating OS update minimum version settings) + mdmtest.StartNewAppleGDMFTestServer(t) + ds.BatchSetMDMProfilesFunc = func( ctx context.Context, tmID *uint, macProfiles []*fleet.MDMAppleConfigProfile, winProfiles []*fleet.MDMWindowsConfigProfile, macDecls []*fleet.MDMAppleDeclaration, androidProfiles []*fleet.MDMAndroidConfigProfile, vars []fleet.MDMProfileIdentifierFleetVariables, @@ -486,14 +490,14 @@ func TestGitOpsBasicGlobalPremium(t *testing.T) { controls: macos_updates: deadline: "2024-03-03" - minimum_version: "18.0" + minimum_version: "14.6.1" ios_updates: deadline: "2022-02-02" minimum_version: "17.6" update_new_hosts: true ipados_updates: deadline: "2023-03-03" - minimum_version: "18.0" + minimum_version: "17.6" update_new_hosts: false enable_disk_encryption: true windows_require_bitlocker_pin: true @@ -565,7 +569,7 @@ 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, "14.6.1", 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) @@ -573,7 +577,7 @@ software: 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, "17.6", savedAppConfig.MDM.IPadOSUpdates.MinimumVersion.Value) require.Equal(t, optjson.Bool{}, savedAppConfig.MDM.IPadOSUpdates.UpdateNewHosts) // Check certificate authorities @@ -673,6 +677,9 @@ func TestGitOpsBasicTeam(t *testing.T) { }, ) + // Mock Apple GDMF API (required for validating OS update minimum version settings) + mdmtest.StartNewAppleGDMFTestServer(t) + const secret = "TestSecret" ds.SetTeamVPPAppsFunc = func(ctx context.Context, teamID *uint, adamIDs []fleet.VPPAppTeam, _ map[string]uint) (bool, error) { @@ -842,11 +849,11 @@ func TestGitOpsBasicTeam(t *testing.T) { controls: macos_updates: deadline: "2025-10-10" - minimum_version: "18.0" + minimum_version: "14.6.1" update_new_hosts: false ios_updates: deadline: "2024-10-10" - minimum_version: "18.0" + minimum_version: "17.6" ipados_updates: deadline: "2025-11-11" minimum_version: "17.6" @@ -911,7 +918,7 @@ software: 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) + assert.Equal(t, "14.6.1", savedTeam.Config.MDM.MacOSUpdates.MinimumVersion.Value) // Ensure the default value (if deadline and minimum_version are set) can be overriden with explicit setting assert.Equal(t, optjson.SetBool(false), savedTeam.Config.MDM.MacOSUpdates.UpdateNewHosts) @@ -2093,6 +2100,10 @@ func TestGitOpsBasicGlobalAndNoTeam(t *testing.T) { KeyValueStore: testing_utils.NewMemKeyValueStore(), }, ) + + // Mock Apple GDMF API (required for validating OS update minimum version settings) + mdmtest.StartNewAppleGDMFTestServer(t) + // Mock appConfig savedAppConfig := &fleet.AppConfig{} ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) { @@ -2347,7 +2358,7 @@ software: controls: ipados_updates: deadline: "2023-03-03" - minimum_version: "18.0" + minimum_version: "17.6.1" policies: name: No team software: @@ -5707,6 +5718,9 @@ func TestGitOpsAppleOSUpdates(t *testing.T) { }, ) + // Mock Apple GDMF API (required for validating OS update minimum version settings) + mdmtest.StartNewAppleGDMFTestServer(t) + const localTeamName = "Team1" var savedTeam *fleet.Team baseTeam := &fleet.Team{ @@ -5924,11 +5938,11 @@ software: t.Run("macos_updates", func(t *testing.T) { t.Run("same values do not trigger BulkSetPendingMDMHostProfiles", func(t *testing.T) { bulkSetPendingCalls = 0 - savedTeam = existingTeamWithMacOSUpdates("2024-03-03", "14.0") + savedTeam = existingTeamWithMacOSUpdates("2024-03-03", "14.6.1") teamFile, err := os.CreateTemp(t.TempDir(), "*.yml") require.NoError(t, err) - _, err = teamFile.WriteString(macOSYAML("2024-03-03", "14.0")) + _, err = teamFile.WriteString(macOSYAML("2024-03-03", "14.6.1")) require.NoError(t, err) _ = RunAppForTest(t, []string{"gitops", "-f", teamFile.Name()}) @@ -5938,11 +5952,11 @@ software: t.Run("changed deadline triggers BulkSetPendingMDMHostProfiles", func(t *testing.T) { bulkSetPendingCalls = 0 - savedTeam = existingTeamWithMacOSUpdates("2024-03-03", "14.0") + savedTeam = existingTeamWithMacOSUpdates("2024-03-03", "14.6.1") teamFile, err := os.CreateTemp(t.TempDir(), "*.yml") require.NoError(t, err) - _, err = teamFile.WriteString(macOSYAML("2024-04-04", "14.0")) + _, err = teamFile.WriteString(macOSYAML("2024-04-04", "14.6.1")) require.NoError(t, err) _ = RunAppForTest(t, []string{"gitops", "-f", teamFile.Name()}) @@ -5952,11 +5966,11 @@ software: t.Run("changed minimum_version triggers BulkSetPendingMDMHostProfiles", func(t *testing.T) { bulkSetPendingCalls = 0 - savedTeam = existingTeamWithMacOSUpdates("2024-03-03", "14.0") + savedTeam = existingTeamWithMacOSUpdates("2024-03-03", "14.6.1") teamFile, err := os.CreateTemp(t.TempDir(), "*.yml") require.NoError(t, err) - _, err = teamFile.WriteString(macOSYAML("2024-03-03", "15.0")) + _, err = teamFile.WriteString(macOSYAML("2024-03-03", "13.6.9")) require.NoError(t, err) _ = RunAppForTest(t, []string{"gitops", "-f", teamFile.Name()}) @@ -5968,11 +5982,11 @@ software: t.Run("ios_updates", func(t *testing.T) { t.Run("same values do not trigger BulkSetPendingMDMHostProfiles", func(t *testing.T) { bulkSetPendingCalls = 0 - savedTeam = existingTeamWithIOSUpdates("2024-03-03", "17.0") + savedTeam = existingTeamWithIOSUpdates("2024-03-03", "17.6.1") teamFile, err := os.CreateTemp(t.TempDir(), "*.yml") require.NoError(t, err) - _, err = teamFile.WriteString(iOSYAML("2024-03-03", "17.0")) + _, err = teamFile.WriteString(iOSYAML("2024-03-03", "17.6.1")) require.NoError(t, err) _ = RunAppForTest(t, []string{"gitops", "-f", teamFile.Name()}) @@ -5982,11 +5996,11 @@ software: t.Run("changed deadline triggers BulkSetPendingMDMHostProfiles", func(t *testing.T) { bulkSetPendingCalls = 0 - savedTeam = existingTeamWithIOSUpdates("2024-03-03", "17.0") + savedTeam = existingTeamWithIOSUpdates("2024-03-03", "17.6.1") teamFile, err := os.CreateTemp(t.TempDir(), "*.yml") require.NoError(t, err) - _, err = teamFile.WriteString(iOSYAML("2024-04-04", "17.0")) + _, err = teamFile.WriteString(iOSYAML("2024-04-04", "17.6.1")) require.NoError(t, err) _ = RunAppForTest(t, []string{"gitops", "-f", teamFile.Name()}) @@ -5996,11 +6010,11 @@ software: t.Run("changed minimum_version triggers BulkSetPendingMDMHostProfiles", func(t *testing.T) { bulkSetPendingCalls = 0 - savedTeam = existingTeamWithIOSUpdates("2024-03-03", "17.0") + savedTeam = existingTeamWithIOSUpdates("2024-03-03", "17.6.1") teamFile, err := os.CreateTemp(t.TempDir(), "*.yml") require.NoError(t, err) - _, err = teamFile.WriteString(iOSYAML("2024-03-03", "18.0")) + _, err = teamFile.WriteString(iOSYAML("2024-03-03", "17.6")) require.NoError(t, err) _ = RunAppForTest(t, []string{"gitops", "-f", teamFile.Name()}) @@ -6012,11 +6026,11 @@ software: t.Run("ipados_updates", func(t *testing.T) { t.Run("same values do not trigger BulkSetPendingMDMHostProfiles", func(t *testing.T) { bulkSetPendingCalls = 0 - savedTeam = existingTeamWithIPadOSUpdates("2024-03-03", "17.0") + savedTeam = existingTeamWithIPadOSUpdates("2024-03-03", "17.6.1") teamFile, err := os.CreateTemp(t.TempDir(), "*.yml") require.NoError(t, err) - _, err = teamFile.WriteString(iPadOSYAML("2024-03-03", "17.0")) + _, err = teamFile.WriteString(iPadOSYAML("2024-03-03", "17.6.1")) require.NoError(t, err) _ = RunAppForTest(t, []string{"gitops", "-f", teamFile.Name()}) @@ -6026,11 +6040,11 @@ software: t.Run("changed deadline triggers BulkSetPendingMDMHostProfiles", func(t *testing.T) { bulkSetPendingCalls = 0 - savedTeam = existingTeamWithIPadOSUpdates("2024-03-03", "17.0") + savedTeam = existingTeamWithIPadOSUpdates("2024-03-03", "17.6.1") teamFile, err := os.CreateTemp(t.TempDir(), "*.yml") require.NoError(t, err) - _, err = teamFile.WriteString(iPadOSYAML("2024-04-04", "17.0")) + _, err = teamFile.WriteString(iPadOSYAML("2024-04-04", "17.6.1")) require.NoError(t, err) _ = RunAppForTest(t, []string{"gitops", "-f", teamFile.Name()}) @@ -6040,11 +6054,11 @@ software: t.Run("changed minimum_version triggers BulkSetPendingMDMHostProfiles", func(t *testing.T) { bulkSetPendingCalls = 0 - savedTeam = existingTeamWithIPadOSUpdates("2024-03-03", "17.0") + savedTeam = existingTeamWithIPadOSUpdates("2024-03-03", "17.6.1") teamFile, err := os.CreateTemp(t.TempDir(), "*.yml") require.NoError(t, err) - _, err = teamFile.WriteString(iPadOSYAML("2024-03-03", "18.0")) + _, err = teamFile.WriteString(iPadOSYAML("2024-03-03", "17.6")) require.NoError(t, err) _ = RunAppForTest(t, []string{"gitops", "-f", teamFile.Name()}) diff --git a/ee/server/service/teams.go b/ee/server/service/teams.go index 75fd845b19..8ceb1da20f 100644 --- a/ee/server/service/teams.go +++ b/ee/server/service/teams.go @@ -19,6 +19,7 @@ import ( "github.com/fleetdm/fleet/v4/server/contexts/logging" "github.com/fleetdm/fleet/v4/server/contexts/viewer" "github.com/fleetdm/fleet/v4/server/fleet" + apple_mdm "github.com/fleetdm/fleet/v4/server/mdm/apple" "github.com/fleetdm/fleet/v4/server/ptr" "github.com/fleetdm/fleet/v4/server/worker" ) @@ -212,6 +213,19 @@ func (svc *Service) ModifyTeam(ctx context.Context, teamID uint, payload fleet.T } } + // Always check whether specified versions are supported by Apple (even if they weren't updated) + // Note that we're validating against the full, non-public asset set of OS versions here because + // in our DEP flow the minimum version just acts as the threshold for whether or not to update + // the host to the latest, public version. We don't need to install the specified version on the + // host during DEP so it doesn't need to be in the public asset set. + if errs := apple_mdm.ValidateMDMSettingsAppleSupportedOSVersion(team.Config.MDM, false); len(errs) > 0 { + invalid := &fleet.InvalidArgumentError{} + for k, v := range errs { + invalid.Append(k, v.Error()) + } + return nil, invalid + } + if payload.MDM.WindowsUpdates != nil { if err := payload.MDM.WindowsUpdates.Validate(); err != nil { return nil, fleet.NewInvalidArgumentError("windows_updates", err.Error()) @@ -1131,6 +1145,8 @@ func (svc *Service) ApplyTeamSpecs(ctx context.Context, specs []*fleet.TeamSpec, if len(secrets) > fleet.MaxEnrollSecretsCount { return nil, ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("secrets", "too many secrets"), "validate secrets") } + // TODO: should we be we validating the other Apple platforms? if so, we should also include + // ValidateMDMSettingsAppleSupportedOSVersion for each platform if err := spec.MDM.MacOSUpdates.Validate(); err != nil { return nil, ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("macos_updates", err.Error())) } diff --git a/frontend/pages/ManageControlsPage/OSUpdates/components/AppleOSTargetForm/AppleOSTargetForm.tsx b/frontend/pages/ManageControlsPage/OSUpdates/components/AppleOSTargetForm/AppleOSTargetForm.tsx index 3d62d197e1..861ac62001 100644 --- a/frontend/pages/ManageControlsPage/OSUpdates/components/AppleOSTargetForm/AppleOSTargetForm.tsx +++ b/frontend/pages/ManageControlsPage/OSUpdates/components/AppleOSTargetForm/AppleOSTargetForm.tsx @@ -1,6 +1,8 @@ import React, { useContext, useState } from "react"; import { isEmpty } from "lodash"; +import { AxiosResponse } from "axios"; +import { IApiError } from "interfaces/errors"; import { APP_CONTEXT_NO_TEAM_ID } from "interfaces/team"; import { NotificationContext } from "context/notification"; import configAPI from "services/entities/config"; @@ -15,6 +17,7 @@ import validatePresence from "components/forms/validators/validate_presence"; import CustomLink from "components/CustomLink"; import { AppContext } from "context/app"; import GitOpsModeTooltipWrapper from "components/GitOpsModeTooltipWrapper"; +import { getErrorMessage } from "./helpers"; const baseClass = "apple-os-target-form"; @@ -164,8 +167,8 @@ const AppleOSTargetForm = ({ ? await configAPI.update(updateData) : await teamsAPI.update(updateData, currentTeamId); renderFlash("success", "Successfully updated."); - } catch { - renderFlash("error", "Couldn’t update. Please try again."); + } catch (err) { + renderFlash("error", getErrorMessage(err as AxiosResponse)); } finally { currentTeamId === APP_CONTEXT_NO_TEAM_ID ? refetchAppConfig() @@ -226,7 +229,7 @@ const AppleOSTargetForm = ({ value={updateNewHosts} className={`${baseClass}__checkbox`} labelTooltipContent={ - "Hosts that automatically enroll (ADE) are updated to Apple's latest version during macOS Setup Assistant." + "During automated enrollment (ADE), hosts below the minimum version are updated to the latest version. If a minimum version isn't set, all hosts are updated to the latest version." } > Update new hosts to latest diff --git a/frontend/pages/ManageControlsPage/OSUpdates/components/AppleOSTargetForm/helpers.tsx b/frontend/pages/ManageControlsPage/OSUpdates/components/AppleOSTargetForm/helpers.tsx new file mode 100644 index 0000000000..8d0ad46547 --- /dev/null +++ b/frontend/pages/ManageControlsPage/OSUpdates/components/AppleOSTargetForm/helpers.tsx @@ -0,0 +1,28 @@ +import React from "react"; +import { AxiosResponse } from "axios"; + +import { IApiError } from "interfaces/errors"; + +// eslint-disable-next-line import/prefer-default-export +export const getErrorMessage = (err: AxiosResponse) => { + const apiReason = err?.data?.errors?.[0]?.reason?.toLowerCase?.(); + + if (apiReason?.includes("version isn't supported by apple")) { + return ( + <> + Couldn't update. The Minimum version isn't supported by + Apple. + + ); + } + + if (apiReason?.includes("deadline isn't a valid date")) { + return ( + <> + Couldn't update. The Deadline isn't a valid date. + + ); + } + + return "Couldn’t update. Please try again."; +}; diff --git a/server/fleet/app.go b/server/fleet/app.go index 927144022b..44e161e9ed 100644 --- a/server/fleet/app.go +++ b/server/fleet/app.go @@ -371,7 +371,7 @@ func (m AppleOSUpdateSettings) Validate() error { } if _, err := time.Parse("2006-01-02", m.Deadline.Value); err != nil { - return errors.New(`deadline accepts YYYY-MM-DD format only (E.g., "2023-06-01.")`) + return errors.New(AppleOSVersionDeadlineInvalidMessage) } return nil diff --git a/server/fleet/errors.go b/server/fleet/errors.go index 5d2853716b..0b913ff4d8 100644 --- a/server/fleet/errors.go +++ b/server/fleet/errors.go @@ -25,6 +25,8 @@ var ( AndroidMDMNotConfiguredMessage = "Android MDM isn't turned on. For more information about setting up MDM, please visit https://fleetdm.com/learn-more-about/how-to-connect-android-enterprise" AppleMDMNotConfiguredMessage = "macOS MDM isn't turned on. Visit https://fleetdm.com/docs/using-fleet to learn how to turn on MDM." AppleABMDefaultTeamDeprecatedMessage = "mdm.apple_bm_default_team has been deprecated. Please use the new mdm.apple_business_manager key documented here: https://fleetdm.com/learn-more-about/apple-business-manager-gitops" + AppleOSVersionUnsupportedMessage = "The minimum version isn't supported by Apple." + AppleOSVersionDeadlineInvalidMessage = "The deadline isn't a valid date." CantTurnOffMDMForWindowsHostsMessage = "Can't turn off MDM for Windows hosts." CantTurnOffMDMForPersonalHostsMessage = "Couldn't turn off MDM. This command isn't available for personal hosts." CantWipePersonalHostsMessage = "Couldn't wipe. This command isn't available for personal hosts." diff --git a/server/mdm/apple/apple_mdm.go b/server/mdm/apple/apple_mdm.go index fcd7eda6eb..59d3fe6eb4 100644 --- a/server/mdm/apple/apple_mdm.go +++ b/server/mdm/apple/apple_mdm.go @@ -19,6 +19,7 @@ import ( "github.com/fleetdm/fleet/v4/server/contexts/ctxerr" "github.com/fleetdm/fleet/v4/server/fleet" "github.com/fleetdm/fleet/v4/server/logging" + "github.com/fleetdm/fleet/v4/server/mdm/apple/gdmf" "github.com/fleetdm/fleet/v4/server/mdm/apple/mobileconfig" "github.com/fleetdm/fleet/v4/server/mdm/internal/commonmdm" "github.com/fleetdm/fleet/v4/server/mdm/nanodep/godep" @@ -1397,7 +1398,8 @@ func (pb *ProfileBimap) add(wantedProfile, currentProfile *fleet.MDMAppleProfile type NewActivityFunc = fleet.NewActivityFunc func IOSiPadOSRefetch(ctx context.Context, ds fleet.Datastore, commander *MDMAppleCommander, logger *slog.Logger, - newActivityFn NewActivityFunc) error { + newActivityFn NewActivityFunc, +) error { appCfg, err := ds.AppConfig(ctx) if err != nil { return ctxerr.Wrap(ctx, err, "fetching app config") @@ -1511,7 +1513,8 @@ func IOSiPadOSRefetch(ctx context.Context, ds fleet.Datastore, commander *MDMApp // turnOffMDMIfAPNSFailed checks if the error is an APNSDeliveryError and turns off MDM for the failed devices. // Returns a boolean value to indicate whether or not MDM was turned off. func turnOffMDMIfAPNSFailed(ctx context.Context, ds fleet.Datastore, err error, logger *slog.Logger, newActivityFn NewActivityFunc) (bool, - error) { + error, +) { var e *APNSDeliveryError if !errors.As(err, &e) { return false, nil @@ -1606,6 +1609,55 @@ func IOSiPadOSRevive(ctx context.Context, ds fleet.Datastore, commander *MDMAppl return nil } +func ValidateMDMSettingsAppleSupportedOSVersion[T fleet.MDM | fleet.TeamMDM](settings T, excludeNonPublicAssetSets bool) map[string]error { + var macOSUpdates, iOSUpdates, iPadOSUpdates fleet.AppleOSUpdateSettings + if m, ok := any(settings).(fleet.MDM); ok { + macOSUpdates = m.MacOSUpdates + iOSUpdates = m.IOSUpdates + iPadOSUpdates = m.IPadOSUpdates + } else if t, ok := any(settings).(fleet.TeamMDM); ok { + macOSUpdates = t.MacOSUpdates + iOSUpdates = t.IOSUpdates + iPadOSUpdates = t.IPadOSUpdates + } else { + return nil + } + + if macOSUpdates.MinimumVersion.Value == "" && iOSUpdates.MinimumVersion.Value == "" && iPadOSUpdates.MinimumVersion.Value == "" { + return nil + } + + am, err := gdmf.GetAssetMetadata() + if err != nil { + return map[string]error{"mdm": fmt.Errorf("fetching Apple asset metadata: %w", err)} + } else if am == nil { + // this should never happen, but just in case, return an error indicating that the metadata is not available instead of panicking with a nil pointer dereference + return map[string]error{"mdm": errors.New("Apple asset metadata is not available")} + } + + errs := make(map[string]error, 3) + if macOSUpdates.MinimumVersion.Value != "" { + if ok := am.IsSupportedMacOSVersion(macOSUpdates.MinimumVersion.Value, excludeNonPublicAssetSets); !ok { + errs["mdm.macos_updates.minimum_version"] = errors.New(fleet.AppleOSVersionUnsupportedMessage) + } + } + if iOSUpdates.MinimumVersion.Value != "" { + // NOTE: iPod generally falls in the category of iOS in Fleet, but we're only validating against iPhone here + // because we assume Apple will eventually remove iPod versions from the Apple Software Lookup Service + // and we want to avoid breaking workflows for users in that event + if ok := am.IsSupportedIOSVersion(iOSUpdates.MinimumVersion.Value, "iphone", excludeNonPublicAssetSets); !ok { + errs["mdm.ios_updates.minimum_version"] = errors.New(fleet.AppleOSVersionUnsupportedMessage) + } + } + if iPadOSUpdates.MinimumVersion.Value != "" { + if ok := am.IsSupportedIOSVersion(iPadOSUpdates.MinimumVersion.Value, "ipad", excludeNonPublicAssetSets); !ok { + errs["mdm.ipados_updates.minimum_version"] = errors.New(fleet.AppleOSVersionUnsupportedMessage) + } + } + + return errs +} + // RecoveryLockCommander defines the interface for sending recovery lock commands. // This interface is implemented by MDMAppleCommander and allows for testing. type RecoveryLockCommander interface { diff --git a/server/mdm/apple/apple_mdm_test.go b/server/mdm/apple/apple_mdm_test.go index ead4d6886d..6281754e6f 100644 --- a/server/mdm/apple/apple_mdm_test.go +++ b/server/mdm/apple/apple_mdm_test.go @@ -4,14 +4,18 @@ import ( "context" "encoding/json" "errors" + "fmt" "io" "log/slog" "net/http" "net/http/httptest" + "os" "regexp" "testing" "time" + "github.com/fleetdm/fleet/v4/pkg/optjson" + "github.com/fleetdm/fleet/v4/server/dev_mode" "github.com/fleetdm/fleet/v4/server/fleet" "github.com/fleetdm/fleet/v4/server/mdm/apple/mobileconfig" "github.com/fleetdm/fleet/v4/server/mdm/nanodep/client" @@ -270,6 +274,168 @@ func TestGenerateEnrollmentProfileMobileconfig(t *testing.T) { } } +func TestValidateMDMSettingsAppleSupportedOSVersion(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + // load the test data from the file + b, err := os.ReadFile("./gdmf/testdata/gdmf.json") + require.NoError(t, err) + _, err = w.Write(b) + require.NoError(t, err) + })) + t.Cleanup(srv.Close) + dev_mode.SetOverride("FLEET_DEV_GDMF_URL", srv.URL, t) + + // selected versions of from testdata/gdmf.json that we'll use in out tests + expectSupportedMacOSPublic := []string{"14.6.1", "13.6.9", "12.7.6", "11.7.10"} + expectSupportedMacOSNonPublic := []string{"14.5", "14.6", "13.6.8", "13.6.7", "12.7.5"} + expectSupportedIOSPublic := "17.6.1" + expectSupportedIOSNonPublic := "17.5.1" + // lastIPodSupportedVersion : = "15.8.3" + + // helper function to initialize app config MDM settings with known good versions (tests will modify as needed) + mockAppConfigMDM := func() fleet.MDM { + return fleet.MDM{ + MacOSUpdates: fleet.AppleOSUpdateSettings{ + MinimumVersion: optjson.SetString(expectSupportedMacOSPublic[0]), + }, + IOSUpdates: fleet.AppleOSUpdateSettings{ + MinimumVersion: optjson.SetString(expectSupportedIOSPublic), + }, + IPadOSUpdates: fleet.AppleOSUpdateSettings{ + MinimumVersion: optjson.SetString(expectSupportedIOSPublic), + }, + } + } + // helper function to initialize team MDM settings with known good versions (tests will modify as needed) + mockTeamMDM := func() fleet.TeamMDM { + return fleet.TeamMDM{ + MacOSUpdates: fleet.AppleOSUpdateSettings{ + MinimumVersion: optjson.SetString(expectSupportedMacOSPublic[0]), + }, + IOSUpdates: fleet.AppleOSUpdateSettings{ + MinimumVersion: optjson.SetString(expectSupportedIOSPublic), + }, + IPadOSUpdates: fleet.AppleOSUpdateSettings{ + MinimumVersion: optjson.SetString(expectSupportedIOSPublic), + }, + } + } + + // helper function to check if the error matches expectations for a given platform and log appropriately + checkErr := func(platform string, wantErr string, gotErrs map[string]error, msg string) { + key := fmt.Sprintf("mdm.%s_updates.minimum_version", platform) + if wantErr == "" { + assert.Empty(t, gotErrs, msg+": expected no error for platform %s but got: %v", platform, gotErrs) + } else { + assert.Len(t, gotErrs, 1, msg+": expected error for platform %s but got no errors", platform) + assert.Contains(t, gotErrs, key, msg+": expected error for platform %s but got no error", platform) + assert.ErrorContains(t, gotErrs[key], wantErr, msg+": expected error for platform %s but got: %v", platform, gotErrs[key]) + } + } + + t.Run("macos", func(t *testing.T) { + t.Run("app config mdm settings", func(t *testing.T) { + ac := mockAppConfigMDM() + for _, v := range expectSupportedMacOSPublic { + ac.MacOSUpdates.MinimumVersion = optjson.SetString(v) + checkErr("macos", "", ValidateMDMSettingsAppleSupportedOSVersion(ac, false), "expect public macOS version to be supported when including non-public asset sets") + checkErr("macos", "", ValidateMDMSettingsAppleSupportedOSVersion(ac, true), "expect public macOS version to be supported when excluding non-public asset sets") + } + for _, v := range expectSupportedMacOSNonPublic { + ac.MacOSUpdates.MinimumVersion = optjson.SetString(v) + checkErr("macos", "", ValidateMDMSettingsAppleSupportedOSVersion(ac, false), "expect non-public macOS version to be supported when including non-public asset sets") + checkErr("macos", fleet.AppleOSVersionUnsupportedMessage, ValidateMDMSettingsAppleSupportedOSVersion(ac, true), "expect non-public macOS version to return error when excluding non-public asset sets") + } + + ac.MacOSUpdates.MinimumVersion = optjson.SetString("11.7.9") // not supported in either asset set, so we expect an error in both cases + checkErr("macos", fleet.AppleOSVersionUnsupportedMessage, ValidateMDMSettingsAppleSupportedOSVersion(ac, false), "expect unsupported macOS version to return error when including non-public asset sets") + checkErr("macos", fleet.AppleOSVersionUnsupportedMessage, ValidateMDMSettingsAppleSupportedOSVersion(ac, true), "expect unsupported macOS version to return error when excluding non-public asset sets") + }) + t.Run("team mdm settings", func(t *testing.T) { + tm := mockTeamMDM() + for _, v := range expectSupportedMacOSPublic { + tm.MacOSUpdates.MinimumVersion = optjson.SetString(v) + checkErr("macos", "", ValidateMDMSettingsAppleSupportedOSVersion(tm, false), "expect public macOS version to be supported when including non-public asset sets") + checkErr("macos", "", ValidateMDMSettingsAppleSupportedOSVersion(tm, true), "expect public macOS version to be supported when excluding non-public asset sets") + } + for _, v := range expectSupportedMacOSNonPublic { + tm.MacOSUpdates.MinimumVersion = optjson.SetString(v) + checkErr("macos", "", ValidateMDMSettingsAppleSupportedOSVersion(tm, false), "expect non-public macOS version to be supported when including non-public asset sets") + checkErr("macos", fleet.AppleOSVersionUnsupportedMessage, ValidateMDMSettingsAppleSupportedOSVersion(tm, true), "expect non-public macOS version to return error when excluding non-public asset sets") + } + + tm.MacOSUpdates.MinimumVersion = optjson.SetString("11.7.9") // not supported in either asset set, so we expect an error in both cases + checkErr("macos", fleet.AppleOSVersionUnsupportedMessage, ValidateMDMSettingsAppleSupportedOSVersion(tm, false), "expect unsupported macOS version to return error when including non-public asset sets") + checkErr("macos", fleet.AppleOSVersionUnsupportedMessage, ValidateMDMSettingsAppleSupportedOSVersion(tm, true), "expect unsupported macOS version to return error when excluding non-public asset sets") + }) + }) + + t.Run("ios", func(t *testing.T) { + t.Run("app config mdm settings", func(t *testing.T) { + ac := mockAppConfigMDM() + ac.IOSUpdates.MinimumVersion = optjson.SetString(expectSupportedIOSPublic) + checkErr("ios", "", ValidateMDMSettingsAppleSupportedOSVersion(ac, false), "expect public iOS version to be supported when including non-public asset sets") + checkErr("ios", "", ValidateMDMSettingsAppleSupportedOSVersion(ac, true), "expect public iOS version to be supported when excluding non-public asset sets") + + ac.IOSUpdates.MinimumVersion = optjson.SetString(expectSupportedIOSNonPublic) + checkErr("ios", "", ValidateMDMSettingsAppleSupportedOSVersion(ac, false), "expect non-public iOS version to be supported when including non-public asset sets") + checkErr("ios", fleet.AppleOSVersionUnsupportedMessage, ValidateMDMSettingsAppleSupportedOSVersion(ac, true), "expect non-public iOS version to return error when excluding non-public asset sets") + + ac.IOSUpdates.MinimumVersion = optjson.SetString("5.3.9") // only supported for Apple Watch, so we expect an error + checkErr("ios", fleet.AppleOSVersionUnsupportedMessage, ValidateMDMSettingsAppleSupportedOSVersion(ac, false), "expect unsupported iOS version to return error when including non-public asset sets") + checkErr("ios", fleet.AppleOSVersionUnsupportedMessage, ValidateMDMSettingsAppleSupportedOSVersion(ac, true), "expect unsupported iOS version to return error when excluding non-public asset sets") + }) + + t.Run("team mdm settings", func(t *testing.T) { + tm := mockTeamMDM() + tm.IOSUpdates.MinimumVersion = optjson.SetString(expectSupportedIOSPublic) + checkErr("ios", "", ValidateMDMSettingsAppleSupportedOSVersion(tm, false), "expect public iOS version to be supported when including non-public asset sets") + checkErr("ios", "", ValidateMDMSettingsAppleSupportedOSVersion(tm, true), "expect public iOS version to be supported when excluding non-public asset sets") + + tm.IOSUpdates.MinimumVersion = optjson.SetString(expectSupportedIOSNonPublic) + checkErr("ios", "", ValidateMDMSettingsAppleSupportedOSVersion(tm, false), "expect non-public iOS version to be supported when including non-public asset sets") + checkErr("ios", fleet.AppleOSVersionUnsupportedMessage, ValidateMDMSettingsAppleSupportedOSVersion(tm, true), "expect non-public iOS version to return error when excluding non-public asset sets") + + tm.IOSUpdates.MinimumVersion = optjson.SetString("5.3.9") // only supported for Apple Watch, so we expect an error + checkErr("ios", fleet.AppleOSVersionUnsupportedMessage, ValidateMDMSettingsAppleSupportedOSVersion(tm, false), "expect unsupported iOS version to return error when including non-public asset sets") + checkErr("ios", fleet.AppleOSVersionUnsupportedMessage, ValidateMDMSettingsAppleSupportedOSVersion(tm, true), "expect unsupported iOS version to return error when excluding non-public asset sets") + }) + }) + + t.Run("ipados", func(t *testing.T) { + t.Run("app config mdm settings", func(t *testing.T) { + ac := mockAppConfigMDM() + ac.IPadOSUpdates.MinimumVersion = optjson.SetString(expectSupportedIOSPublic) + checkErr("ipados", "", ValidateMDMSettingsAppleSupportedOSVersion(ac, false), "expect public iPadOS version to be supported when including non-public asset sets") + checkErr("ipados", "", ValidateMDMSettingsAppleSupportedOSVersion(ac, true), "expect public iPadOS version to be supported when excluding non-public asset sets") + + ac.IPadOSUpdates.MinimumVersion = optjson.SetString(expectSupportedIOSNonPublic) + checkErr("ipados", "", ValidateMDMSettingsAppleSupportedOSVersion(ac, false), "expect non-public iPadOS version to be supported when including non-public asset sets") + checkErr("ipados", fleet.AppleOSVersionUnsupportedMessage, ValidateMDMSettingsAppleSupportedOSVersion(ac, true), "expect non-public iPadOS version to return error when excluding non-public asset sets") + + ac.IPadOSUpdates.MinimumVersion = optjson.SetString("5.3.9") // only supported for Apple Watch, so we expect an error + checkErr("ipados", fleet.AppleOSVersionUnsupportedMessage, ValidateMDMSettingsAppleSupportedOSVersion(ac, false), "expect unsupported iPadOS version to return error when including non-public asset sets") + checkErr("ipados", fleet.AppleOSVersionUnsupportedMessage, ValidateMDMSettingsAppleSupportedOSVersion(ac, true), "expect unsupported iPadOS version to return error when excluding non-public asset sets") + }) + + t.Run("team mdm settings", func(t *testing.T) { + tm := mockTeamMDM() + tm.IPadOSUpdates.MinimumVersion = optjson.SetString(expectSupportedIOSPublic) + checkErr("ipados", "", ValidateMDMSettingsAppleSupportedOSVersion(tm, false), "expect public iPadOS version to be supported when including non-public asset sets") + checkErr("ipados", "", ValidateMDMSettingsAppleSupportedOSVersion(tm, true), "expect public iPadOS version to be supported when excluding non-public asset sets") + + tm.IPadOSUpdates.MinimumVersion = optjson.SetString(expectSupportedIOSNonPublic) + checkErr("ipados", "", ValidateMDMSettingsAppleSupportedOSVersion(tm, false), "expect non-public iPadOS version to be supported when including non-public asset sets") + checkErr("ipados", fleet.AppleOSVersionUnsupportedMessage, ValidateMDMSettingsAppleSupportedOSVersion(tm, true), "expect non-public iPadOS version to return error when excluding non-public asset sets") + + tm.IPadOSUpdates.MinimumVersion = optjson.SetString("5.3.9") // only supported for Apple Watch, so we expect an error + checkErr("ipados", fleet.AppleOSVersionUnsupportedMessage, ValidateMDMSettingsAppleSupportedOSVersion(tm, false), "expect unsupported iPadOS version to return error when including non-public asset sets") + checkErr("ipados", fleet.AppleOSVersionUnsupportedMessage, ValidateMDMSettingsAppleSupportedOSVersion(tm, true), "expect unsupported iPadOS version to return error when excluding non-public asset sets") + }) + }) +} + type notFoundError struct{} func (e notFoundError) IsNotFound() bool { return true } diff --git a/server/mdm/apple/gdmf/api.go b/server/mdm/apple/gdmf/api.go index 7d5977e7a5..10913b421a 100644 --- a/server/mdm/apple/gdmf/api.go +++ b/server/mdm/apple/gdmf/api.go @@ -58,34 +58,79 @@ type AssetSets struct { // XROS []Asset `json:"xrOS"` // Fleet doesn't support xrOS yet } -// APIResponse represents the response from the Apple Software Lookup Service[1][2]. +// AssetMetadata represents the response from the Apple Software Lookup Service[1][2]. // [1]: http://gdmf.apple.com/v2/pmv // [2]: https://support.apple.com/guide/deployment/use-mdm-to-deploy-software-updates-depafd2fad80/web -type APIResponse struct { +type AssetMetadata struct { PublicAssetSets AssetSets `json:"PublicAssetSets"` AssetSets AssetSets `json:"AssetSets"` // PublicRapidSecurityResponses interface{} `json:"PublicRapidSecurityResponses"` // Fleet doesn't support PublicRapidSecurityResponses yet } +// IsSupportedMacOSVersion checks if the given macOS version is supported by Apple. The +// excludeNonPublicAssetSets parameter controls whether to check against the full asset set or just +// the public asset set, which is relevant for DEP enrollment where only public versions are valid. +func (a AssetMetadata) IsSupportedMacOSVersion(version string, excludeNonPublicAssetSets bool) bool { + as := a.AssetSets.MacOS + if excludeNonPublicAssetSets { + as = a.PublicAssetSets.MacOS + } + + for _, s := range as { + if s.ProductVersion == version { + return true // version is supported + } + } + + return false // version is not supported +} + +// IsSupportedIOSVersion checks if the given iOS version is supported by Apple for the given device +// prefix (e.g. "iPhone", "iPad"). If devicePrefix is empty, it checks if the version is supported +// for any iOS device (which includes things like iPod, Apple Watch, and Apple TV). The +// excludeNonPublicAssetSets parameter controls whether to check against the full asset set or just +// the public asset set, which is relevant for DEP enrollment where only public versions are valid. +func (a AssetMetadata) IsSupportedIOSVersion(version string, devicePrefix string, excludeNonPublicAssetSets bool) bool { + as := a.AssetSets.IOS + if excludeNonPublicAssetSets { + as = a.PublicAssetSets.IOS + } + + for _, s := range as { + if s.ProductVersion == version { + if devicePrefix == "" { + return true // version is supported for iOS with any device prefix + } + for _, d := range s.SupportedDevices { + if strings.HasPrefix(strings.ToLower(d), strings.ToLower(devicePrefix)) { + return true // version is supported for device with the given prefix + } + } + } + } + + return false // version is not supported +} + // GetLatestOSVersion returns the latest OS version for the given device. The device is matched -// against the Apple Software Update Lookup Service[1][2] to find the latest version. If no matching -// asset is found, an error is returned. +// against the Apple Software Update Lookup Service[1][2] to find the latest version in the +// PublicAssetSets. If no matching asset is found, an error is returned. // [1]: http://gdmf.apple.com/v2/pmv // [2]: https://support.apple.com/guide/deployment/use-mdm-to-deploy-software-updates-depafd2fad80/web func GetLatestOSVersion(device fleet.MDMAppleMachineInfo) (*Asset, error) { - r, err := GetAssetMetadata() + am, err := GetAssetMetadata() if err != nil { return nil, fmt.Errorf("retrieving asset metadata: %w", err) } - assetSet := r.PublicAssetSets.MacOS // default to public asset set; note that if the device is not macOS, iPhone, iPad, or iPod we'll fail to match the supported device and return an error below + assetSet := am.PublicAssetSets.MacOS // default to public asset set; note that if the device is not macOS, iPhone, iPad, or iPod we'll fail to match the supported device and return an error below if strings.HasPrefix(device.Product, "iPhone") || strings.HasPrefix(device.Product, "iPod") || strings.HasPrefix(device.Product, "iPad") || strings.HasPrefix(device.SoftwareUpdateDeviceID, "iPhone") || strings.HasPrefix(device.SoftwareUpdateDeviceID, "iPod") || strings.HasPrefix(device.SoftwareUpdateDeviceID, "iPad") { - assetSet = r.PublicAssetSets.IOS + assetSet = am.PublicAssetSets.IOS } latestIdx := -1 for i, s := range assetSet { @@ -129,7 +174,7 @@ func createClient() *http.Client { // GetAssetMetadata retrieves the asset metadata from the Apple Software Lookup Service[1][2]. // [1]: http://gdmf.apple.com/v2/pmv // [2]: https://support.apple.com/guide/deployment/use-mdm-to-deploy-software-updates-depafd2fad80/web -func GetAssetMetadata() (*APIResponse, error) { +func GetAssetMetadata() (*AssetMetadata, error) { baseURL := getBaseURL() reqURL, err := url.Parse(baseURL) if err != nil { @@ -151,7 +196,7 @@ func GetAssetMetadata() (*APIResponse, error) { if err != nil { return nil, fmt.Errorf("reading response body from Apple endpoint: %w", err) } - var dest APIResponse + var dest AssetMetadata if err := json.Unmarshal(body, &dest); err != nil { return nil, fmt.Errorf("decoding response data from Apple endpoint: %w", err) } diff --git a/server/mdm/apple/gdmf/api_test.go b/server/mdm/apple/gdmf/api_test.go index 2d1d82cd29..9d2dfe98cd 100644 --- a/server/mdm/apple/gdmf/api_test.go +++ b/server/mdm/apple/gdmf/api_test.go @@ -24,7 +24,7 @@ func TestGetLatest(t *testing.T) { _, err = w.Write(b) require.NoError(t, err) })) - defer srv.Close() + t.Cleanup(srv.Close) dev_mode.SetOverride("FLEET_DEV_GDMF_URL", srv.URL, t) // test the function @@ -225,11 +225,8 @@ func TestRetries(t *testing.T) { _, err := w.Write([]byte(`{"error": "bad request"}`)) require.NoError(t, err) })) - dev_mode.SetOverride("FLEET_DEV_GDMF_URL", srv.URL) - t.Cleanup(func() { - srv.Close() - dev_mode.ClearOverride("FLEET_DEV_GDMF_URL") - }) + t.Cleanup(srv.Close) + dev_mode.SetOverride("FLEET_DEV_GDMF_URL", srv.URL, t) latest, err := GetLatestOSVersion(fleet.MDMAppleMachineInfo{ OSVersion: "14.4.1", diff --git a/server/mdm/testing_utils/testing_utils.go b/server/mdm/testing_utils/testing_utils.go index e3b3f76b54..727c1fc665 100644 --- a/server/mdm/testing_utils/testing_utils.go +++ b/server/mdm/testing_utils/testing_utils.go @@ -5,7 +5,16 @@ import ( "crypto/x509/pkix" "encoding/asn1" "math/big" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "runtime" + "testing" "time" + + "github.com/fleetdm/fleet/v4/server/dev_mode" + "github.com/stretchr/testify/require" ) func NewTestMDMAppleCertTemplate() *x509.Certificate { @@ -27,3 +36,23 @@ func NewTestMDMAppleCertTemplate() *x509.Certificate { BasicConstraintsValid: true, } } + +// StartNewAppleGDMFTestServer creates a new test server that serves the GDMF data from the testdata +// file. It also sets the necessary dev mode overrides to point to the test server and disable +// caching. It closes the server and clears the underlying overrides when the test finishes. +func StartNewAppleGDMFTestServer(t *testing.T) { + _, thisFile, _, _ := runtime.Caller(0) + gdmfTestDataPath := filepath.Join(filepath.Dir(thisFile), "../apple/gdmf/testdata/gdmf.json") + + appleGDMFSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + // load the test data from the file + b, err := os.ReadFile(gdmfTestDataPath) + require.NoError(t, err) + _, err = w.Write(b) + require.NoError(t, err) + })) + t.Cleanup(appleGDMFSrv.Close) + + dev_mode.SetOverride("FLEET_DEV_GDMF_URL", appleGDMFSrv.URL, t) +} diff --git a/server/service/appconfig.go b/server/service/appconfig.go index c58cfe2fd1..c6487833ea 100644 --- a/server/service/appconfig.go +++ b/server/service/appconfig.go @@ -27,6 +27,7 @@ import ( "github.com/fleetdm/fleet/v4/server/contexts/license" "github.com/fleetdm/fleet/v4/server/contexts/viewer" "github.com/fleetdm/fleet/v4/server/fleet" + apple_mdm "github.com/fleetdm/fleet/v4/server/mdm/apple" "github.com/fleetdm/fleet/v4/server/platform/endpointer" "github.com/fleetdm/fleet/v4/server/platform/logging" "github.com/fleetdm/fleet/v4/server/version" @@ -1486,6 +1487,15 @@ func (svc *Service) validateMDM( invalid.Append("ipados_updates", err.Error()) } + // Always check whether specified versions are supported by Apple (even if they weren't updated) + // Note that we're validating against the full, non-public asset set of OS versions here because + // in our DEP flow the minimum version just acts as the threshold for whether or not to update + // the host to the latest, public version. We don't need to install the specified version on the + // host during DEP so it doesn't need to be in the public asset set. + for k, v := range apple_mdm.ValidateMDMSettingsAppleSupportedOSVersion(*mdm, false) { + invalid.Append(k, v.Error()) + } + if err := mdm.MacOSSetup.Validate(); err != nil { var invalidArgErr *fleet.InvalidArgumentError if errors.As(err, &invalidArgErr) { diff --git a/server/service/apple_mdm.go b/server/service/apple_mdm.go index 977b91c5c4..64e1c5dcfe 100644 --- a/server/service/apple_mdm.go +++ b/server/service/apple_mdm.go @@ -2081,16 +2081,17 @@ func (svc *Service) CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx context.Cont return nil, nil } - needsUpdate, err := svc.needsOSUpdateForDEPEnrollment(ctx, *m) + // shouldUpdate depends on the app_config settings for minimum_version and update_new_hosts + shouldUpdate, err := svc.shouldOSUpdateForDEPEnrollment(ctx, *m) if err != nil { return nil, ctxerr.Wrap(ctx, err, "checking os updates settings", "serial", m.Serial) - } - - if !needsUpdate { + } else if !shouldUpdate { svc.logger.DebugContext(ctx, "device is above minimum or update new host not checked, skipping os version check", "serial", m.Serial) return nil, nil } + // if the device should update based on appconfig settings, we also need to check what versions + // are actually available for the device from Apple sur, err := svc.getAppleSoftwareUpdateRequiredForDEPEnrollment(*m) if err != nil { // log for debugging but allow enrollment to proceed @@ -2101,7 +2102,7 @@ func (svc *Service) CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx context.Cont return sur, nil } -func (svc *Service) needsOSUpdateForDEPEnrollment(ctx context.Context, m fleet.MDMAppleMachineInfo) (bool, error) { +func (svc *Service) shouldOSUpdateForDEPEnrollment(ctx context.Context, m fleet.MDMAppleMachineInfo) (bool, error) { // NOTE: Under the hood, the datastore is joining host_dep_assignments to the hosts table to // look up DEP hosts by serial number. It grabs the team id and platform from the // hosts table. Then it uses the team id to get either the global config or team config. @@ -2124,35 +2125,40 @@ func (svc *Service) needsOSUpdateForDEPEnrollment(ctx context.Context, m fleet.M } minVersion := settings.MinimumVersion.Value - hasMinVersion := settings.MinimumVersion.Set && settings.MinimumVersion.Valid && minVersion != "" - - // For macOS hosts, whether to update new hosts during DEP enrollment is determined solely by UpdateNewHosts - if platform == "darwin" { - updateNewHosts := settings.UpdateNewHosts.Set && settings.UpdateNewHosts.Valid && settings.UpdateNewHosts.Value - - svc.logger.InfoContext(ctx, "checking os updates settings for macos, update will be forced if UpdateNewHosts is set", - "update_new_hosts", updateNewHosts, - "serial", m.Serial, - ) - return updateNewHosts, nil + isSetMinVersion := settings.MinimumVersion.Set && settings.MinimumVersion.Valid && minVersion != "" + logs := []any{ + "platform", platform, + "minimum_version", minVersion, + "current_version", m.OSVersion, + "serial", m.Serial, } - // TODO: confirm what this check should do - if !hasMinVersion { - svc.logger.InfoContext(ctx, "checking os updates settings, minimum version not set", - "serial", m.Serial, - "current_version", m.OSVersion, - "minimum_version", minVersion, - ) + if platform != "darwin" && !isSetMinVersion { + svc.logger.InfoContext(ctx, "checking os updates settings for non-macos platform, minimum version not set, skipping version check", logs...) + return false, nil + } + + if platform == "darwin" { + updateNewHosts := settings.UpdateNewHosts.Set && settings.UpdateNewHosts.Valid && settings.UpdateNewHosts.Value + logs = append(logs, "update_new_hosts", updateNewHosts) + switch { + case !updateNewHosts: + // never update macos if updateNewHosts is false + svc.logger.InfoContext(ctx, "checking os updates settings for macos, new hosts should not update", logs...) + return false, nil + case !isSetMinVersion: + // always update macos if updateNewHosts is true and minimum version is not set + svc.logger.InfoContext(ctx, "checking os updates settings for macos, new hosts should always update to latest", logs...) + return true, nil + default: + // default to normal version check (require update if less than minimum version) + svc.logger.InfoContext(ctx, "checking os updates settings for macos, new hosts should update to latest if below minimum version", logs...) + } } needsUpdate, err := apple_mdm.IsLessThanVersion(m.OSVersion, minVersion) if err != nil { - svc.logger.InfoContext(ctx, "checking os updates settings, cannot compare versions", - "serial", m.Serial, - "current_version", m.OSVersion, - "minimum_version", minVersion, - ) + svc.logger.InfoContext(ctx, "checking os updates settings, cannot compare versions", logs...) return false, nil } diff --git a/server/service/apple_mdm_test.go b/server/service/apple_mdm_test.go index ed3381a09c..9c1fe79f62 100644 --- a/server/service/apple_mdm_test.go +++ b/server/service/apple_mdm_test.go @@ -5287,7 +5287,7 @@ func TestUnmarshalAppList(t *testing.T) { assert.ElementsMatch(t, expectedSoftware, software) } -func TestNeedsOSUpdateForDEPEnrollment(t *testing.T) { +func TestShouldOSUpdateForDEPEnrollment(t *testing.T) { testCases := []struct { name string platform string @@ -5321,7 +5321,7 @@ func TestNeedsOSUpdateForDEPEnrollment(t *testing.T) { expectedResult: false, }, { - name: "if platform is macOS and update_new_hosts is set", + name: "if platform is macOS and both update_new_hosts and minimum_version are set and host is below the minimum version", platform: string(fleet.MacOSPlatform), appleMachineInfo: fleet.MDMAppleMachineInfo{ OSVersion: "16.0.1", @@ -5332,6 +5332,29 @@ func TestNeedsOSUpdateForDEPEnrollment(t *testing.T) { }, expectedResult: true, }, + { + name: "if platform is macOS and both update_new_hosts and minimum_version are set and host is at the minimum version", + platform: string(fleet.MacOSPlatform), + appleMachineInfo: fleet.MDMAppleMachineInfo{ + OSVersion: "16.0.2", + }, + appleOSUpdateSettings: fleet.AppleOSUpdateSettings{ + MinimumVersion: optjson.SetString("16.0.2"), + UpdateNewHosts: optjson.SetBool(true), + }, + expectedResult: false, + }, + { + name: "if platform is macOS and update_new_hosts is set but minimum_version is not set", + platform: string(fleet.MacOSPlatform), + appleMachineInfo: fleet.MDMAppleMachineInfo{ + OSVersion: "16.0.1", + }, + appleOSUpdateSettings: fleet.AppleOSUpdateSettings{ + UpdateNewHosts: optjson.SetBool(true), + }, + expectedResult: true, + }, { name: "if platform is not macOS and min_version is not set", platform: string(fleet.IPadOSPlatform), @@ -5376,7 +5399,7 @@ func TestNeedsOSUpdateForDEPEnrollment(t *testing.T) { svc := &Service{ds: ds, logger: slog.New(slog.DiscardHandler)} t.Run(tt.name, func(t *testing.T) { - result, err := svc.needsOSUpdateForDEPEnrollment(ctx, tt.appleMachineInfo) + result, err := svc.shouldOSUpdateForDEPEnrollment(ctx, tt.appleMachineInfo) require.Equal(t, tt.expectedResult, result) require.Equal(t, tt.expectedErr, err) }) @@ -5400,6 +5423,9 @@ func TestCheckMDMAppleEnrollmentWithMinimumOSVersion(t *testing.T) { latestMacOSVersion := "14.6.1" latestMacOSBuild := "23G93" + latestIOSVersion := "17.6.1" + latestIOSBuild := "21G93" + testCases := []struct { name string machineInfo *fleet.MDMAppleMachineInfo @@ -5485,17 +5511,62 @@ func TestCheckMDMAppleEnrollmentWithMinimumOSVersion(t *testing.T) { }, } + // FIXME: When we have more time, this whole test is overdue for a refactor because a bunch of jank + // came with the update new hosts settings for macOS that made the test cases more dependent on + // subtle differences in the machine info for macOS vs non-macOS platforms and made the setup + // more complex and harder to reason about. For now, we can get away with some nested subtests + // to reuse the test cases for both macOS and non-macOS platforms, but ideally we would refactor + // the function under test to separate out the platform-specific logic so that we can have + // clearer and more focused tests for each platform without needing to have a bunch of + // conditional logic in the test itself. for _, tt := range testCases { // Non-macOS platforms for _, platform := range []string{"ios", "ipados"} { + + if tt.name == "no match for software update device ID" { + // skip this test case for non-macOS platforms since SUDeviceID is really only relevant for macOS updates + continue + } + t.Run(fmt.Sprintf("%s: %s", platform, tt.name), func(t *testing.T) { + // switch up the machine info to match the platform because test cases were + // originally written with macOS in mind + var product, osVersion, suDeviceID string + var mi *fleet.MDMAppleMachineInfo + if tt.machineInfo != nil { + osVersion = strings.Replace(tt.machineInfo.OSVersion, "14", "17", 1) + if platform == "ios" { + product = "iPhone16,2" + suDeviceID = strings.Replace(tt.machineInfo.SoftwareUpdateDeviceID, "J516sAP", "iPhone", 1) + } else { + product = "iPad14,11" + suDeviceID = strings.Replace(tt.machineInfo.SoftwareUpdateDeviceID, "J516sAP", "iPad", 1) + } + + mi = &fleet.MDMAppleMachineInfo{ + MDMCanRequestSoftwareUpdate: tt.machineInfo.MDMCanRequestSoftwareUpdate, + Product: product, + OSVersion: osVersion, + SupplementalBuildVersion: tt.machineInfo.SupplementalBuildVersion, + SoftwareUpdateDeviceID: suDeviceID, + } + } + // same for update required details + var details *fleet.MDMAppleSoftwareUpdateRequiredDetails + if tt.updateRequired != nil { + details = &fleet.MDMAppleSoftwareUpdateRequiredDetails{ + OSVersion: latestIOSVersion, + BuildVersion: latestIOSBuild, + } + } + 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), + return platform, &fleet.AppleOSUpdateSettings{ + MinimumVersion: optjson.SetString(latestIOSVersion), }, nil } - sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, tt.machineInfo) + sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, mi) if tt.err != "" { require.Error(t, err) require.Contains(t, err.Error(), tt.err) @@ -5505,7 +5576,7 @@ func TestCheckMDMAppleEnrollmentWithMinimumOSVersion(t *testing.T) { if tt.updateRequired != nil { require.Equal(t, &fleet.MDMAppleSoftwareUpdateRequired{ Code: fleet.MDMAppleSoftwareUpdateRequiredCode, - Details: *tt.updateRequired, + Details: *details, }, sur) } else { require.Nil(t, sur) @@ -5514,11 +5585,11 @@ func TestCheckMDMAppleEnrollmentWithMinimumOSVersion(t *testing.T) { 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"), + return platform, &fleet.AppleOSUpdateSettings{ + MinimumVersion: optjson.SetString("17.5"), }, nil } - sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, tt.machineInfo) + sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, mi) if tt.err != "" { require.Error(t, err) require.Contains(t, err.Error(), tt.err) @@ -5528,7 +5599,7 @@ func TestCheckMDMAppleEnrollmentWithMinimumOSVersion(t *testing.T) { if tt.updateRequired != nil { require.Equal(t, &fleet.MDMAppleSoftwareUpdateRequired{ Code: fleet.MDMAppleSoftwareUpdateRequiredCode, - Details: *tt.updateRequired, + Details: *details, }, sur) } else { require.Nil(t, sur) @@ -5538,11 +5609,11 @@ 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) (string, *fleet.AppleOSUpdateSettings, error) { - return "ios", &fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("14.7"), + return platform, &fleet.AppleOSUpdateSettings{ + MinimumVersion: optjson.SetString("17.7"), }, nil } - sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, tt.machineInfo) + sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, mi) if tt.err != "" { require.Error(t, err) require.Contains(t, err.Error(), tt.err) @@ -5552,7 +5623,7 @@ func TestCheckMDMAppleEnrollmentWithMinimumOSVersion(t *testing.T) { if tt.updateRequired != nil { require.Equal(t, &fleet.MDMAppleSoftwareUpdateRequired{ Code: fleet.MDMAppleSoftwareUpdateRequiredCode, - Details: *tt.updateRequired, + Details: *details, }, sur) } else { require.Nil(t, sur) @@ -5561,11 +5632,11 @@ func TestCheckMDMAppleEnrollmentWithMinimumOSVersion(t *testing.T) { 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"), + return platform, &fleet.AppleOSUpdateSettings{ + MinimumVersion: optjson.SetString("17.1"), }, nil } - sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, tt.machineInfo) + sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, mi) if tt.err != "" { require.Error(t, err) require.Contains(t, err.Error(), tt.err) @@ -5578,27 +5649,27 @@ func TestCheckMDMAppleEnrollmentWithMinimumOSVersion(t *testing.T) { 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 + return platform, &fleet.AppleOSUpdateSettings{}, nil } - sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, tt.machineInfo) + sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, mi) require.NoError(t, err) require.Nil(t, sur) ds.GetMDMAppleOSUpdatesSettingsByHostSerialFunc = func(ctx context.Context, serial string) (string, *fleet.AppleOSUpdateSettings, error) { - return "ios", &fleet.AppleOSUpdateSettings{ + return platform, &fleet.AppleOSUpdateSettings{ MinimumVersion: optjson.SetString(""), }, nil } - sur, err = svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, tt.machineInfo) + sur, err = svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, mi) 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{} + return platform, nil, ¬FoundError{} } - sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, tt.machineInfo) + sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, mi) require.NoError(t, err) require.Nil(t, sur) }) @@ -5621,6 +5692,7 @@ func TestCheckMDMAppleEnrollmentWithMinimumOSVersion(t *testing.T) { }) t.Run("when UpdateNewHosts is set and minimum is not set", func(t *testing.T) { + // test with min version not present ds.GetMDMAppleOSUpdatesSettingsByHostSerialFunc = func(ctx context.Context, serial string) (string, *fleet.AppleOSUpdateSettings, error) { return "darwin", &fleet.AppleOSUpdateSettings{ UpdateNewHosts: optjson.SetBool(true), @@ -5640,6 +5712,7 @@ func TestCheckMDMAppleEnrollmentWithMinimumOSVersion(t *testing.T) { require.Nil(t, sur) } + // test again with min version explicitly set to empty string ds.GetMDMAppleOSUpdatesSettingsByHostSerialFunc = func(ctx context.Context, serial string) (string, *fleet.AppleOSUpdateSettings, error) { return "darwin", &fleet.AppleOSUpdateSettings{ UpdateNewHosts: optjson.SetBool(true), @@ -5664,6 +5737,7 @@ func TestCheckMDMAppleEnrollmentWithMinimumOSVersion(t *testing.T) { ds.GetMDMAppleOSUpdatesSettingsByHostSerialFunc = func(ctx context.Context, serial string) (string, *fleet.AppleOSUpdateSettings, error) { return "darwin", nil, ¬FoundError{} } + // never block enrollment when settings are not found sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, tt.machineInfo) require.NoError(t, err) require.Nil(t, sur) @@ -5692,6 +5766,30 @@ func TestCheckMDMAppleEnrollmentWithMinimumOSVersion(t *testing.T) { require.Nil(t, sur) } }) + + t.Run("when UpdateNewHosts is set and required minimum is less than 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("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) + } + }) }) } @@ -5701,7 +5799,7 @@ 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) (string, *fleet.AppleOSUpdateSettings, error) { - return "ios", &fleet.AppleOSUpdateSettings{MinimumVersion: optjson.SetString(latestMacOSVersion)}, nil + return "macos", &fleet.AppleOSUpdateSettings{MinimumVersion: optjson.SetString(latestMacOSVersion), UpdateNewHosts: optjson.SetBool(true)}, nil } sur, err := svc.CheckMDMAppleEnrollmentWithMinimumOSVersion(ctx, tt.machineInfo) diff --git a/server/service/integration_enterprise_test.go b/server/service/integration_enterprise_test.go index 28fd3c6eb2..fa58ef0454 100644 --- a/server/service/integration_enterprise_test.go +++ b/server/service/integration_enterprise_test.go @@ -51,6 +51,7 @@ import ( "github.com/fleetdm/fleet/v4/server/live_query/live_query_mock" "github.com/fleetdm/fleet/v4/server/mdm" maintained_apps "github.com/fleetdm/fleet/v4/server/mdm/maintainedapps" + mdmtest "github.com/fleetdm/fleet/v4/server/mdm/testing_utils" "github.com/fleetdm/fleet/v4/server/policies" "github.com/fleetdm/fleet/v4/server/ptr" "github.com/fleetdm/fleet/v4/server/pubsub" @@ -180,6 +181,9 @@ func (s *integrationEnterpriseTestSuite) clearOktaConditionalAccess() { func (s *integrationEnterpriseTestSuite) TestTeamSpecs() { t := s.T() + // Mock Apple GDMF API (required for validating OS update minimum version settings) + mdmtest.StartNewAppleGDMFTestServer(t) + // create a team through the service so it initializes the agent ops teamName := t.Name() + "team1" teamNameDecomposed := teamName + "ᄀ" + "ᅡ" // Add a decomposed Unicode character @@ -228,16 +232,16 @@ func (s *integrationEnterpriseTestSuite) TestTeamSpecs() { "features": &features, "mdm": map[string]any{ "macos_updates": map[string]any{ - "minimum_version": "10.15.0", + "minimum_version": "14.6.1", "deadline": "2021-01-01", "update_new_hosts": true, }, "ios_updates": map[string]any{ - "minimum_version": "17.5.1", + "minimum_version": "17.6.1", "deadline": "2024-07-23", }, "ipados_updates": map[string]any{ - "minimum_version": "18.0", + "minimum_version": "17.6.1", "deadline": "2024-08-24", }, }, @@ -260,17 +264,17 @@ func (s *integrationEnterpriseTestSuite) TestTeamSpecs() { }, team.Config.Features) require.Equal(t, fleet.TeamMDM{ MacOSUpdates: fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("10.15.0"), + MinimumVersion: optjson.SetString("14.6.1"), Deadline: optjson.SetString("2021-01-01"), UpdateNewHosts: optjson.SetBool(true), }, IOSUpdates: fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("17.5.1"), + MinimumVersion: optjson.SetString("17.6.1"), Deadline: optjson.SetString("2024-07-23"), UpdateNewHosts: optjson.Bool{Set: true}, }, IPadOSUpdates: fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("18.0"), + MinimumVersion: optjson.SetString("17.6.1"), Deadline: optjson.SetString("2024-08-24"), UpdateNewHosts: optjson.Bool{Set: true}, }, @@ -393,17 +397,17 @@ func (s *integrationEnterpriseTestSuite) TestTeamSpecs() { require.Equal(t, applyResp.TeamIDsByName[teamName], team.ID) require.Equal(t, fleet.TeamMDM{ MacOSUpdates: fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("10.15.0"), + MinimumVersion: optjson.SetString("14.6.1"), Deadline: optjson.SetString("2021-01-01"), UpdateNewHosts: optjson.SetBool(true), }, IOSUpdates: fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("17.5.1"), + MinimumVersion: optjson.SetString("17.6.1"), Deadline: optjson.SetString("2024-07-23"), UpdateNewHosts: optjson.Bool{Set: true}, }, IPadOSUpdates: fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("18.0"), + MinimumVersion: optjson.SetString("17.6.1"), Deadline: optjson.SetString("2024-08-24"), UpdateNewHosts: optjson.Bool{Set: true}, }, @@ -434,17 +438,17 @@ func (s *integrationEnterpriseTestSuite) TestTeamSpecs() { s.DoJSON("GET", "/api/latest/fleet/teams/"+fmt.Sprint(team.ID), nil, http.StatusOK, &getTmResp) require.Equal(t, fleet.TeamMDM{ MacOSUpdates: fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("10.15.0"), + MinimumVersion: optjson.SetString("14.6.1"), Deadline: optjson.SetString("2021-01-01"), UpdateNewHosts: optjson.SetBool(true), }, IOSUpdates: fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("17.5.1"), + MinimumVersion: optjson.SetString("17.6.1"), Deadline: optjson.SetString("2024-07-23"), UpdateNewHosts: optjson.Bool{Set: true}, }, IPadOSUpdates: fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("18.0"), + MinimumVersion: optjson.SetString("17.6.1"), Deadline: optjson.SetString("2024-08-24"), UpdateNewHosts: optjson.Bool{Set: true}, }, @@ -477,17 +481,17 @@ func (s *integrationEnterpriseTestSuite) TestTeamSpecs() { require.Equal(t, team.ID, listTmResp.Teams[0].ID) require.Equal(t, fleet.TeamMDM{ MacOSUpdates: fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("10.15.0"), + MinimumVersion: optjson.SetString("14.6.1"), Deadline: optjson.SetString("2021-01-01"), UpdateNewHosts: optjson.SetBool(true), }, IOSUpdates: fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("17.5.1"), + MinimumVersion: optjson.SetString("17.6.1"), Deadline: optjson.SetString("2024-07-23"), UpdateNewHosts: optjson.Bool{Set: true}, }, IPadOSUpdates: fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("18.0"), + MinimumVersion: optjson.SetString("17.6.1"), Deadline: optjson.SetString("2024-08-24"), UpdateNewHosts: optjson.Bool{Set: true}, }, @@ -2979,6 +2983,9 @@ func (s *integrationEnterpriseTestSuite) TestNoTeamFailingPolicyWebhookTrigger() func (s *integrationEnterpriseTestSuite) TestWindowsUpdatesTeamConfig() { t := s.T() + // Mock Apple GDMF API (required for validating OS update minimum version settings) + mdmtest.StartNewAppleGDMFTestServer(t) + // Create a team team := &fleet.Team{ Name: t.Name(), @@ -3075,12 +3082,12 @@ func (s *integrationEnterpriseTestSuite) TestWindowsUpdatesTeamConfig() { s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{ "mdm": map[string]any{ "macos_updates": &fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("10.15.0"), + MinimumVersion: optjson.SetString("14.6.1"), Deadline: optjson.SetString("2021-01-01"), }, }, }, http.StatusOK, &tmResp) - require.Equal(t, "10.15.0", tmResp.Team.Config.MDM.MacOSUpdates.MinimumVersion.Value) + require.Equal(t, "14.6.1", tmResp.Team.Config.MDM.MacOSUpdates.MinimumVersion.Value) require.Equal(t, "2021-01-01", tmResp.Team.Config.MDM.MacOSUpdates.Deadline.Value) require.Equal(t, 6, tmResp.Team.Config.MDM.WindowsUpdates.DeadlineDays.Value) require.Equal(t, 2, tmResp.Team.Config.MDM.WindowsUpdates.GracePeriodDays.Value) @@ -3102,7 +3109,7 @@ func (s *integrationEnterpriseTestSuite) TestWindowsUpdatesTeamConfig() { "windows_updates": nil, }, }, http.StatusOK, &tmResp) - require.Equal(t, "10.15.0", tmResp.Team.Config.MDM.MacOSUpdates.MinimumVersion.Value) + require.Equal(t, "14.6.1", tmResp.Team.Config.MDM.MacOSUpdates.MinimumVersion.Value) require.Equal(t, "2021-01-01", tmResp.Team.Config.MDM.MacOSUpdates.Deadline.Value) require.Equal(t, 6, tmResp.Team.Config.MDM.WindowsUpdates.DeadlineDays.Value) require.Equal(t, 2, tmResp.Team.Config.MDM.WindowsUpdates.GracePeriodDays.Value) @@ -3246,6 +3253,9 @@ func (s *integrationEnterpriseTestSuite) assertAppleOSUpdatesDeclaration(teamID func (s *integrationEnterpriseTestSuite) TestAppleOSUpdatesTeamConfig() { t := s.T() + // Mock Apple GDMF API (required for validating OS update minimum version settings) + mdmtest.StartNewAppleGDMFTestServer(t) + team := &fleet.Team{ Name: t.Name(), Description: "Team description", @@ -3263,7 +3273,7 @@ func (s *integrationEnterpriseTestSuite) TestAppleOSUpdatesTeamConfig() { // modify the team's config (macOS first) macOSUpdates := &fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("10.15.0"), + MinimumVersion: optjson.SetString("14.6.1"), Deadline: optjson.SetString("2021-01-01"), UpdateNewHosts: optjson.SetBool(true), } @@ -3272,13 +3282,13 @@ func (s *integrationEnterpriseTestSuite) TestAppleOSUpdatesTeamConfig() { "macos_updates": macOSUpdates, }, }, http.StatusOK, &tmResp) - require.Equal(t, "10.15.0", tmResp.Team.Config.MDM.MacOSUpdates.MinimumVersion.Value) + require.Equal(t, "14.6.1", tmResp.Team.Config.MDM.MacOSUpdates.MinimumVersion.Value) require.Equal(t, "2021-01-01", tmResp.Team.Config.MDM.MacOSUpdates.Deadline.Value) require.Equal(t, true, tmResp.Team.Config.MDM.MacOSUpdates.UpdateNewHosts.Value) s.lastActivityOfTypeMatches( fleet.ActivityTypeEditedMacOSMinVersion{}.ActivityName(), - fmt.Sprintf(`{"team_id": %d, "team_name": %q, "fleet_id": %d, "fleet_name": %q, "minimum_version": "10.15.0", "deadline": "2021-01-01"}`, team.ID, team.Name, team.ID, team.Name), 0) + fmt.Sprintf(`{"team_id": %d, "team_name": %q, "fleet_id": %d, "fleet_name": %q, "minimum_version": "14.6.1", "deadline": "2021-01-01"}`, team.ID, team.Name, team.ID, team.Name), 0) s.lastActivityOfTypeMatches( fleet.ActivityTypeEnabledMacosUpdateNewHosts{}.ActivityName(), @@ -3295,12 +3305,12 @@ func (s *integrationEnterpriseTestSuite) TestAppleOSUpdatesTeamConfig() { // modify the team's config (now iOS and iPadOS) iOSUpdates := &fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("11.11.11"), + MinimumVersion: optjson.SetString("17.6.1"), Deadline: optjson.SetString("2022-02-02"), UpdateNewHosts: optjson.SetBool(true), } iPadOSUpdates := &fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("12.12.12"), + MinimumVersion: optjson.SetString("17.6.1"), Deadline: optjson.SetString("2023-03-03"), UpdateNewHosts: optjson.SetBool(true), } @@ -3311,23 +3321,23 @@ func (s *integrationEnterpriseTestSuite) TestAppleOSUpdatesTeamConfig() { "ipados_updates": iPadOSUpdates, }, }, http.StatusOK, &tmResp) - require.Equal(t, "10.15.0", tmResp.Team.Config.MDM.MacOSUpdates.MinimumVersion.Value) + require.Equal(t, "14.6.1", 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, "17.6.1", 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, "17.6.1", 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, "fleet_id": %d, "fleet_name": %q, "minimum_version": "10.15.0", "deadline": "2021-01-01"}`, team.ID, team.Name, team.ID, team.Name), 0) - s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIOSMinVersion{}.ActivityName(), fmt.Sprintf(`{"team_id": %d, "team_name": %q, "fleet_id": %d, "fleet_name": %q, "minimum_version": "11.11.11", "deadline": "2022-02-02"}`, team.ID, team.Name, team.ID, team.Name), 0) - s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIPadOSMinVersion{}.ActivityName(), fmt.Sprintf(`{"team_id": %d, "team_name": %q, "fleet_id": %d, "fleet_name": %q, "minimum_version": "12.12.12", "deadline": "2023-03-03"}`, team.ID, team.Name, team.ID, team.Name), 0) + s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedMacOSMinVersion{}.ActivityName(), fmt.Sprintf(`{"team_id": %d, "team_name": %q, "fleet_id": %d, "fleet_name": %q, "minimum_version": "14.6.1", "deadline": "2021-01-01"}`, team.ID, team.Name, team.ID, team.Name), 0) + s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIOSMinVersion{}.ActivityName(), fmt.Sprintf(`{"team_id": %d, "team_name": %q, "fleet_id": %d, "fleet_name": %q, "minimum_version": "17.6.1", "deadline": "2022-02-02"}`, team.ID, team.Name, team.ID, team.Name), 0) + s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIPadOSMinVersion{}.ActivityName(), fmt.Sprintf(`{"team_id": %d, "team_name": %q, "fleet_id": %d, "fleet_name": %q, "minimum_version": "17.6.1", "deadline": "2023-03-03"}`, team.ID, team.Name, team.ID, team.Name), 0) s.assertAppleOSUpdatesDeclaration(&team.ID, mdm.FleetMacOSUpdatesProfileName, macOSUpdates) s.assertAppleOSUpdatesDeclaration(&team.ID, mdm.FleetIOSUpdatesProfileName, iOSUpdates) @@ -3335,16 +3345,16 @@ func (s *integrationEnterpriseTestSuite) TestAppleOSUpdatesTeamConfig() { // only update the deadlines macOSUpdates = &fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("10.15.0"), + MinimumVersion: optjson.SetString("14.6.1"), Deadline: optjson.SetString("2025-10-01"), UpdateNewHosts: optjson.SetBool(true), } iOSUpdates = &fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("11.11.11"), + MinimumVersion: optjson.SetString("17.6.1"), Deadline: optjson.SetString("2024-02-02"), } iPadOSUpdates = &fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("12.12.12"), + MinimumVersion: optjson.SetString("17.6.1"), Deadline: optjson.SetString("2024-03-03"), } s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{ @@ -3354,21 +3364,21 @@ func (s *integrationEnterpriseTestSuite) TestAppleOSUpdatesTeamConfig() { "ipados_updates": iPadOSUpdates, }, }, http.StatusOK, &tmResp) - require.Equal(t, "10.15.0", tmResp.Team.Config.MDM.MacOSUpdates.MinimumVersion.Value) + require.Equal(t, "14.6.1", 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, "17.6.1", 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, "17.6.1", 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, "fleet_id": %d, "fleet_name": %q, "minimum_version": "10.15.0", "deadline": "2025-10-01"}`, team.ID, team.Name, team.ID, team.Name), 0) - iOSLastActivity := s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIOSMinVersion{}.ActivityName(), fmt.Sprintf(`{"team_id": %d, "team_name": %q, "fleet_id": %d, "fleet_name": %q, "minimum_version": "11.11.11", "deadline": "2024-02-02"}`, team.ID, team.Name, team.ID, team.Name), 0) - iPadOSLastActivity := s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIPadOSMinVersion{}.ActivityName(), fmt.Sprintf(`{"team_id": %d, "team_name": %q, "fleet_id": %d, "fleet_name": %q, "minimum_version": "12.12.12", "deadline": "2024-03-03"}`, team.ID, team.Name, team.ID, team.Name), 0) + macOSLastActivity := s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedMacOSMinVersion{}.ActivityName(), fmt.Sprintf(`{"team_id": %d, "team_name": %q, "fleet_id": %d, "fleet_name": %q, "minimum_version": "14.6.1", "deadline": "2025-10-01"}`, team.ID, team.Name, team.ID, team.Name), 0) + iOSLastActivity := s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIOSMinVersion{}.ActivityName(), fmt.Sprintf(`{"team_id": %d, "team_name": %q, "fleet_id": %d, "fleet_name": %q, "minimum_version": "17.6.1", "deadline": "2024-02-02"}`, team.ID, team.Name, team.ID, team.Name), 0) + iPadOSLastActivity := s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIPadOSMinVersion{}.ActivityName(), fmt.Sprintf(`{"team_id": %d, "team_name": %q, "fleet_id": %d, "fleet_name": %q, "minimum_version": "17.6.1", "deadline": "2024-03-03"}`, team.ID, team.Name, team.ID, team.Name), 0) s.assertAppleOSUpdatesDeclaration(&team.ID, mdm.FleetMacOSUpdatesProfileName, macOSUpdates) s.assertAppleOSUpdatesDeclaration(&team.ID, mdm.FleetIOSUpdatesProfileName, iOSUpdates) @@ -3376,16 +3386,16 @@ func (s *integrationEnterpriseTestSuite) TestAppleOSUpdatesTeamConfig() { // Unchecking the UpdateNewHosts flag should register as an activity macOSUpdates = &fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("10.15.0"), + MinimumVersion: optjson.SetString("14.6.1"), Deadline: optjson.SetString("2025-10-01"), UpdateNewHosts: optjson.SetBool(false), } iOSUpdates = &fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("11.11.11"), + MinimumVersion: optjson.SetString("17.6.1"), Deadline: optjson.SetString("2024-02-02"), } iPadOSUpdates = &fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("12.12.12"), + MinimumVersion: optjson.SetString("17.6.1"), Deadline: optjson.SetString("2024-03-03"), } s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{ @@ -3415,11 +3425,11 @@ func (s *integrationEnterpriseTestSuite) TestAppleOSUpdatesTeamConfig() { }, }, }, http.StatusOK, &tmResp) - require.Equal(t, "10.15.0", tmResp.Team.Config.MDM.MacOSUpdates.MinimumVersion.Value) + require.Equal(t, "14.6.1", tmResp.Team.Config.MDM.MacOSUpdates.MinimumVersion.Value) require.Equal(t, "2025-10-01", tmResp.Team.Config.MDM.MacOSUpdates.Deadline.Value) - require.Equal(t, "11.11.11", tmResp.Team.Config.MDM.IOSUpdates.MinimumVersion.Value) + require.Equal(t, "17.6.1", tmResp.Team.Config.MDM.IOSUpdates.MinimumVersion.Value) require.Equal(t, "2024-02-02", tmResp.Team.Config.MDM.IOSUpdates.Deadline.Value) - require.Equal(t, "12.12.12", tmResp.Team.Config.MDM.IPadOSUpdates.MinimumVersion.Value) + require.Equal(t, "17.6.1", tmResp.Team.Config.MDM.IPadOSUpdates.MinimumVersion.Value) require.Equal(t, "2024-03-03", tmResp.Team.Config.MDM.IPadOSUpdates.Deadline.Value) require.Equal(t, 10, tmResp.Team.Config.MDM.WindowsUpdates.DeadlineDays.Value) require.Equal(t, 2, tmResp.Team.Config.MDM.WindowsUpdates.GracePeriodDays.Value) @@ -3442,11 +3452,11 @@ func (s *integrationEnterpriseTestSuite) TestAppleOSUpdatesTeamConfig() { "macos_updates": nil, }, }, http.StatusOK, &tmResp) - require.Equal(t, "10.15.0", tmResp.Team.Config.MDM.MacOSUpdates.MinimumVersion.Value) + require.Equal(t, "14.6.1", tmResp.Team.Config.MDM.MacOSUpdates.MinimumVersion.Value) require.Equal(t, "2025-10-01", tmResp.Team.Config.MDM.MacOSUpdates.Deadline.Value) - require.Equal(t, "11.11.11", tmResp.Team.Config.MDM.IOSUpdates.MinimumVersion.Value) + require.Equal(t, "17.6.1", tmResp.Team.Config.MDM.IOSUpdates.MinimumVersion.Value) require.Equal(t, "2024-02-02", tmResp.Team.Config.MDM.IOSUpdates.Deadline.Value) - require.Equal(t, "12.12.12", tmResp.Team.Config.MDM.IPadOSUpdates.MinimumVersion.Value) + require.Equal(t, "17.6.1", tmResp.Team.Config.MDM.IPadOSUpdates.MinimumVersion.Value) require.Equal(t, "2024-03-03", tmResp.Team.Config.MDM.IPadOSUpdates.Deadline.Value) // no new activity is created s.lastActivityMatches("", "", lastActivity) @@ -3463,7 +3473,7 @@ func (s *integrationEnterpriseTestSuite) TestAppleOSUpdatesTeamConfig() { }, }, }, http.StatusOK, &tmResp) - require.Equal(t, "10.15.0", tmResp.Team.Config.MDM.MacOSUpdates.MinimumVersion.Value) + require.Equal(t, "14.6.1", tmResp.Team.Config.MDM.MacOSUpdates.MinimumVersion.Value) require.Equal(t, "2025-10-01", tmResp.Team.Config.MDM.MacOSUpdates.Deadline.Value) // no new activity is created s.lastActivityMatches("", "", lastActivity) @@ -3509,7 +3519,7 @@ func (s *integrationEnterpriseTestSuite) TestAppleOSUpdatesTeamConfig() { s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{ "mdm": map[string]any{ "macos_updates": map[string]any{ - "minimum_version": "10.15.0", + "minimum_version": "14.6.1", "deadline": "2021-01-01T00:00:00Z", }, }, @@ -3517,7 +3527,7 @@ func (s *integrationEnterpriseTestSuite) TestAppleOSUpdatesTeamConfig() { s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{ "mdm": map[string]any{ "ios_updates": map[string]any{ - "minimum_version": "10.15.0", + "minimum_version": "14.6.1", "deadline": "2021-01-01T00:00:00Z", }, }, @@ -3525,7 +3535,7 @@ func (s *integrationEnterpriseTestSuite) TestAppleOSUpdatesTeamConfig() { s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{ "mdm": map[string]any{ "ipados_updates": map[string]any{ - "minimum_version": "10.15.0", + "minimum_version": "14.6.1", "deadline": "2021-01-01T00:00:00Z", }, }, @@ -3535,7 +3545,7 @@ func (s *integrationEnterpriseTestSuite) TestAppleOSUpdatesTeamConfig() { s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{ "mdm": map[string]any{ "macos_updates": map[string]any{ - "minimum_version": "10.15.0 (19A583)", + "minimum_version": "14.6.1 (19A583)", "deadline": "2021-01-01T00:00:00Z", }, }, @@ -3543,7 +3553,7 @@ func (s *integrationEnterpriseTestSuite) TestAppleOSUpdatesTeamConfig() { s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{ "mdm": map[string]any{ "ios_updates": map[string]any{ - "minimum_version": "10.15.0 (19A583)", + "minimum_version": "14.6.1 (19A583)", "deadline": "2021-01-01T00:00:00Z", }, }, @@ -3551,7 +3561,7 @@ func (s *integrationEnterpriseTestSuite) TestAppleOSUpdatesTeamConfig() { s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{ "mdm": map[string]any{ "ipados_updates": map[string]any{ - "minimum_version": "10.15.0 (19A583)", + "minimum_version": "14.6.1 (19A583)", "deadline": "2021-01-01T00:00:00Z", }, }, @@ -3607,21 +3617,21 @@ func (s *integrationEnterpriseTestSuite) TestAppleOSUpdatesTeamConfig() { s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{ "mdm": map[string]any{ "macos_updates": map[string]any{ - "minimum_version": "10.15.0 (19A583)", + "minimum_version": "14.6.1 (19A583)", }, }, }, http.StatusUnprocessableEntity, &tmResp) s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{ "mdm": map[string]any{ "ios_updates": map[string]any{ - "minimum_version": "10.15.0 (19A583)", + "minimum_version": "14.6.1 (19A583)", }, }, }, http.StatusUnprocessableEntity, &tmResp) s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), map[string]any{ "mdm": map[string]any{ "ipados_updates": map[string]any{ - "minimum_version": "10.15.0 (19A583)", + "minimum_version": "14.6.1 (19A583)", }, }, }, http.StatusUnprocessableEntity, &tmResp) @@ -4363,6 +4373,9 @@ func (s *integrationEnterpriseTestSuite) TestMDMWindowsUpdates() { func (s *integrationEnterpriseTestSuite) TestMDMAppleOSUpdates() { t := s.T() + // Mock Apple GDMF API (required for validating OS update minimum version settings) + mdmtest.StartNewAppleGDMFTestServer(t) + // keep the last activity, to detect newly created ones var activitiesResp listActivitiesResponse s.DoJSON("GET", "/api/latest/fleet/activities", nil, http.StatusOK, &activitiesResp, "order_key", "a.id", "order_direction", "desc") @@ -4489,64 +4502,64 @@ func (s *integrationEnterpriseTestSuite) TestMDMAppleOSUpdates() { s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{ "mdm": { "macos_updates": { - "minimum_version": "12.3.1", + "minimum_version": "13.6.9", "deadline": "2022-01-01", "update_new_hosts": true }, "ios_updates": { - "minimum_version": "13.13.13", + "minimum_version": "17.6", "deadline": "2023-03-03", "update_new_hosts": true }, "ipados_updates": { - "minimum_version": "14.14.14", + "minimum_version": "17.6", "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, "13.6.9", 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, "17.6", 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, "17.6", 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, "fleet_id": null, "fleet_name": null}`, 0) - s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIOSMinVersion{}.ActivityName(), `{"deadline":"2023-03-03", "minimum_version":"13.13.13", "team_id": null, "team_name": null, "fleet_id": null, "fleet_name": null}`, 0) - s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIPadOSMinVersion{}.ActivityName(), `{"deadline":"2024-04-04", "minimum_version":"14.14.14", "team_id": null, "team_name": null, "fleet_id": null, "fleet_name": null}`, 0) + s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedMacOSMinVersion{}.ActivityName(), `{"deadline":"2022-01-01", "minimum_version":"13.6.9", "team_id": null, "team_name": null, "fleet_id": null, "fleet_name": null}`, 0) + s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIOSMinVersion{}.ActivityName(), `{"deadline":"2023-03-03", "minimum_version":"17.6", "team_id": null, "team_name": null, "fleet_id": null, "fleet_name": null}`, 0) + s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIPadOSMinVersion{}.ActivityName(), `{"deadline":"2024-04-04", "minimum_version":"17.6", "team_id": null, "team_name": null, "fleet_id": null, "fleet_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"), + MinimumVersion: optjson.SetString("13.6.9"), Deadline: optjson.SetString("2022-01-01"), }) s.assertAppleOSUpdatesDeclaration(nil, mdm.FleetIOSUpdatesProfileName, &fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("13.13.13"), Deadline: optjson.SetString("2023-03-03"), + MinimumVersion: optjson.SetString("17.6"), Deadline: optjson.SetString("2023-03-03"), }) s.assertAppleOSUpdatesDeclaration(nil, mdm.FleetIPadOSUpdatesProfileName, &fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("14.14.14"), Deadline: optjson.SetString("2024-04-04"), + MinimumVersion: optjson.SetString("17.6"), Deadline: optjson.SetString("2024-04-04"), }) // get the appconfig acResp = appConfigResponse{} 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, "13.6.9", 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, "17.6", 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, "17.6", 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) @@ -4555,63 +4568,63 @@ func (s *integrationEnterpriseTestSuite) TestMDMAppleOSUpdates() { s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{ "mdm": { "macos_updates": { - "minimum_version": "12.3.1", + "minimum_version": "13.6.9", "deadline": "2024-01-01", "update_new_hosts": false }, "ios_updates": { - "minimum_version": "13.13.13", + "minimum_version": "17.6", "deadline": "2025-05-05" }, "ipados_updates": { - "minimum_version": "14.14.14", + "minimum_version": "17.6", "deadline": "2026-06-06" } } }`), http.StatusOK, &acResp) - require.Equal(t, "12.3.1", acResp.MDM.MacOSUpdates.MinimumVersion.Value) + require.Equal(t, "13.6.9", 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, "17.6", 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, "17.6", 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, "fleet_id": null, "fleet_name": null}`, 0) - s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIOSMinVersion{}.ActivityName(), `{"deadline":"2025-05-05", "minimum_version":"13.13.13", "team_id": null, "team_name": null, "fleet_id": null, "fleet_name": null}`, 0) - lastActivity = s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIPadOSMinVersion{}.ActivityName(), `{"deadline":"2026-06-06", "minimum_version":"14.14.14", "team_id": null, "team_name": null, "fleet_id": null, "fleet_name": null}`, 0) + s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedMacOSMinVersion{}.ActivityName(), `{"deadline":"2024-01-01", "minimum_version":"13.6.9", "team_id": null, "team_name": null, "fleet_id": null, "fleet_name": null}`, 0) + s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIOSMinVersion{}.ActivityName(), `{"deadline":"2025-05-05", "minimum_version":"17.6", "team_id": null, "team_name": null, "fleet_id": null, "fleet_name": null}`, 0) + lastActivity = s.lastActivityOfTypeMatches(fleet.ActivityTypeEditedIPadOSMinVersion{}.ActivityName(), `{"deadline":"2026-06-06", "minimum_version":"17.6", "team_id": null, "team_name": null, "fleet_id": null, "fleet_name": null}`, 0) s.assertAppleOSUpdatesDeclaration(nil, mdm.FleetMacOSUpdatesProfileName, &fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("12.3.1"), Deadline: optjson.SetString("2024-01-01"), + MinimumVersion: optjson.SetString("13.6.9"), Deadline: optjson.SetString("2024-01-01"), }) s.assertAppleOSUpdatesDeclaration(nil, mdm.FleetIOSUpdatesProfileName, &fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("13.13.13"), Deadline: optjson.SetString("2025-05-05"), + MinimumVersion: optjson.SetString("17.6"), Deadline: optjson.SetString("2025-05-05"), }) s.assertAppleOSUpdatesDeclaration(nil, mdm.FleetIPadOSUpdatesProfileName, &fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("14.14.14"), Deadline: optjson.SetString("2026-06-06"), + MinimumVersion: optjson.SetString("17.6"), Deadline: optjson.SetString("2026-06-06"), }) // update something unrelated - the transparency url acResp = appConfigResponse{} s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{"fleet_desktop":{"transparency_url": "customURL"}}`), http.StatusOK, &acResp) - require.Equal(t, "12.3.1", acResp.MDM.MacOSUpdates.MinimumVersion.Value) + require.Equal(t, "13.6.9", acResp.MDM.MacOSUpdates.MinimumVersion.Value) require.Equal(t, "2024-01-01", acResp.MDM.MacOSUpdates.Deadline.Value) // no activity got created s.lastActivityMatches("", ``, lastActivity) s.assertAppleOSUpdatesDeclaration(nil, mdm.FleetMacOSUpdatesProfileName, &fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("12.3.1"), Deadline: optjson.SetString("2024-01-01"), + MinimumVersion: optjson.SetString("13.6.9"), Deadline: optjson.SetString("2024-01-01"), }) s.assertAppleOSUpdatesDeclaration(nil, mdm.FleetIOSUpdatesProfileName, &fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("13.13.13"), Deadline: optjson.SetString("2025-05-05"), + MinimumVersion: optjson.SetString("17.6"), Deadline: optjson.SetString("2025-05-05"), }) s.assertAppleOSUpdatesDeclaration(nil, mdm.FleetIPadOSUpdatesProfileName, &fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("14.14.14"), Deadline: optjson.SetString("2026-06-06"), + MinimumVersion: optjson.SetString("17.6"), Deadline: optjson.SetString("2026-06-06"), }) // clear the apple OS requirements diff --git a/server/service/integration_mdm_profiles_test.go b/server/service/integration_mdm_profiles_test.go index 9175101b1e..d210b96ac6 100644 --- a/server/service/integration_mdm_profiles_test.go +++ b/server/service/integration_mdm_profiles_test.go @@ -579,7 +579,7 @@ func (s *integrationMDMTestSuite) TestAppleProfileManagement() { "mdm": { "macos_updates": { "deadline": "2023-12-31", - "minimum_version": "13.3.7" + "minimum_version": "14.6.1" } } }`), http.StatusOK) @@ -587,7 +587,7 @@ func (s *integrationMDMTestSuite) TestAppleProfileManagement() { MDM: &fleet.TeamPayloadMDM{ MacOSUpdates: &fleet.AppleOSUpdateSettings{ Deadline: optjson.SetString("1992-01-01"), - MinimumVersion: optjson.SetString("13.1.1"), + MinimumVersion: optjson.SetString("14.6.1"), }, }, }, http.StatusOK) @@ -5275,7 +5275,7 @@ func (s *integrationMDMTestSuite) TestMDMBatchSetProfilesKeepsReservedNames() { }, "macos_updates": { "deadline": "2023-12-31", - "minimum_version": "13.3.7" + "minimum_version": "14.6.1" } } }`), http.StatusOK, &acResp) @@ -5328,7 +5328,7 @@ func (s *integrationMDMTestSuite) TestMDMBatchSetProfilesKeepsReservedNames() { }, MacOSUpdates: &fleet.AppleOSUpdateSettings{ Deadline: optjson.SetString("2023-12-31"), - MinimumVersion: optjson.SetString("13.3.8"), + MinimumVersion: optjson.SetString("14.6.1"), UpdateNewHosts: optjson.SetBool(true), }, }, @@ -5340,7 +5340,7 @@ func (s *integrationMDMTestSuite) TestMDMBatchSetProfilesKeepsReservedNames() { require.Equal(t, 4, tmResp.Team.Config.MDM.WindowsUpdates.DeadlineDays.Value) 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, "14.6.1", 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)) @@ -8814,15 +8814,15 @@ func (s *integrationMDMTestSuite) TestSpecTeamsOSUpdatesDeployToHosts() { Name: teamName, MDM: fleet.TeamSpecMDM{ MacOSUpdates: fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("14.1.0"), + MinimumVersion: optjson.SetString("14.6.1"), Deadline: optjson.SetString("2024-03-01"), }, IOSUpdates: fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("17.1.0"), + MinimumVersion: optjson.SetString("17.6.1"), Deadline: optjson.SetString("2024-03-01"), }, IPadOSUpdates: fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("17.1.0"), + MinimumVersion: optjson.SetString("17.6.1"), Deadline: optjson.SetString("2024-03-01"), }, WindowsUpdates: fleet.WindowsUpdates{ @@ -8878,11 +8878,11 @@ func (s *integrationMDMTestSuite) TestSpecTeamsOSUpdatesDeployToHosts() { Deadline: optjson.SetString("2025-06-01"), }, IOSUpdates: fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("17.3.0"), + MinimumVersion: optjson.SetString("16.7.2"), Deadline: optjson.SetString("2025-06-01"), }, IPadOSUpdates: fleet.AppleOSUpdateSettings{ - MinimumVersion: optjson.SetString("17.3.0"), + MinimumVersion: optjson.SetString("16.7.2"), Deadline: optjson.SetString("2025-06-01"), }, WindowsUpdates: fleet.WindowsUpdates{ diff --git a/server/service/integration_mdm_test.go b/server/service/integration_mdm_test.go index 65a0cadfdd..f3a6ebe660 100644 --- a/server/service/integration_mdm_test.go +++ b/server/service/integration_mdm_test.go @@ -709,8 +709,8 @@ func (s *integrationMDMTestSuite) SetupSuite() { _, err = w.Write(b) require.NoError(s.T(), err) })) + dev_mode.SetOverride("FLEET_DEV_GDMF_URL", s.appleGDMFSrv.URL, s.T()) - s.T().Setenv("FLEET_DEV_GDMF_URL", s.appleGDMFSrv.URL) s.T().Setenv("TEST_FLEETDM_API_URL", fleetdmSrv.URL) s.T().Setenv("FLEET_DEV_STOKEN_AUTHENTICATED_APPS_URL", s.appleVPPProxySrv.URL) @@ -7950,7 +7950,7 @@ func (s *integrationMDMTestSuite) TestOrbitConfigNudgeSettings() { mdm: macos_updates: deadline: 2022-01-04 - minimum_version: 12.1.3 + minimum_version: 14.6.1 `)) // still empty if MDM is turned off for the host @@ -7971,7 +7971,7 @@ func (s *integrationMDMTestSuite) TestOrbitConfigNudgeSettings() { resp = orbitGetConfigResponse{} s.DoJSON("POST", "/api/fleet/orbit/config", json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *h.OrbitNodeKey)), http.StatusOK, &resp) - wantCfg, err := fleet.NewNudgeConfig(fleet.AppleOSUpdateSettings{Deadline: optjson.SetString("2022-01-04"), MinimumVersion: optjson.SetString("12.1.3")}) + wantCfg, err := fleet.NewNudgeConfig(fleet.AppleOSUpdateSettings{Deadline: optjson.SetString("2022-01-04"), MinimumVersion: optjson.SetString("14.6.1")}) require.NoError(t, err) require.Equal(t, wantCfg, resp.NudgeConfig) require.Equal(t, wantCfg.OSVersionRequirements[0].RequiredInstallationDate.String(), "2022-01-04 20:00:00 +0000 UTC") @@ -8001,7 +8001,7 @@ func (s *integrationMDMTestSuite) TestOrbitConfigNudgeSettings() { MDM: &fleet.TeamPayloadMDM{ MacOSUpdates: &fleet.AppleOSUpdateSettings{ Deadline: optjson.SetString("1992-01-01"), - MinimumVersion: optjson.SetString("13.1.1"), + MinimumVersion: optjson.SetString("13.6.9"), }, }, }, http.StatusOK, &tmResp) @@ -8009,7 +8009,7 @@ func (s *integrationMDMTestSuite) TestOrbitConfigNudgeSettings() { resp = orbitGetConfigResponse{} s.DoJSON("POST", "/api/fleet/orbit/config", json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *h.OrbitNodeKey)), http.StatusOK, &resp) - wantCfg, err = fleet.NewNudgeConfig(fleet.AppleOSUpdateSettings{Deadline: optjson.SetString("1992-01-01"), MinimumVersion: optjson.SetString("13.1.1")}) + wantCfg, err = fleet.NewNudgeConfig(fleet.AppleOSUpdateSettings{Deadline: optjson.SetString("1992-01-01"), MinimumVersion: optjson.SetString("13.6.9")}) require.NoError(t, err) require.Equal(t, wantCfg, resp.NudgeConfig) require.Equal(t, wantCfg.OSVersionRequirements[0].RequiredInstallationDate.String(), "1992-01-01 20:00:00 +0000 UTC") @@ -8031,7 +8031,7 @@ func (s *integrationMDMTestSuite) TestOrbitConfigNudgeSettings() { resp = orbitGetConfigResponse{} s.DoJSON("POST", "/api/fleet/orbit/config", json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *h2.OrbitNodeKey)), http.StatusOK, &resp) - wantCfg, err = fleet.NewNudgeConfig(fleet.AppleOSUpdateSettings{Deadline: optjson.SetString("2022-01-04"), MinimumVersion: optjson.SetString("12.1.3")}) + wantCfg, err = fleet.NewNudgeConfig(fleet.AppleOSUpdateSettings{Deadline: optjson.SetString("2022-01-04"), MinimumVersion: optjson.SetString("14.6.1")}) require.NoError(t, err) require.Equal(t, wantCfg, resp.NudgeConfig) require.Equal(t, wantCfg.OSVersionRequirements[0].RequiredInstallationDate.String(), "2022-01-04 20:00:00 +0000 UTC")