Install setup-experience VPP apps on manually-enrolled iOS/iPadOS devices (#35906)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #34042 # 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`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files) for more information. ## Testing - [X] Added/updated automated tests - [X] QA'd all new/changed functionality manually Tested on iPad and iOS. Full disclosure, VPP installs on my devices seemed to sometimes (not not always) fail silently the first time I tried them, with no `error` showing in the setup experience results. This could be due to the vagaries of user-based vpp licensing vs. device-based, which is perhaps not a real-world situation, or something else I'm not following with [how VPP license assignments work](https://github.com/fleetdm/fleet/blob/10889199a1754011119bb19df022b545e352c77d/ee/server/service/software_installers.go#L1299-L1310). I'll continue trying to reproduce it but it's difficult since it only seems to happen once per app at most, and I can't remove the user licenses from a device without wiping it (I don't have any physical devices I can do this on).
This commit is contained in:
@@ -0,0 +1 @@
|
||||
- Support installation of setup-experience VPP apps on manual-enrolled iOS/iPadOS devices
|
||||
@@ -3518,17 +3518,33 @@ func (svc *MDMAppleCheckinAndCommandService) TokenUpdate(r *mdm.Request, m *mdm.
|
||||
}
|
||||
|
||||
var hasSetupExpItems bool
|
||||
enqueueSetupExperienceItems := false
|
||||
|
||||
if m.AwaitingConfiguration {
|
||||
// Always run setup experience on non-macOS hosts(i.e. iOS/iPadOS), only run it on macOS if
|
||||
// this is not an ABM MDM migration
|
||||
if info.Platform != "darwin" || !info.MigrationInProgress {
|
||||
// Enqueue setup experience items and mark the host as being in setup experience
|
||||
hasSetupExpItems, err = svc.ds.EnqueueSetupExperienceItems(r.Context, info.Platform, r.ID, info.TeamID)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(r.Context, err, "queueing setup experience tasks")
|
||||
}
|
||||
} else {
|
||||
if info.MigrationInProgress {
|
||||
svc.logger.Log("info", "skipping setup experience enqueueing because DEP migration is in progress", "host_uuid", r.ID)
|
||||
} else {
|
||||
enqueueSetupExperienceItems = true
|
||||
}
|
||||
} else if info.Platform != "darwin" && r.Type == mdm.Device && !info.InstalledFromDEP {
|
||||
// For manual iOS/iPadOS device enrollments, check the `TokenUpdateTally` so that
|
||||
// we only run the setup experience enqueueing once per device.
|
||||
nanoEnroll, err := svc.ds.GetNanoMDMEnrollment(r.Context, r.ID)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(r.Context, err, "getting nanomdm enrollment")
|
||||
}
|
||||
if nanoEnroll != nil && nanoEnroll.TokenUpdateTally == 1 {
|
||||
enqueueSetupExperienceItems = true
|
||||
}
|
||||
}
|
||||
|
||||
// TODO -- See if there's a way to check license here to avoid unnecessary work.
|
||||
// We do check the license before actually _running_ setup experience items.
|
||||
if enqueueSetupExperienceItems {
|
||||
// Enqueue setup experience items and mark the host as being in setup experience
|
||||
hasSetupExpItems, err = svc.ds.EnqueueSetupExperienceItems(r.Context, info.Platform, r.ID, info.TeamID)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(r.Context, err, "queueing setup experience tasks")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1437,7 +1437,7 @@ func TestMDMUnenrollment(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMDMTokenUpdate(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
ctx := license.NewContext(context.Background(), &fleet.LicenseInfo{Tier: fleet.TierPremium})
|
||||
ds := new(mock.Store)
|
||||
mdmStorage := &mdmmock.MDMAppleStore{}
|
||||
pushFactory, _ := newMockAPNSPushProviderFactory()
|
||||
@@ -1600,6 +1600,164 @@ func TestMDMTokenUpdate(t *testing.T) {
|
||||
require.True(t, ds.SetHostMDMMigrationCompletedFuncInvoked)
|
||||
}
|
||||
|
||||
func TestMDMTokenUpdateIOS(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
ds := new(mock.Store)
|
||||
mdmStorage := &mdmmock.MDMAppleStore{}
|
||||
pushFactory, _ := newMockAPNSPushProviderFactory()
|
||||
pusher := nanomdm_pushsvc.New(
|
||||
mdmStorage,
|
||||
mdmStorage,
|
||||
pushFactory,
|
||||
NewNanoMDMLogger(kitlog.NewJSONLogger(os.Stdout)),
|
||||
)
|
||||
cmdr := apple_mdm.NewMDMAppleCommander(mdmStorage, pusher)
|
||||
mdmLifecycle := mdmlifecycle.New(ds, kitlog.NewNopLogger(), newActivity)
|
||||
svc := MDMAppleCheckinAndCommandService{
|
||||
ds: ds,
|
||||
mdmLifecycle: mdmLifecycle,
|
||||
commander: cmdr,
|
||||
logger: kitlog.NewNopLogger(),
|
||||
}
|
||||
uuid, serial, model, wantTeamID := "ABC-DEF-GHI", "XYZABC", "MacBookPro 16,1", uint(12)
|
||||
|
||||
ds.GetMDMIdPAccountByHostUUIDFunc = func(ctx context.Context, hostUUID string) (*fleet.MDMIdPAccount, error) {
|
||||
require.Equal(t, uuid, hostUUID)
|
||||
return &fleet.MDMIdPAccount{
|
||||
UUID: "some-uuid",
|
||||
Username: "some-user",
|
||||
Email: "some-user@example.com",
|
||||
Fullname: "Some User",
|
||||
}, nil
|
||||
}
|
||||
|
||||
ds.NewJobFunc = func(ctx context.Context, j *fleet.Job) (*fleet.Job, error) {
|
||||
return j, nil
|
||||
}
|
||||
|
||||
ds.GetHostMDMCheckinInfoFunc = func(ct context.Context, hostUUID string) (*fleet.HostMDMCheckinInfo, error) {
|
||||
require.Equal(t, uuid, hostUUID)
|
||||
return &fleet.HostMDMCheckinInfo{
|
||||
HostID: 1337,
|
||||
HardwareSerial: serial,
|
||||
DisplayName: model,
|
||||
InstalledFromDEP: true,
|
||||
TeamID: wantTeamID,
|
||||
DEPAssignedToFleet: true,
|
||||
Platform: "ios",
|
||||
}, nil
|
||||
}
|
||||
|
||||
ds.GetNanoMDMEnrollmentFunc = func(ctx context.Context, hostUUID string) (*fleet.NanoEnrollment, error) {
|
||||
return &fleet.NanoEnrollment{Enabled: true, Type: "Device", TokenUpdateTally: 1}, nil
|
||||
}
|
||||
|
||||
ds.EnqueueSetupExperienceItemsFunc = func(ctx context.Context, hostPlatformLike string, hostUUID string, teamID uint) (bool, error) {
|
||||
require.Equal(t, "ios", hostPlatformLike)
|
||||
require.Equal(t, uuid, hostUUID)
|
||||
require.Equal(t, wantTeamID, teamID)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// DEP-installed without AwaitingConfiguration - should not enqueue SetupExperience items
|
||||
err := svc.TokenUpdate(
|
||||
&mdm.Request{
|
||||
Context: ctx,
|
||||
EnrollID: &mdm.EnrollID{ID: uuid, Type: mdm.Device},
|
||||
Params: map[string]string{"enroll_reference": "abcd"},
|
||||
},
|
||||
&mdm.TokenUpdate{
|
||||
Enrollment: mdm.Enrollment{
|
||||
UDID: uuid,
|
||||
},
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.False(t, ds.EnqueueSetupExperienceItemsFuncInvoked)
|
||||
|
||||
// Non-DEP-installed, non device-type enrollment should not enqueue SetupExperience items
|
||||
err = svc.TokenUpdate(
|
||||
&mdm.Request{
|
||||
Context: ctx,
|
||||
EnrollID: &mdm.EnrollID{ID: uuid, Type: mdm.User},
|
||||
Params: map[string]string{"enroll_reference": "abcd"},
|
||||
},
|
||||
&mdm.TokenUpdate{
|
||||
Enrollment: mdm.Enrollment{
|
||||
UDID: uuid,
|
||||
},
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.False(t, ds.EnqueueSetupExperienceItemsFuncInvoked)
|
||||
|
||||
// Non-DEP-installed without AwaitingConfiguration - should not enqueue SetupExperience items if token count is > 1
|
||||
ds.GetHostMDMCheckinInfoFunc = func(ct context.Context, hostUUID string) (*fleet.HostMDMCheckinInfo, error) {
|
||||
require.Equal(t, uuid, hostUUID)
|
||||
return &fleet.HostMDMCheckinInfo{
|
||||
HostID: 1337,
|
||||
HardwareSerial: serial,
|
||||
DisplayName: model,
|
||||
InstalledFromDEP: false,
|
||||
TeamID: wantTeamID,
|
||||
DEPAssignedToFleet: true,
|
||||
Platform: "ios",
|
||||
}, nil
|
||||
}
|
||||
|
||||
ds.GetNanoMDMEnrollmentFunc = func(ctx context.Context, hostUUID string) (*fleet.NanoEnrollment, error) {
|
||||
return &fleet.NanoEnrollment{Enabled: true, Type: "Device", TokenUpdateTally: 2}, nil
|
||||
}
|
||||
|
||||
err = svc.TokenUpdate(
|
||||
&mdm.Request{
|
||||
Context: ctx,
|
||||
EnrollID: &mdm.EnrollID{ID: uuid, Type: mdm.Device},
|
||||
Params: map[string]string{"enroll_reference": "abcd"},
|
||||
},
|
||||
&mdm.TokenUpdate{
|
||||
Enrollment: mdm.Enrollment{
|
||||
UDID: uuid,
|
||||
},
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.False(t, ds.EnqueueSetupExperienceItemsFuncInvoked)
|
||||
|
||||
// Non-DEP-installed without AwaitingConfiguration - should enqueue SetupExperience items if token count is 1
|
||||
ds.GetHostMDMCheckinInfoFunc = func(ct context.Context, hostUUID string) (*fleet.HostMDMCheckinInfo, error) {
|
||||
require.Equal(t, uuid, hostUUID)
|
||||
return &fleet.HostMDMCheckinInfo{
|
||||
HostID: 1337,
|
||||
HardwareSerial: serial,
|
||||
DisplayName: model,
|
||||
InstalledFromDEP: false,
|
||||
TeamID: wantTeamID,
|
||||
DEPAssignedToFleet: true,
|
||||
Platform: "ios",
|
||||
}, nil
|
||||
}
|
||||
|
||||
ds.GetNanoMDMEnrollmentFunc = func(ctx context.Context, hostUUID string) (*fleet.NanoEnrollment, error) {
|
||||
return &fleet.NanoEnrollment{Enabled: true, Type: "Device", TokenUpdateTally: 1}, nil
|
||||
}
|
||||
|
||||
err = svc.TokenUpdate(
|
||||
&mdm.Request{
|
||||
Context: ctx,
|
||||
EnrollID: &mdm.EnrollID{ID: uuid, Type: mdm.Device},
|
||||
Params: map[string]string{"enroll_reference": "abcd"},
|
||||
},
|
||||
&mdm.TokenUpdate{
|
||||
Enrollment: mdm.Enrollment{
|
||||
UDID: uuid,
|
||||
},
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.True(t, ds.EnqueueSetupExperienceItemsFuncInvoked)
|
||||
}
|
||||
|
||||
func TestMDMCheckout(t *testing.T) {
|
||||
ds := new(mock.Store)
|
||||
mdmLifecycle := mdmlifecycle.New(ds, kitlog.NewNopLogger(), newActivity)
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/fleetdm/fleet/v4/pkg/fleetdbase"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/license"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
apple_mdm "github.com/fleetdm/fleet/v4/server/mdm/apple"
|
||||
"github.com/fleetdm/fleet/v4/server/mdm/apple/appmanifest"
|
||||
@@ -114,6 +115,15 @@ func (a *AppleMDM) runPostManualEnrollment(ctx context.Context, args appleMDMArg
|
||||
if _, err := a.installFleetd(ctx, args.HostUUID); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "installing post-enrollment packages")
|
||||
}
|
||||
} else {
|
||||
// We shouldn't have any setup experience steps if we're not on a premium license,
|
||||
// but best to check anyway plus it saves some db queries.
|
||||
if license.IsPremium(ctx) {
|
||||
_, err := a.installSetupExperienceVPPAppsOnIosIpadOS(ctx, args.HostUUID)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "installing setup experience VPP apps on iOS/iPadOS")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user