diff --git a/changes/18085-fix-repeated-install-commands-of-fleetd-on-windows-mdm b/changes/18085-fix-repeated-install-commands-of-fleetd-on-windows-mdm new file mode 100644 index 0000000000..ee04c643b4 --- /dev/null +++ b/changes/18085-fix-repeated-install-commands-of-fleetd-on-windows-mdm @@ -0,0 +1 @@ +* Fixed an issue on Windows hosts enrolled in MDM via Azure AD where the command to install Fleetd on the device was sent repeatedly, even though `fleetd` had been properly installed. diff --git a/server/datastore/mysql/hosts.go b/server/datastore/mysql/hosts.go index 9c799edf59..ce86ef3f4e 100644 --- a/server/datastore/mysql/hosts.go +++ b/server/datastore/mysql/hosts.go @@ -2680,13 +2680,13 @@ func (ds *Datastore) HostByIdentifier(ctx context.Context, identifier string) (* COALESCE(hd.percent_disk_space_available, 0) as percent_disk_space_available, COALESCE(hd.gigs_total_disk_space, 0) as gigs_total_disk_space, COALESCE(hst.seen_time, h.created_at) AS seen_time, - COALESCE(hu.software_updated_at, h.created_at) AS software_updated_at - ` + hostMDMSelect + ` + COALESCE(hu.software_updated_at, h.created_at) AS software_updated_at + ` + hostMDMSelect + ` FROM hosts h LEFT JOIN host_seen_times hst ON (h.id = hst.host_id) - LEFT JOIN host_updates hu ON (h.id = hu.host_id) + LEFT JOIN host_updates hu ON (h.id = hu.host_id) LEFT JOIN host_disks hd ON hd.host_id = h.id - ` + hostMDMJoin + ` + ` + hostMDMJoin + ` WHERE ? IN (h.hostname, h.osquery_host_id, h.node_key, h.uuid, h.hardware_serial) LIMIT 1 ` @@ -3715,9 +3715,11 @@ func (ds *Datastore) GetHostOrbitInfo(ctx context.Context, hostID uint) (*fleet. err := sqlx.GetContext( ctx, ds.reader(ctx), &orbit, ` SELECT - scripts_enabled + version, + desktop_version, + scripts_enabled FROM - host_orbit_info + host_orbit_info WHERE host_id = ?`, hostID, ) if err != nil { @@ -5037,7 +5039,7 @@ func (ds *Datastore) loadHostLite(ctx context.Context, id *uint, identifier *str stmt := ` SELECT h.id, - h.team_id, + h.team_id, h.osquery_host_id, h.node_key, h.hostname, @@ -5048,7 +5050,7 @@ func (ds *Datastore) loadHostLite(ctx context.Context, id *uint, identifier *str COALESCE(hst.seen_time, h.created_at) AS seen_time FROM hosts h LEFT JOIN host_seen_times hst ON (h.id = hst.host_id) - %s + %s LIMIT 1 ` var ( diff --git a/server/fleet/hosts.go b/server/fleet/hosts.go index ae0124c0d8..0ed16f60c3 100644 --- a/server/fleet/hosts.go +++ b/server/fleet/hosts.go @@ -361,7 +361,9 @@ type Host struct { // HostOrbitInfo maps to the host_orbit_info table in the database, which maps to the orbit_info agent table. type HostOrbitInfo struct { - ScriptsEnabled *bool `json:"scripts_enabled" db:"scripts_enabled"` + Version string `json:"version" db:"version"` + DesktopVersion *string `json:"desktop_version" db:"desktop_version"` + ScriptsEnabled *bool `json:"scripts_enabled" db:"scripts_enabled"` } // HostHealth contains a subset of Host data that indicates how healthy a Host is. For fields with diff --git a/server/service/integration_mdm_test.go b/server/service/integration_mdm_test.go index 4510cfc2e6..dedd92f3e0 100644 --- a/server/service/integration_mdm_test.go +++ b/server/service/integration_mdm_test.go @@ -6069,22 +6069,79 @@ func (s *integrationMDMTestSuite) TestWindowsAutomaticEnrollmentCommands() { d := mdmtest.NewTestMDMClientWindowsAutomatic(s.server.URL, azureMail) require.NoError(t, d.Enroll()) - cmds, err := d.StartManagementSession() + checkinAndAck := func(expectFleetdCmds bool) { + cmds, err := d.StartManagementSession() + require.NoError(t, err) + + if !expectFleetdCmds { + // receives only the 2 status commands + require.Len(t, cmds, 2) + for _, c := range cmds { + require.Equal(t, "Status", c.Verb, c) + } + return + } + + // 2 status + 2 commands to install fleetd + require.Len(t, cmds, 4) + var fleetdAddCmd, fleetdExecCmd fleet.ProtoCmdOperation + for _, c := range cmds { + switch c.Verb { + case "Add": + fleetdAddCmd = c + case "Exec": + fleetdExecCmd = c + } + } + require.Equal(t, syncml.FleetdWindowsInstallerGUID, fleetdAddCmd.Cmd.GetTargetURI()) + require.Equal(t, syncml.FleetdWindowsInstallerGUID, fleetdExecCmd.Cmd.GetTargetURI()) + + // reply with success for both commands + msgID, err := d.GetCurrentMsgID() + require.NoError(t, err) + + d.AppendResponse(fleet.SyncMLCmd{ + XMLName: xml.Name{Local: fleet.CmdStatus}, + MsgRef: &msgID, + CmdRef: &fleetdAddCmd.Cmd.CmdID.Value, + Cmd: &fleetdAddCmd.Verb, + Data: ptr.String("200"), + Items: nil, + CmdID: fleet.CmdID{Value: uuid.NewString()}, + }) + d.AppendResponse(fleet.SyncMLCmd{ + XMLName: xml.Name{Local: fleet.CmdStatus}, + MsgRef: &msgID, + CmdRef: &fleetdExecCmd.Cmd.CmdID.Value, + Cmd: &fleetdExecCmd.Verb, + Data: ptr.String("200"), + Items: nil, + CmdID: fleet.CmdID{Value: uuid.NewString()}, + }) + cmds, err = d.SendResponse() + require.NoError(t, err) + + // the ack of the message should be the only returned command + require.Len(t, cmds, 1) + } + + // start a management session, will receive the install fleetd commands + checkinAndAck(true) + + // start a new management session again, Fleetd is not reported as installed + // so it receives the commands again + checkinAndAck(true) + + // simulate fleetd installed and enrolled + host := createOrbitEnrolledHost(t, "windows", "h1", s.ds) + err = s.ds.UpdateMDMWindowsEnrollmentsHostUUID(ctx, host.UUID, d.DeviceID) + require.NoError(t, err) + err = s.ds.SetOrUpdateHostOrbitInfo(ctx, host.ID, "1.23", sql.NullString{}, sql.NullBool{}) require.NoError(t, err) - // 2 status + 2 commands to install fleetd - require.Len(t, cmds, 4) - var fleetdAddCmd, fleetdExecCmd fleet.ProtoCmdOperation - for _, c := range cmds { - switch c.Verb { - case "Add": - fleetdAddCmd = c - case "Exec": - fleetdExecCmd = c - } - } - require.Equal(t, syncml.FleetdWindowsInstallerGUID, fleetdAddCmd.Cmd.GetTargetURI()) - require.Equal(t, syncml.FleetdWindowsInstallerGUID, fleetdExecCmd.Cmd.GetTargetURI()) + // start a new management session again, Fleetd is reported as installed so + // it does not receive the commands + checkinAndAck(false) } func (s *integrationMDMTestSuite) TestValidManagementUnenrollRequest() { diff --git a/server/service/microsoft_mdm.go b/server/service/microsoft_mdm.go index 1a6e113f50..4ceab6d20b 100644 --- a/server/service/microsoft_mdm.go +++ b/server/service/microsoft_mdm.go @@ -1274,7 +1274,23 @@ func (svc *Service) isFleetdPresentOnDevice(ctx context.Context, deviceID string // If user identity is a MS-MDM UPN it means that the device was enrolled through user-driven flow // This means that fleetd might not be installed if isValidUPN(enrolledDevice.MDMEnrollUserID) { - return false, nil + var isPresent bool + if enrolledDevice.HostUUID != "" { + host, err := svc.ds.HostLiteByIdentifier(ctx, enrolledDevice.HostUUID) + if err != nil && !fleet.IsNotFound(err) { + return false, ctxerr.Wrap(ctx, err, "get host lite by identifier") + } + if host != nil { + orbitInfo, err := svc.ds.GetHostOrbitInfo(ctx, host.ID) + if err != nil && !fleet.IsNotFound(err) { + return false, ctxerr.Wrap(ctx, err, "get host orbit info") + } + if orbitInfo != nil { + isPresent = orbitInfo.Version != "" + } + } + } + return isPresent, nil } // TODO: Add check here to determine if MDM DeviceID is connected with Smbios UUID present on