Windows MDM migration: implement fleetd notification and migration (#24185)

This commit is contained in:
Martin Angers
2024-12-02 09:14:10 -05:00
committed by GitHub
parent c4404d9d68
commit c27c859b3a
15 changed files with 218 additions and 74 deletions
+1 -3
View File
@@ -17,8 +17,6 @@ func (svc *Service) ListDevicePolicies(ctx context.Context, host *fleet.Host) ([
return svc.ds.ListPoliciesForHost(ctx, host)
}
const refetchMDMUnenrollCriticalQueryDuration = 3 * time.Minute
// TriggerMigrateMDMDevice triggers the webhook associated with the MDM
// migration to Fleet configuration. It is located in the ee package instead of
// the server/webhooks one because it is a Fleet Premium only feature and for
@@ -88,7 +86,7 @@ func (svc *Service) TriggerMigrateMDMDevice(ctx context.Context, host *fleet.Hos
// if the webhook was successfully triggered, we update the host to
// constantly run the query to check if it has been unenrolled from its
// existing third-party MDM.
refetchUntil := svc.clock.Now().Add(refetchMDMUnenrollCriticalQueryDuration)
refetchUntil := svc.clock.Now().Add(fleet.RefetchMDMUnenrollCriticalQueryDuration)
host.RefetchCriticalQueriesUntil = &refetchUntil
if err := svc.ds.UpdateHostRefetchCriticalQueriesUntil(ctx, host.ID, &refetchUntil); err != nil {
return ctxerr.Wrap(ctx, err, "save host with refetch critical queries timestamp")
@@ -0,0 +1 @@
* Added support to migrate the MDM provider of Windows devices to Fleet.
+2 -1
View File
@@ -175,7 +175,8 @@ func generateWindowsMDMAccessTokenPayload(args WindowsMDMEnrollmentArgs) ([]byte
return json.Marshal(pld)
}
// IsRunningOnWindowsServer determines if the process is running on a Windows server. Exported so it can be used across packages.
// IsRunningOnWindowsServer determines if the process is running on a Windows
// server. Exported so it can be used across packages.
func IsRunningOnWindowsServer() (bool, error) {
installType, err := readInstallationType()
if err != nil {
+20 -12
View File
@@ -165,14 +165,22 @@ func ApplyWindowsMDMEnrollmentFetcherMiddleware(
var errIsWindowsServer = errors.New("device is a Windows Server")
// GetConfig calls the wrapped Fetcher's GetConfig method, and if the fleet
// server set the "needs windows enrollment" flag to true, executes the command
// to enroll into Windows MDM (or not, if the device is a Windows Server).
// Run checks if the fleet server set the "needs windows {un}enrollment" flag
// to true, and executes the command to {un}enroll into Windows MDM (or not, if
// the device is a Windows Server). It also unenrolls the device if the flag
// "needs MDM migration" is set to true, so that the device can then be
// enrolled in Fleet MDM.
func (w *windowsMDMEnrollmentConfigReceiver) Run(cfg *fleet.OrbitConfig) error {
if cfg.Notifications.NeedsProgrammaticWindowsMDMEnrollment {
switch {
case cfg.Notifications.NeedsProgrammaticWindowsMDMEnrollment:
w.attemptEnrollment(cfg.Notifications)
} else if cfg.Notifications.NeedsProgrammaticWindowsMDMUnenrollment {
w.attemptUnenrollment()
case cfg.Notifications.NeedsProgrammaticWindowsMDMUnenrollment,
cfg.Notifications.NeedsMDMMigration:
label := "unenroll"
if cfg.Notifications.NeedsMDMMigration {
label = "migrate"
}
w.attemptUnenrollment(label)
}
return nil
}
@@ -227,18 +235,18 @@ func (w *windowsMDMEnrollmentConfigReceiver) attemptEnrollment(notifs fleet.Orbi
}
}
func (w *windowsMDMEnrollmentConfigReceiver) attemptUnenrollment() {
func (w *windowsMDMEnrollmentConfigReceiver) attemptUnenrollment(actionLabel string) {
if w.mu.TryLock() {
defer w.mu.Unlock()
// do not unenroll Windows Servers, and do not attempt unenrollment if the
// last run is not at least Frequency ago.
if w.isWindowsServer {
log.Debug().Msg("skipped calling UnregisterDeviceWithManagement to unenroll Windows device, device is a server")
log.Debug().Msgf("skipped calling UnregisterDeviceWithManagement to %s Windows device, device is a server", actionLabel)
return
}
if time.Since(w.lastUnenrollRun) <= w.Frequency {
log.Debug().Msg("skipped calling UnregisterDeviceWithManagement to unenroll Windows device, last run was too recent")
log.Debug().Msgf("skipped calling UnregisterDeviceWithManagement to %s Windows device, last run was too recent", actionLabel)
return
}
@@ -252,15 +260,15 @@ func (w *windowsMDMEnrollmentConfigReceiver) attemptUnenrollment() {
if err := fn(args); err != nil {
if errors.Is(err, errIsWindowsServer) {
w.isWindowsServer = true
log.Info().Msg("device is a Windows Server, skipping unenrollment")
log.Info().Msgf("device is a Windows Server, skipping %s", actionLabel)
} else {
log.Info().Err(err).Msg("calling UnregisterDeviceWithManagement to unenroll Windows device failed")
log.Info().Err(err).Msgf("calling UnregisterDeviceWithManagement to %s Windows device failed", actionLabel)
}
return
}
w.lastUnenrollRun = time.Now()
log.Info().Msg("successfully called UnregisterDeviceWithManagement to unenroll Windows device")
log.Info().Msgf("successfully called UnregisterDeviceWithManagement to %s Windows device", actionLabel)
}
}
+18 -10
View File
@@ -191,21 +191,27 @@ func TestWindowsMDMEnrollment(t *testing.T) {
desc string
enrollFlag *bool
unenrollFlag *bool
migrateFlag *bool
discoveryURL string
apiErr error
wantAPICalled bool
wantLog string
}{
{"enroll=false", ptr.Bool(false), nil, "", nil, false, ""},
{"enroll=true,discovery=''", ptr.Bool(true), nil, "", nil, false, "discovery endpoint is empty"},
{"enroll=true,discovery!='',success", ptr.Bool(true), nil, "http://example.com", nil, true, "successfully called RegisterDeviceWithManagement"},
{"enroll=true,discovery!='',fail", ptr.Bool(true), nil, "http://example.com", io.ErrUnexpectedEOF, true, "enroll Windows device failed"},
{"enroll=true,discovery!='',server", ptr.Bool(true), nil, "http://example.com", errIsWindowsServer, true, "device is a Windows Server, skipping enrollment"},
{"enroll=false", ptr.Bool(false), nil, nil, "", nil, false, ""},
{"enroll=true,discovery=''", ptr.Bool(true), nil, nil, "", nil, false, "discovery endpoint is empty"},
{"enroll=true,discovery!='',success", ptr.Bool(true), nil, nil, "http://example.com", nil, true, "successfully called RegisterDeviceWithManagement"},
{"enroll=true,discovery!='',fail", ptr.Bool(true), nil, nil, "http://example.com", io.ErrUnexpectedEOF, true, "enroll Windows device failed"},
{"enroll=true,discovery!='',server", ptr.Bool(true), nil, nil, "http://example.com", errIsWindowsServer, true, "device is a Windows Server, skipping enrollment"},
{"unenroll=false", nil, ptr.Bool(false), "", nil, false, ""},
{"unenroll=true,success", nil, ptr.Bool(true), "", nil, true, "successfully called UnregisterDeviceWithManagement"},
{"unenroll=true,fail", nil, ptr.Bool(true), "", io.ErrUnexpectedEOF, true, "unenroll Windows device failed"},
{"unenroll=true,server", nil, ptr.Bool(true), "", errIsWindowsServer, true, "device is a Windows Server, skipping unenrollment"},
{"unenroll=false", nil, ptr.Bool(false), nil, "", nil, false, ""},
{"unenroll=true,success", nil, ptr.Bool(true), nil, "", nil, true, "successfully called UnregisterDeviceWithManagement to unenroll"},
{"unenroll=true,fail", nil, ptr.Bool(true), nil, "", io.ErrUnexpectedEOF, true, "unenroll Windows device failed"},
{"unenroll=true,server", nil, ptr.Bool(true), nil, "", errIsWindowsServer, true, "device is a Windows Server, skipping unenroll"},
{"migrate=false", nil, nil, ptr.Bool(false), "", nil, false, ""},
{"migrate=true,success", nil, nil, ptr.Bool(true), "", nil, true, "successfully called UnregisterDeviceWithManagement to migrate"},
{"migrate=true,fail", nil, nil, ptr.Bool(true), "", io.ErrUnexpectedEOF, true, "migrate Windows device failed"},
{"migrate=true,server", nil, nil, ptr.Bool(true), "", errIsWindowsServer, true, "device is a Windows Server, skipping migrate"},
}
for _, c := range cases {
@@ -215,12 +221,14 @@ func TestWindowsMDMEnrollment(t *testing.T) {
var (
enroll = c.enrollFlag != nil && *c.enrollFlag
unenroll = c.unenrollFlag != nil && *c.unenrollFlag
migrate = c.migrateFlag != nil && *c.migrateFlag
isUnenroll = c.unenrollFlag != nil
)
testConfig := &fleet.OrbitConfig{Notifications: fleet.OrbitConfigNotifications{
NeedsProgrammaticWindowsMDMEnrollment: enroll,
NeedsProgrammaticWindowsMDMUnenrollment: unenroll,
NeedsMDMMigration: migrate,
WindowsMDMDiscoveryEndpoint: c.discoveryURL,
}}
@@ -241,7 +249,7 @@ func TestWindowsMDMEnrollment(t *testing.T) {
err := enrollReceiver.Run(testConfig)
require.NoError(t, err) // the dummy receiver never returns an error
if isUnenroll {
if isUnenroll || migrate {
require.Equal(t, c.wantAPICalled, unenrollGotCalled)
require.False(t, enrollGotCalled)
} else {
+1 -1
View File
@@ -344,7 +344,7 @@ type Host struct {
// is that the latter is a one-time request, while this one is a persistent
// until the timestamp expires. The initial use-case is to check for a host
// to be unenrolled from its old MDM solution, in the "migrate to Fleet MDM"
// workflow.
// workflow (both Apple and Windows).
//
// In the future, if we want to use it for more than one use-case, we could
// add a "reason" field with well-known labels so we know what condition(s)
+5
View File
@@ -18,6 +18,11 @@ const (
MDMAppleDeclarationUUIDPrefix = "d"
MDMAppleProfileUUIDPrefix = "a"
MDMWindowsProfileUUIDPrefix = "w"
// RefetchMDMUnenrollCriticalQueryDuration is the duration to set the
// RefetchCriticalQueriesUntil field when migrating a device from a
// third-party MDM solution to Fleet.
RefetchMDMUnenrollCriticalQueryDuration = 3 * time.Minute
)
type AppleMDM struct {
+6 -1
View File
@@ -8,7 +8,12 @@ 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"`
// NeedsMDMMigration is set to true if MDM is enabled for the host's
// platform, MDM migration is enabled for that platform, and the host is
// eligible for such a migration (e.g. it is enrolled in a third-party MDM
// solution).
NeedsMDMMigration bool `json:"needs_mdm_migration,omitempty"`
// NeedsProgrammaticWindowsMDMEnrollment is sent as true if Windows MDM is
// enabled and the device should be enrolled as far as the server knows (e.g.
+101 -36
View File
@@ -5926,7 +5926,6 @@ func (s *integrationMDMTestSuite) TestAppConfigWindowsMDM() {
err = s.ds.SaveAppConfig(context.Background(), appConf)
require.NoError(s.T(), err)
// the feature flag is enabled for the MDM test suite
var acResp appConfigResponse
s.DoJSON("GET", "/api/latest/fleet/config", nil, http.StatusOK, &acResp)
assert.False(t, acResp.MDM.WindowsEnabledAndConfigured)
@@ -5937,47 +5936,66 @@ func (s *integrationMDMTestSuite) TestAppConfigWindowsMDM() {
tm2, err := s.ds.NewTeam(ctx, &fleet.Team{Name: t.Name() + "2"})
require.NoError(t, err)
// create some hosts - a Windows workstation in each team and no-team,
// Windows server in no team, Windows workstation enrolled in a 3rd-party in
// team 2, Windows workstation already enrolled in Fleet in no team, and a
// macOS host in no team.
metadataHosts := []struct {
os string
suffix string
isServer bool
teamID *uint
enrolledName string
shouldEnroll bool
}{
{"windows", "win-no-team", false, nil, "", true},
{"windows", "win-team-1", false, &tm1.ID, "", true},
{"windows", "win-team-2", false, &tm2.ID, "", true},
{"windows", "win-server", true, nil, "", false}, // is a server
{"windows", "win-third-party", false, &tm2.ID, fleet.WellKnownMDMSimpleMDM, false}, // is enrolled in 3rd-party
{"windows", "win-fleet", false, nil, fleet.WellKnownMDMFleet, false}, // is already Fleet-enrolled
{"darwin", "macos-no-team", false, nil, "", false}, // is not Windows
}
hostsBySuffix := make(map[string]*fleet.Host, len(metadataHosts))
for _, meta := range metadataHosts {
h := createOrbitEnrolledHost(t, meta.os, meta.suffix, s.ds)
createDeviceTokenForHost(t, s.ds, h.ID, meta.suffix)
err := s.ds.SetOrUpdateMDMData(ctx, h.ID, meta.isServer, meta.enrolledName != "", "https://example.com", false, meta.enrolledName, "")
require.NoError(t, err)
if meta.teamID != nil {
err = s.ds.AddHostsToTeam(ctx, meta.teamID, []uint{h.ID})
require.NoError(t, err)
}
hostsBySuffix[meta.suffix] = h
}
// enable Windows MDM
acResp = appConfigResponse{}
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
"mdm": { "windows_enabled_and_configured": true }
}`), http.StatusOK, &acResp)
assert.True(t, acResp.MDM.WindowsEnabledAndConfigured)
assert.False(t, acResp.MDM.WindowsMigrationEnabled)
s.lastActivityOfTypeMatches(fleet.ActivityTypeEnabledWindowsMDM{}.ActivityName(), `{}`, 0)
// create some hosts - a Windows workstation in each team and no-team,
// Windows server in no team, Windows workstation enrolled in a 3rd-party in
// team 2, Windows workstation already enrolled in Fleet in no team, and a
// macOS host in no team.
metadataHosts := []struct {
os string
suffix string
isServer bool
teamID *uint
enrolledName string
shouldEnroll bool
shouldMigrate bool
}{
{"windows", "win-no-team", false, nil, "", true, false},
{"windows", "win-team-1", false, &tm1.ID, "", true, false},
{"windows", "win-team-2", false, &tm2.ID, "", true, false},
{"windows", "win-server", true, nil, "", false, false}, // is a server
{"windows", "win-third-party", false, &tm2.ID, fleet.WellKnownMDMSimpleMDM, false, true}, // is enrolled in 3rd-party
{"windows", "win-fleet", false, nil, fleet.WellKnownMDMFleet, false, false}, // is already Fleet-enrolled
{"darwin", "macos-no-team", false, nil, "", false, false}, // is not Windows
{"windows", "win-server-third-party", true, nil, fleet.WellKnownMDMSimpleMDM, false, false}, // is enrolled in 3rd-party, but is a server
}
hostsBySuffix := make(map[string]*fleet.Host, len(metadataHosts))
for _, meta := range metadataHosts {
var host *fleet.Host
if meta.os == "windows" && meta.enrolledName == fleet.WellKnownMDMFleet {
// special-case to create a properly MDM-enrolled into Fleet host
host = createOrbitEnrolledHost(t, meta.os, meta.suffix, s.ds)
mdmDevice := mdmtest.NewTestMDMClientWindowsProgramatic(s.server.URL, *host.OrbitNodeKey)
err := mdmDevice.Enroll()
require.NoError(t, err)
err = s.ds.UpdateMDMWindowsEnrollmentsHostUUID(ctx, host.UUID, mdmDevice.DeviceID)
require.NoError(t, err)
err = s.ds.SetOrUpdateMDMData(ctx, host.ID, meta.isServer, true, s.server.URL, false, fleet.WellKnownMDMFleet, "")
require.NoError(t, err)
} else {
host = createOrbitEnrolledHost(t, meta.os, meta.suffix, s.ds)
createDeviceTokenForHost(t, s.ds, host.ID, meta.suffix)
serverURL := "https://example.com"
err := s.ds.SetOrUpdateMDMData(ctx, host.ID, meta.isServer, meta.enrolledName != "", serverURL, false, meta.enrolledName, "")
require.NoError(t, err)
}
if meta.teamID != nil {
err = s.ds.AddHostsToTeam(ctx, meta.teamID, []uint{host.ID})
require.NoError(t, err)
}
hostsBySuffix[meta.suffix] = host
}
// get the orbit config for each host, verify that only the expected ones
// receive the "needs enrollment to Windows MDM" notification.
for _, meta := range metadataHosts {
@@ -5987,6 +6005,7 @@ func (s *integrationMDMTestSuite) TestAppConfigWindowsMDM() {
http.StatusOK, &resp)
require.Equal(t, meta.shouldEnroll, resp.Notifications.NeedsProgrammaticWindowsMDMEnrollment)
require.False(t, resp.Notifications.NeedsProgrammaticWindowsMDMUnenrollment)
require.False(t, resp.Notifications.NeedsMDMMigration)
if meta.shouldEnroll {
require.Contains(t, resp.Notifications.WindowsMDMDiscoveryEndpoint, microsoft_mdm.MDE2DiscoveryPath)
} else {
@@ -5994,7 +6013,34 @@ func (s *integrationMDMTestSuite) TestAppConfigWindowsMDM() {
}
}
// turn on MDM for a host
// enable Windows MDM migration
acResp = appConfigResponse{}
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
"mdm": { "windows_migration_enabled": true }
}`), http.StatusOK, &acResp)
assert.True(t, acResp.MDM.WindowsEnabledAndConfigured)
assert.True(t, acResp.MDM.WindowsMigrationEnabled)
s.lastActivityMatches(fleet.ActivityTypeEnabledWindowsMDMMigration{}.ActivityName(), `{}`, 0)
// get the orbit config for each host, verify that only the expected ones
// receive the "needs enrollment to Windows MDM" and "needs migration" notifications.
// They still get enrollment notifications as we have not proceeded with enrollment.
for _, meta := range metadataHosts {
var resp orbitGetConfigResponse
s.DoJSON("POST", "/api/fleet/orbit/config",
json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *hostsBySuffix[meta.suffix].OrbitNodeKey)),
http.StatusOK, &resp)
require.Equal(t, meta.shouldEnroll, resp.Notifications.NeedsProgrammaticWindowsMDMEnrollment)
require.Equal(t, meta.shouldMigrate, resp.Notifications.NeedsMDMMigration)
require.False(t, resp.Notifications.NeedsProgrammaticWindowsMDMUnenrollment)
if meta.shouldEnroll {
require.Contains(t, resp.Notifications.WindowsMDMDiscoveryEndpoint, microsoft_mdm.MDE2DiscoveryPath)
} else {
require.Empty(t, resp.Notifications.WindowsMDMDiscoveryEndpoint)
}
}
// turn on MDM for another host
orbitHost, _ := createWindowsHostThenEnrollMDM(s.ds, s.server.URL, t)
// disable Microsoft MDM
@@ -6002,9 +6048,10 @@ func (s *integrationMDMTestSuite) TestAppConfigWindowsMDM() {
"mdm": { "windows_enabled_and_configured": false }
}`), http.StatusOK, &acResp)
assert.False(t, acResp.MDM.WindowsEnabledAndConfigured)
assert.False(t, acResp.MDM.WindowsMigrationEnabled)
s.lastActivityOfTypeMatches(fleet.ActivityTypeDisabledWindowsMDM{}.ActivityName(), `{}`, 0)
// get the orbit config for win-no-team should return true for the
// get the orbit config for that MDM-enrolled host returns true for the
// unenrollment notification
var resp orbitGetConfigResponse
s.DoJSON("POST", "/api/fleet/orbit/config",
@@ -6012,7 +6059,25 @@ func (s *integrationMDMTestSuite) TestAppConfigWindowsMDM() {
http.StatusOK, &resp)
require.True(t, resp.Notifications.NeedsProgrammaticWindowsMDMUnenrollment)
require.False(t, resp.Notifications.NeedsProgrammaticWindowsMDMEnrollment)
require.False(t, resp.Notifications.NeedsMDMMigration)
require.Empty(t, resp.Notifications.WindowsMDMDiscoveryEndpoint)
// get the orbit config for each host, only the fleet-enrolled ones get the unenrollment,
// and none get enrollment/migration (because MDM is now off).
for _, meta := range metadataHosts {
var resp orbitGetConfigResponse
s.DoJSON("POST", "/api/fleet/orbit/config",
json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *hostsBySuffix[meta.suffix].OrbitNodeKey)),
http.StatusOK, &resp)
require.False(t, resp.Notifications.NeedsProgrammaticWindowsMDMEnrollment)
require.False(t, resp.Notifications.NeedsMDMMigration)
if meta.enrolledName == fleet.WellKnownMDMFleet {
require.True(t, resp.Notifications.NeedsProgrammaticWindowsMDMUnenrollment)
} else {
require.False(t, resp.Notifications.NeedsProgrammaticWindowsMDMUnenrollment)
}
require.Empty(t, resp.Notifications.WindowsMDMDiscoveryEndpoint)
}
}
func (s *integrationMDMTestSuite) TestOrbitConfigNudgeSettings() {
+11 -3
View File
@@ -615,14 +615,22 @@ func NewCertStoreProvisioningData(enrollmentType string, identityFingerprint str
return certStore
}
// IsEligibleForWindowsMDMEnrollment returns true if the host can be enrolled
// isEligibleForWindowsMDMEnrollment returns true if the host can be enrolled
// in Fleet's Windows MDM (if it was enabled).
func IsEligibleForWindowsMDMEnrollment(host *fleet.Host, mdmInfo *fleet.HostMDM) bool {
func isEligibleForWindowsMDMEnrollment(host *fleet.Host, mdmInfo *fleet.HostMDM) bool {
return host.FleetPlatform() == "windows" &&
host.IsOsqueryEnrolled() &&
(mdmInfo == nil || (!mdmInfo.IsServer && !mdmInfo.Enrolled))
}
// isEligibleForWindowsMDMMigration returns true if the host can be migrated to
// Fleet's Windows MDM (if it was enabled).
func isEligibleForWindowsMDMMigration(host *fleet.Host, mdmInfo *fleet.HostMDM) bool {
return host.FleetPlatform() == "windows" &&
host.IsOsqueryEnrolled() &&
(mdmInfo != nil && !mdmInfo.IsServer && mdmInfo.Enrolled && mdmInfo.Name != fleet.WellKnownMDMFleet)
}
// NewApplicationProvisioningData returns a new ApplicationProvisioningData Characteristic
// The Application Provisioning configuration is used for bootstrapping a device with an OMA DM account
// The paramenters here maps to the W7 application CSP
@@ -976,7 +984,7 @@ func (svc *Service) authBinarySecurityToken(ctx context.Context, authToken *flee
}
// This ensures that only hosts that are eligible for Windows enrollment can be enrolled
if !IsEligibleForWindowsMDMEnrollment(host, mdmInfo) {
if !isEligibleForWindowsMDMEnrollment(host, mdmInfo) {
return "", "", errors.New("host is not elegible for Windows MDM enrollment")
}
+14 -1
View File
@@ -268,13 +268,26 @@ func (svc *Service) GetOrbitConfig(ctx context.Context) (fleet.OrbitConfig, erro
// set the host's orbit notifications for Windows MDM
if appConfig.MDM.WindowsEnabledAndConfigured {
if IsEligibleForWindowsMDMEnrollment(host, mdmInfo) {
if isEligibleForWindowsMDMEnrollment(host, mdmInfo) {
discoURL, err := microsoft_mdm.ResolveWindowsMDMDiscovery(appConfig.ServerSettings.ServerURL)
if err != nil {
return fleet.OrbitConfig{}, err
}
notifs.WindowsMDMDiscoveryEndpoint = discoURL
notifs.NeedsProgrammaticWindowsMDMEnrollment = true
} else if appConfig.MDM.WindowsMigrationEnabled && isEligibleForWindowsMDMMigration(host, mdmInfo) {
notifs.NeedsMDMMigration = true
// Set the host to refetch the "critical queries" quickly for some time,
// to improve ingestion time of the unenroll and make the host eligible to
// enroll into Fleet faster.
if host.RefetchCriticalQueriesUntil == nil {
refetchUntil := svc.clock.Now().Add(fleet.RefetchMDMUnenrollCriticalQueryDuration)
host.RefetchCriticalQueriesUntil = &refetchUntil
if err := svc.ds.UpdateHostRefetchCriticalQueriesUntil(ctx, host.ID, &refetchUntil); err != nil {
return fleet.OrbitConfig{}, err
}
}
}
}
if !appConfig.MDM.WindowsEnabledAndConfigured {
+2 -1
View File
@@ -691,7 +691,8 @@ const alwaysTrueQuery = "SELECT 1"
// list of detail queries that are returned when only the critical queries
// should be returned (due to RefetchCriticalQueriesUntil timestamp being set).
var criticalDetailQueries = map[string]bool{
"mdm": true,
"mdm": true,
"mdm_windows": true,
}
// detailQueriesForHost returns the map of detail+additional queries that should be executed by
+7 -3
View File
@@ -179,7 +179,7 @@ func TestGetClientConfig(t *testing.T) {
// Check scheduled queries are loaded properly
conf, err = svc.GetClientConfig(ctx3)
require.NoError(t, err)
assert.JSONEq(t, `{
assert.JSONEq(t, `{
"pack_by_label": {
"queries":{
"time":{"query":"select * from time","interval":30,"removed":false}
@@ -208,7 +208,7 @@ func TestGetClientConfig(t *testing.T) {
"version": ""
}
}
}
}
}`,
string(conf["packs"].(json.RawMessage)),
)
@@ -1165,8 +1165,12 @@ func TestHostDetailQueries(t *testing.T) {
host.RefetchCriticalQueriesUntil = ptr.Time(mockClock.Now().Add(1 * time.Minute))
queries, discovery, err = svc.detailQueriesForHost(ctx, &host)
require.NoError(t, err)
require.Equal(t, len(criticalDetailQueries), len(queries), distQueriesMapKeys(queries))
// host is darwin so it gets only the darwin critical query
require.Equal(t, 1, len(queries), distQueriesMapKeys(queries))
for name := range criticalDetailQueries {
if strings.HasSuffix(name, "_windows") {
continue
}
assert.Contains(t, queries, hostDetailQueryPrefix+name)
}
verifyDiscovery(t, queries, discovery)
+13 -1
View File
@@ -1885,6 +1885,11 @@ func directIngestMDMWindows(ctx context.Context, logger log.Logger, host *fleet.
return nil
}
if host.RefetchCriticalQueriesUntil != nil {
level.Debug(logger).Log("msg", "ingesting Windows mdm data during refetch critical queries window", "host_id", host.ID,
"data", fmt.Sprintf("%+v", rows))
}
data := rows[0]
var enrolled bool
var automatic bool
@@ -1900,13 +1905,20 @@ func directIngestMDMWindows(ctx context.Context, logger log.Logger, host *fleet.
}
isServer := strings.Contains(strings.ToLower(data["installation_type"]), "server")
mdmSolutionName := deduceMDMNameWindows(data)
if !enrolled && mdmSolutionName != fleet.WellKnownMDMFleet && host.RefetchCriticalQueriesUntil != nil {
// the host was unenrolled from a non-Fleet MDM solution, and the refetch
// critical queries timestamp was set, so clear it.
host.RefetchCriticalQueriesUntil = nil
}
return ds.SetOrUpdateMDMData(ctx,
host.ID,
isServer,
enrolled,
serverURL,
automatic,
deduceMDMNameWindows(data),
mdmSolutionName,
"",
)
}
+16 -1
View File
@@ -23,6 +23,17 @@ This code is MIT licensed and it was forked from [here](https://github.com/oscar
## Usage
On the server side, you just need to run the project using the already provided cert and keys. The certificate is in `.pfx` file format, so you need to extract the certificate and key first, see https://stackoverflow.com/a/59120388/1094941.
The "Import password" is "testpassword", and the names of the output files matter, on Linux something like this works (assuming you are in the certs/ directory):
```
# for the cert
$ openssl pkcs12 -in dev_cert_mdmwindows_com.pfx -clcerts -nokeys -out dev_cert_mdmwindows_com_cert.pem
# for the key
$ openssl pkcs12 -in dev_cert_mdmwindows_com.pfx -out dev_cert_mdmwindows_com.key -nocerts -nodes
```
Note that an asn1 error might occur when running the server, if that's the case you need to patch your local Go toolchain by running `$ go run ./patch/patch.go` (`GOROOT` env var must be set to point to your `go env GOROOT` directory). It may require `sudo` depending on where your `go` installation is (due to https://github.com/golang/go/issues/14017).
Next go to the project folder and run.
@@ -30,7 +41,9 @@ Next go to the project folder and run.
go run .
```
On the Windows client side, you need to import a custom CA certificate to the certificate store, and populate the `hosts` file before running the Windows Enrollment. The certificate to import is on the certs directory and it is called `dev_cert_mdmwindows_com.pfx`. You need to copy this certificate to the client machine and run the powershell command below. This is required because the project uses a local dev https endpoint.
Note that the server binds to the standard and usually firewall-protected `443` port, so you may need to configure your firewall to allow connections to it for the duration of your test.
On the Windows client side, you need to import the custom CA certificate to the certificate store, and populate the `hosts` file before running the Windows Enrollment. The certificate to import is on the certs directory and it is called `dev_cert_mdmwindows_com.pfx`. You need to copy this certificate to the client machine and run the powershell command below (in the console, not in a powershell terminal). This is required because the project uses a local dev https endpoint.
1) Import certificate to Trusted CAs repository (be sure to update the path to the pfx certificate)
@@ -42,6 +55,8 @@ On the Windows client side, you need to import a custom CA certificate to the ce
echo <server_ip> autodiscovery.mdmwindows.com >> %SystemRoot%\System32\drivers\etc\hosts
echo <server_ip> enterpriseenrollment.mdmwindows.com >> %SystemRoot%\System32\drivers\etc\hosts
To enroll the device into this MDM server, go to `Settings > Accounts > Access work or school` and click the connect button, enter the email provided to the server when you ran `go run .` (default: `demo@mdmwindows.com`) and it should automatically detect the server and proceed with enrollment. This is why the server must run on port `:443`, because it uses automatic discovery and will not attempt a custom port.
## Protocol Details
Below is the raw https exchange of the MS-MDE and MS-MDM protocols when run using the -verbose mode: