mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
add migration support to FD and orbit (#11741)
https://github.com/fleetdm/fleet/issues/11534
This commit is contained in:
parent
9a7f3cf674
commit
8829b84a63
20 changed files with 763 additions and 43 deletions
4
Makefile
4
Makefile
|
|
@ -374,8 +374,8 @@ endif
|
|||
pkgutil --expand $(TMP_DIR)/swiftDialog-$(version).pkg $(TMP_DIR)/swiftDialog_pkg_expanded
|
||||
mkdir -p $(TMP_DIR)/swiftDialog_pkg_payload_expanded
|
||||
tar xvf $(TMP_DIR)/swiftDialog_pkg_expanded/Payload --directory $(TMP_DIR)/swiftDialog_pkg_payload_expanded
|
||||
$(TMP_DIR)/swiftDialog_pkg_payload_expanded/usr/local/bin/dialog --version
|
||||
tar czf $(out-path)/swiftDialog.app.tar.gz -C $(TMP_DIR)/swiftDialog_pkg_payload_expanded/usr/local bin
|
||||
$(TMP_DIR)/swiftDialog_pkg_payload_expanded/Library/Application\ Support/Dialog/Dialog.app/Contents/MacOS/Dialog --version
|
||||
tar czf $(out-path)/swiftDialog.app.tar.gz -C $(TMP_DIR)/swiftDialog_pkg_payload_expanded/Library/Application\ Support/Dialog/ Dialog.app
|
||||
rm -rf $(TMP_DIR)
|
||||
|
||||
# Build and generate desktop.app.tar.gz bundle.
|
||||
|
|
|
|||
|
|
@ -92,18 +92,30 @@ func (svc *Service) GetFleetDesktopSummary(ctx context.Context) (fleet.DesktopSu
|
|||
}
|
||||
sum.FailingPolicies = &r
|
||||
|
||||
appCfg, err := svc.ds.AppConfig(ctx)
|
||||
appCfg, err := svc.AppConfigObfuscated(ctx)
|
||||
if err != nil {
|
||||
return sum, ctxerr.Wrap(ctx, err, "retrieving app config")
|
||||
}
|
||||
|
||||
if appCfg.MDM.EnabledAndConfigured &&
|
||||
appCfg.MDM.MacOSMigration.Enable &&
|
||||
host.IsOsqueryEnrolled() &&
|
||||
host.MDMInfo.IsDEPCapable() &&
|
||||
host.MDMInfo.IsEnrolledInThirdPartyMDM() {
|
||||
sum.Notifications.NeedsMDMMigration = true
|
||||
if appCfg.MDM.EnabledAndConfigured && appCfg.MDM.MacOSMigration.Enable {
|
||||
if host.MDMInfo.IsPendingDEPFleetEnrollment() {
|
||||
sum.Notifications.RenewEnrollmentProfile = true
|
||||
}
|
||||
|
||||
if host.IsOsqueryEnrolled() &&
|
||||
host.MDMInfo.IsDEPCapable() &&
|
||||
host.MDMInfo.IsEnrolledInThirdPartyMDM() {
|
||||
sum.Notifications.NeedsMDMMigration = true
|
||||
}
|
||||
}
|
||||
|
||||
// organization information
|
||||
sum.Config.OrgInfo.OrgName = appCfg.OrgInfo.OrgName
|
||||
sum.Config.OrgInfo.OrgLogoURL = appCfg.OrgInfo.OrgLogoURL
|
||||
sum.Config.OrgInfo.ContactURL = appCfg.OrgInfo.ContactURL
|
||||
|
||||
// mdm information
|
||||
sum.Config.MDM.MacOSMigration.Mode = appCfg.MDM.MacOSMigration.Mode
|
||||
|
||||
return sum, nil
|
||||
}
|
||||
|
|
|
|||
1
orbit/changes/11534-orbit-fd
Normal file
1
orbit/changes/11534-orbit-fd
Normal file
|
|
@ -0,0 +1 @@
|
|||
* MDM: added support to enhance the DEP migration flow in macOS.
|
||||
|
|
@ -11,8 +11,11 @@ import (
|
|||
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/constant"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/token"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/update"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/useraction"
|
||||
"github.com/fleetdm/fleet/v4/pkg/certificate"
|
||||
"github.com/fleetdm/fleet/v4/pkg/open"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/service"
|
||||
"github.com/getlantern/systray"
|
||||
"github.com/oklog/run"
|
||||
|
|
@ -82,10 +85,16 @@ func main() {
|
|||
if fleetClientCrt != nil {
|
||||
log.Info().Msg("Using TLS client certificate and key to authenticate to the server.")
|
||||
}
|
||||
tufUpdateRoot := os.Getenv("FLEET_DESKTOP_TUF_UPDATE_ROOT")
|
||||
if tufUpdateRoot != "" {
|
||||
log.Info().Msgf("got a TUF update root: %s", tufUpdateRoot)
|
||||
}
|
||||
|
||||
// Setting up working runners such as signalHandler runner
|
||||
go setupRunners()
|
||||
|
||||
var mdmMigrator useraction.MDMMigrator
|
||||
|
||||
onReady := func() {
|
||||
log.Info().Msg("ready")
|
||||
|
||||
|
|
@ -220,6 +229,22 @@ func main() {
|
|||
}
|
||||
}()
|
||||
|
||||
if runtime.GOOS == "darwin" {
|
||||
_, swiftDialogPath, _ := update.LocalTargetPaths(
|
||||
tufUpdateRoot,
|
||||
"swiftDialog",
|
||||
update.SwiftDialogMacOSTarget,
|
||||
)
|
||||
mdmMigrator = useraction.NewMDMMigrator(
|
||||
swiftDialogPath,
|
||||
15*time.Minute,
|
||||
&mdmMigrationHandler{
|
||||
client: client,
|
||||
tokenReader: &tokenReader,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// poll the server to check the policy status of the host and update the
|
||||
// tray icon accordingly
|
||||
go func() {
|
||||
|
|
@ -271,9 +296,32 @@ func main() {
|
|||
}
|
||||
myDeviceItem.Enable()
|
||||
|
||||
if sum.Notifications.NeedsMDMMigration {
|
||||
if runtime.GOOS == "darwin" &&
|
||||
(sum.Notifications.NeedsMDMMigration || sum.Notifications.RenewEnrollmentProfile) &&
|
||||
mdmMigrator.CanRun() {
|
||||
|
||||
// if the device is unmanaged or we're
|
||||
// in force mode and the device needs
|
||||
// migration, enable aggressive mode.
|
||||
aggressive := sum.Notifications.RenewEnrollmentProfile ||
|
||||
(sum.Notifications.NeedsMDMMigration && sum.Config.MDM.MacOSMigration.Mode == fleet.MacOSMigrationModeForced)
|
||||
|
||||
// update org info in case it changed
|
||||
mdmMigrator.SetProps(useraction.MDMMigratorProps{
|
||||
OrgInfo: sum.Config.OrgInfo,
|
||||
Aggressive: aggressive,
|
||||
})
|
||||
|
||||
// enable tray items
|
||||
migrateMDMItem.Enable()
|
||||
migrateMDMItem.Show()
|
||||
|
||||
if aggressive {
|
||||
log.Info().Msg("MDM migration is in aggressive mode, automatically showing dialog")
|
||||
if err := mdmMigrator.ShowInterval(); err != nil {
|
||||
log.Error().Err(err).Msg("showing MDM migration dialog at interval")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
migrateMDMItem.Disable()
|
||||
migrateMDMItem.Hide()
|
||||
|
|
@ -296,25 +344,45 @@ func main() {
|
|||
log.Error().Err(err).Str("url", openURL).Msg("open browser transparency")
|
||||
}
|
||||
case <-migrateMDMItem.ClickedCh:
|
||||
// TODO: we should be signaling Orbit to show
|
||||
// swiftDialog instead. To be done in a
|
||||
// follow up.
|
||||
openURL := client.BrowserDeviceURL(tokenReader.GetCached())
|
||||
if err := open.Browser(openURL); err != nil {
|
||||
log.Error().Err(err).Msg("open browser to migrate MDM")
|
||||
|
||||
if err := mdmMigrator.Show(); err != nil {
|
||||
log.Error().Err(err).Msg("showing MDM migration dialog on user action")
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
onExit := func() {
|
||||
if mdmMigrator != nil {
|
||||
mdmMigrator.Exit()
|
||||
}
|
||||
log.Info().Msg("exit")
|
||||
}
|
||||
|
||||
systray.Run(onReady, onExit)
|
||||
}
|
||||
|
||||
type mdmMigrationHandler struct {
|
||||
client *service.DeviceClient
|
||||
tokenReader *token.Reader
|
||||
}
|
||||
|
||||
func (m *mdmMigrationHandler) NotifyRemote() error {
|
||||
log.Debug().Msg("sending request to trigger mdm migration webhook")
|
||||
if err := m.client.MigrateMDM(m.tokenReader.GetCached()); err != nil {
|
||||
log.Error().Err(err).Msg("triggering migration webhook")
|
||||
return fmt.Errorf("on migration start: %w", err)
|
||||
}
|
||||
log.Debug().Msg("successfully sent request to trigger mdm migration webhook")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mdmMigrationHandler) ShowInstructions() {
|
||||
openURL := m.client.BrowserDeviceURL(m.tokenReader.GetCached())
|
||||
if err := open.Browser(openURL); err != nil {
|
||||
log.Error().Err(err).Str("url", openURL).Msg("open browser")
|
||||
}
|
||||
}
|
||||
|
||||
// setupLogs configures our logging system to write logs to rolling files and
|
||||
// stderr, if for some reason we can't write a log file the logs are still
|
||||
// printed to stderr.
|
||||
|
|
|
|||
|
|
@ -624,6 +624,7 @@ func main() {
|
|||
})
|
||||
|
||||
configFetcher = update.ApplyDiskEncryptionRunnerMiddleware(configFetcher)
|
||||
configFetcher = update.ApplySwiftDialogDownloaderMiddleware(configFetcher, updateRunner)
|
||||
}
|
||||
|
||||
const orbitFlagsUpdateInterval = 30 * time.Second
|
||||
|
|
@ -858,6 +859,7 @@ func main() {
|
|||
rawClientCrt,
|
||||
rawClientKey,
|
||||
c.String("fleet-desktop-alternative-browser-host"),
|
||||
opt.RootDirectory,
|
||||
)
|
||||
g.Add(desktopRunner.actor())
|
||||
}
|
||||
|
|
@ -895,6 +897,7 @@ func registerExtensionRunner(g *run.Group, extSockPath string, opts ...table.Opt
|
|||
type desktopRunner struct {
|
||||
// desktopPath is the path to the desktop executable.
|
||||
desktopPath string
|
||||
updateRoot string
|
||||
// fleetURL is the URL of the Fleet server.
|
||||
fleetURL string
|
||||
// trw is the Fleet Desktop token reader and writer (implements token rotation).
|
||||
|
|
@ -924,9 +927,11 @@ func newDesktopRunner(
|
|||
trw *token.ReadWriter,
|
||||
fleetClientCrt []byte, fleetClientKey []byte,
|
||||
fleetAlternativeBrowserHost string,
|
||||
updateRoot string,
|
||||
) *desktopRunner {
|
||||
return &desktopRunner{
|
||||
desktopPath: desktopPath,
|
||||
updateRoot: updateRoot,
|
||||
fleetURL: fleetURL,
|
||||
trw: trw,
|
||||
fleetRootCA: fleetRootCA,
|
||||
|
|
@ -985,6 +990,7 @@ func (d *desktopRunner) execute() error {
|
|||
execuser.WithEnv("FLEET_DESKTOP_FLEET_TLS_CLIENT_KEY", string(d.fleetClientKey)),
|
||||
|
||||
execuser.WithEnv("FLEET_DESKTOP_ALTERNATIVE_BROWSER_HOST", d.fleetAlternativeBrowserHost),
|
||||
execuser.WithEnv("FLEET_DESKTOP_TUF_UPDATE_ROOT", d.updateRoot),
|
||||
}
|
||||
if d.fleetRootCA != "" {
|
||||
opts = append(opts, execuser.WithEnv("FLEET_DESKTOP_FLEET_ROOT_CA", d.fleetRootCA))
|
||||
|
|
|
|||
|
|
@ -89,10 +89,10 @@ var (
|
|||
ExtractedExecSubPath: []string{"Nudge.app", "Contents", "MacOS", "Nudge"},
|
||||
}
|
||||
|
||||
SwiftDialogTarget = TargetInfo{
|
||||
SwiftDialogMacOSTarget = TargetInfo{
|
||||
Platform: "macos",
|
||||
Channel: "stable",
|
||||
TargetFile: "swiftDialog.app.tar.gz",
|
||||
ExtractedExecSubPath: []string{"bin", "dialog"},
|
||||
ExtractedExecSubPath: []string{"Dialog.app", "Contents", "MacOS", "Dialog"},
|
||||
}
|
||||
)
|
||||
|
|
|
|||
60
orbit/pkg/update/swift_dialog.go
Normal file
60
orbit/pkg/update/swift_dialog.go
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
package update
|
||||
|
||||
import (
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type SwiftDialogDownloader struct {
|
||||
Fetcher OrbitConfigFetcher
|
||||
UpdateRunner *Runner
|
||||
}
|
||||
|
||||
type SwiftDialogDownloaderOptions struct {
|
||||
// UpdateRunner is the wrapped Runner where swiftDialog will be set as a target. It is responsible for
|
||||
// actually ensuring that swiftDialog is installed and updated via the designated TUF server.
|
||||
UpdateRunner *Runner
|
||||
}
|
||||
|
||||
func ApplySwiftDialogDownloaderMiddleware(
|
||||
f OrbitConfigFetcher,
|
||||
runner *Runner,
|
||||
) OrbitConfigFetcher {
|
||||
return &SwiftDialogDownloader{Fetcher: f, UpdateRunner: runner}
|
||||
}
|
||||
|
||||
func (s *SwiftDialogDownloader) GetConfig() (*fleet.OrbitConfig, error) {
|
||||
log.Debug().Msg("running swiftDialog installer middleware")
|
||||
cfg, err := s.Fetcher.GetConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cfg == nil {
|
||||
log.Debug().Msg("SwiftDialogDownloader received nil config")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if !cfg.Notifications.NeedsMDMMigration && !cfg.Notifications.RenewEnrollmentProfile {
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
updaterHasTarget := s.UpdateRunner.HasRunnerOptTarget("swiftDialog")
|
||||
runnerHasLocalHash := s.UpdateRunner.HasLocalHash("swiftDialog")
|
||||
if !updaterHasTarget || !runnerHasLocalHash {
|
||||
log.Info().Msg("refreshing the update runner config with swiftDialog targets and hashes")
|
||||
log.Debug().Msgf("updater has target: %t, runner has local hash: %t", updaterHasTarget, runnerHasLocalHash)
|
||||
s.UpdateRunner.AddRunnerOptTarget("swiftDialog")
|
||||
s.UpdateRunner.updater.SetTargetInfo("swiftDialog", SwiftDialogMacOSTarget)
|
||||
// we don't want to keep swiftDialog as a target if we failed to update the
|
||||
// cached hashes in the runner.
|
||||
if err := s.UpdateRunner.StoreLocalHash("swiftDialog"); err != nil {
|
||||
log.Debug().Msgf("removing swiftDialog from target options, error updating local hashes: %e", err)
|
||||
s.UpdateRunner.RemoveRunnerOptTarget("swiftDialog")
|
||||
s.UpdateRunner.updater.RemoveTargetInfo("swiftDialog")
|
||||
return cfg, err
|
||||
}
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
|
@ -229,6 +229,22 @@ func (u *Updater) DirLocalPath(target string) (string, error) {
|
|||
return localTarget.DirPath, nil
|
||||
}
|
||||
|
||||
// LocalTargetPaths returns (path, execPath, dirPath) to be used by this
|
||||
// package for a given TargetInfo target.
|
||||
func LocalTargetPaths(rootDir string, targetName string, t TargetInfo) (path, execPath, dirPath string) {
|
||||
path = filepath.Join(
|
||||
rootDir, binDir, targetName, t.Platform, t.Channel, t.TargetFile,
|
||||
)
|
||||
|
||||
execPath = path
|
||||
if strings.HasSuffix(path, ".tar.gz") {
|
||||
execPath = filepath.Join(append([]string{filepath.Dir(path)}, t.ExtractedExecSubPath...)...)
|
||||
dirPath = filepath.Join(filepath.Dir(path), t.ExtractedExecSubPath[0])
|
||||
}
|
||||
|
||||
return path, execPath, dirPath
|
||||
}
|
||||
|
||||
// LocalTarget holds local paths of a target.
|
||||
//
|
||||
// E.g., for a osqueryd target:
|
||||
|
|
@ -263,16 +279,12 @@ func (u *Updater) localTarget(target string) (*LocalTarget, error) {
|
|||
if !ok {
|
||||
return nil, fmt.Errorf("unknown target: %s", target)
|
||||
}
|
||||
path, execPath, dirPath := LocalTargetPaths(u.opt.RootDirectory, target, t)
|
||||
lt := &LocalTarget{
|
||||
Info: t,
|
||||
Path: filepath.Join(
|
||||
u.opt.RootDirectory, binDir, target, t.Platform, t.Channel, t.TargetFile,
|
||||
),
|
||||
}
|
||||
lt.ExecPath = lt.Path
|
||||
if strings.HasSuffix(lt.Path, ".tar.gz") {
|
||||
lt.ExecPath = filepath.Join(append([]string{filepath.Dir(lt.Path)}, t.ExtractedExecSubPath...)...)
|
||||
lt.DirPath = filepath.Join(filepath.Dir(lt.Path), lt.Info.ExtractedExecSubPath[0])
|
||||
Info: t,
|
||||
Path: path,
|
||||
ExecPath: execPath,
|
||||
DirPath: dirPath,
|
||||
}
|
||||
return lt, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,3 +63,44 @@ func TestMakeRepoPath(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalTargetPaths(t *testing.T) {
|
||||
testCases := []struct {
|
||||
info TargetInfo
|
||||
wantPath string
|
||||
wantExecPath string
|
||||
wantDirPath string
|
||||
}{
|
||||
{
|
||||
DesktopWindowsTarget,
|
||||
"root/bin/target/windows/stable/fleet-desktop.exe",
|
||||
"root/bin/target/windows/stable/fleet-desktop.exe",
|
||||
"",
|
||||
},
|
||||
{
|
||||
NudgeMacOSTarget,
|
||||
"root/bin/target/macos/stable/nudge.app.tar.gz",
|
||||
"root/bin/target/macos/stable/Nudge.app/Contents/MacOS/Nudge",
|
||||
"root/bin/target/macos/stable/Nudge.app",
|
||||
},
|
||||
{
|
||||
DesktopLinuxTarget,
|
||||
"root/bin/target/linux/stable/desktop.tar.gz",
|
||||
"root/bin/target/linux/stable/fleet-desktop/fleet-desktop",
|
||||
"root/bin/target/linux/stable/fleet-desktop",
|
||||
},
|
||||
{
|
||||
SwiftDialogMacOSTarget,
|
||||
"root/bin/target/macos/stable/swiftDialog.app.tar.gz",
|
||||
"root/bin/target/macos/stable/Dialog.app/Contents/MacOS/Dialog",
|
||||
"root/bin/target/macos/stable/Dialog.app",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
path, execPath, dirPath := LocalTargetPaths("root", "target", tt.info)
|
||||
require.Equal(t, tt.wantPath, path)
|
||||
require.Equal(t, tt.wantExecPath, execPath)
|
||||
require.Equal(t, tt.wantDirPath, dirPath)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
35
orbit/pkg/useraction/mdm_migration.go
Normal file
35
orbit/pkg/useraction/mdm_migration.go
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
package useraction
|
||||
|
||||
import "github.com/fleetdm/fleet/v4/server/fleet"
|
||||
|
||||
// MDMMigrator represents the minimum set of methods a migration must implement
|
||||
// in order to be used by Fleet Desktop.
|
||||
type MDMMigrator interface {
|
||||
// CanRun indicates if the migrator is able to run, for example, for macOS it
|
||||
// checks if the swiftDialog executable is present.
|
||||
CanRun() bool
|
||||
// SetProps sets/updates the props.
|
||||
SetProps(MDMMigratorProps)
|
||||
// Show displays the dialog if there's no other dialog running.
|
||||
Show() error
|
||||
// ShowInterval is used to display dialogs at an interval. It displays
|
||||
// the dialog if there's no other dialog running and a given interval
|
||||
// (defined by the migrator itself) has passed since the last time the
|
||||
// dialog was shown.
|
||||
ShowInterval() error
|
||||
// Exit tries to stop any processes started by the migrator.
|
||||
Exit()
|
||||
}
|
||||
|
||||
// MDMMigratorProps are props required to display the dialog. It's akin to the
|
||||
// concept of props in UI frameworks like React.
|
||||
type MDMMigratorProps struct {
|
||||
OrgInfo fleet.DesktopOrgInfo
|
||||
Aggressive bool
|
||||
}
|
||||
|
||||
// MDMMigratorHandler handles remote actions/callbacks that the migrator calls.
|
||||
type MDMMigratorHandler interface {
|
||||
NotifyRemote() error
|
||||
ShowInstructions()
|
||||
}
|
||||
294
orbit/pkg/useraction/mdm_migration_darwin.go
Normal file
294
orbit/pkg/useraction/mdm_migration_darwin.go
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
//go:build darwin
|
||||
|
||||
package useraction
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type swiftDialogExitCode int
|
||||
|
||||
const (
|
||||
primaryBtnExitCode = 0
|
||||
errorExitCode = 1
|
||||
secondaryBtnExitCode = 2
|
||||
infoBtnExitCode = 3
|
||||
timeoutExitCode = 4
|
||||
userQuitExitCode = 10
|
||||
unknownExitCode = 99
|
||||
)
|
||||
|
||||
var mdmMigrationTemplate = template.Must(template.New("mdmMigrationTemplate").Parse(`
|
||||
## Migrate to Fleet
|
||||
|
||||
To begin, click "Start." Your default browser will open your My Device page.
|
||||
|
||||
{{ if .Aggressive }}You {{ else }} Once you start, you {{ end -}} will see this dialog every 15 minutes until you click "Turn on MDM" and complete the instructions.
|
||||
|
||||
\\
|
||||
|
||||
Unsure? Contact {{ .OrgInfo.OrgName }} IT [here]({{ .OrgInfo.ContactURL }}).
|
||||
`))
|
||||
|
||||
var errorTemplate = template.Must(template.New("").Parse(`
|
||||
### Something's gone wrong.
|
||||
|
||||
Please contact your IT admin [here]({{ .ContactURL }}).
|
||||
`))
|
||||
|
||||
// baseDialog implements the basic building blocks to render dialogs using
|
||||
// swiftDialog.
|
||||
type baseDialog struct {
|
||||
path string
|
||||
interruptCh chan struct{}
|
||||
}
|
||||
|
||||
func newBaseDialog(path string) *baseDialog {
|
||||
return &baseDialog{path: path, interruptCh: make(chan struct{})}
|
||||
}
|
||||
|
||||
func (b *baseDialog) CanRun() bool {
|
||||
if _, err := os.Stat(b.path); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Exit sends the interrupt signal to try and stop the current swiftDialog
|
||||
// instance.
|
||||
func (b *baseDialog) Exit() {
|
||||
b.interruptCh <- struct{}{}
|
||||
log.Info().Msg("dialog exit message sent")
|
||||
}
|
||||
|
||||
// render is a general-purpose render method that receives the flags used to
|
||||
// display swiftDialog, and starts an asyncronous routine to display the dialog
|
||||
// without blocking.
|
||||
//
|
||||
// The first returned channel sends the exit code returned by swiftDialog, and
|
||||
// the second channel is used to send errors.
|
||||
func (b *baseDialog) render(flags ...string) (chan swiftDialogExitCode, chan error) {
|
||||
exitCodeCh := make(chan swiftDialogExitCode, 1)
|
||||
errCh := make(chan error, 1)
|
||||
go func() {
|
||||
cmd := exec.Command(b.path, flags...) //nolint:gosec
|
||||
done := make(chan error)
|
||||
stopInterruptCh := make(chan struct{})
|
||||
defer close(stopInterruptCh)
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
|
||||
go func() { done <- cmd.Wait() }()
|
||||
go func() {
|
||||
select {
|
||||
case <-b.interruptCh:
|
||||
if err := cmd.Process.Signal(os.Interrupt); err != nil {
|
||||
log.Error().Err(err).Msg("sending interrupt signal to swiftDialog process")
|
||||
if err := cmd.Process.Kill(); err != nil {
|
||||
log.Error().Err(err).Msg("killing swiftDialog process")
|
||||
errCh <- errors.New("failed to stop/kill swiftDialog process")
|
||||
}
|
||||
}
|
||||
case <-stopInterruptCh:
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
if err := <-done; err != nil {
|
||||
// non-zero exit codes
|
||||
if exitError, ok := err.(*exec.ExitError); ok {
|
||||
ec := exitError.ExitCode()
|
||||
switch ec {
|
||||
case errorExitCode:
|
||||
exitCodeCh <- errorExitCode
|
||||
case secondaryBtnExitCode, infoBtnExitCode, timeoutExitCode:
|
||||
exitCodeCh <- swiftDialogExitCode(ec)
|
||||
default:
|
||||
errCh <- fmt.Errorf("unknown exit code showing dialog: %w", exitError)
|
||||
}
|
||||
} else {
|
||||
errCh <- fmt.Errorf("running swiftDialog: %w", err)
|
||||
}
|
||||
} else {
|
||||
exitCodeCh <- 0
|
||||
}
|
||||
}()
|
||||
return exitCodeCh, errCh
|
||||
}
|
||||
|
||||
func NewMDMMigrator(path string, frequency time.Duration, handler MDMMigratorHandler) MDMMigrator {
|
||||
return &swiftDialogMDMMigrator{
|
||||
handler: handler,
|
||||
baseDialog: newBaseDialog(path),
|
||||
frequency: frequency,
|
||||
}
|
||||
}
|
||||
|
||||
// swiftDialogMDMMigrator implements MDMMigrator for macOS using swiftDialog as
|
||||
// the underlying mechanism for user action.
|
||||
type swiftDialogMDMMigrator struct {
|
||||
*baseDialog
|
||||
props MDMMigratorProps
|
||||
frequency time.Duration
|
||||
handler MDMMigratorHandler
|
||||
|
||||
// ensures only one dialog is open at a time, protects access to
|
||||
// lastShown
|
||||
showMu sync.Mutex
|
||||
lastShown time.Time
|
||||
|
||||
// ensures only one dialog is open at a given interval
|
||||
intervalMu sync.Mutex
|
||||
}
|
||||
|
||||
func (m *swiftDialogMDMMigrator) render(message string, flags ...string) (chan swiftDialogExitCode, chan error) {
|
||||
icon := m.props.OrgInfo.OrgLogoURL
|
||||
if icon == "" {
|
||||
icon = "https://fleetdm.com/images/permanent/fleet-mark-color-40x40@4x.png"
|
||||
}
|
||||
|
||||
flags = append([]string{
|
||||
// disable the built-in title so we have full control over the
|
||||
// content
|
||||
"--title", "none",
|
||||
// top icon
|
||||
"--icon", icon,
|
||||
"--iconsize", "80",
|
||||
"--centreicon",
|
||||
// modal content
|
||||
"--message", message,
|
||||
"--messagefont", "size=16",
|
||||
"--alignment", "center",
|
||||
"--ontop",
|
||||
}, flags...)
|
||||
|
||||
return m.baseDialog.render(flags...)
|
||||
}
|
||||
|
||||
func (m *swiftDialogMDMMigrator) renderLoadingSpinner() (chan swiftDialogExitCode, chan error) {
|
||||
return m.render("## Migrate to Fleet\n\nCommunicating with MDM server...",
|
||||
"--button1text", "Start",
|
||||
"--button1disabled",
|
||||
"--quitkey", "x",
|
||||
)
|
||||
}
|
||||
|
||||
func (m *swiftDialogMDMMigrator) renderError() (chan swiftDialogExitCode, chan error) {
|
||||
var errorMessage bytes.Buffer
|
||||
if err := errorTemplate.Execute(
|
||||
&errorMessage,
|
||||
m.props.OrgInfo,
|
||||
); err != nil {
|
||||
codeChan := make(chan swiftDialogExitCode, 1)
|
||||
errChan := make(chan error, 1)
|
||||
errChan <- fmt.Errorf("execute error template: %w", err)
|
||||
return codeChan, errChan
|
||||
}
|
||||
|
||||
return m.render(errorMessage.String(), "--button1text", "Close")
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
exitCodeCh, errCh := m.render(message.String(),
|
||||
// info button
|
||||
"--infobuttontext", "?",
|
||||
"--infobuttonaction", "https://fleetdm.com/handbook/company/why-this-way#why-open-source",
|
||||
// main button
|
||||
"--button1text", "Start",
|
||||
// secondary button
|
||||
"--button2text", "Later",
|
||||
"--blurscreen", "--ontop", "--height", "600",
|
||||
)
|
||||
|
||||
select {
|
||||
case err := <-errCh:
|
||||
return fmt.Errorf("showing start migration dialog: %w", err)
|
||||
case exitCode := <-exitCodeCh:
|
||||
// we don't perform any action for all the other buttons
|
||||
if exitCode != primaryBtnExitCode {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !m.props.Aggressive {
|
||||
// show the loading spinner
|
||||
m.renderLoadingSpinner()
|
||||
|
||||
// send the API call
|
||||
if notifyErr := m.handler.NotifyRemote(); notifyErr != nil {
|
||||
m.baseDialog.Exit()
|
||||
errDialogExitChan, errDialogErrChan := m.renderError()
|
||||
select {
|
||||
case <-errDialogExitChan:
|
||||
return nil
|
||||
case err := <-errDialogErrChan:
|
||||
return fmt.Errorf("rendering errror dialog: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Info().Msg("webhook sent, closing spinner")
|
||||
|
||||
// close the spinner
|
||||
// TODO: maybe it's better to use
|
||||
// https://github.com/bartreardon/swiftDialog/wiki/Updating-Dialog-with-new-content
|
||||
// instead? it uses a file as IPC
|
||||
m.baseDialog.Exit()
|
||||
}
|
||||
|
||||
log.Info().Msg("showing instructions")
|
||||
m.handler.ShowInstructions()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *swiftDialogMDMMigrator) Show() error {
|
||||
if m.showMu.TryLock() {
|
||||
defer m.showMu.Unlock()
|
||||
|
||||
if err := m.renderMigration(); err != nil {
|
||||
return fmt.Errorf("show: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *swiftDialogMDMMigrator) ShowInterval() error {
|
||||
if m.intervalMu.TryLock() {
|
||||
defer m.intervalMu.Unlock()
|
||||
if time.Since(m.lastShown) > m.frequency {
|
||||
if err := m.Show(); err != nil {
|
||||
return fmt.Errorf("show interval: %w", err)
|
||||
}
|
||||
m.lastShown = time.Now()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *swiftDialogMDMMigrator) SetProps(props MDMMigratorProps) {
|
||||
m.props = props
|
||||
}
|
||||
17
orbit/pkg/useraction/mdm_migration_notdarwin.go
Normal file
17
orbit/pkg/useraction/mdm_migration_notdarwin.go
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
//go:build !darwin
|
||||
|
||||
package useraction
|
||||
|
||||
import "time"
|
||||
|
||||
func NewMDMMigrator(path string, frequency time.Duration, handler MDMMigratorHandler) MDMMigrator {
|
||||
return &NoopMDMMigrator{}
|
||||
}
|
||||
|
||||
type NoopMDMMigrator struct{}
|
||||
|
||||
func (m *NoopMDMMigrator) CanRun() bool { return false }
|
||||
func (m *NoopMDMMigrator) SetProps(MDMMigratorProps) {}
|
||||
func (m *NoopMDMMigrator) Show() error { return nil }
|
||||
func (m *NoopMDMMigrator) ShowInterval() error { return nil }
|
||||
func (m *NoopMDMMigrator) Exit() {}
|
||||
|
|
@ -7,13 +7,38 @@ import "time"
|
|||
type DesktopSummary struct {
|
||||
FailingPolicies *uint `json:"failing_policies_count,omitempty"`
|
||||
Notifications DesktopNotifications `json:"notifications,omitempty"`
|
||||
Config DesktopConfig `json:"config"`
|
||||
}
|
||||
|
||||
// DesktopNotifications are notifications that the fleet server sends to
|
||||
// Fleet Desktop so that it can run commands or more generally react to this
|
||||
// information.
|
||||
type DesktopNotifications struct {
|
||||
NeedsMDMMigration bool `json:"needs_mdm_migration,omitempty"`
|
||||
NeedsMDMMigration bool `json:"needs_mdm_migration,omitempty"`
|
||||
RenewEnrollmentProfile bool `json:"renew_enrollment_profile,omitempty"`
|
||||
}
|
||||
|
||||
// DesktopConfig is a subset of AppConfig with information relevant to Fleet
|
||||
// Desktop to operate.
|
||||
type DesktopConfig struct {
|
||||
OrgInfo DesktopOrgInfo `json:"org_info,omitempty"`
|
||||
MDM DesktopMDMConfig `json:"mdm"`
|
||||
}
|
||||
|
||||
// DesktopMDMConfig is a subset of fleet.MDM with configuration that's relevant
|
||||
// to Fleet Desktop to operate.
|
||||
type DesktopMDMConfig struct {
|
||||
MacOSMigration struct {
|
||||
Mode MacOSMigrationMode `json:"mode"`
|
||||
} `json:"macos_migration"`
|
||||
}
|
||||
|
||||
// DesktopMDMConfig is a subset of fleet.OrgInfo with configuration that's relevant
|
||||
// to Fleet Desktop to operate.
|
||||
type DesktopOrgInfo struct {
|
||||
OrgName string `json:"org_name"`
|
||||
OrgLogoURL string `json:"org_logo_url"`
|
||||
ContactURL string `json:"contact_url"`
|
||||
}
|
||||
|
||||
type MigrateMDMDeviceWebhookPayload struct {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import "encoding/json"
|
|||
type OrbitConfigNotifications struct {
|
||||
RenewEnrollmentProfile bool `json:"renew_enrollment_profile,omitempty"`
|
||||
RotateDiskEncryptionKey bool `json:"rotate_disk_encryption_key,omitempty"`
|
||||
NeedsMDMMigration bool `json:"needs_mdm_migration,omitempty"`
|
||||
}
|
||||
|
||||
type OrbitConfig struct {
|
||||
|
|
|
|||
|
|
@ -138,3 +138,8 @@ func (dc *DeviceClient) DesktopSummary(token string) (*fleetDesktopResponse, err
|
|||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (dc *DeviceClient) MigrateMDM(token string) error {
|
||||
verb, path := "POST", "/api/latest/fleet/device/"+token+"/migrate_mdm"
|
||||
return dc.request(verb, path, "", nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/host"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/mock"
|
||||
"github.com/fleetdm/fleet/v4/server/ptr"
|
||||
"github.com/fleetdm/fleet/v4/server/test"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
|
@ -22,7 +22,7 @@ func TestGetFleetDesktopSummary(t *testing.T) {
|
|||
require.Empty(t, sum)
|
||||
})
|
||||
|
||||
t.Run("different app config values", func(t *testing.T) {
|
||||
t.Run("different app config values for managed host", func(t *testing.T) {
|
||||
ds := new(mock.Store)
|
||||
license := &fleet.LicenseInfo{Tier: fleet.TierPremium, Expiration: time.Now().Add(24 * time.Hour)}
|
||||
svc, ctx := newTestService(t, ds, nil, nil, &TestServerOpts{License: license, SkipCreateTestUsers: true})
|
||||
|
|
@ -42,7 +42,8 @@ func TestGetFleetDesktopSummary(t *testing.T) {
|
|||
},
|
||||
},
|
||||
out: fleet.DesktopNotifications{
|
||||
NeedsMDMMigration: true,
|
||||
NeedsMDMMigration: true,
|
||||
RenewEnrollmentProfile: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -53,7 +54,8 @@ func TestGetFleetDesktopSummary(t *testing.T) {
|
|||
},
|
||||
},
|
||||
out: fleet.DesktopNotifications{
|
||||
NeedsMDMMigration: false,
|
||||
NeedsMDMMigration: false,
|
||||
RenewEnrollmentProfile: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -64,7 +66,8 @@ func TestGetFleetDesktopSummary(t *testing.T) {
|
|||
},
|
||||
},
|
||||
out: fleet.DesktopNotifications{
|
||||
NeedsMDMMigration: false,
|
||||
NeedsMDMMigration: false,
|
||||
RenewEnrollmentProfile: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -75,7 +78,8 @@ func TestGetFleetDesktopSummary(t *testing.T) {
|
|||
},
|
||||
},
|
||||
out: fleet.DesktopNotifications{
|
||||
NeedsMDMMigration: false,
|
||||
NeedsMDMMigration: false,
|
||||
RenewEnrollmentProfile: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -87,7 +91,7 @@ func TestGetFleetDesktopSummary(t *testing.T) {
|
|||
return &appCfg, nil
|
||||
}
|
||||
|
||||
ctx = host.NewContext(ctx, &fleet.Host{
|
||||
ctx := test.HostContext(ctx, &fleet.Host{
|
||||
OsqueryHostID: ptr.String("test"),
|
||||
MDMInfo: &fleet.HostMDM{
|
||||
IsServer: false,
|
||||
|
|
@ -103,6 +107,91 @@ func TestGetFleetDesktopSummary(t *testing.T) {
|
|||
|
||||
})
|
||||
|
||||
t.Run("different app config values for unmanaged host", func(t *testing.T) {
|
||||
ds := new(mock.Store)
|
||||
license := &fleet.LicenseInfo{Tier: fleet.TierPremium, Expiration: time.Now().Add(24 * time.Hour)}
|
||||
svc, ctx := newTestService(t, ds, nil, nil, &TestServerOpts{License: license, SkipCreateTestUsers: true})
|
||||
ds.FailingPoliciesCountFunc = func(ctx context.Context, host *fleet.Host) (uint, error) {
|
||||
return uint(1), nil
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
mdm fleet.MDM
|
||||
out fleet.DesktopNotifications
|
||||
}{
|
||||
{
|
||||
mdm: fleet.MDM{
|
||||
EnabledAndConfigured: true,
|
||||
MacOSMigration: fleet.MacOSMigration{
|
||||
Enable: true,
|
||||
},
|
||||
},
|
||||
out: fleet.DesktopNotifications{
|
||||
NeedsMDMMigration: false,
|
||||
RenewEnrollmentProfile: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
mdm: fleet.MDM{
|
||||
EnabledAndConfigured: false,
|
||||
MacOSMigration: fleet.MacOSMigration{
|
||||
Enable: true,
|
||||
},
|
||||
},
|
||||
out: fleet.DesktopNotifications{
|
||||
NeedsMDMMigration: false,
|
||||
RenewEnrollmentProfile: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
mdm: fleet.MDM{
|
||||
EnabledAndConfigured: true,
|
||||
MacOSMigration: fleet.MacOSMigration{
|
||||
Enable: false,
|
||||
},
|
||||
},
|
||||
out: fleet.DesktopNotifications{
|
||||
NeedsMDMMigration: false,
|
||||
RenewEnrollmentProfile: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
mdm: fleet.MDM{
|
||||
EnabledAndConfigured: false,
|
||||
MacOSMigration: fleet.MacOSMigration{
|
||||
Enable: false,
|
||||
},
|
||||
},
|
||||
out: fleet.DesktopNotifications{
|
||||
NeedsMDMMigration: false,
|
||||
RenewEnrollmentProfile: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
|
||||
appCfg := fleet.AppConfig{}
|
||||
appCfg.MDM = c.mdm
|
||||
return &appCfg, nil
|
||||
}
|
||||
|
||||
ctx = test.HostContext(ctx, &fleet.Host{
|
||||
OsqueryHostID: ptr.String("test"),
|
||||
MDMInfo: &fleet.HostMDM{
|
||||
IsServer: false,
|
||||
InstalledFromDep: true,
|
||||
Enrolled: false,
|
||||
Name: fleet.WellKnownMDMFleet,
|
||||
}})
|
||||
sum, err := svc.GetFleetDesktopSummary(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, c.out, sum.Notifications, fmt.Sprintf("enabled_and_configured: %t | macos_migration.enable: %t", c.mdm.EnabledAndConfigured, c.mdm.MacOSMigration.Enable))
|
||||
require.EqualValues(t, 1, *sum.FailingPolicies)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
t.Run("different host attributes", func(t *testing.T) {
|
||||
ds := new(mock.Store)
|
||||
license := &fleet.LicenseInfo{Tier: fleet.TierPremium, Expiration: time.Now().Add(24 * time.Hour)}
|
||||
|
|
@ -136,7 +225,8 @@ func TestGetFleetDesktopSummary(t *testing.T) {
|
|||
host: &fleet.Host{OsqueryHostID: nil},
|
||||
err: nil,
|
||||
out: fleet.DesktopNotifications{
|
||||
NeedsMDMMigration: false,
|
||||
NeedsMDMMigration: false,
|
||||
RenewEnrollmentProfile: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -151,7 +241,8 @@ func TestGetFleetDesktopSummary(t *testing.T) {
|
|||
}},
|
||||
err: nil,
|
||||
out: fleet.DesktopNotifications{
|
||||
NeedsMDMMigration: false,
|
||||
NeedsMDMMigration: false,
|
||||
RenewEnrollmentProfile: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -162,11 +253,12 @@ func TestGetFleetDesktopSummary(t *testing.T) {
|
|||
IsServer: false,
|
||||
InstalledFromDep: true,
|
||||
Enrolled: false,
|
||||
Name: fleet.WellKnownMDMIntune,
|
||||
Name: fleet.WellKnownMDMFleet,
|
||||
}},
|
||||
err: nil,
|
||||
out: fleet.DesktopNotifications{
|
||||
NeedsMDMMigration: false,
|
||||
NeedsMDMMigration: false,
|
||||
RenewEnrollmentProfile: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -181,7 +273,8 @@ func TestGetFleetDesktopSummary(t *testing.T) {
|
|||
}},
|
||||
err: nil,
|
||||
out: fleet.DesktopNotifications{
|
||||
NeedsMDMMigration: false,
|
||||
NeedsMDMMigration: false,
|
||||
RenewEnrollmentProfile: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -196,14 +289,15 @@ func TestGetFleetDesktopSummary(t *testing.T) {
|
|||
}},
|
||||
err: nil,
|
||||
out: fleet.DesktopNotifications{
|
||||
NeedsMDMMigration: true,
|
||||
NeedsMDMMigration: true,
|
||||
RenewEnrollmentProfile: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
ctx = host.NewContext(ctx, c.host)
|
||||
ctx = test.HostContext(ctx, c.host)
|
||||
sum, err := svc.GetFleetDesktopSummary(ctx)
|
||||
|
||||
if c.err != nil {
|
||||
|
|
|
|||
|
|
@ -4392,6 +4392,12 @@ func (s *integrationMDMTestSuite) TestDesktopMDMMigration() {
|
|||
token := "token_test_migration"
|
||||
host := createHostAndDeviceToken(t, s.ds, token)
|
||||
|
||||
// enable migration
|
||||
var acResp appConfigResponse
|
||||
s.DoJSON("PATCH", "/api/v1/fleet/config", json.RawMessage(`{
|
||||
"mdm": { "macos_migration": { "enable": true, "mode": "voluntary", "webhook_url": "https://example.com" } }
|
||||
}`), http.StatusOK, &acResp)
|
||||
|
||||
getDesktopResp := fleetDesktopResponse{}
|
||||
res := s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token+"/desktop", nil, http.StatusOK)
|
||||
require.NoError(t, json.NewDecoder(res.Body).Decode(&getDesktopResp))
|
||||
|
|
@ -4399,6 +4405,11 @@ func (s *integrationMDMTestSuite) TestDesktopMDMMigration() {
|
|||
require.NoError(t, getDesktopResp.Err)
|
||||
require.Zero(t, *getDesktopResp.FailingPolicies)
|
||||
require.False(t, getDesktopResp.Notifications.NeedsMDMMigration)
|
||||
require.False(t, getDesktopResp.Notifications.RenewEnrollmentProfile)
|
||||
require.Equal(t, acResp.OrgInfo.OrgLogoURL, getDesktopResp.Config.OrgInfo.OrgLogoURL)
|
||||
require.Equal(t, acResp.OrgInfo.ContactURL, getDesktopResp.Config.OrgInfo.ContactURL)
|
||||
require.Equal(t, acResp.OrgInfo.OrgName, getDesktopResp.Config.OrgInfo.OrgName)
|
||||
require.Equal(t, acResp.MDM.MacOSMigration.Mode, getDesktopResp.Config.MDM.MacOSMigration.Mode)
|
||||
|
||||
// simulate that the device is enrolled in a third-party MDM and DEP capable
|
||||
err := s.ds.SetOrUpdateMDMData(
|
||||
|
|
@ -4419,6 +4430,36 @@ func (s *integrationMDMTestSuite) TestDesktopMDMMigration() {
|
|||
require.NoError(t, getDesktopResp.Err)
|
||||
require.Zero(t, *getDesktopResp.FailingPolicies)
|
||||
require.True(t, getDesktopResp.Notifications.NeedsMDMMigration)
|
||||
require.False(t, getDesktopResp.Notifications.RenewEnrollmentProfile)
|
||||
require.Equal(t, acResp.OrgInfo.OrgLogoURL, getDesktopResp.Config.OrgInfo.OrgLogoURL)
|
||||
require.Equal(t, acResp.OrgInfo.ContactURL, getDesktopResp.Config.OrgInfo.ContactURL)
|
||||
require.Equal(t, acResp.OrgInfo.OrgName, getDesktopResp.Config.OrgInfo.OrgName)
|
||||
require.Equal(t, acResp.MDM.MacOSMigration.Mode, getDesktopResp.Config.MDM.MacOSMigration.Mode)
|
||||
|
||||
// simulate that the device needs to be enrolled in fleet, DEP capable
|
||||
err = s.ds.SetOrUpdateMDMData(
|
||||
ctx,
|
||||
host.ID,
|
||||
false,
|
||||
false,
|
||||
s.server.URL,
|
||||
true,
|
||||
fleet.WellKnownMDMFleet,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
getDesktopResp = fleetDesktopResponse{}
|
||||
res = s.DoRawNoAuth("GET", "/api/latest/fleet/device/"+token+"/desktop", nil, http.StatusOK)
|
||||
require.NoError(t, json.NewDecoder(res.Body).Decode(&getDesktopResp))
|
||||
require.NoError(t, res.Body.Close())
|
||||
require.NoError(t, getDesktopResp.Err)
|
||||
require.Zero(t, *getDesktopResp.FailingPolicies)
|
||||
require.False(t, getDesktopResp.Notifications.NeedsMDMMigration)
|
||||
require.True(t, getDesktopResp.Notifications.RenewEnrollmentProfile)
|
||||
require.Equal(t, acResp.OrgInfo.OrgLogoURL, getDesktopResp.Config.OrgInfo.OrgLogoURL)
|
||||
require.Equal(t, acResp.OrgInfo.ContactURL, getDesktopResp.Config.OrgInfo.ContactURL)
|
||||
require.Equal(t, acResp.OrgInfo.OrgName, getDesktopResp.Config.OrgInfo.OrgName)
|
||||
require.Equal(t, acResp.MDM.MacOSMigration.Mode, getDesktopResp.Config.MDM.MacOSMigration.Mode)
|
||||
}
|
||||
|
||||
func (s *integrationMDMTestSuite) runWorker() {
|
||||
|
|
|
|||
|
|
@ -252,6 +252,14 @@ func (svc *Service) GetOrbitConfig(ctx context.Context) (fleet.OrbitConfig, erro
|
|||
}
|
||||
}
|
||||
|
||||
if config.MDM.EnabledAndConfigured &&
|
||||
config.MDM.MacOSMigration.Enable &&
|
||||
host.IsOsqueryEnrolled() &&
|
||||
host.MDMInfo.IsDEPCapable() &&
|
||||
host.MDMInfo.IsEnrolledInThirdPartyMDM() {
|
||||
notifs.NeedsMDMMigration = true
|
||||
}
|
||||
|
||||
return fleet.OrbitConfig{
|
||||
Flags: opts.CommandLineStartUpFlags,
|
||||
Extensions: opts.Extensions,
|
||||
|
|
|
|||
BIN
website/assets/images/permanent/fleet-mark-color-40x40@4x.png
vendored
Normal file
BIN
website/assets/images/permanent/fleet-mark-color-40x40@4x.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.6 KiB |
BIN
website/assets/images/permanent/mdm-migration-screenshot-768x180@2x.jpg
vendored
Normal file
BIN
website/assets/images/permanent/mdm-migration-screenshot-768x180@2x.jpg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 167 KiB |
Loading…
Reference in a new issue