diff --git a/changes/13363-match-dep-profiles b/changes/13363-match-dep-profiles new file mode 100644 index 0000000000..ccfd00f272 --- /dev/null +++ b/changes/13363-match-dep-profiles @@ -0,0 +1 @@ +* Automatically set the DEP profile to be the same as "no team" (if set) for teams created using the `/match` endpoint (used by Puppet) diff --git a/ee/server/service/mdm.go b/ee/server/service/mdm.go index 7c769dfec4..32782940e2 100644 --- a/ee/server/service/mdm.go +++ b/ee/server/service/mdm.go @@ -897,6 +897,7 @@ func (svc *Service) getOrCreatePreassignTeam(ctx context.Context, groups []strin EnableDiskEncryption: true, }, MacOSSetup: &fleet.MacOSSetup{ + MacOSSetupAssistant: ac.MDM.MacOSSetup.MacOSSetupAssistant, // NOTE: BootstrapPackage is currently ignored by svc.ModifyTeam and gets set // instead by CopyDefaultMDMAppleBootstrapPackage below // BootstrapPackage: ac.MDM.MacOSSetup.BootstrapPackage, @@ -914,6 +915,29 @@ func (svc *Service) getOrCreatePreassignTeam(ctx context.Context, groups []strin if err := svc.ds.CopyDefaultMDMAppleBootstrapPackage(ctx, ac, team.ID); err != nil { return nil, err } + + // get the global setup assistant contents (this is different + // from MDM.MacOSSetup.MacOSSetupAssistant we set above, the + // prior is the path to the file, this is the actual file + // contents. + asst, err := svc.ds.GetMDMAppleSetupAssistant(ctx, nil) + if err != nil { + // if "no team" doesn't have custom setup assistant + // settings configured, this team won't have either. + if fleet.IsNotFound(err) { + return team, nil + } + return nil, ctxerr.Wrap(ctx, err, "get global setup assistant") + + } + _, err = svc.SetOrUpdateMDMAppleSetupAssistant(ctx, &fleet.MDMAppleSetupAssistant{ + TeamID: &team.ID, + Name: asst.Name, + Profile: asst.Profile, + }) + if err != nil { + return nil, ctxerr.Wrap(ctx, err, "set setup assistant for new team") + } } return team, nil } diff --git a/ee/server/service/mdm_test.go b/ee/server/service/mdm_test.go index 06c179d6f9..ac4043c372 100644 --- a/ee/server/service/mdm_test.go +++ b/ee/server/service/mdm_test.go @@ -134,6 +134,8 @@ func TestGetOrCreatePreassignTeam(t *testing.T) { ds.CopyDefaultMDMAppleBootstrapPackageFuncInvoked = false ds.AppConfigFuncInvoked = false ds.NewJobFuncInvoked = false + ds.GetMDMAppleSetupAssistantFuncInvoked = false + ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked = false } setupDS := func(t *testing.T) { resetInvoked() @@ -181,6 +183,9 @@ func TestGetOrCreatePreassignTeam(t *testing.T) { ds.NewJobFunc = func(ctx context.Context, job *fleet.Job) (*fleet.Job, error) { return nil, errors.New("not implemented") } + ds.GetMDMAppleSetupAssistantFunc = func(ctx context.Context, teamID *uint) (*fleet.MDMAppleSetupAssistant, error) { + return nil, errors.New("not implemented") + } } ctx := viewer.NewContext(context.Background(), viewer.Viewer{User: test.UserAdmin}) @@ -206,6 +211,7 @@ func TestGetOrCreatePreassignTeam(t *testing.T) { t.Run("create preassign team", func(t *testing.T) { // setup ds with assertions for this test setupDS(t) + lastTeamID := uint(0) ds.NewTeamFunc = func(ctx context.Context, team *fleet.Team) (*fleet.Team, error) { for _, tm := range teamStore { if tm.Name == team.Name { @@ -217,6 +223,7 @@ func TestGetOrCreatePreassignTeam(t *testing.T) { require.False(t, ok) // sanity check team.ID = id teamStore[id] = team + lastTeamID = id return team, nil } ds.SaveTeamFunc = func(ctx context.Context, team *fleet.Team) (*fleet.Team, error) { @@ -230,21 +237,22 @@ func TestGetOrCreatePreassignTeam(t *testing.T) { // // instead by CopyDefaultMDMAppleBootstrapPackage below // require.Equal(t, appConfig.MDM.MacOSSetup.BootstrapPackage.Value, team.Config.MDM.MacOSSetup.BootstrapPackage.Value) require.Equal(t, appConfig.MDM.MacOSSetup.EnableEndUserAuthentication, team.Config.MDM.MacOSSetup.EnableEndUserAuthentication) // set to default + require.Equal(t, appConfig.MDM.MacOSSetup.MacOSSetupAssistant, team.Config.MDM.MacOSSetup.MacOSSetupAssistant) // set to default teamStore[tm.ID] = team return team, nil } ds.NewMDMAppleConfigProfileFunc = func(ctx context.Context, profile fleet.MDMAppleConfigProfile) (*fleet.MDMAppleConfigProfile, error) { - require.Equal(t, uint(3), *profile.TeamID) + require.Equal(t, lastTeamID, *profile.TeamID) require.Equal(t, mobileconfig.FleetFileVaultPayloadIdentifier, profile.Identifier) return &profile, nil } ds.DeleteMDMAppleConfigProfileByTeamAndIdentifierFunc = func(ctx context.Context, teamID *uint, profileIdentifier string) error { - require.Equal(t, uint(3), *teamID) + require.Equal(t, lastTeamID, *teamID) require.Equal(t, mobileconfig.FleetFileVaultPayloadIdentifier, profileIdentifier) return nil } ds.CopyDefaultMDMAppleBootstrapPackageFunc = func(ctx context.Context, ac *fleet.AppConfig, toTeamID uint) error { - require.Equal(t, uint(3), toTeamID) + require.Equal(t, lastTeamID, toTeamID) require.NotNil(t, ac) require.Equal(t, "https://example.com/bootstrap.pkg", ac.MDM.MacOSSetup.BootstrapPackage.Value) teamStore[toTeamID].Config.MDM.MacOSSetup.BootstrapPackage = optjson.SetString(ac.MDM.MacOSSetup.BootstrapPackage.Value) @@ -253,7 +261,7 @@ func TestGetOrCreatePreassignTeam(t *testing.T) { ds.NewJobFunc = func(ctx context.Context, job *fleet.Job) (*fleet.Job, error) { wantArgs, err := json.Marshal(map[string]interface{}{ "task": worker.MacosSetupAssistantUpdateProfile, - "team_id": 3, + "team_id": lastTeamID, }) require.NoError(t, err) wantJob := &fleet.Job{ @@ -266,6 +274,33 @@ func TestGetOrCreatePreassignTeam(t *testing.T) { require.Equal(t, wantJob.State, job.State) return job, nil } + globalSetupAsst := &fleet.MDMAppleSetupAssistant{ + ID: 15, + TeamID: nil, + Name: "test asst", + Profile: json.RawMessage(`{"foo": "bar"}`), + ProfileUUID: "abc-def", + } + getSetupAsstFuncCalls := 0 + ds.GetMDMAppleSetupAssistantFunc = func(ctx context.Context, teamID *uint) (*fleet.MDMAppleSetupAssistant, error) { + // first call is to grab the global team setup assistant, the + // rest are for the team being created + if getSetupAsstFuncCalls == 0 { + require.Nil(t, teamID) + } else { + require.NotNil(t, teamID) + require.EqualValues(t, lastTeamID, *teamID) + } + getSetupAsstFuncCalls++ + return globalSetupAsst, nil + } + ds.SetOrUpdateMDMAppleSetupAssistantFunc = func(ctx context.Context, asst *fleet.MDMAppleSetupAssistant) (*fleet.MDMAppleSetupAssistant, error) { + require.Equal(t, globalSetupAsst.Name, asst.Name) + require.JSONEq(t, string(globalSetupAsst.Profile), string(asst.Profile)) + require.NotNil(t, asst.TeamID) + require.EqualValues(t, lastTeamID, *asst.TeamID) + return asst, nil + } // new team is created with bootstrap package and end user auth based on app config team, err := svc.getOrCreatePreassignTeam(ctx, preassignGroups) @@ -278,8 +313,11 @@ func TestGetOrCreatePreassignTeam(t *testing.T) { require.True(t, ds.NewMDMAppleConfigProfileFuncInvoked) require.True(t, ds.CopyDefaultMDMAppleBootstrapPackageFuncInvoked) require.True(t, ds.AppConfigFuncInvoked) + require.True(t, ds.GetMDMAppleSetupAssistantFuncInvoked) + require.True(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) require.NotEmpty(t, team.Config.MDM.MacOSSetup.BootstrapPackage.Value) require.Equal(t, appConfig.MDM.MacOSSetup.BootstrapPackage.Value, team.Config.MDM.MacOSSetup.BootstrapPackage.Value) + require.Equal(t, appConfig.MDM.MacOSSetup.MacOSSetupAssistant.Value, team.Config.MDM.MacOSSetup.MacOSSetupAssistant.Value) require.True(t, team.Config.MDM.MacOSSetup.EnableEndUserAuthentication) require.Equal(t, appConfig.MDM.MacOSSetup.EnableEndUserAuthentication, team.Config.MDM.MacOSSetup.EnableEndUserAuthentication) require.True(t, ds.NewJobFuncInvoked) @@ -297,6 +335,33 @@ func TestGetOrCreatePreassignTeam(t *testing.T) { require.False(t, ds.CopyDefaultMDMAppleBootstrapPackageFuncInvoked) require.False(t, ds.AppConfigFuncInvoked) require.False(t, ds.NewJobFuncInvoked) + require.False(t, ds.GetMDMAppleSetupAssistantFuncInvoked) + require.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + require.NotEmpty(t, team.Config.MDM.MacOSSetup.BootstrapPackage.Value) + require.Equal(t, appConfig.MDM.MacOSSetup.BootstrapPackage.Value, team.Config.MDM.MacOSSetup.BootstrapPackage.Value) + require.Equal(t, appConfig.MDM.MacOSSetup.MacOSSetupAssistant.Value, team.Config.MDM.MacOSSetup.MacOSSetupAssistant.Value) + require.True(t, team.Config.MDM.MacOSSetup.EnableEndUserAuthentication) + require.Equal(t, appConfig.MDM.MacOSSetup.EnableEndUserAuthentication, team.Config.MDM.MacOSSetup.EnableEndUserAuthentication) + resetInvoked() + + // when a custom setup assistant is not set for "no team", we don't create a custom setup assistant + ds.GetMDMAppleSetupAssistantFunc = func(ctx context.Context, teamID *uint) (*fleet.MDMAppleSetupAssistant, error) { + require.Nil(t, teamID) + return nil, ctxerr.Wrap(ctx, ¬FoundError{}) + } + preassignGrousWithFoo := append(preassignGroups, "foo") + team, err = svc.getOrCreatePreassignTeam(ctx, preassignGrousWithFoo) + require.NoError(t, err) + require.Equal(t, uint(4), team.ID) + require.Equal(t, teamNameFromPreassignGroups(preassignGrousWithFoo), team.Name) + require.True(t, ds.TeamByNameFuncInvoked) + require.True(t, ds.NewTeamFuncInvoked) + require.True(t, ds.SaveTeamFuncInvoked) + require.True(t, ds.NewMDMAppleConfigProfileFuncInvoked) + require.True(t, ds.CopyDefaultMDMAppleBootstrapPackageFuncInvoked) + require.True(t, ds.AppConfigFuncInvoked) + require.True(t, ds.GetMDMAppleSetupAssistantFuncInvoked) + require.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) require.NotEmpty(t, team.Config.MDM.MacOSSetup.BootstrapPackage.Value) require.Equal(t, appConfig.MDM.MacOSSetup.BootstrapPackage.Value, team.Config.MDM.MacOSSetup.BootstrapPackage.Value) require.True(t, team.Config.MDM.MacOSSetup.EnableEndUserAuthentication) diff --git a/server/service/integration_mdm_test.go b/server/service/integration_mdm_test.go index bd53ae2fc2..5e3a4bd82a 100644 --- a/server/service/integration_mdm_test.go +++ b/server/service/integration_mdm_test.go @@ -690,6 +690,22 @@ func (s *integrationMDMTestSuite) TestPuppetMatchPreassignProfiles() { }) require.NoError(t, err) + // create a setup assistant for no team, for this we need to: + // 1. mock the ABM API, as it gets called to set the profile + // 2. run the DEP schedule, as this registers the default profile + s.mockDEPResponse(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"auth_session_token": "xyz"}`)) + })) + s.runDEPSchedule() + noTeamProf := `{"x": 1}` + var globalAsstResp createMDMAppleSetupAssistantResponse + s.DoJSON("POST", "/api/latest/fleet/mdm/apple/enrollment_profile", createMDMAppleSetupAssistantRequest{ + TeamID: nil, + Name: "no-team", + EnrollmentProfile: json.RawMessage(noTeamProf), + }, http.StatusOK, &globalAsstResp) + // preassign an empty profile, fails s.Do("POST", "/api/latest/fleet/mdm/apple/profiles/preassign", preassignMDMAppleProfileRequest{MDMApplePreassignProfilePayload: fleet.MDMApplePreassignProfilePayload{ExternalHostIdentifier: "empty", HostUUID: nonMDMHost.UUID, Profile: nil}}, http.StatusUnprocessableEntity) @@ -725,8 +741,8 @@ func (s *integrationMDMTestSuite) TestPuppetMatchPreassignProfiles() { require.NoError(t, err) require.Equal(t, "g1", tm1.Name) - // it create activities for the new team, the profiles assigned to it, and - // the host moved to it + // it create activities for the new team, the profiles assigned to it, + // the host moved to it, and setup assistant s.lastActivityOfTypeMatches( fleet.ActivityTypeCreatedTeam{}.ActivityName(), fmt.Sprintf(`{"team_id": %d, "team_name": %q}`, tm1.ID, tm1.Name), @@ -740,6 +756,11 @@ func (s *integrationMDMTestSuite) TestPuppetMatchPreassignProfiles() { fmt.Sprintf(`{"team_id": %d, "team_name": %q, "host_ids": [%d], "host_display_names": [%q]}`, tm1.ID, tm1.Name, h.ID, h.DisplayName()), 0) + s.lastActivityOfTypeMatches( + fleet.ActivityTypeChangedMacosSetupAssistant{}.ActivityName(), + fmt.Sprintf(`{"team_id": %d, "name": %q, "team_name": %q}`, + tm1.ID, globalAsstResp.Name, tm1.Name), + 0) // and the team has the expected profiles profs, err := s.ds.ListMDMAppleConfigProfiles(ctx, &tm1.ID) @@ -750,6 +771,11 @@ func (s *integrationMDMTestSuite) TestPuppetMatchPreassignProfiles() { require.Equal(t, prof2, []byte(profs[1].Mobileconfig)) // filevault is enabled by default require.True(t, tm1.Config.MDM.MacOSSettings.EnableDiskEncryption) + // setup assistant settings are copyied from "no team" + teamAsst, err := s.ds.GetMDMAppleSetupAssistant(ctx, &tm1.ID) + require.NoError(t, err) + require.Equal(t, globalAsstResp.Name, teamAsst.Name) + require.JSONEq(t, string(globalAsstResp.Profile), string(teamAsst.Profile)) // create a team and set profiles to it tm2, err := s.ds.NewTeam(context.Background(), &fleet.Team{ @@ -1276,15 +1302,6 @@ func (s *integrationMDMTestSuite) TestDEPProfileAssignment() { } profileAssignmentReqs := []profileAssignmentReq{} - runDEPSchedule := func() { - profileAssignmentReqs = []profileAssignmentReq{} - ch := make(chan bool) - s.onDEPScheduleDone = func() { close(ch) } - _, err := s.depSchedule.Trigger() - require.NoError(t, err) - <-ch - } - // add global profiles globalProfile := mobileconfigForTest("N1", "I1") s.Do("POST", "/api/v1/fleet/mdm/apple/profiles/batch", batchSetMDMAppleProfilesRequest{Profiles: [][]byte{globalProfile}}, http.StatusNoContent) @@ -1366,7 +1383,7 @@ func (s *integrationMDMTestSuite) TestDEPProfileAssignment() { require.Empty(t, listHostsRes.Hosts) // trigger a profile sync - runDEPSchedule() + s.runDEPSchedule() // all hosts should be returned from the hosts endpoint listHostsRes = listHostsResponse{} @@ -1471,7 +1488,8 @@ func (s *integrationMDMTestSuite) TestDEPProfileAssignment() { {SerialNumber: deletedSerial, Model: "MacBook Mini", OS: "osx", OpType: "deleted"}, {SerialNumber: addedSerial, Model: "MacBook Mini", OS: "osx", OpType: "added"}, } - runDEPSchedule() + profileAssignmentReqs = []profileAssignmentReq{} + s.runDEPSchedule() // all hosts should be returned from the hosts endpoint listHostsRes = listHostsResponse{} @@ -4315,11 +4333,7 @@ func (s *integrationMDMTestSuite) TestMigrateMDMDeviceWebhook() { require.NoError(t, err) } })) - ch := make(chan bool) - s.onDEPScheduleDone = func() { close(ch) } - _, err = s.depSchedule.Trigger() - require.NoError(t, err) - <-ch + s.runDEPSchedule() // hosts meets all requirements, webhook is run s.Do("POST", fmt.Sprintf("/api/v1/fleet/device/%s/migrate_mdm", "good-token"), nil, http.StatusNoContent) @@ -5209,11 +5223,7 @@ func (s *integrationMDMTestSuite) TestSSO() { })) // sync the list of ABM devices - ch := make(chan bool) - s.onDEPScheduleDone = func() { close(ch) } - _, err := s.depSchedule.Trigger() - require.NoError(t, err) - <-ch + s.runDEPSchedule() // MDM SSO fields are empty by default acResp := appConfigResponse{} @@ -5575,14 +5585,10 @@ func (s *integrationMDMTestSuite) TestMDMMigration() { require.NoError(t, err) } })) - ch := make(chan bool) - s.onDEPScheduleDone = func() { close(ch) } - _, err := s.depSchedule.Trigger() - require.NoError(t, err) - <-ch + s.runDEPSchedule() // simulate that the device is enrolled in a third-party MDM and DEP capable - err = s.ds.SetOrUpdateMDMData( + err := s.ds.SetOrUpdateMDMData( ctx, host.ID, false, @@ -6445,6 +6451,14 @@ func (s *integrationMDMTestSuite) runWorker() { require.Empty(s.T(), pending) } +func (s *integrationMDMTestSuite) runDEPSchedule() { + ch := make(chan bool) + s.onDEPScheduleDone = func() { close(ch) } + _, err := s.depSchedule.Trigger() + require.NoError(s.T(), err) + <-ch +} + func (s *integrationMDMTestSuite) getRawTokenValue(content string) string { // Create a regex object with the defined pattern pattern := `inputToken.value\s*=\s*'([^']*)'`