From c4479c6a841ff308ac585b09b536b6873347b62f Mon Sep 17 00:00:00 2001 From: Victor Lyuboslavsky <2685025+getvictor@users.noreply.github.com> Date: Mon, 6 Apr 2026 17:39:59 -0500 Subject: [PATCH] Add require_all_software_windows config option (#43011) **Related issue:** Resolves #42853 This PR simply adds the `require_all_software_windows` config option. It doesn't use it. The logic to use it will be hooked up in subsequent PRs. The fleetctl TestIntegrationsPreview test is expected to fail since it builds the server against main and doesn't know about our new config option. # Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. ## Testing - [x] Added/updated automated tests - [x] QA'd all new/changed functionality manually ## New Fleet configuration settings - [x] Verified that the setting is exported via `fleetctl generate-gitops` - Not exported. generate-gitops does not export require_all_software_windows (or require_all_software_macos either). The generateControls function (generate_gitops.go) outputs a "TODO: update with your setup_experience configuration" placeholder when any setup experience config exists, rather than exporting individual field values. This is a pre-existing limitation that applies equally to both fields - not something introduced by our PR. - [x] Verified the setting is documented in a separate PR to [the GitOps documentation](https://github.com/fleetdm/fleet/blob/main/docs/Configuration/yaml-files.md#L485) - Yes. PR #42046 adds require_all_software_windows to both docs/REST API/rest-api.md and docs/Configuration/yaml-files.md. - [x] Verified that the setting is cleared on the server if it is not supplied in a YAML file (or that it is documented as being optional) - Yes, it gets cleared to false - both when setup_experience: is present without the field, and when setup_experience: is omitted entirely. This is the same behavior as the existing require_all_software_macos field - [x] Verified that any relevant UI is disabled when GitOps mode is enabled - Covered by #42854 (frontend subtask). The existing macOS checkbox in InstallSoftwareForm.tsx:271 already checks gitOpsModeEnabled to disable itself. The Windows checkbox to be added in #42854 will follow the same pattern. ## Summary by CodeRabbit * **New Features** * Added a Windows setup experience software requirement setting. When enabled, Windows devices will cancel the Autopilot setup if any required software installation fails. * **Tests** * Added test coverage for the new Windows software requirement configuration. --- changes/38785-windows-setup-experience-cancel | 1 + cmd/fleetctl/fleetctl/apply_test.go | 33 +++++++++ .../expectedGetConfigAppConfigJson.json | 6 +- ...dGetConfigAppConfigTeamMaintainerJson.json | 6 +- ...edGetConfigAppConfigTeamMaintainerYaml.yml | 2 + .../expectedGetConfigAppConfigYaml.yml | 2 + ...ectedGetConfigIncludeServerConfigJson.json | 6 +- ...pectedGetConfigIncludeServerConfigYaml.yml | 2 + .../testdata/expectedGetTeamsJson.json | 4 + .../testdata/expectedGetTeamsYaml.yml | 4 + .../macosSetupExpectedAppConfigEmpty.yml | 2 + .../macosSetupExpectedAppConfigSet.yml | 2 + .../macosSetupExpectedTeam1And2Empty.yml | 2 + .../macosSetupExpectedTeam1And2Set.yml | 2 + .../testdata/macosSetupExpectedTeam1Empty.yml | 2 + .../testdata/macosSetupExpectedTeam1Set.yml | 2 + ee/server/service/mdm.go | 9 +++ ee/server/service/teams.go | 19 +++++ server/datastore/mysql/schema.sql | 2 +- server/fleet/app.go | 1 + server/fleet/apple_mdm.go | 1 + server/service/setup_experience.go | 29 +++++--- server/service/setup_experience_test.go | 73 +++++++++++++++++++ .../generated_files/appconfig.txt | 1 + .../generated_files/teamconfig.txt | 1 + .../cloner-check/generated_files/teammdm.txt | 1 + 26 files changed, 198 insertions(+), 17 deletions(-) create mode 100644 changes/38785-windows-setup-experience-cancel diff --git a/changes/38785-windows-setup-experience-cancel b/changes/38785-windows-setup-experience-cancel new file mode 100644 index 0000000000..7fffb77759 --- /dev/null +++ b/changes/38785-windows-setup-experience-cancel @@ -0,0 +1 @@ +- Added `require_all_software_windows` setting to cancel the Windows setup experience if any software install fails during Autopilot enrollment, matching the existing macOS behavior. diff --git a/cmd/fleetctl/fleetctl/apply_test.go b/cmd/fleetctl/fleetctl/apply_test.go index 68955d9254..716253d71e 100644 --- a/cmd/fleetctl/fleetctl/apply_test.go +++ b/cmd/fleetctl/fleetctl/apply_test.go @@ -2703,6 +2703,39 @@ spec: _, err = RunAppNoChecks([]string{"apply", "-f", name}) require.ErrorContains(t, err, `The profile can't include "await_device_configured" option.`) assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + + }) + + t.Run("require_all_software_windows", func(t *testing.T) { + ds := setupServer(t, true) + + // Enable Windows MDM in the app config. + mockStore.Lock() + mockStore.appConfig.MDM.WindowsEnabledAndConfigured = true + mockStore.Unlock() + + b, err := os.ReadFile(filepath.Join("testdata", "macosSetupExpectedTeam1Set.yml")) + require.NoError(t, err) + expectedTm1 := fmt.Sprintf(string(b), "", "", "", "") + + // Apply team with require_all_software_windows enabled. + windowsRequireSpec := ` +apiVersion: v1 +kind: fleet +spec: + team: + name: tm1 + mdm: + setup_experience: + require_all_software_windows: true +` + name := writeTmpYml(t, windowsRequireSpec) + assert.Equal(t, "[+] applied 1 fleet\n", RunAppForTest(t, []string{"apply", "-f", name})) + assert.True(t, ds.SaveTeamFuncInvoked) + + // Verify the output includes require_all_software_windows: true. + expectedWithWindowsRequire := strings.ReplaceAll(expectedTm1, `require_all_software_windows: false`, `require_all_software_windows: true`) + assert.YAMLEq(t, expectedWithWindowsRequire, RunAppForTest(t, []string{"get", "teams", "--yaml"})) }) t.Run("new bootstrap package", func(t *testing.T) { diff --git a/cmd/fleetctl/fleetctl/testdata/expectedGetConfigAppConfigJson.json b/cmd/fleetctl/fleetctl/testdata/expectedGetConfigAppConfigJson.json index 66ac60ee7e..87c98a88fa 100644 --- a/cmd/fleetctl/fleetctl/testdata/expectedGetConfigAppConfigJson.json +++ b/cmd/fleetctl/fleetctl/testdata/expectedGetConfigAppConfigJson.json @@ -162,7 +162,8 @@ "script": null, "software": null, "manual_agent_install": null, - "require_all_software_macos": false + "require_all_software_macos": false, + "require_all_software_windows": false }, "setup_experience": { "macos_bootstrap_package": null, @@ -173,7 +174,8 @@ "macos_script": null, "software": null, "macos_manual_agent_install": null, - "require_all_software_macos": false + "require_all_software_macos": false, + "require_all_software_windows": false }, "windows_settings": { "custom_settings": null, diff --git a/cmd/fleetctl/fleetctl/testdata/expectedGetConfigAppConfigTeamMaintainerJson.json b/cmd/fleetctl/fleetctl/testdata/expectedGetConfigAppConfigTeamMaintainerJson.json index 210aceb130..8b542ea98a 100644 --- a/cmd/fleetctl/fleetctl/testdata/expectedGetConfigAppConfigTeamMaintainerJson.json +++ b/cmd/fleetctl/fleetctl/testdata/expectedGetConfigAppConfigTeamMaintainerJson.json @@ -134,7 +134,8 @@ "script": null, "software": null, "manual_agent_install": null, - "require_all_software_macos": false + "require_all_software_macos": false, + "require_all_software_windows": false }, "setup_experience": { "macos_bootstrap_package": null, @@ -145,7 +146,8 @@ "macos_script": null, "software": null, "macos_manual_agent_install": null, - "require_all_software_macos": false + "require_all_software_macos": false, + "require_all_software_windows": false }, "windows_settings": { "custom_settings": null, diff --git a/cmd/fleetctl/fleetctl/testdata/expectedGetConfigAppConfigTeamMaintainerYaml.yml b/cmd/fleetctl/fleetctl/testdata/expectedGetConfigAppConfigTeamMaintainerYaml.yml index f301e2eab7..e8c575aade 100644 --- a/cmd/fleetctl/fleetctl/testdata/expectedGetConfigAppConfigTeamMaintainerYaml.yml +++ b/cmd/fleetctl/fleetctl/testdata/expectedGetConfigAppConfigTeamMaintainerYaml.yml @@ -74,6 +74,7 @@ spec: macos_setup_assistant: manual_agent_install: require_all_software_macos: false + require_all_software_windows: false script: software: setup_experience: @@ -84,6 +85,7 @@ spec: apple_setup_assistant: macos_manual_agent_install: require_all_software_macos: false + require_all_software_windows: false macos_script: software: windows_settings: diff --git a/cmd/fleetctl/fleetctl/testdata/expectedGetConfigAppConfigYaml.yml b/cmd/fleetctl/fleetctl/testdata/expectedGetConfigAppConfigYaml.yml index 410076020e..f213630f5d 100644 --- a/cmd/fleetctl/fleetctl/testdata/expectedGetConfigAppConfigYaml.yml +++ b/cmd/fleetctl/fleetctl/testdata/expectedGetConfigAppConfigYaml.yml @@ -74,6 +74,7 @@ spec: macos_setup_assistant: manual_agent_install: require_all_software_macos: false + require_all_software_windows: false script: software: setup_experience: @@ -84,6 +85,7 @@ spec: apple_setup_assistant: macos_manual_agent_install: require_all_software_macos: false + require_all_software_windows: false macos_script: software: windows_settings: diff --git a/cmd/fleetctl/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json b/cmd/fleetctl/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json index 21b585ce49..b9eeaab2c9 100644 --- a/cmd/fleetctl/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json +++ b/cmd/fleetctl/fleetctl/testdata/expectedGetConfigIncludeServerConfigJson.json @@ -112,7 +112,8 @@ "script": null, "software": null, "manual_agent_install": null, - "require_all_software_macos": false + "require_all_software_macos": false, + "require_all_software_windows": false }, "setup_experience": { "macos_bootstrap_package": null, @@ -123,7 +124,8 @@ "macos_script": null, "software": null, "macos_manual_agent_install": null, - "require_all_software_macos": false + "require_all_software_macos": false, + "require_all_software_windows": false }, "windows_settings": { "custom_settings": null, diff --git a/cmd/fleetctl/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml b/cmd/fleetctl/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml index b2be79c2e2..e97e583db6 100644 --- a/cmd/fleetctl/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml +++ b/cmd/fleetctl/fleetctl/testdata/expectedGetConfigIncludeServerConfigYaml.yml @@ -74,6 +74,7 @@ spec: macos_setup_assistant: manual_agent_install: require_all_software_macos: false + require_all_software_windows: false script: software: setup_experience: @@ -84,6 +85,7 @@ spec: apple_setup_assistant: macos_manual_agent_install: require_all_software_macos: false + require_all_software_windows: false macos_script: software: windows_settings: diff --git a/cmd/fleetctl/fleetctl/testdata/expectedGetTeamsJson.json b/cmd/fleetctl/fleetctl/testdata/expectedGetTeamsJson.json index e891389497..333658d36c 100644 --- a/cmd/fleetctl/fleetctl/testdata/expectedGetTeamsJson.json +++ b/cmd/fleetctl/fleetctl/testdata/expectedGetTeamsJson.json @@ -55,6 +55,7 @@ "macos_manual_agent_install": null, "macos_script": null, "require_all_software_macos": false, + "require_all_software_windows": false, "software": null }, "windows_require_bitlocker_pin": false, @@ -126,6 +127,7 @@ "macos_setup_assistant": null, "manual_agent_install": null, "require_all_software_macos": false, + "require_all_software_windows": false, "script": null, "software": null }, @@ -230,6 +232,7 @@ "macos_manual_agent_install": null, "macos_script": null, "require_all_software_macos": false, + "require_all_software_windows": false, "software": null }, "windows_require_bitlocker_pin": false, @@ -316,6 +319,7 @@ "macos_setup_assistant": null, "manual_agent_install": null, "require_all_software_macos": false, + "require_all_software_windows": false, "script": null, "software": null }, diff --git a/cmd/fleetctl/fleetctl/testdata/expectedGetTeamsYaml.yml b/cmd/fleetctl/fleetctl/testdata/expectedGetTeamsYaml.yml index 2d73880c74..a472e8e5e1 100644 --- a/cmd/fleetctl/fleetctl/testdata/expectedGetTeamsYaml.yml +++ b/cmd/fleetctl/fleetctl/testdata/expectedGetTeamsYaml.yml @@ -46,6 +46,7 @@ spec: apple_setup_assistant: macos_manual_agent_install: require_all_software_macos: false + require_all_software_windows: false macos_script: software: scripts: null @@ -98,6 +99,7 @@ spec: macos_setup_assistant: manual_agent_install: require_all_software_macos: false + require_all_software_windows: false script: software: scripts: null @@ -157,6 +159,7 @@ spec: apple_setup_assistant: macos_manual_agent_install: require_all_software_macos: false + require_all_software_windows: false macos_script: software: scripts: null @@ -210,6 +213,7 @@ spec: macos_setup_assistant: manual_agent_install: require_all_software_macos: false + require_all_software_windows: false script: software: scripts: null diff --git a/cmd/fleetctl/fleetctl/testdata/macosSetupExpectedAppConfigEmpty.yml b/cmd/fleetctl/fleetctl/testdata/macosSetupExpectedAppConfigEmpty.yml index a131550bff..c9906fd875 100644 --- a/cmd/fleetctl/fleetctl/testdata/macosSetupExpectedAppConfigEmpty.yml +++ b/cmd/fleetctl/fleetctl/testdata/macosSetupExpectedAppConfigEmpty.yml @@ -65,6 +65,7 @@ spec: macos_setup_assistant: null manual_agent_install: null require_all_software_macos: false + require_all_software_windows: false enable_release_device_manually: false script: null software: null @@ -75,6 +76,7 @@ spec: apple_setup_assistant: null macos_manual_agent_install: null require_all_software_macos: false + require_all_software_windows: false apple_enable_release_device_manually: false macos_script: null software: null diff --git a/cmd/fleetctl/fleetctl/testdata/macosSetupExpectedAppConfigSet.yml b/cmd/fleetctl/fleetctl/testdata/macosSetupExpectedAppConfigSet.yml index 2c424ee842..4c54455d08 100644 --- a/cmd/fleetctl/fleetctl/testdata/macosSetupExpectedAppConfigSet.yml +++ b/cmd/fleetctl/fleetctl/testdata/macosSetupExpectedAppConfigSet.yml @@ -65,6 +65,7 @@ spec: macos_setup_assistant: %s manual_agent_install: null require_all_software_macos: false + require_all_software_windows: false enable_release_device_manually: false script: null software: null @@ -75,6 +76,7 @@ spec: apple_setup_assistant: %s macos_manual_agent_install: null require_all_software_macos: false + require_all_software_windows: false apple_enable_release_device_manually: false macos_script: null software: null diff --git a/cmd/fleetctl/fleetctl/testdata/macosSetupExpectedTeam1And2Empty.yml b/cmd/fleetctl/fleetctl/testdata/macosSetupExpectedTeam1And2Empty.yml index eaf8cce8c6..7b3d1032d5 100644 --- a/cmd/fleetctl/fleetctl/testdata/macosSetupExpectedTeam1And2Empty.yml +++ b/cmd/fleetctl/fleetctl/testdata/macosSetupExpectedTeam1And2Empty.yml @@ -30,6 +30,7 @@ spec: apple_setup_assistant: null macos_manual_agent_install: null require_all_software_macos: false + require_all_software_windows: false apple_enable_release_device_manually: false macos_script: null software: null @@ -82,6 +83,7 @@ spec: macos_setup_assistant: null manual_agent_install: null require_all_software_macos: false + require_all_software_windows: false enable_release_device_manually: false script: null software: null diff --git a/cmd/fleetctl/fleetctl/testdata/macosSetupExpectedTeam1And2Set.yml b/cmd/fleetctl/fleetctl/testdata/macosSetupExpectedTeam1And2Set.yml index 9217c85d39..a86a21c458 100644 --- a/cmd/fleetctl/fleetctl/testdata/macosSetupExpectedTeam1And2Set.yml +++ b/cmd/fleetctl/fleetctl/testdata/macosSetupExpectedTeam1And2Set.yml @@ -31,6 +31,7 @@ spec: macos_manual_agent_install: null apple_enable_release_device_manually: false require_all_software_macos: false + require_all_software_windows: false macos_script: null software: null macos_updates: @@ -83,6 +84,7 @@ spec: manual_agent_install: null enable_release_device_manually: false require_all_software_macos: false + require_all_software_windows: false script: null software: null macos_updates: diff --git a/cmd/fleetctl/fleetctl/testdata/macosSetupExpectedTeam1Empty.yml b/cmd/fleetctl/fleetctl/testdata/macosSetupExpectedTeam1Empty.yml index beb2c5166b..903eb3ff3a 100644 --- a/cmd/fleetctl/fleetctl/testdata/macosSetupExpectedTeam1Empty.yml +++ b/cmd/fleetctl/fleetctl/testdata/macosSetupExpectedTeam1Empty.yml @@ -28,6 +28,7 @@ spec: apple_setup_assistant: null macos_manual_agent_install: null require_all_software_macos: false + require_all_software_windows: false apple_enable_release_device_manually: false macos_script: null software: null @@ -80,6 +81,7 @@ spec: macos_setup_assistant: null manual_agent_install: null require_all_software_macos: false + require_all_software_windows: false enable_release_device_manually: false script: null software: null diff --git a/cmd/fleetctl/fleetctl/testdata/macosSetupExpectedTeam1Set.yml b/cmd/fleetctl/fleetctl/testdata/macosSetupExpectedTeam1Set.yml index bba76c874d..e88d3a338a 100644 --- a/cmd/fleetctl/fleetctl/testdata/macosSetupExpectedTeam1Set.yml +++ b/cmd/fleetctl/fleetctl/testdata/macosSetupExpectedTeam1Set.yml @@ -29,6 +29,7 @@ spec: apple_setup_assistant: %s macos_manual_agent_install: null require_all_software_macos: false + require_all_software_windows: false apple_enable_release_device_manually: false macos_script: null software: null @@ -81,6 +82,7 @@ spec: macos_setup_assistant: %s manual_agent_install: null require_all_software_macos: false + require_all_software_windows: false enable_release_device_manually: false script: null software: null diff --git a/ee/server/service/mdm.go b/ee/server/service/mdm.go index b0571b4f42..e4695b351b 100644 --- a/ee/server/service/mdm.go +++ b/ee/server/service/mdm.go @@ -233,6 +233,11 @@ func (svc *Service) updateAppConfigMDMAppleSetup(ctx context.Context, payload fl didUpdate = true } + if payload.RequireAllSoftwareWindows != nil && ac.MDM.MacOSSetup.RequireAllSoftwareWindows != *payload.RequireAllSoftwareWindows { + ac.MDM.MacOSSetup.RequireAllSoftwareWindows = *payload.RequireAllSoftwareWindows + didUpdate = true + } + if payload.EnableReleaseDeviceManually != nil { if ac.MDM.MacOSSetup.EnableReleaseDeviceManually.Value != *payload.EnableReleaseDeviceManually { ac.MDM.MacOSSetup.EnableReleaseDeviceManually = optjson.SetBool(*payload.EnableReleaseDeviceManually) @@ -311,6 +316,10 @@ func (svc *Service) validateMDMAppleSetupPayload(ctx context.Context, payload fl return fleet.ErrMDMNotConfigured } + if payload.RequireAllSoftwareWindows != nil && *payload.RequireAllSoftwareWindows && !ac.MDM.WindowsEnabledAndConfigured { + return fleet.ErrWindowsMDMNotConfigured + } + if payload.EnableEndUserAuthentication != nil && *payload.EnableEndUserAuthentication { if ac.MDM.EndUserAuthentication.IsEmpty() { // TODO: update this error message to include steps to resolve the issue once docs for IdP diff --git a/ee/server/service/teams.go b/ee/server/service/teams.go index 32fd806c03..bf04a05d7a 100644 --- a/ee/server/service/teams.go +++ b/ee/server/service/teams.go @@ -1267,6 +1267,11 @@ func (svc *Service) createTeamFromSpec( return nil, ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("setup_experience.lock_end_user_info", `"enable_end_user_authentication" must be set to "true" in order to enable "lock_end_user_info"`)) } + if macOSSetup.RequireAllSoftwareWindows && !appCfg.MDM.WindowsEnabledAndConfigured { + return nil, ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("setup_experience.require_all_software_windows", + `Couldn't update setup_experience.require_all_software_windows. `+fleet.ErrWindowsMDMNotConfigured.Error())) + } + // Default the value of "lock_end_user_info" to the value of "enable_end_user_authentication" if not explicitly set in the spec to keep prior // behavior. if !macOSSetup.LockEndUserInfo.Valid { @@ -1593,10 +1598,19 @@ func (svc *Service) editTeamFromSpec( } team.Config.MDM.MacOSSetup.RequireAllSoftware = spec.MDM.MacOSSetup.RequireAllSoftware + didUpdateWindowsRequireAllSoftware := spec.MDM.MacOSSetup.RequireAllSoftwareWindows != oldMacOSSetup.RequireAllSoftwareWindows windowsEnabledAndConfigured := appCfg.MDM.WindowsEnabledAndConfigured if opts.DryRunAssumptions != nil && opts.DryRunAssumptions.WindowsEnabledAndConfigured.Valid { windowsEnabledAndConfigured = opts.DryRunAssumptions.WindowsEnabledAndConfigured.Value } + if didUpdateWindowsRequireAllSoftware && spec.MDM.MacOSSetup.RequireAllSoftwareWindows { + if !windowsEnabledAndConfigured { + return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("setup_experience.require_all_software_windows", + `Couldn't update setup_experience.require_all_software_windows. `+fleet.ErrWindowsMDMNotConfigured.Error())) + } + } + team.Config.MDM.MacOSSetup.RequireAllSoftwareWindows = spec.MDM.MacOSSetup.RequireAllSoftwareWindows + if spec.MDM.WindowsSettings.CustomSettings.Set { if !windowsEnabledAndConfigured && len(spec.MDM.WindowsSettings.CustomSettings.Value) > 0 && @@ -2036,6 +2050,11 @@ func (svc *Service) updateTeamMDMAppleSetup(ctx context.Context, tm *fleet.Team, didUpdate = true } + if payload.RequireAllSoftwareWindows != nil && tm.Config.MDM.MacOSSetup.RequireAllSoftwareWindows != *payload.RequireAllSoftwareWindows { + tm.Config.MDM.MacOSSetup.RequireAllSoftwareWindows = *payload.RequireAllSoftwareWindows + didUpdate = true + } + if payload.ManualAgentInstall != nil { if tm.Config.MDM.MacOSSetup.ManualAgentInstall.Value != *payload.ManualAgentInstall { // Try to load the bootstrap package to verify it exists. diff --git a/server/datastore/mysql/schema.sql b/server/datastore/mysql/schema.sql index 72053f1c6b..333f7389a2 100644 --- a/server/datastore/mysql/schema.sql +++ b/server/datastore/mysql/schema.sql @@ -221,7 +221,7 @@ CREATE TABLE `app_config_json` ( PRIMARY KEY (`id`) ) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; -INSERT INTO `app_config_json` VALUES (1,'{\"mdm\": {\"ios_updates\": {\"deadline\": null, \"minimum_version\": null, \"update_new_hosts\": null}, \"macos_setup\": {\"script\": null, \"software\": null, \"bootstrap_package\": null, \"lock_end_user_info\": false, \"manual_agent_install\": null, \"macos_setup_assistant\": null, \"require_all_software_macos\": false, \"enable_end_user_authentication\": false, \"enable_release_device_manually\": false}, \"macos_updates\": {\"deadline\": null, \"minimum_version\": null, \"update_new_hosts\": false}, \"ipados_updates\": {\"deadline\": null, \"minimum_version\": null, \"update_new_hosts\": null}, \"macos_settings\": {\"custom_settings\": null}, \"macos_migration\": {\"mode\": \"\", \"enable\": false, \"webhook_url\": \"\"}, \"windows_updates\": {\"deadline_days\": null, \"grace_period_days\": null}, \"android_settings\": {\"certificates\": null, \"custom_settings\": null}, \"apple_server_url\": \"\", \"windows_settings\": {\"custom_settings\": null}, \"apple_bm_terms_expired\": false, \"apple_business_manager\": null, \"enable_disk_encryption\": false, \"enabled_and_configured\": false, \"end_user_authentication\": {\"idp_name\": \"\", \"metadata\": \"\", \"entity_id\": \"\", \"issuer_uri\": \"\", \"metadata_url\": \"\"}, \"windows_entra_tenant_ids\": [], \"volume_purchasing_program\": null, \"windows_migration_enabled\": false, \"enable_recovery_lock_password\": false, \"windows_require_bitlocker_pin\": null, \"android_enabled_and_configured\": false, \"windows_enabled_and_configured\": false, \"apple_bm_enabled_and_configured\": false, \"apple_require_hardware_attestation\": false, \"enable_turn_on_windows_mdm_manually\": false}, \"gitops\": {\"exceptions\": {\"labels\": true, \"secrets\": true, \"software\": false}, \"repository_url\": \"\", \"gitops_mode_enabled\": false}, \"scripts\": null, \"features\": {\"enable_host_users\": true, \"enable_software_inventory\": false}, \"org_info\": {\"org_name\": \"\", \"contact_url\": \"\", \"org_logo_url\": \"\", \"org_logo_url_light_background\": \"\"}, \"integrations\": {\"jira\": null, \"zendesk\": null, \"google_calendar\": null, \"conditional_access_enabled\": null}, \"sso_settings\": {\"idp_name\": \"\", \"metadata\": \"\", \"entity_id\": \"\", \"enable_sso\": false, \"issuer_uri\": \"\", \"metadata_url\": \"\", \"idp_image_url\": \"\", \"sso_server_url\": \"\", \"enable_jit_role_sync\": false, \"enable_sso_idp_login\": false, \"enable_jit_provisioning\": false}, \"agent_options\": {\"config\": {\"options\": {\"logger_plugin\": \"tls\", \"pack_delimiter\": \"/\", \"logger_tls_period\": 10, \"distributed_plugin\": \"tls\", \"disable_distributed\": false, \"logger_tls_endpoint\": \"/api/osquery/log\", \"distributed_interval\": 10, \"distributed_tls_max_attempts\": 3}, \"decorators\": {\"load\": [\"SELECT uuid AS host_uuid FROM system_info;\", \"SELECT hostname AS hostname FROM system_info;\"]}}, \"overrides\": {}}, \"fleet_desktop\": {\"transparency_url\": \"\", \"alternative_browser_host\": \"\"}, \"smtp_settings\": {\"port\": 587, \"domain\": \"\", \"server\": \"\", \"password\": \"\", \"user_name\": \"\", \"configured\": false, \"enable_smtp\": false, \"enable_ssl_tls\": true, \"sender_address\": \"\", \"enable_start_tls\": true, \"verify_ssl_certs\": true, \"authentication_type\": \"0\", \"authentication_method\": \"0\"}, \"server_settings\": {\"server_url\": \"\", \"enable_analytics\": false, \"query_report_cap\": 0, \"scripts_disabled\": false, \"deferred_save_host\": false, \"live_query_disabled\": false, \"ai_features_disabled\": false, \"query_reports_disabled\": false}, \"webhook_settings\": {\"interval\": \"0s\", \"activities_webhook\": {\"destination_url\": \"\", \"enable_activities_webhook\": false}, \"host_status_webhook\": {\"days_count\": 0, \"destination_url\": \"\", \"host_percentage\": 0, \"enable_host_status_webhook\": false}, \"vulnerabilities_webhook\": {\"destination_url\": \"\", \"host_batch_size\": 0, \"enable_vulnerabilities_webhook\": false}, \"failing_policies_webhook\": {\"policy_ids\": null, \"destination_url\": \"\", \"host_batch_size\": 0, \"enable_failing_policies_webhook\": false}}, \"host_expiry_settings\": {\"host_expiry_window\": 0, \"host_expiry_enabled\": false}, \"vulnerability_settings\": {\"databases_path\": \"\"}, \"activity_expiry_settings\": {\"activity_expiry_window\": 0, \"activity_expiry_enabled\": false}}','2020-01-01 01:01:01','2020-01-01 01:01:01'); +INSERT INTO `app_config_json` VALUES (1,'{\"mdm\": {\"ios_updates\": {\"deadline\": null, \"minimum_version\": null, \"update_new_hosts\": null}, \"macos_setup\": {\"script\": null, \"software\": null, \"bootstrap_package\": null, \"lock_end_user_info\": false, \"manual_agent_install\": null, \"macos_setup_assistant\": null, \"require_all_software_macos\": false, \"require_all_software_windows\": false, \"enable_end_user_authentication\": false, \"enable_release_device_manually\": false}, \"macos_updates\": {\"deadline\": null, \"minimum_version\": null, \"update_new_hosts\": false}, \"ipados_updates\": {\"deadline\": null, \"minimum_version\": null, \"update_new_hosts\": null}, \"macos_settings\": {\"custom_settings\": null}, \"macos_migration\": {\"mode\": \"\", \"enable\": false, \"webhook_url\": \"\"}, \"windows_updates\": {\"deadline_days\": null, \"grace_period_days\": null}, \"android_settings\": {\"certificates\": null, \"custom_settings\": null}, \"apple_server_url\": \"\", \"windows_settings\": {\"custom_settings\": null}, \"apple_bm_terms_expired\": false, \"apple_business_manager\": null, \"enable_disk_encryption\": false, \"enabled_and_configured\": false, \"end_user_authentication\": {\"idp_name\": \"\", \"metadata\": \"\", \"entity_id\": \"\", \"issuer_uri\": \"\", \"metadata_url\": \"\"}, \"windows_entra_tenant_ids\": [], \"volume_purchasing_program\": null, \"windows_migration_enabled\": false, \"enable_recovery_lock_password\": false, \"windows_require_bitlocker_pin\": null, \"android_enabled_and_configured\": false, \"windows_enabled_and_configured\": false, \"apple_bm_enabled_and_configured\": false, \"apple_require_hardware_attestation\": false, \"enable_turn_on_windows_mdm_manually\": false}, \"gitops\": {\"exceptions\": {\"labels\": true, \"secrets\": true, \"software\": false}, \"repository_url\": \"\", \"gitops_mode_enabled\": false}, \"scripts\": null, \"features\": {\"enable_host_users\": true, \"enable_software_inventory\": false}, \"org_info\": {\"org_name\": \"\", \"contact_url\": \"\", \"org_logo_url\": \"\", \"org_logo_url_light_background\": \"\"}, \"integrations\": {\"jira\": null, \"zendesk\": null, \"google_calendar\": null, \"conditional_access_enabled\": null}, \"sso_settings\": {\"idp_name\": \"\", \"metadata\": \"\", \"entity_id\": \"\", \"enable_sso\": false, \"issuer_uri\": \"\", \"metadata_url\": \"\", \"idp_image_url\": \"\", \"sso_server_url\": \"\", \"enable_jit_role_sync\": false, \"enable_sso_idp_login\": false, \"enable_jit_provisioning\": false}, \"agent_options\": {\"config\": {\"options\": {\"logger_plugin\": \"tls\", \"pack_delimiter\": \"/\", \"logger_tls_period\": 10, \"distributed_plugin\": \"tls\", \"disable_distributed\": false, \"logger_tls_endpoint\": \"/api/osquery/log\", \"distributed_interval\": 10, \"distributed_tls_max_attempts\": 3}, \"decorators\": {\"load\": [\"SELECT uuid AS host_uuid FROM system_info;\", \"SELECT hostname AS hostname FROM system_info;\"]}}, \"overrides\": {}}, \"fleet_desktop\": {\"transparency_url\": \"\", \"alternative_browser_host\": \"\"}, \"smtp_settings\": {\"port\": 587, \"domain\": \"\", \"server\": \"\", \"password\": \"\", \"user_name\": \"\", \"configured\": false, \"enable_smtp\": false, \"enable_ssl_tls\": true, \"sender_address\": \"\", \"enable_start_tls\": true, \"verify_ssl_certs\": true, \"authentication_type\": \"0\", \"authentication_method\": \"0\"}, \"server_settings\": {\"server_url\": \"\", \"enable_analytics\": false, \"query_report_cap\": 0, \"scripts_disabled\": false, \"deferred_save_host\": false, \"live_query_disabled\": false, \"ai_features_disabled\": false, \"query_reports_disabled\": false}, \"webhook_settings\": {\"interval\": \"0s\", \"activities_webhook\": {\"destination_url\": \"\", \"enable_activities_webhook\": false}, \"host_status_webhook\": {\"days_count\": 0, \"destination_url\": \"\", \"host_percentage\": 0, \"enable_host_status_webhook\": false}, \"vulnerabilities_webhook\": {\"destination_url\": \"\", \"host_batch_size\": 0, \"enable_vulnerabilities_webhook\": false}, \"failing_policies_webhook\": {\"policy_ids\": null, \"destination_url\": \"\", \"host_batch_size\": 0, \"enable_failing_policies_webhook\": false}}, \"host_expiry_settings\": {\"host_expiry_window\": 0, \"host_expiry_enabled\": false}, \"vulnerability_settings\": {\"databases_path\": \"\"}, \"activity_expiry_settings\": {\"activity_expiry_window\": 0, \"activity_expiry_enabled\": false}}','2020-01-01 01:01:01','2020-01-01 01:01:01'); /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; CREATE TABLE `batch_activities` ( diff --git a/server/fleet/app.go b/server/fleet/app.go index 8f099f10b7..725ea74647 100644 --- a/server/fleet/app.go +++ b/server/fleet/app.go @@ -552,6 +552,7 @@ type MacOSSetup struct { Software optjson.Slice[*MacOSSetupSoftware] `json:"software"` ManualAgentInstall optjson.Bool `json:"manual_agent_install" renameto:"macos_manual_agent_install"` RequireAllSoftware bool `json:"require_all_software_macos"` + RequireAllSoftwareWindows bool `json:"require_all_software_windows"` } func (mos *MacOSSetup) Validate() error { diff --git a/server/fleet/apple_mdm.go b/server/fleet/apple_mdm.go index 386ea25879..2fe4aa19cc 100644 --- a/server/fleet/apple_mdm.go +++ b/server/fleet/apple_mdm.go @@ -526,6 +526,7 @@ type MDMAppleSetupPayload struct { EnableReleaseDeviceManually *bool `json:"enable_release_device_manually" renameto:"apple_enable_release_device_manually"` ManualAgentInstall *bool `json:"manual_agent_install" renameto:"macos_manual_agent_install"` RequireAllSoftware *bool `json:"require_all_software_macos"` + RequireAllSoftwareWindows *bool `json:"require_all_software_windows"` LockEndUserInfo *bool `json:"lock_end_user_info"` } diff --git a/server/service/setup_experience.go b/server/service/setup_experience.go index 9cfa7af3fa..f4a159f42b 100644 --- a/server/service/setup_experience.go +++ b/server/service/setup_experience.go @@ -238,22 +238,31 @@ func (svc *Service) IsAllSetupExperienceSoftwareRequired(ctx context.Context, ho } func isAllSetupExperienceSoftwareRequired(ctx context.Context, ds fleet.Datastore, host *fleet.Host) (bool, error) { + // Only macOS and Windows support canceling setup if software fails. + if host.Platform != "darwin" && host.Platform != "windows" { + return false, nil + } + teamID := host.TeamID - requireAllSoftware := false if teamID == nil || *teamID == 0 { ac, err := ds.AppConfig(ctx) if err != nil { return false, ctxerr.Wrap(ctx, err, "getting app config") } - requireAllSoftware = ac.MDM.MacOSSetup.RequireAllSoftware - } else { - team, err := ds.TeamLite(ctx, *teamID) - if err != nil { - return false, ctxerr.Wrap(ctx, err, "load team") + if host.Platform == "windows" { + return ac.MDM.MacOSSetup.RequireAllSoftwareWindows, nil } - requireAllSoftware = team.Config.MDM.MacOSSetup.RequireAllSoftware + return ac.MDM.MacOSSetup.RequireAllSoftware, nil } - return requireAllSoftware, nil + + team, err := ds.TeamLite(ctx, *teamID) + if err != nil { + return false, ctxerr.Wrap(ctx, err, "load team") + } + if host.Platform == "windows" { + return team.Config.MDM.MacOSSetup.RequireAllSoftwareWindows, nil + } + return team.Config.MDM.MacOSSetup.RequireAllSoftware, nil } func (svc *Service) MaybeCancelPendingSetupExperienceSteps(ctx context.Context, host *fleet.Host) error { @@ -261,8 +270,8 @@ func (svc *Service) MaybeCancelPendingSetupExperienceSteps(ctx context.Context, } func maybeCancelPendingSetupExperienceSteps(ctx context.Context, ds fleet.Datastore, host *fleet.Host) error { - // If the host is not MacOS, we do nothing. - if host.Platform != "darwin" { + // Only macOS and Windows support canceling setup experience steps. + if host.Platform != "darwin" && host.Platform != "windows" { return nil } diff --git a/server/service/setup_experience_test.go b/server/service/setup_experience_test.go index ec7719f7f0..93b7dd0ebd 100644 --- a/server/service/setup_experience_test.go +++ b/server/service/setup_experience_test.go @@ -221,6 +221,79 @@ func TestSetupExperienceAuth(t *testing.T) { } } +func TestIsAllSetupExperienceSoftwareRequired(t *testing.T) { + ds := new(mock.Store) + + teamID := uint(1) + // Use different values for macOS vs Windows to ensure the correct field is read for each platform. + appCfg := &fleet.AppConfig{} + appCfg.MDM.MacOSSetup.RequireAllSoftware = true + appCfg.MDM.MacOSSetup.RequireAllSoftwareWindows = false + + ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) { + return appCfg, nil + } + ds.TeamLiteFunc = func(ctx context.Context, tid uint) (*fleet.TeamLite, error) { + return &fleet.TeamLite{ + ID: tid, + Name: "team", + Config: fleet.TeamConfigLite{ + MDM: fleet.TeamMDM{ + MacOSSetup: fleet.MacOSSetup{ + RequireAllSoftware: false, + RequireAllSoftwareWindows: true, + }, + }, + }, + }, nil + } + + tests := []struct { + name string + host *fleet.Host + expected bool + }{ + { + name: "macOS host, no team, reads macOS global config (true)", + host: &fleet.Host{Platform: "darwin"}, + expected: true, + }, + { + name: "macOS host, with team, reads macOS team config (false)", + host: &fleet.Host{Platform: "darwin", TeamID: &teamID}, + expected: false, + }, + { + name: "windows host, no team, reads Windows global config (false)", + host: &fleet.Host{Platform: "windows"}, + expected: false, + }, + { + name: "windows host, with team, reads Windows team config (true)", + host: &fleet.Host{Platform: "windows", TeamID: &teamID}, + expected: true, + }, + { + name: "linux host returns false", + host: &fleet.Host{Platform: "ubuntu"}, + expected: false, + }, + { + name: "ios host returns false", + host: &fleet.Host{Platform: "ios"}, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := isAllSetupExperienceSoftwareRequired(t.Context(), ds, tt.host) + require.NoError(t, err) + require.Equal(t, tt.expected, result) + }) + } +} + func TestMaybeUpdateSetupExperience(t *testing.T) { ds := new(mock.Store) // _, ctx := newTestService(t, ds, nil, nil, nil) diff --git a/tools/cloner-check/generated_files/appconfig.txt b/tools/cloner-check/generated_files/appconfig.txt index f1759a55d9..e2c759d966 100644 --- a/tools/cloner-check/generated_files/appconfig.txt +++ b/tools/cloner-check/generated_files/appconfig.txt @@ -156,6 +156,7 @@ github.com/fleetdm/fleet/v4/server/fleet/MacOSSetupSoftware AppStoreID string github.com/fleetdm/fleet/v4/server/fleet/MacOSSetupSoftware PackagePath string github.com/fleetdm/fleet/v4/server/fleet/MacOSSetup ManualAgentInstall optjson.Bool github.com/fleetdm/fleet/v4/server/fleet/MacOSSetup RequireAllSoftware bool +github.com/fleetdm/fleet/v4/server/fleet/MacOSSetup RequireAllSoftwareWindows bool github.com/fleetdm/fleet/v4/server/fleet/MDM MacOSMigration fleet.MacOSMigration github.com/fleetdm/fleet/v4/server/fleet/MacOSMigration Enable bool github.com/fleetdm/fleet/v4/server/fleet/MacOSMigration Mode fleet.MacOSMigrationMode string diff --git a/tools/cloner-check/generated_files/teamconfig.txt b/tools/cloner-check/generated_files/teamconfig.txt index 99cf158509..c773a2a395 100644 --- a/tools/cloner-check/generated_files/teamconfig.txt +++ b/tools/cloner-check/generated_files/teamconfig.txt @@ -72,6 +72,7 @@ github.com/fleetdm/fleet/v4/server/fleet/MacOSSetupSoftware AppStoreID string github.com/fleetdm/fleet/v4/server/fleet/MacOSSetupSoftware PackagePath string github.com/fleetdm/fleet/v4/server/fleet/MacOSSetup ManualAgentInstall optjson.Bool github.com/fleetdm/fleet/v4/server/fleet/MacOSSetup RequireAllSoftware bool +github.com/fleetdm/fleet/v4/server/fleet/MacOSSetup RequireAllSoftwareWindows bool github.com/fleetdm/fleet/v4/server/fleet/TeamMDM WindowsSettings fleet.WindowsSettings github.com/fleetdm/fleet/v4/server/fleet/WindowsSettings CustomSettings optjson.Slice[github.com/fleetdm/fleet/v4/server/fleet.MDMProfileSpec] github.com/fleetdm/fleet/v4/pkg/optjson/Slice[github.com/fleetdm/fleet/v4/server/fleet.MDMProfileSpec] Set bool diff --git a/tools/cloner-check/generated_files/teammdm.txt b/tools/cloner-check/generated_files/teammdm.txt index 3340ed963e..390c34da11 100644 --- a/tools/cloner-check/generated_files/teammdm.txt +++ b/tools/cloner-check/generated_files/teammdm.txt @@ -43,6 +43,7 @@ github.com/fleetdm/fleet/v4/server/fleet/MacOSSetupSoftware AppStoreID string github.com/fleetdm/fleet/v4/server/fleet/MacOSSetupSoftware PackagePath string github.com/fleetdm/fleet/v4/server/fleet/MacOSSetup ManualAgentInstall optjson.Bool github.com/fleetdm/fleet/v4/server/fleet/MacOSSetup RequireAllSoftware bool +github.com/fleetdm/fleet/v4/server/fleet/MacOSSetup RequireAllSoftwareWindows bool github.com/fleetdm/fleet/v4/server/fleet/TeamMDM WindowsSettings fleet.WindowsSettings github.com/fleetdm/fleet/v4/server/fleet/WindowsSettings CustomSettings optjson.Slice[github.com/fleetdm/fleet/v4/server/fleet.MDMProfileSpec] github.com/fleetdm/fleet/v4/pkg/optjson/Slice[github.com/fleetdm/fleet/v4/server/fleet.MDMProfileSpec] Set bool