Skip to content

Commit 00112e6

Browse files
authored
Cherry-pick of #24207 for #24024 fix (#24219)
1 parent 963ad26 commit 00112e6

8 files changed

+181
-61
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
* Bypass the setup experience UI if there is no setup experience item to process (no software to install, no script to execute), so that releasing the device is done without going through that window.
2+
* Fixed releasing a DEP-enrolled macOS device if mTLS is configured for `fleetd`.

server/mdm/lifecycle/lifecycle.go

+10-7
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,14 @@ const (
3232
// Not all options are required for all actions, each individual action should
3333
// validate that it receives the required information.
3434
type HostOptions struct {
35-
Action HostAction
36-
Platform string
37-
UUID string
38-
HardwareSerial string
39-
HardwareModel string
40-
EnrollReference string
41-
Host *fleet.Host
35+
Action HostAction
36+
Platform string
37+
UUID string
38+
HardwareSerial string
39+
HardwareModel string
40+
EnrollReference string
41+
Host *fleet.Host
42+
HasSetupExperienceItems bool
4243
}
4344

4445
// HostLifecycle manages MDM host lifecycle actions
@@ -174,6 +175,7 @@ func (t *HostLifecycle) turnOnDarwin(ctx context.Context, opts HostOptions) erro
174175
opts.Platform,
175176
tmID,
176177
opts.EnrollReference,
178+
!opts.HasSetupExperienceItems,
177179
)
178180
return ctxerr.Wrap(ctx, err, "queue DEP post-enroll task")
179181
}
@@ -189,6 +191,7 @@ func (t *HostLifecycle) turnOnDarwin(ctx context.Context, opts HostOptions) erro
189191
opts.Platform,
190192
tmID,
191193
opts.EnrollReference,
194+
false,
192195
); err != nil {
193196
return ctxerr.Wrap(ctx, err, "queue manual post-enroll task")
194197
}

server/service/apple_mdm.go

+7-6
Original file line numberDiff line numberDiff line change
@@ -2769,20 +2769,21 @@ func (svc *MDMAppleCheckinAndCommandService) TokenUpdate(r *mdm.Request, m *mdm.
27692769
return ctxerr.Wrap(r.Context, err, "cleaning SCEP refs")
27702770
}
27712771

2772+
var hasSetupExpItems bool
27722773
if m.AwaitingConfiguration {
27732774
// Enqueue setup experience items and mark the host as being in setup experience
2774-
_, err := svc.ds.EnqueueSetupExperienceItems(r.Context, r.ID, info.TeamID)
2775+
hasSetupExpItems, err = svc.ds.EnqueueSetupExperienceItems(r.Context, r.ID, info.TeamID)
27752776
if err != nil {
27762777
return ctxerr.Wrap(r.Context, err, "queueing setup experience tasks")
27772778
}
2778-
27792779
}
27802780

27812781
return svc.mdmLifecycle.Do(r.Context, mdmlifecycle.HostOptions{
2782-
Action: mdmlifecycle.HostActionTurnOn,
2783-
Platform: info.Platform,
2784-
UUID: r.ID,
2785-
EnrollReference: r.Params[mobileconfig.FleetEnrollReferenceKey],
2782+
Action: mdmlifecycle.HostActionTurnOn,
2783+
Platform: info.Platform,
2784+
UUID: r.ID,
2785+
EnrollReference: r.Params[mobileconfig.FleetEnrollReferenceKey],
2786+
HasSetupExperienceItems: hasSetupExpItems,
27862787
})
27872788
}
27882789

server/service/integration_mdm_dep_test.go

