Fix validations for applying MDM config changes (#14517)

This commit is contained in:
gillespi314 2023-10-26 15:48:32 -05:00 committed by GitHub
parent 653aeceb06
commit c10ee875f2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 692 additions and 33 deletions

View file

@ -0,0 +1 @@
- Fixed issue where applying config changes would cause validation errors when MDM features were not enabled.

View file

@ -771,19 +771,19 @@ func (svc *Service) ApplyTeamSpecs(ctx context.Context, specs []*fleet.TeamSpec,
func (svc *Service) createTeamFromSpec(
ctx context.Context,
spec *fleet.TeamSpec,
defaults *fleet.AppConfig,
appCfg *fleet.AppConfig,
secrets []*fleet.EnrollSecret,
dryRun bool,
) (*fleet.Team, error) {
agentOptions := &spec.AgentOptions
if len(spec.AgentOptions) == 0 {
agentOptions = defaults.AgentOptions
agentOptions = appCfg.AgentOptions
}
// if a team spec is not provided, use the global features, otherwise
// build a new config from the spec with default values applied.
var err error
features := defaults.Features
features := appCfg.Features
if spec.Features != nil {
features, err = unmarshalWithGlobalDefaults(spec.Features)
if err != nil {
@ -796,8 +796,8 @@ func (svc *Service) createTeamFromSpec(
return nil, err
}
macOSSetup := spec.MDM.MacOSSetup
if macOSSetup.MacOSSetupAssistant.Set || macOSSetup.BootstrapPackage.Set {
if !defaults.MDM.EnabledAndConfigured {
if macOSSetup.MacOSSetupAssistant.Value != "" || macOSSetup.BootstrapPackage.Value != "" {
if !appCfg.MDM.EnabledAndConfigured {
return nil, ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("macos_setup",
`Couldn't update macos_setup because MDM features aren't turned on in Fleet. Use fleetctl generate mdm-apple and then fleet serve with mdm configuration to turn on MDM features.`))
}
@ -809,7 +809,7 @@ func (svc *Service) createTeamFromSpec(
}
}
if enableDiskEncryption && !defaults.MDM.AtLeastOnePlatformEnabledAndConfigured() {
if enableDiskEncryption && !appCfg.MDM.AtLeastOnePlatformEnabledAndConfigured() {
return nil, ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("mdm",
`Couldn't edit enable_disk_encryption. Neither macOS MDM nor Windows is turned on. Visit https://fleetdm.com/docs/using-fleet to learn how to turn on MDM.`))
}
@ -836,7 +836,8 @@ func (svc *Service) createTeamFromSpec(
return nil, err
}
if enableDiskEncryption && defaults.MDM.EnabledAndConfigured {
if enableDiskEncryption && appCfg.MDM.EnabledAndConfigured {
// TODO: Are we missing an activity or anything else for BitLocker here?
if err := svc.MDMAppleEnableFileVaultAndEscrow(ctx, &tm.ID); err != nil {
return nil, ctxerr.Wrap(ctx, err, "enable team filevault and escrow")
}
@ -883,7 +884,7 @@ func (svc *Service) editTeamFromSpec(
team.Config.MDM.MacOSUpdates = spec.MDM.MacOSUpdates
}
oldMacOSDiskEncryption := team.Config.MDM.EnableDiskEncryption
oldEnableDiskEncryption := team.Config.MDM.EnableDiskEncryption
if err := svc.applyTeamMacOSSettings(ctx, spec, &team.Config.MDM.MacOSSettings); err != nil {
return err
}
@ -896,38 +897,41 @@ func (svc *Service) editTeamFromSpec(
} else if de := team.Config.MDM.MacOSSettings.DeprecatedEnableDiskEncryption; de != nil {
team.Config.MDM.EnableDiskEncryption = *de
}
if team.Config.MDM.EnableDiskEncryption && !appCfg.MDM.AtLeastOnePlatformEnabledAndConfigured() {
didUpdateDiskEncryption := team.Config.MDM.EnableDiskEncryption != oldEnableDiskEncryption
if !appCfg.MDM.AtLeastOnePlatformEnabledAndConfigured() && didUpdateDiskEncryption && team.Config.MDM.EnableDiskEncryption {
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("mdm",
`Couldn't edit enable_disk_encryption. Neither macOS MDM nor Windows is turned on. Visit https://fleetdm.com/docs/using-fleet to learn how to turn on MDM.`))
}
oldMacOSSetup := team.Config.MDM.MacOSSetup
if spec.MDM.MacOSSetup.MacOSSetupAssistant.Set || spec.MDM.MacOSSetup.BootstrapPackage.Set {
if !appCfg.MDM.EnabledAndConfigured {
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("macos_setup",
`Couldn't update macos_setup because MDM features aren't turned on in Fleet. Use fleetctl generate mdm-apple and then fleet serve with mdm configuration to turn on MDM features.`))
}
if spec.MDM.MacOSSetup.MacOSSetupAssistant.Set {
team.Config.MDM.MacOSSetup.MacOSSetupAssistant = spec.MDM.MacOSSetup.MacOSSetupAssistant
}
if spec.MDM.MacOSSetup.BootstrapPackage.Set {
team.Config.MDM.MacOSSetup.BootstrapPackage = spec.MDM.MacOSSetup.BootstrapPackage
}
var didUpdateSetupAssistant, didUpdateBootstrapPackage bool
if spec.MDM.MacOSSetup.MacOSSetupAssistant.Set {
didUpdateSetupAssistant = oldMacOSSetup.MacOSSetupAssistant.Value != spec.MDM.MacOSSetup.MacOSSetupAssistant.Value
team.Config.MDM.MacOSSetup.MacOSSetupAssistant = spec.MDM.MacOSSetup.MacOSSetupAssistant
}
if spec.MDM.MacOSSetup.BootstrapPackage.Set {
didUpdateBootstrapPackage = oldMacOSSetup.BootstrapPackage.Value != spec.MDM.MacOSSetup.BootstrapPackage.Value
team.Config.MDM.MacOSSetup.BootstrapPackage = spec.MDM.MacOSSetup.BootstrapPackage
}
if !appCfg.MDM.EnabledAndConfigured &&
((didUpdateSetupAssistant && team.Config.MDM.MacOSSetup.MacOSSetupAssistant.Value != "") ||
(didUpdateBootstrapPackage && team.Config.MDM.MacOSSetup.BootstrapPackage.Value != "")) {
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("macos_setup",
`Couldn't update macos_setup because MDM features aren't turned on in Fleet. Use fleetctl generate mdm-apple and then fleet serve with mdm configuration to turn on MDM features.`))
}
var didUpdateMacOSEndUserAuth bool
if spec.MDM.MacOSSetup.EnableEndUserAuthentication != oldMacOSSetup.EnableEndUserAuthentication {
didUpdateMacOSEndUserAuth := spec.MDM.MacOSSetup.EnableEndUserAuthentication != oldMacOSSetup.EnableEndUserAuthentication
if didUpdateMacOSEndUserAuth && spec.MDM.MacOSSetup.EnableEndUserAuthentication {
if !appCfg.MDM.EnabledAndConfigured {
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("macos_setup.enable_end_user_authentication",
`Couldn't update macos_setup.enable_end_user_authentication because MDM features aren't turned on in Fleet. Use fleetctl generate mdm-apple and then fleet serve with mdm configuration to turn on MDM features.`))
}
if spec.MDM.MacOSSetup.EnableEndUserAuthentication && appCfg.MDM.EndUserAuthentication.IsEmpty() {
if appCfg.MDM.EndUserAuthentication.IsEmpty() {
// TODO: update this error message to include steps to resolve the issue once docs for IdP
// config are available
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("macos_setup.enable_end_user_authentication",
`Couldn't enable macos_setup.enable_end_user_authentication because no IdP is configured for MDM features.`))
}
didUpdateMacOSEndUserAuth = true
}
team.Config.MDM.MacOSSetup.EnableEndUserAuthentication = spec.MDM.MacOSSetup.EnableEndUserAuthentication
@ -953,7 +957,8 @@ func (svc *Service) editTeamFromSpec(
return err
}
}
if appCfg.MDM.EnabledAndConfigured && oldMacOSDiskEncryption != team.Config.MDM.EnableDiskEncryption {
if appCfg.MDM.EnabledAndConfigured && didUpdateDiskEncryption {
// TODO: Are we missing an activity or anything else for BitLocker here?
var act fleet.ActivityDetails
if team.Config.MDM.EnableDiskEncryption {
act = fleet.ActivityTypeEnabledMacosDiskEncryption{TeamID: &team.ID, TeamName: &team.Name}
@ -1016,6 +1021,8 @@ func (svc *Service) applyTeamMacOSSettings(ctx context.Context, spec *fleet.Team
field = "enable_disk_encryption"
}
if !appCfg.MDM.EnabledAndConfigured {
// TODO: Address potential edge cases when teams that previously utilized MDM features
// are edited later edited when MDM disabled
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError(fmt.Sprintf("macos_settings.%s", field),
`Couldn't update macos_settings because MDM features aren't turned on in Fleet. Use fleetctl generate mdm-apple and then fleet serve with mdm configuration to turn on MDM features.`))
}

View file

@ -606,34 +606,34 @@ func (svc *Service) validateMDM(
if mdm.EnableDiskEncryption.Value && !license.IsPremium() {
invalid.Append("macos_settings.enable_disk_encryption", ErrMissingLicense.Error())
}
if oldMdm.MacOSSetup.MacOSSetupAssistant.Value != mdm.MacOSSetup.MacOSSetupAssistant.Value && !license.IsPremium() {
if mdm.MacOSSetup.MacOSSetupAssistant.Value != "" && oldMdm.MacOSSetup.MacOSSetupAssistant.Value != mdm.MacOSSetup.MacOSSetupAssistant.Value && !license.IsPremium() {
invalid.Append("macos_setup.macos_setup_assistant", ErrMissingLicense.Error())
}
if oldMdm.MacOSSetup.BootstrapPackage.Value != mdm.MacOSSetup.BootstrapPackage.Value && !license.IsPremium() {
if mdm.MacOSSetup.BootstrapPackage.Value != "" && oldMdm.MacOSSetup.BootstrapPackage.Value != mdm.MacOSSetup.BootstrapPackage.Value && !license.IsPremium() {
invalid.Append("macos_setup.bootstrap_package", ErrMissingLicense.Error())
}
if oldMdm.MacOSSetup.EnableEndUserAuthentication != mdm.MacOSSetup.EnableEndUserAuthentication && !license.IsPremium() {
if mdm.MacOSSetup.EnableEndUserAuthentication && oldMdm.MacOSSetup.EnableEndUserAuthentication != mdm.MacOSSetup.EnableEndUserAuthentication && !license.IsPremium() {
invalid.Append("macos_setup.enable_end_user_authentication", ErrMissingLicense.Error())
}
// we want to use `oldMdm` here as this boolean is set by the fleet
// server at startup and can't be modified by the user
if !oldMdm.EnabledAndConfigured {
if len(mdm.MacOSSettings.CustomSettings) != len(oldMdm.MacOSSettings.CustomSettings) {
if len(mdm.MacOSSettings.CustomSettings) > 0 && len(mdm.MacOSSettings.CustomSettings) != len(oldMdm.MacOSSettings.CustomSettings) {
invalid.Append("macos_settings.custom_settings",
`Couldn't update macos_settings because MDM features aren't turned on in Fleet. Use fleetctl generate mdm-apple and then fleet serve with mdm configuration to turn on MDM features.`)
}
if oldMdm.MacOSSetup.MacOSSetupAssistant.Value != mdm.MacOSSetup.MacOSSetupAssistant.Value {
if mdm.MacOSSetup.MacOSSetupAssistant.Value != "" && oldMdm.MacOSSetup.MacOSSetupAssistant.Value != mdm.MacOSSetup.MacOSSetupAssistant.Value {
invalid.Append("macos_setup.macos_setup_assistant",
`Couldn't update macos_setup because MDM features aren't turned on in Fleet. Use fleetctl generate mdm-apple and then fleet serve with mdm configuration to turn on MDM features.`)
}
if oldMdm.MacOSSetup.BootstrapPackage.Value != mdm.MacOSSetup.BootstrapPackage.Value {
if mdm.MacOSSetup.BootstrapPackage.Value != "" && oldMdm.MacOSSetup.BootstrapPackage.Value != mdm.MacOSSetup.BootstrapPackage.Value {
invalid.Append("macos_setup.bootstrap_package",
`Couldn't update macos_setup because MDM features aren't turned on in Fleet. Use fleetctl generate mdm-apple and then fleet serve with mdm configuration to turn on MDM features.`)
}
if oldMdm.MacOSSetup.EnableEndUserAuthentication != mdm.MacOSSetup.EnableEndUserAuthentication {
if mdm.MacOSSetup.EnableEndUserAuthentication && oldMdm.MacOSSetup.EnableEndUserAuthentication != mdm.MacOSSetup.EnableEndUserAuthentication {
invalid.Append("macos_setup.enable_end_user_authentication",
`Couldn't update macos_setup because MDM features aren't turned on in Fleet. Use fleetctl generate mdm-apple and then fleet serve with mdm configuration to turn on MDM features.`)
}
@ -656,6 +656,8 @@ func (svc *Service) validateMDM(
mdm.MacOSUpdates.Deadline != oldMdm.MacOSUpdates.Deadline
if updatingVersion || updatingDeadline {
// TODO: Should we validate MDM configured on here too?
if !license.IsPremium() {
invalid.Append("macos_updates.minimum_version", ErrMissingLicense.Error())
return
@ -668,6 +670,8 @@ func (svc *Service) validateMDM(
// EndUserAuthentication
// only validate SSO settings if they changed
if mdm.EndUserAuthentication.SSOProviderSettings != oldMdm.EndUserAuthentication.SSOProviderSettings {
// TODO: Should we validate MDM configured on here too?
if !license.IsPremium() {
invalid.Append("end_user_authentication", ErrMissingLicense.Error())
return
@ -684,6 +688,7 @@ func (svc *Service) validateMDM(
invalid.Append("macos_setup.enable_end_user_authentication",
`Couldn't enable macos_setup.enable_end_user_authentication because no IdP is configured for MDM features.`)
}
// TODO: Should we validate MDM configured on here too?
}
updatingMacOSMigration := mdm.MacOSMigration.Enable != oldMdm.MacOSMigration.Enable ||
@ -692,6 +697,8 @@ func (svc *Service) validateMDM(
// MacOSMigration validation
if updatingMacOSMigration {
// TODO: Should we validate MDM configured on here too?
if mdm.MacOSMigration.Enable {
if license.Tier != fleet.TierPremium {
invalid.Append("macos_migration.enable", ErrMissingLicense.Error())
@ -719,7 +726,7 @@ func (svc *Service) validateMDM(
// if either macOS or Windows MDM is enabled, this setting can be set.
if !mdm.AtLeastOnePlatformEnabledAndConfigured() {
if mdm.EnableDiskEncryption.Valid && mdm.EnableDiskEncryption.Value != oldMdm.EnableDiskEncryption.Value {
if mdm.EnableDiskEncryption.Valid && mdm.EnableDiskEncryption.Value && mdm.EnableDiskEncryption.Value != oldMdm.EnableDiskEncryption.Value {
invalid.Append("mdm.enable_disk_encryption",
`Couldn't edit enable_disk_encryption. Neither macOS MDM nor Windows is turned on. Visit https://fleetdm.com/docs/using-fleet to learn how to turn on MDM.`)
}

View file

@ -7387,6 +7387,650 @@ func (s *integrationMDMTestSuite) TestHostDiskEncryptionKey() {
require.Equal(t, "", hostResp.Host.MDM.OSSettings.DiskEncryption.Detail)
}
// ///////////////////////////////////////////////////////////////////////////
// Common MDM config test
func (s *integrationMDMTestSuite) TestMDMEnabledAndConfigured() {
t := s.T()
ctx := context.Background()
appConfig, err := s.ds.AppConfig(ctx)
originalCopy := appConfig.Copy()
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, s.ds.SaveAppConfig(ctx, originalCopy))
})
checkAppConfig := func(t *testing.T, mdmEnabled, winEnabled bool) appConfigResponse {
acResp := appConfigResponse{}
s.DoJSON("GET", "/api/latest/fleet/config", nil, http.StatusOK, &acResp)
require.True(t, acResp.AppConfig.MDM.AppleBMEnabledAndConfigured)
require.Equal(t, mdmEnabled, acResp.AppConfig.MDM.EnabledAndConfigured)
require.Equal(t, winEnabled, acResp.AppConfig.MDM.WindowsEnabledAndConfigured)
return acResp
}
compareMacOSSetupValues := (func(t *testing.T, got fleet.MacOSSetup, want fleet.MacOSSetup) {
require.Equal(t, want.BootstrapPackage.Value, got.BootstrapPackage.Value)
require.Equal(t, want.MacOSSetupAssistant.Value, got.MacOSSetupAssistant.Value)
require.Equal(t, want.EnableEndUserAuthentication, got.EnableEndUserAuthentication)
})
insertBootstrapPackageAndSetupAssistant := func(t *testing.T, teamID *uint) {
var tmID uint
if teamID != nil {
tmID = *teamID
}
// cleanup any residual bootstrap package
_ = s.ds.DeleteMDMAppleBootstrapPackage(ctx, tmID)
// add new bootstrap package
require.NoError(t, s.ds.InsertMDMAppleBootstrapPackage(ctx, &fleet.MDMAppleBootstrapPackage{
TeamID: tmID,
Name: "foo",
Token: uuid.New().String(),
Bytes: []byte("foo"),
Sha256: []byte("foo-sha256"),
}))
// add new setup assistant
_, err := s.ds.SetOrUpdateMDMAppleSetupAssistant(ctx, &fleet.MDMAppleSetupAssistant{
TeamID: teamID,
Name: "bar",
ProfileUUID: uuid.New().String(),
Profile: []byte("{}"),
})
require.NoError(t, err)
}
// TODO: SOme global MDM config settings don't have MDMEnabledAndConfigured or
// WindowsMDMEnabledAndConfigured validations currently. Either add validations
// and test them or test abscence of validation.
t.Run("apply app config spec", func(t *testing.T) {
t.Run("disk encryption", func(t *testing.T) {
t.Cleanup(func() {
require.NoError(t, s.ds.SaveAppConfig(ctx, appConfig))
})
acResp := checkAppConfig(t, true, true)
require.False(t, acResp.AppConfig.MDM.EnableDiskEncryption.Value) // disabled by default
// initialize our test app config
ac := appConfig.Copy()
ac.AgentOptions = nil
// enable disk encryption
ac.MDM.EnableDiskEncryption = optjson.SetBool(true)
s.DoJSON("PATCH", "/api/latest/fleet/config", ac, http.StatusOK, &acResp)
acResp = checkAppConfig(t, true, true) // both mac and windows mdm enabled
require.True(t, acResp.AppConfig.MDM.EnableDiskEncryption.Value) // enabled
// directly set MDM.EnabledAndConfigured to false
ac.MDM.EnabledAndConfigured = false
require.NoError(t, s.ds.SaveAppConfig(ctx, ac))
acResp = checkAppConfig(t, false, true) // only windows mdm enabled
require.True(t, acResp.AppConfig.MDM.EnableDiskEncryption.Value) // disabling mdm doesn't change disk encryption
// making an unrelated change should not cause validation error
ac.OrgInfo.OrgName = "f1337"
s.DoJSON("PATCH", "/api/latest/fleet/config", ac, http.StatusOK, &acResp)
acResp = checkAppConfig(t, false, true) // only windows mdm enabled
require.True(t, acResp.AppConfig.MDM.EnableDiskEncryption.Value) // no change
require.Equal(t, "f1337", acResp.AppConfig.OrgInfo.OrgName)
// disabling disk encryption doesn't cause validation error because Windows is still enabled
ac.MDM.EnableDiskEncryption = optjson.SetBool(false)
s.DoJSON("PATCH", "/api/latest/fleet/config", ac, http.StatusOK, &acResp)
acResp = checkAppConfig(t, false, true) // only windows mdm enabled
require.False(t, acResp.AppConfig.MDM.EnableDiskEncryption.Value) // disabled
require.Equal(t, "f1337", acResp.AppConfig.OrgInfo.OrgName)
// enabling disk encryption doesn't cause validation error because Windows is still enabled
ac.MDM.EnableDiskEncryption = optjson.SetBool(true)
s.DoJSON("PATCH", "/api/latest/fleet/config", ac, http.StatusOK, &acResp)
s.DoJSON("GET", "/api/latest/fleet/config", nil, http.StatusOK, &acResp)
acResp = checkAppConfig(t, false, true) // only windows mdm enabled
require.True(t, acResp.AppConfig.MDM.EnableDiskEncryption.Value) // enabled
// directly set MDM.WindowsEnabledAndConfigured to false
ac.MDM.WindowsEnabledAndConfigured = false
require.NoError(t, s.ds.SaveAppConfig(ctx, ac))
acResp = checkAppConfig(t, false, false) // both mac and windows mdm disabled
require.True(t, acResp.AppConfig.MDM.EnableDiskEncryption.Value) // disabling mdm doesn't change disk encryption
// changing unrelated config doesn't cause validation error
ac.OrgInfo.OrgName = "f1338"
s.DoJSON("PATCH", "/api/latest/fleet/config", ac, http.StatusOK, &acResp)
acResp = checkAppConfig(t, false, false) // both mac and windows mdm disabled
require.True(t, acResp.AppConfig.MDM.EnableDiskEncryption.Value) // no change
require.Equal(t, "f1338", acResp.AppConfig.OrgInfo.OrgName)
// changing MDM config doesn't cause validation error when switching to default values
ac.MDM.EnableDiskEncryption = optjson.SetBool(false)
// TODO: Should it be ok to disable disk encryption when MDM is disabled?
s.DoJSON("PATCH", "/api/latest/fleet/config", ac, http.StatusOK, &acResp)
acResp = checkAppConfig(t, false, false) // both mac and windows mdm disabled
require.False(t, acResp.AppConfig.MDM.EnableDiskEncryption.Value) // changed to disabled
// changing MDM config does cause validation error when switching to non-default vailes
ac.MDM.EnableDiskEncryption = optjson.SetBool(true)
s.DoJSON("PATCH", "/api/latest/fleet/config", ac, http.StatusUnprocessableEntity, &acResp)
acResp = checkAppConfig(t, false, false) // both mac and windows mdm disabled
require.False(t, acResp.AppConfig.MDM.EnableDiskEncryption.Value) // still disabled
})
t.Run("macos setup", func(t *testing.T) {
t.Cleanup(func() {
require.NoError(t, s.ds.SaveAppConfig(ctx, appConfig))
})
acResp := checkAppConfig(t, true, true)
compareMacOSSetupValues(t, fleet.MacOSSetup{}, acResp.AppConfig.MDM.MacOSSetup) // disabled by default
// initialize our test app config
ac := appConfig.Copy()
ac.AgentOptions = nil
ac.MDM.EndUserAuthentication = fleet.MDMEndUserAuthentication{
SSOProviderSettings: fleet.SSOProviderSettings{
EntityID: "sso-provider",
IDPName: "sso-provider",
MetadataURL: "https://sso-provider.example.com/metadata",
},
}
// add db records for bootstrap package and setup assistant
insertBootstrapPackageAndSetupAssistant(t, nil)
// enable MacOSSetup options
ac.MDM.MacOSSetup = fleet.MacOSSetup{
BootstrapPackage: optjson.SetString("foo"),
EnableEndUserAuthentication: true,
MacOSSetupAssistant: optjson.SetString("bar"),
}
s.DoJSON("PATCH", "/api/latest/fleet/config", ac, http.StatusOK, &acResp)
acResp = checkAppConfig(t, true, true) // both mac and windows mdm enabled
compareMacOSSetupValues(t, acResp.MDM.MacOSSetup, ac.MDM.MacOSSetup) // applied
// directly set MDM.EnabledAndConfigured to false
ac.MDM.EnabledAndConfigured = false
require.NoError(t, s.ds.SaveAppConfig(ctx, ac))
acResp = checkAppConfig(t, false, true) // only windows mdm enabled
compareMacOSSetupValues(t, acResp.MDM.MacOSSetup, ac.MDM.MacOSSetup) // still applied
// making an unrelated change should not cause validation error
ac.OrgInfo.OrgName = "f1337"
s.DoJSON("PATCH", "/api/latest/fleet/config", ac, http.StatusOK, &acResp)
acResp = checkAppConfig(t, false, true) // only windows mdm enabled
compareMacOSSetupValues(t, acResp.MDM.MacOSSetup, ac.MDM.MacOSSetup) // still applied
require.Equal(t, "f1337", acResp.AppConfig.OrgInfo.OrgName)
// disabling doesn't cause validation error
ac.MDM.MacOSSetup = fleet.MacOSSetup{
BootstrapPackage: optjson.SetString(""),
EnableEndUserAuthentication: false,
MacOSSetupAssistant: optjson.SetString(""),
}
s.DoJSON("PATCH", "/api/latest/fleet/config", ac, http.StatusOK, &acResp)
acResp = checkAppConfig(t, false, true) // only windows mdm enabled
compareMacOSSetupValues(t, acResp.MDM.MacOSSetup, ac.MDM.MacOSSetup) // applied
require.Equal(t, "f1337", acResp.AppConfig.OrgInfo.OrgName)
// bootstrap package and setup assistant were removed so reinsert records for next test
insertBootstrapPackageAndSetupAssistant(t, nil)
// enable MacOSSetup options fails because only Windows is enabled.
ac.MDM.MacOSSetup = fleet.MacOSSetup{
BootstrapPackage: optjson.SetString("foo"),
EnableEndUserAuthentication: true,
MacOSSetupAssistant: optjson.SetString("bar"),
}
s.DoJSON("PATCH", "/api/latest/fleet/config", ac, http.StatusUnprocessableEntity, &acResp)
acResp = checkAppConfig(t, false, true) // only windows enabled
// directly set MDM.EnabledAndConfigured to true and windows to false
ac.MDM.EnabledAndConfigured = true
ac.MDM.WindowsEnabledAndConfigured = false
require.NoError(t, s.ds.SaveAppConfig(ctx, ac))
acResp = checkAppConfig(t, true, false) // mac enabled, windows disabled
compareMacOSSetupValues(t, acResp.MDM.MacOSSetup, ac.MDM.MacOSSetup) // directly applied
// changing unrelated config doesn't cause validation error
ac.OrgInfo.OrgName = "f1338"
s.DoJSON("PATCH", "/api/latest/fleet/config", ac, http.StatusOK, &acResp)
acResp = checkAppConfig(t, true, false) // mac enabled, windows disabled
compareMacOSSetupValues(t, acResp.MDM.MacOSSetup, ac.MDM.MacOSSetup) // no change
require.Equal(t, "f1338", acResp.AppConfig.OrgInfo.OrgName)
// disabling doesn't cause validation error
ac.MDM.MacOSSetup = fleet.MacOSSetup{
BootstrapPackage: optjson.SetString(""),
EnableEndUserAuthentication: false,
MacOSSetupAssistant: optjson.SetString(""),
}
s.DoJSON("PATCH", "/api/latest/fleet/config", ac, http.StatusOK, &acResp)
acResp = checkAppConfig(t, true, false) // only windows mdm enabled
compareMacOSSetupValues(t, acResp.MDM.MacOSSetup, ac.MDM.MacOSSetup) // applied
// bootstrap package and setup assistant were removed so reinsert records for next test
insertBootstrapPackageAndSetupAssistant(t, nil)
// enable MacOSSetup options succeeds because only Windows is disabled
ac.MDM.MacOSSetup = fleet.MacOSSetup{
BootstrapPackage: optjson.SetString("foo"),
EnableEndUserAuthentication: true,
MacOSSetupAssistant: optjson.SetString("bar"),
}
s.DoJSON("PATCH", "/api/latest/fleet/config", ac, http.StatusOK, &acResp)
acResp = checkAppConfig(t, true, false) // only windows enabled
compareMacOSSetupValues(t, acResp.MDM.MacOSSetup, ac.MDM.MacOSSetup) // applied
// directly set MDM.EnabledAndConfigured to false
ac.MDM.EnabledAndConfigured = false
require.NoError(t, s.ds.SaveAppConfig(ctx, ac))
acResp = checkAppConfig(t, false, false) // both mac and windows mdm disabled
compareMacOSSetupValues(t, acResp.MDM.MacOSSetup, ac.MDM.MacOSSetup) // still applied
// changing unrelated config doesn't cause validation error
ac.OrgInfo.OrgName = "f1339"
s.DoJSON("PATCH", "/api/latest/fleet/config", ac, http.StatusOK, &acResp)
acResp = checkAppConfig(t, false, false) // both disabled
compareMacOSSetupValues(t, acResp.MDM.MacOSSetup, ac.MDM.MacOSSetup) // no change
require.Equal(t, "f1339", acResp.AppConfig.OrgInfo.OrgName)
// setting macos setup empty values doesn't cause validation error when mdm is disabled
ac.MDM.MacOSSetup = fleet.MacOSSetup{
BootstrapPackage: optjson.SetString(""),
EnableEndUserAuthentication: false,
MacOSSetupAssistant: optjson.SetString(""),
}
s.DoJSON("PATCH", "/api/latest/fleet/config", ac, http.StatusOK, &acResp)
acResp = checkAppConfig(t, false, false) // both disabled
compareMacOSSetupValues(t, acResp.MDM.MacOSSetup, ac.MDM.MacOSSetup) // applied
// setting macos setup to non-empty values fails because mdm disabled
ac.MDM.MacOSSetup = fleet.MacOSSetup{
BootstrapPackage: optjson.SetString("foo"),
EnableEndUserAuthentication: true,
MacOSSetupAssistant: optjson.SetString("bar"),
}
s.DoJSON("PATCH", "/api/latest/fleet/config", ac, http.StatusUnprocessableEntity, &acResp)
acResp = checkAppConfig(t, false, false) // both disabled
})
t.Run("macos settings", func(t *testing.T) {
t.Cleanup(func() {
require.NoError(t, s.ds.SaveAppConfig(ctx, appConfig))
})
// initialize our test app config
ac := appConfig.Copy()
ac.AgentOptions = nil
ac.MDM.MacOSSettings.CustomSettings = []string{}
require.NoError(t, s.ds.SaveAppConfig(ctx, ac))
acResp := checkAppConfig(t, true, true)
require.Empty(t, acResp.MDM.MacOSSettings.CustomSettings)
// add custom settings
ac.MDM.MacOSSettings.CustomSettings = []string{"foo", "bar"}
s.DoJSON("PATCH", "/api/latest/fleet/config", ac, http.StatusOK, &acResp)
acResp = checkAppConfig(t, true, true) // both mac and windows mdm enabled
require.ElementsMatch(t, acResp.MDM.MacOSSettings.CustomSettings, ac.MDM.MacOSSettings.CustomSettings) // applied
// directly set MDM.EnabledAndConfigured to false
ac.MDM.EnabledAndConfigured = false
require.NoError(t, s.ds.SaveAppConfig(ctx, ac))
acResp = checkAppConfig(t, false, true) // only windows mdm enabled
require.ElementsMatch(t, acResp.MDM.MacOSSettings.CustomSettings, ac.MDM.MacOSSettings.CustomSettings) // still applied
// making an unrelated change should not cause validation error
ac.OrgInfo.OrgName = "f1337"
s.DoJSON("PATCH", "/api/latest/fleet/config", ac, http.StatusOK, &acResp)
acResp = checkAppConfig(t, false, true) // only windows mdm enabled
require.ElementsMatch(t, acResp.MDM.MacOSSettings.CustomSettings, ac.MDM.MacOSSettings.CustomSettings) // still applied
require.Equal(t, "f1337", acResp.AppConfig.OrgInfo.OrgName)
// remove custom settings
ac.MDM.MacOSSettings.CustomSettings = []string{}
s.DoJSON("PATCH", "/api/latest/fleet/config", ac, http.StatusOK, &acResp)
acResp = checkAppConfig(t, false, true) // only windows mdm enabled
require.Empty(t, acResp.MDM.MacOSSettings.CustomSettings)
// add custom settings fails because only windows is enabled
ac.MDM.MacOSSettings.CustomSettings = []string{"foo", "bar"}
s.DoJSON("PATCH", "/api/latest/fleet/config", ac, http.StatusUnprocessableEntity, &acResp)
acResp = checkAppConfig(t, false, true) // only windows enabled
require.Empty(t, acResp.MDM.MacOSSettings.CustomSettings)
// directly set MDM.EnabledAndConfigured to true and windows to false
ac.MDM.EnabledAndConfigured = true
ac.MDM.WindowsEnabledAndConfigured = false
require.NoError(t, s.ds.SaveAppConfig(ctx, ac))
acResp = checkAppConfig(t, true, false) // mac enabled, windows disabled
require.ElementsMatch(t, acResp.MDM.MacOSSettings.CustomSettings, ac.MDM.MacOSSettings.CustomSettings) // directly applied
// changing unrelated config doesn't cause validation error
ac.OrgInfo.OrgName = "f1338"
s.DoJSON("PATCH", "/api/latest/fleet/config", ac, http.StatusOK, &acResp)
acResp = checkAppConfig(t, true, false) // mac enabled, windows disabled
require.ElementsMatch(t, acResp.MDM.MacOSSettings.CustomSettings, ac.MDM.MacOSSettings.CustomSettings) // no change
require.Equal(t, "f1338", acResp.AppConfig.OrgInfo.OrgName)
// remove custom settings doesn't cause validation error
ac.MDM.MacOSSettings.CustomSettings = []string{}
s.DoJSON("PATCH", "/api/latest/fleet/config", ac, http.StatusOK, &acResp)
acResp = checkAppConfig(t, true, false) // only windows mdm enabled
require.Empty(t, acResp.MDM.MacOSSettings.CustomSettings)
// add custom settings suceeds because only Windows is disabled
ac.MDM.MacOSSettings.CustomSettings = []string{"foo", "bar"}
s.DoJSON("PATCH", "/api/latest/fleet/config", ac, http.StatusOK, &acResp)
acResp = checkAppConfig(t, true, false) // both mac and windows mdm enabled
require.ElementsMatch(t, acResp.MDM.MacOSSettings.CustomSettings, ac.MDM.MacOSSettings.CustomSettings) // applied
// directly set MDM.WindowsEnabledAndConfigured to false
ac.MDM.EnabledAndConfigured = false
require.NoError(t, s.ds.SaveAppConfig(ctx, ac))
acResp = checkAppConfig(t, false, false) // both mac and windows mdm disabled
require.ElementsMatch(t, acResp.MDM.MacOSSettings.CustomSettings, ac.MDM.MacOSSettings.CustomSettings) // applied
// changing unrelated config doesn't cause validation error
ac.OrgInfo.OrgName = "f1339"
s.DoJSON("PATCH", "/api/latest/fleet/config", ac, http.StatusOK, &acResp)
acResp = checkAppConfig(t, false, false) // both disabled
require.ElementsMatch(t, acResp.MDM.MacOSSettings.CustomSettings, ac.MDM.MacOSSettings.CustomSettings) // applied
require.Equal(t, "f1339", acResp.AppConfig.OrgInfo.OrgName)
// setting empty values doesn't cause validation error when mdm is disabled
ac.MDM.MacOSSettings.CustomSettings = []string{}
s.DoJSON("PATCH", "/api/latest/fleet/config", ac, http.StatusOK, &acResp)
acResp = checkAppConfig(t, false, false) // both disabled
require.Empty(t, acResp.MDM.MacOSSettings.CustomSettings)
// setting non-empty values fails because mdm disabled
ac.MDM.MacOSSettings.CustomSettings = []string{"foo", "bar"}
s.DoJSON("PATCH", "/api/latest/fleet/config", ac, http.StatusUnprocessableEntity, &acResp)
acResp = checkAppConfig(t, false, false) // both disabled
require.Empty(t, acResp.MDM.MacOSSettings.CustomSettings)
})
})
// TODO: Improve validations and related test coverage of team MDM config.
// Some settings don't have MDMEnabledAndConfigured or WindowsMDMEnabledAndConfigured
// validations currently. Either add vailidations and test them or test abscence
// of validation. Also, the tests below only cover a limited set of permutations
// compared to the app config tests above and should be expanded accordingly.
t.Run("modify team", func(t *testing.T) {
t.Cleanup(func() {
require.NoError(t, s.ds.SaveAppConfig(ctx, appConfig))
})
checkTeam := func(t *testing.T, team *fleet.Team, checkMDM *fleet.TeamPayloadMDM) teamResponse {
var wantDiskEncryption bool
var wantMacOSSetup fleet.MacOSSetup
if checkMDM != nil {
if checkMDM.MacOSSetup != nil {
wantMacOSSetup = *checkMDM.MacOSSetup
// bootstrap package always ignored by modify team endpoint so expect original value
wantMacOSSetup.BootstrapPackage = team.Config.MDM.MacOSSetup.BootstrapPackage
// setup assistant always ignored by modify team endpoint so expect original value
wantMacOSSetup.MacOSSetupAssistant = team.Config.MDM.MacOSSetup.MacOSSetupAssistant
}
wantDiskEncryption = checkMDM.EnableDiskEncryption.Value
}
var resp teamResponse
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), nil, http.StatusOK, &resp)
require.Equal(t, team.Name, resp.Team.Name)
require.Equal(t, wantDiskEncryption, resp.Team.Config.MDM.EnableDiskEncryption)
require.Equal(t, wantMacOSSetup.BootstrapPackage.Value, resp.Team.Config.MDM.MacOSSetup.BootstrapPackage.Value)
require.Equal(t, wantMacOSSetup.MacOSSetupAssistant.Value, resp.Team.Config.MDM.MacOSSetup.MacOSSetupAssistant.Value)
require.Equal(t, wantMacOSSetup.EnableEndUserAuthentication, resp.Team.Config.MDM.MacOSSetup.EnableEndUserAuthentication)
return resp
}
// initialize our test app config
ac := appConfig.Copy()
ac.AgentOptions = nil
ac.MDM.EnabledAndConfigured = false
ac.MDM.WindowsEnabledAndConfigured = false
require.NoError(t, s.ds.SaveAppConfig(ctx, ac))
checkAppConfig(t, false, false) // both mac and windows mdm disabled
var createTeamResp teamResponse
s.DoJSON("POST", "/api/latest/fleet/teams", createTeamRequest{fleet.TeamPayload{
Name: ptr.String("Ninjas"),
MDM: &fleet.TeamPayloadMDM{EnableDiskEncryption: optjson.SetBool(true)}, // mdm is ignored by the create team endpoint
}}, http.StatusOK, &createTeamResp)
team := createTeamResp.Team
getTeamResp := checkTeam(t, team, nil) // newly created team has empty mdm config
t.Cleanup(func() {
require.NoError(t, s.ds.DeleteTeam(ctx, team.ID))
})
// TODO: Add cases for other team MDM config (e.g., macos settings, macos updates,
// migration) and for other permutations of starting values (see app config tests above).
cases := []struct {
name string
mdm *fleet.TeamPayloadMDM
expectedStatus int
}{
{
"mdm empty",
&fleet.TeamPayloadMDM{},
http.StatusOK,
},
{
"mdm all zero values",
&fleet.TeamPayloadMDM{
EnableDiskEncryption: optjson.SetBool(false),
MacOSSetup: &fleet.MacOSSetup{
BootstrapPackage: optjson.SetString(""),
EnableEndUserAuthentication: false,
MacOSSetupAssistant: optjson.SetString(""),
},
},
http.StatusOK,
},
{
"bootstrap package",
&fleet.TeamPayloadMDM{
MacOSSetup: &fleet.MacOSSetup{
BootstrapPackage: optjson.SetString("some-package"),
},
},
// bootstrap package is always ignored by the modify team endpoint
http.StatusOK,
},
{
"setup assistant",
&fleet.TeamPayloadMDM{
MacOSSetup: &fleet.MacOSSetup{
MacOSSetupAssistant: optjson.SetString("some-setup-assistant"),
},
},
// setup assistant is always ignored by the modify team endpoint
http.StatusOK,
},
{
"enable disk encryption",
&fleet.TeamPayloadMDM{
EnableDiskEncryption: optjson.SetBool(true),
},
// disk encryption requires mdm enabled and configured
http.StatusUnprocessableEntity,
},
{
"enable end user auth",
&fleet.TeamPayloadMDM{
MacOSSetup: &fleet.MacOSSetup{
EnableEndUserAuthentication: true,
},
},
// disk encryption requires mdm enabled and configured
http.StatusUnprocessableEntity,
},
}
for _, c := range cases {
// TODO: Add tests for other combinations of mac and windows mdm enabled/disabled
t.Run(c.name, func(t *testing.T) {
checkAppConfig(t, false, false) // both mac and windows mdm disabled
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), fleet.TeamPayload{
Name: &team.Name,
Description: ptr.String(c.name),
MDM: c.mdm,
}, c.expectedStatus, &getTeamResp)
if c.expectedStatus == http.StatusOK {
getTeamResp = checkTeam(t, team, c.mdm)
require.Equal(t, c.name, getTeamResp.Team.Description)
} else {
checkTeam(t, team, nil)
}
})
}
})
// TODO: Improve validations and related test coverage of team MDM config.
// Some settings don't have MDMEnabledAndConfigured or WindowsMDMEnabledAndConfigured
// validations currently. Either add vailidations and test them or test abscence
// of validation. Also, the tests below only cover a limited set of permutations
// compared to the app config tests above and should be expanded accordingly.
t.Run("edit team spec", func(t *testing.T) {
t.Cleanup(func() {
require.NoError(t, s.ds.SaveAppConfig(ctx, appConfig))
})
checkTeam := func(t *testing.T, team *fleet.Team, checkMDM *fleet.TeamSpecMDM) teamResponse {
var wantDiskEncryption bool
var wantMacOSSetup fleet.MacOSSetup
if checkMDM != nil {
wantMacOSSetup = checkMDM.MacOSSetup
wantDiskEncryption = checkMDM.EnableDiskEncryption.Value
}
var resp teamResponse
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/teams/%d", team.ID), nil, http.StatusOK, &resp)
require.Equal(t, team.Name, resp.Team.Name)
require.Equal(t, wantDiskEncryption, resp.Team.Config.MDM.EnableDiskEncryption)
require.Equal(t, wantMacOSSetup.BootstrapPackage.Value, resp.Team.Config.MDM.MacOSSetup.BootstrapPackage.Value)
require.Equal(t, wantMacOSSetup.MacOSSetupAssistant.Value, resp.Team.Config.MDM.MacOSSetup.MacOSSetupAssistant.Value)
require.Equal(t, wantMacOSSetup.EnableEndUserAuthentication, resp.Team.Config.MDM.MacOSSetup.EnableEndUserAuthentication)
return resp
}
// initialize our test app config
ac := appConfig.Copy()
ac.AgentOptions = nil
ac.MDM.EnabledAndConfigured = false
ac.MDM.WindowsEnabledAndConfigured = false
require.NoError(t, s.ds.SaveAppConfig(ctx, ac))
checkAppConfig(t, false, false) // both mac and windows mdm disabled
// create a team from spec
tmSpecReq := applyTeamSpecsRequest{Specs: []*fleet.TeamSpec{{Name: "Pirates"}}}
var tmSpecResp applyTeamSpecsResponse
s.DoJSON("POST", "/api/latest/fleet/spec/teams", tmSpecReq, http.StatusOK, &tmSpecResp)
teamID, ok := tmSpecResp.TeamIDsByName["Pirates"]
require.True(t, ok)
team := fleet.Team{ID: teamID, Name: "Pirates"}
checkTeam(t, &team, nil) // newly created team has empty mdm config
t.Cleanup(func() {
require.NoError(t, s.ds.DeleteTeam(ctx, team.ID))
})
// TODO: Add cases for other team MDM config (e.g., macos settings, macos updates,
// migration) and for other permutations of starting values (see app config tests above).
cases := []struct {
name string
mdm *fleet.TeamSpecMDM
expectedStatus int
}{
{
"mdm empty",
&fleet.TeamSpecMDM{},
http.StatusOK,
},
{
"mdm all zero values",
&fleet.TeamSpecMDM{
EnableDiskEncryption: optjson.SetBool(false),
MacOSSetup: fleet.MacOSSetup{
BootstrapPackage: optjson.SetString(""),
EnableEndUserAuthentication: false,
MacOSSetupAssistant: optjson.SetString(""),
},
},
http.StatusOK,
},
{
"bootstrap package",
&fleet.TeamSpecMDM{
MacOSSetup: fleet.MacOSSetup{
BootstrapPackage: optjson.SetString("some-package"),
},
},
// bootstrap package requires mdm enabled and configured
http.StatusUnprocessableEntity,
},
{
"setup assistant",
&fleet.TeamSpecMDM{
MacOSSetup: fleet.MacOSSetup{
MacOSSetupAssistant: optjson.SetString("some-setup-assistant"),
},
},
// setup assistant requires mdm enabled and configured
http.StatusUnprocessableEntity,
},
{
"enable disk encryption",
&fleet.TeamSpecMDM{
EnableDiskEncryption: optjson.SetBool(true),
},
// disk encryption requires mdm enabled and configured
http.StatusUnprocessableEntity,
},
{
"enable end user auth",
&fleet.TeamSpecMDM{
MacOSSetup: fleet.MacOSSetup{
EnableEndUserAuthentication: true,
},
},
// disk encryption requires mdm enabled and configured
http.StatusUnprocessableEntity,
},
}
for _, c := range cases {
// TODO: Add tests for other combinations of mac and windows mdm enabled/disabled
t.Run(c.name, func(t *testing.T) {
checkAppConfig(t, false, false) // both mac and windows mdm disabled
tmSpecReq = applyTeamSpecsRequest{Specs: []*fleet.TeamSpec{{
Name: team.Name,
MDM: *c.mdm,
}}}
s.DoJSON("POST", "/api/latest/fleet/spec/teams", tmSpecReq, c.expectedStatus, &tmSpecResp)
if c.expectedStatus == http.StatusOK {
checkTeam(t, &team, c.mdm)
} else {
checkTeam(t, &team, nil)
}
})
}
})
}
// ///////////////////////////////////////////////////////////////////////////
// Common helpers