Fix bug where MDM migration fails when attempting to renew enrollment profiles on macOS Sonoma devices (#19726)
This commit is contained in:
@@ -0,0 +1 @@
|
||||
- Fixed bug where MDM migration failed when attempting to renew enrollment profiles on macOS Sonoma devices.
|
||||
+24
-5
@@ -2,16 +2,27 @@ import React from "react";
|
||||
|
||||
import Button from "components/buttons/Button";
|
||||
import Modal from "components/Modal";
|
||||
import { IDeviceUserResponse } from "interfaces/host";
|
||||
|
||||
interface IAutoEnrollMdmModalProps {
|
||||
host: IDeviceUserResponse["host"];
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
const baseClass = "auto-enroll-mdm-modal";
|
||||
|
||||
const AutoEnrollMdmModal = ({
|
||||
host: { platform, os_version },
|
||||
onCancel,
|
||||
}: IAutoEnrollMdmModalProps): JSX.Element => {
|
||||
let isMacOsSonomaOrLater = false;
|
||||
if (platform === "darwin" && os_version.startsWith("macOS ")) {
|
||||
const [major] = os_version
|
||||
.replace("macOS ", "")
|
||||
.split(".")
|
||||
.map((s) => parseInt(s, 10));
|
||||
isMacOsSonomaOrLater = major >= 14;
|
||||
}
|
||||
return (
|
||||
<Modal
|
||||
title="Turn on MDM"
|
||||
@@ -25,13 +36,21 @@ const AutoEnrollMdmModal = ({
|
||||
</p>
|
||||
<ol>
|
||||
<li>
|
||||
Open your Mac’s notification center by selecting the date and time
|
||||
in the top right corner of your screen.
|
||||
From the Apple menu in the top left corner of your screen, select{" "}
|
||||
<b>System Settings</b> or <b>System Preferences</b>.
|
||||
</li>
|
||||
<li>
|
||||
Select the <b>Device Enrollment</b> notification. This will open{" "}
|
||||
<b>System Settings</b> or <b>System Preferences</b>. Select{" "}
|
||||
<b>Allow</b>.
|
||||
{isMacOsSonomaOrLater ? (
|
||||
<>
|
||||
In the sidebar menu, select <b>Enroll in Remote Management</b>,
|
||||
and select <b>Enroll</b>.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
In the search bar, type “Profiles.” Select <b>Profiles</b>, find
|
||||
and select <b>Enrollment Profile</b>, and select <b>Install</b>.
|
||||
</>
|
||||
)}
|
||||
</li>
|
||||
<li>
|
||||
Enter your password, and select <b>Enroll</b>.
|
||||
|
||||
@@ -311,7 +311,7 @@ const DeviceUserPage = ({
|
||||
|
||||
const renderEnrollMdmModal = () => {
|
||||
return host?.dep_assigned_to_fleet ? (
|
||||
<AutoEnrollMdmModal onCancel={toggleEnrollMdmModal} />
|
||||
<AutoEnrollMdmModal host={host} onCancel={toggleEnrollMdmModal} />
|
||||
) : (
|
||||
<ManualEnrollMdmModal
|
||||
onCancel={toggleEnrollMdmModal}
|
||||
|
||||
@@ -247,6 +247,6 @@ func parseEnrollmentProfileValue(line []byte, key string) (string, bool) {
|
||||
|
||||
// showEnrollmentProfileCmd is declared as a variable so it can be overwritten by tests.
|
||||
var showEnrollmentProfileCmd = func() ([]byte, error) {
|
||||
cmd := exec.Command("/usr/bin/profiles", "show", "-type", "enrollment")
|
||||
cmd := exec.Command("sh", "-c", `launchctl asuser $(id -u $(stat -f "%u" /dev/console)) profiles show -type enrollment`)
|
||||
return cmd.Output()
|
||||
}
|
||||
|
||||
@@ -3,5 +3,6 @@
|
||||
package update
|
||||
|
||||
func runRenewEnrollmentProfile() error {
|
||||
return runCmdCollectErr("/usr/bin/profiles", "renew", "--type", "enrollment")
|
||||
cmd := `launchctl asuser $(id -u $(stat -f "%u" /dev/console)) profiles renew -type enrollment`
|
||||
return runCmdCollectErr("sh", "-c", cmd)
|
||||
}
|
||||
|
||||
@@ -104,14 +104,15 @@ func (h *renewEnrollmentProfileConfigReceiver) Run(config *fleet.OrbitConfig) er
|
||||
fn = runRenewEnrollmentProfile
|
||||
}
|
||||
if err := fn(); err != nil {
|
||||
// TODO: Look into whether we should increment lastRun here or implement a
|
||||
// backoff to avoid unnecessary user notification popups and mitigate rate
|
||||
// limiting by Apple.
|
||||
log.Info().Err(err).Msg("calling /usr/bin/profiles to renew enrollment profile failed")
|
||||
} else {
|
||||
h.lastRun = time.Now()
|
||||
log.Info().Msg("successfully called /usr/bin/profiles to renew enrollment profile")
|
||||
// TODO: Design a better way to backoff `profiles show` so that the device doesn't get rate
|
||||
// limited by Apple. For now, wait at least 2 minutes before retrying.
|
||||
h.lastRun = time.Now().Add(-h.Frequency).Add(2 * time.Minute)
|
||||
return nil
|
||||
}
|
||||
h.lastRun = time.Now()
|
||||
log.Info().Msg("successfully called /usr/bin/profiles to renew enrollment profile")
|
||||
|
||||
} else {
|
||||
log.Debug().Msg("skipped calling /usr/bin/profiles to renew enrollment profile, last run was too recent")
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/template"
|
||||
"time"
|
||||
@@ -48,7 +50,7 @@ const mdmUnenrollmentTotalWaitTime = 90 * time.Second
|
||||
// between unenrollment checks.
|
||||
const defaultUnenrollmentRetryInterval = 5 * time.Second
|
||||
|
||||
var mdmMigrationTemplate = template.Must(template.New("mdmMigrationTemplate").Parse(`
|
||||
var mdmMigrationTemplatePreSonoma = template.Must(template.New("mdmMigrationTemplate").Parse(`
|
||||
## Migrate to Fleet
|
||||
|
||||
Select **Start** and look for this notification in your notification center:` +
|
||||
@@ -56,6 +58,14 @@ Select **Start** and look for this notification in your notification center:` +
|
||||
"After you start, this window will popup every 15-20 minutes until you finish.",
|
||||
))
|
||||
|
||||
var mdmMigrationTemplate = template.Must(template.New("mdmMigrationTemplate").Parse(`
|
||||
## Migrate to Fleet
|
||||
|
||||
Select **Start** and Remote Management window will appear soon:` +
|
||||
"\n\n\n\n" +
|
||||
"After you start, this window will popup every 15-20 minutes until you finish.",
|
||||
))
|
||||
|
||||
var errorTemplate = template.Must(template.New("").Parse(`
|
||||
### Something's gone wrong.
|
||||
|
||||
@@ -291,29 +301,9 @@ func (m *swiftDialogMDMMigrator) waitForUnenrollment() error {
|
||||
}
|
||||
|
||||
func (m *swiftDialogMDMMigrator) renderMigration() error {
|
||||
var message bytes.Buffer
|
||||
if err := mdmMigrationTemplate.Execute(
|
||||
&message,
|
||||
m.props,
|
||||
); err != nil {
|
||||
return fmt.Errorf("execute template: %w", err)
|
||||
}
|
||||
|
||||
flags := []string{
|
||||
// main button
|
||||
"--button1text", "Start",
|
||||
// secondary button
|
||||
"--button2text", "Later",
|
||||
"--height", "440",
|
||||
}
|
||||
|
||||
if m.props.OrgInfo.ContactURL != "" {
|
||||
flags = append(flags,
|
||||
// info button
|
||||
"--infobuttontext", "Unsure? Contact IT",
|
||||
"--infobuttonaction", m.props.OrgInfo.ContactURL,
|
||||
"--quitoninfo",
|
||||
)
|
||||
message, flags, err := m.getMessageAndFlags()
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting mdm migrator message: %w", err)
|
||||
}
|
||||
|
||||
exitCodeCh, errCh := m.render(message.String(), flags...)
|
||||
@@ -419,3 +409,71 @@ func (m *swiftDialogMDMMigrator) ShowInterval() error {
|
||||
func (m *swiftDialogMDMMigrator) SetProps(props MDMMigratorProps) {
|
||||
m.props = props
|
||||
}
|
||||
|
||||
func (m *swiftDialogMDMMigrator) getMessageAndFlags() (*bytes.Buffer, []string, error) {
|
||||
vers, err := m.getMacOSMajorVersion()
|
||||
if err != nil {
|
||||
// log error for debugging and continue with default template
|
||||
log.Error().Err(err).Msg("getting macOS major version failed: using default migration template")
|
||||
}
|
||||
|
||||
tmpl := mdmMigrationTemplate
|
||||
height := "669"
|
||||
if vers != 0 && vers < 14 {
|
||||
height = "440"
|
||||
tmpl = mdmMigrationTemplatePreSonoma
|
||||
}
|
||||
|
||||
var message bytes.Buffer
|
||||
if err := tmpl.Execute(
|
||||
&message,
|
||||
m.props,
|
||||
); err != nil {
|
||||
return nil, nil, fmt.Errorf("executing migrqation template: %w", err)
|
||||
}
|
||||
|
||||
flags := []string{
|
||||
// main button
|
||||
"--button1text", "Start",
|
||||
// secondary button
|
||||
"--button2text", "Later",
|
||||
"--height", height,
|
||||
}
|
||||
|
||||
if m.props.OrgInfo.ContactURL != "" {
|
||||
flags = append(flags,
|
||||
// info button
|
||||
"--infobuttontext", "Unsure? Contact IT",
|
||||
"--infobuttonaction", m.props.OrgInfo.ContactURL,
|
||||
"--quitoninfo",
|
||||
)
|
||||
}
|
||||
|
||||
return &message, flags, nil
|
||||
}
|
||||
|
||||
// TODO: make this a variable for testing
|
||||
func (m *swiftDialogMDMMigrator) getMacOSMajorVersion() (int, error) {
|
||||
cmd := exec.Command("sw_vers", "-productVersion")
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("getting macOS version: %w", err)
|
||||
}
|
||||
parts := strings.SplitN(string(out), ".", 2)
|
||||
switch len(parts) {
|
||||
case 0:
|
||||
// this should never happen
|
||||
return 0, errors.New("getting macOS version: sw_vers command returned no output")
|
||||
case 1:
|
||||
// unexpected, so log for debugging
|
||||
log.Debug().Msgf("parsing macOS version: expected 2 parts, got 1: %s", out)
|
||||
default:
|
||||
// ok
|
||||
}
|
||||
|
||||
major, err := strconv.Atoi(parts[0])
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("parsing macOS major version: %w", err)
|
||||
}
|
||||
return major, nil
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 117 KiB |
Reference in New Issue
Block a user