+75-8
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,33 @@ func (s *integrationMDMTestSuite) TestDEPEnrollReleaseDeviceGlobal() {
121121

122122
s.enableABM("fleet_ade_test")
123123

124+
// add a setup experience script to run for no team
125+
extraArgs := make(map[string][]string)
126+
body, headers := generateNewScriptMultipartRequest(t,
127+
"script.sh", []byte(`echo "hello"`), s.token, extraArgs)
128+
s.DoRawWithHeaders("POST", "/api/latest/fleet/setup_experience/script", body.Bytes(), http.StatusOK, headers)
129+
130+
// test manual and automatic release with the new setup experience flow
131+
for _, enableReleaseManually := range []bool{false, true} {
132+
t.Run(fmt.Sprintf("enableReleaseManually=%t;new_flow", enableReleaseManually), func(t *testing.T) {
133+
s.runDEPEnrollReleaseDeviceTest(t, globalDevice, enableReleaseManually, nil, "I1", false)
134+
})
135+
}
124136
// test manual and automatic release with the old worker flow
125137
for _, enableReleaseManually := range []bool{false, true} {
126-
t.Run(fmt.Sprintf("enableReleaseManually=%t", enableReleaseManually), func(t *testing.T) {
138+
t.Run(fmt.Sprintf("enableReleaseManually=%t;old_flow", enableReleaseManually), func(t *testing.T) {
127139
s.runDEPEnrollReleaseDeviceTest(t, globalDevice, enableReleaseManually, nil, "I1", true)
128140
})
129141
}
142+
143+
// remove the setup experience script, run the new setup experience flow when
144+
// there is no setup experience item to process (so it is bypassed)
145+
s.Do("DELETE", "/api/latest/fleet/setup_experience/script", nil, http.StatusOK)
146+
for _, enableReleaseManually := range []bool{false, true} {
147+
t.Run(fmt.Sprintf("enableReleaseManually=%t;bypass_flow", enableReleaseManually), func(t *testing.T) {
148+
s.runDEPEnrollReleaseDeviceTest(t, globalDevice, enableReleaseManually, nil, "I1", false)
149+
})
150+
}
130151
}
131152

132153
func (s *integrationMDMTestSuite) TestDEPEnrollReleaseDeviceTeam() {
@@ -211,12 +232,35 @@ func (s *integrationMDMTestSuite) TestDEPEnrollReleaseDeviceTeam() {
211232
// enable FileVault
212233
s.Do("PATCH", "/api/latest/fleet/mdm/apple/settings", json.RawMessage([]byte(fmt.Sprintf(`{"enable_disk_encryption":true,"team_id":%d}`, tm.ID))), http.StatusNoContent)
213234

235+
// add a setup experience script to run for this team
236+
extraArgs := map[string][]string{
237+
"team_id": {fmt.Sprintf("%d", tm.ID)},
238+
}
239+
body, headers := generateNewScriptMultipartRequest(t,
240+
"script.sh", []byte(`echo "hello"`), s.token, extraArgs)
241+
s.DoRawWithHeaders("POST", "/api/latest/fleet/setup_experience/script", body.Bytes(), http.StatusOK, headers)
242+
243+
// test manual and automatic release with the new setup experience flow
244+
for _, enableReleaseManually := range []bool{false, true} {
245+
t.Run(fmt.Sprintf("enableReleaseManually=%t;new_flow", enableReleaseManually), func(t *testing.T) {
246+
s.runDEPEnrollReleaseDeviceTest(t, teamDevice, enableReleaseManually, &tm.ID, "I2", false)
247+
})
248+
}
214249
// test manual and automatic release with the old worker flow
215250
for _, enableReleaseManually := range []bool{false, true} {
216-
t.Run(fmt.Sprintf("enableReleaseManually=%t", enableReleaseManually), func(t *testing.T) {
251+
t.Run(fmt.Sprintf("enableReleaseManually=%t;old_flow", enableReleaseManually), func(t *testing.T) {
217252
s.runDEPEnrollReleaseDeviceTest(t, teamDevice, enableReleaseManually, &tm.ID, "I2", true)
218253
})
219254
}
255+
256+
// remove the setup experience script, run the new setup experience flow when
257+
// there is no setup experience item to process (so it is bypassed)
258+
s.Do("DELETE", "/api/latest/fleet/setup_experience/script", nil, http.StatusOK, "team_id", fmt.Sprint(tm.ID))
259+
for _, enableReleaseManually := range []bool{false, true} {
260+
t.Run(fmt.Sprintf("enableReleaseManually=%t;bypass_flow", enableReleaseManually), func(t *testing.T) {
261+
s.runDEPEnrollReleaseDeviceTest(t, teamDevice, enableReleaseManually, &tm.ID, "I2", false)
262+
})
263+
}
220264
}
221265

222266
func (s *integrationMDMTestSuite) TestDEPEnrollReleaseIphoneTeam() {
@@ -286,6 +330,11 @@ func (s *integrationMDMTestSuite) TestDEPEnrollReleaseIphoneTeam() {
286330
func (s *integrationMDMTestSuite) runDEPEnrollReleaseDeviceTest(t *testing.T, device godep.Device, enableReleaseManually bool, teamID *uint, customProfileIdent string, useOldFleetdFlow bool) {
287331
ctx := context.Background()
288332

333+
var isIphone bool
334+
if device.DeviceFamily == "iPhone" {
335+
isIphone = true
336+
}
337+
289338
// set the enable release device manually option
290339
payload := map[string]any{
291340
"enable_release_device_manually": enableReleaseManually,
@@ -359,15 +408,22 @@ func (s *integrationMDMTestSuite) runDEPEnrollReleaseDeviceTest(t *testing.T, de
359408
// enroll the host
360409
depURLToken := loadEnrollmentProfileDEPToken(t, s.ds)
361410
mdmDevice := mdmtest.NewTestMDMClientAppleDEP(s.server.URL, depURLToken)
362-
var isIphone bool
363-
if device.DeviceFamily == "iPhone" {
411+
if isIphone {
364412
mdmDevice.Model = "iPhone 14,6"
365-
isIphone = true
366413
}
367414
mdmDevice.SerialNumber = device.SerialNumber
368415
err := mdmDevice.Enroll()
369416
require.NoError(t, err)
370417

418+
// check if it has setup experience items or not
419+
hasSetupExpItems := true
420+
_, err = s.ds.GetHostAwaitingConfiguration(ctx, mdmDevice.UUID)
421+
if fleet.IsNotFound(err) {
422+
hasSetupExpItems = false
423+
} else if err != nil {
424+
require.NoError(t, err)
425+
}
426+
371427
// run the worker to process the DEP enroll request
372428
s.runWorker()
373429
// run the cron to assign configuration profiles
@@ -525,8 +581,13 @@ func (s *integrationMDMTestSuite) runDEPEnrollReleaseDeviceTest(t *testing.T, de
525581
b, err := io.ReadAll(res.Body)
526582
require.NoError(t, err)
527583
require.NoError(t, json.Unmarshal(b, &orbitConfigResp))
528-
// should be notified of the setup experience flow
529-
require.False(t, orbitConfigResp.Notifications.RunSetupExperience)
584+
if hasSetupExpItems {
585+
// should be notified of the setup experience flow
586+
require.True(t, orbitConfigResp.Notifications.RunSetupExperience)
587+
} else {
588+
// should bypass the setup experience flow
589+
require.False(t, orbitConfigResp.Notifications.RunSetupExperience)
590+
}
530591

531592
if enableReleaseManually {
532593
// get the worker's pending job from the future, there should not be any
@@ -537,7 +598,7 @@ func (s *integrationMDMTestSuite) runDEPEnrollReleaseDeviceTest(t *testing.T, de
537598
return
538599
}
539600

540-
if useOldFleetdFlow {
601+
if useOldFleetdFlow || !hasSetupExpItems {
541602
// there should be a Release Device pending job
542603
pending, err := s.ds.GetQueuedJobs(ctx, 2, time.Now().UTC().Add(time.Minute))
543604
require.NoError(t, err)
@@ -574,6 +635,12 @@ func (s *integrationMDMTestSuite) runDEPEnrollReleaseDeviceTest(t *testing.T, de
574635
require.NoError(t, err)
575636
require.Len(t, pending, 0)
576637

638+
// mark the setup experience script as done
639+
mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error {
640+
_, err := q.ExecContext(ctx, `UPDATE setup_experience_status_results SET status = 'success' WHERE host_uuid = ?`, mdmDevice.UUID)
641+
return err
642+
})
643+
577644
// call the /status endpoint to automatically release the host
578645
var statusResp getOrbitSetupExperienceStatusResponse
579646
s.DoJSON("POST", "/api/fleet/orbit/setup_experience/status", json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *enrolledHost.OrbitNodeKey)), http.StatusOK, &statusResp)

server/service/integration_mdm_test.go

+8
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,14 @@ func (s *integrationMDMTestSuite) TearDownTest() {
677677
_, err := tx.ExecContext(ctx, "DELETE FROM vpp_tokens;")
678678
return err
679679
})
680+
mysql.ExecAdhocSQL(t, s.ds, func(tx sqlx.ExtContext) error {
681+
_, err := tx.ExecContext(ctx, "DELETE FROM setup_experience_status_results;")
682+
return err
683+
})
684+
mysql.ExecAdhocSQL(t, s.ds, func(tx sqlx.ExtContext) error {
685+
_, err := tx.ExecContext(ctx, "DELETE FROM setup_experience_scripts;")
686+
return err
687+
})
680688
}
681689

682690
func (s *integrationMDMTestSuite) mockDEPResponse(orgName string, handler http.Handler) {

server/service/orbit.go

+7-9
Original file line numberDiff line numberDiff line change
@@ -249,15 +249,13 @@ func (svc *Service) GetOrbitConfig(ctx context.Context) (fleet.OrbitConfig, erro
249249
notifs.RunSetupExperience = true
250250
}
251251

252-
if inSetupAssistant || fleet.IsNotFound(err) {
253-
// If the client is running a fleetd that doesn't support setup experience, or if no
254-
// software/script has been configured for setup experience, then we should fall back to
255-
// the "old way" of releasing the device. We do an additional check for
256-
// !inSetupAssistant to prevent enqueuing a new job every time the /config
257-
// endpoint is hit.
252+
if inSetupAssistant {
253+
// If the client is running a fleetd that doesn't support setup
254+
// experience, then we should fall back to the "old way" of releasing
255+
// the device.
258256
mp, ok := capabilities.FromContext(ctx)
259-
if !ok || !mp.Has(fleet.CapabilitySetupExperience) || !inSetupAssistant {
260-
level.Debug(svc.logger).Log("msg", "host doesn't support setup experience or no setup experience configured, falling back to worker-based device release", "host_uuid", host.UUID)
257+
if !ok || !mp.Has(fleet.CapabilitySetupExperience) {
258+
level.Debug(svc.logger).Log("msg", "host doesn't support setup experience, falling back to worker-based device release", "host_uuid", host.UUID)
261259
if err := svc.processReleaseDeviceForOldFleetd(ctx, host); err != nil {
262260
return fleet.OrbitConfig{}, err
263261
}
@@ -521,7 +519,7 @@ func (svc *Service) processReleaseDeviceForOldFleetd(ctx context.Context, host *
521519

522520
// Enroll reference arg is not used in the release device task, passing empty string.
523521
if err := worker.QueueAppleMDMJob(ctx, svc.ds, svc.logger, worker.AppleMDMPostDEPReleaseDeviceTask,
524-
host.UUID, host.Platform, host.TeamID, "", bootstrapCmdUUID, acctConfigCmdUUID); err != nil {
522+
host.UUID, host.Platform, host.TeamID, "", false, bootstrapCmdUUID, acctConfigCmdUUID); err != nil {
525523
return ctxerr.Wrap(ctx, err, "queue Apple Post-DEP release device job")
526524
}
527525
}

server/worker/apple_mdm.go

+25-20
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,13 @@ func (a *AppleMDM) Name() string {
5050

5151
// appleMDMArgs is the payload for the Apple MDM job.
5252
type appleMDMArgs struct {
53-
Task AppleMDMTask `json:"task"`
54-
HostUUID string `json:"host_uuid"`
55-
TeamID *uint `json:"team_id,omitempty"`
56-
EnrollReference string `json:"enroll_reference,omitempty"`
57-
EnrollmentCommands []string `json:"enrollment_commands,omitempty"`
58-
Platform string `json:"platform,omitempty"`
53+
Task AppleMDMTask `json:"task"`
54+
HostUUID string `json:"host_uuid"`
55+
TeamID *uint `json:"team_id,omitempty"`
56+
EnrollReference string `json:"enroll_reference,omitempty"`
57+
EnrollmentCommands []string `json:"enrollment_commands,omitempty"`
58+
Platform string `json:"platform,omitempty"`
59+
UseWorkerDeviceRelease bool `json:"use_worker_device_release,omitempty"`
5960
}
6061

6162
// Run executes the apple_mdm job.
@@ -163,9 +164,10 @@ func (a *AppleMDM) runPostDEPEnrollment(ctx context.Context, args appleMDMArgs)
163164
}
164165
}
165166

166-
// proceed to release the device only if it is not a macos, as those are
167-
// released via the setup experience flow.
168-
if !isMacOS(args.Platform) {
167+
// proceed to release the device if it is not a macos, as those are released
168+
// via the setup experience flow, or if we were told to use the worker based
169+
// release.
170+
if !isMacOS(args.Platform) || args.UseWorkerDeviceRelease {
169171
var manualRelease bool
170172
if args.TeamID == nil {
171173
ac, err := a.Datastore.AppConfig(ctx)
@@ -187,7 +189,7 @@ func (a *AppleMDM) runPostDEPEnrollment(ctx context.Context, args appleMDMArgs)
187189
// be final and same for MDM profiles of that host; it means the DEP
188190
// enrollment process is done and the device can be released.
189191
if err := QueueAppleMDMJob(ctx, a.Datastore, a.Log, AppleMDMPostDEPReleaseDeviceTask,
190-
args.HostUUID, args.Platform, args.TeamID, args.EnrollReference, awaitCmdUUIDs...); err != nil {
192+
args.HostUUID, args.Platform, args.TeamID, args.EnrollReference, false, awaitCmdUUIDs...); err != nil {
191193
return ctxerr.Wrap(ctx, err, "queue Apple Post-DEP release device job")
192194
}
193195
}
@@ -198,10 +200,11 @@ func (a *AppleMDM) runPostDEPEnrollment(ctx context.Context, args appleMDMArgs)
198200

199201
// This job is deprecated for macos because releasing devices is now done via
200202
// the orbit endpoint /setup_experience/status that is polled by a swift dialog
201-
// UI window during the setup process, and automatically releases the device
202-
// once all pending setup tasks are done. However, it must remain implemented
203-
// for iOS and iPadOS and in case there are such jobs to process after a Fleet
204-
// migration to a new version.
203+
// UI window during the setup process (unless there are no setup experience
204+
// items, in which case this worker job is used), and automatically releases
205+
// the device once all pending setup tasks are done. However, it must remain
206+
// implemented for iOS and iPadOS and in case there are such jobs to process
207+
// after a Fleet migration to a new version.
205208
func (a *AppleMDM) runPostDEPReleaseDevice(ctx context.Context, args appleMDMArgs) error {
206209
// Edge cases:
207210
// - if the device goes offline for a long time, should we go ahead and
@@ -355,6 +358,7 @@ func QueueAppleMDMJob(
355358
platform string,
356359
teamID *uint,
357360
enrollReference string,
361+
useWorkerDeviceRelease bool,
358362
enrollmentCommandUUIDs ...string,
359363
) error {
360364
attrs := []interface{}{
@@ -373,12 +377,13 @@ func QueueAppleMDMJob(
373377
level.Info(logger).Log(attrs...)
374378

375379
args := &appleMDMArgs{
376-
Task: task,
377-
HostUUID: hostUUID,
378-
TeamID: teamID,
379-
EnrollReference: enrollReference,
380-
EnrollmentCommands: enrollmentCommandUUIDs,
381-
Platform: platform,
380+
Task: task,
381+
HostUUID: hostUUID,
382+
TeamID: teamID,
383+
EnrollReference: enrollReference,
384+
EnrollmentCommands: enrollmentCommandUUIDs,
385+
Platform: platform,
386+
UseWorkerDeviceRelease: useWorkerDeviceRelease,
382387
}
383388

384389
// the release device task is always added with a delay

0 commit comments

Comments
 (0)