From 2b4798c4abce022a82cdf8475eabe04b3f96025e Mon Sep 17 00:00:00 2001 From: Roberto Dip Date: Thu, 6 Jul 2023 15:33:40 -0300 Subject: [PATCH] add activity items when a Windows host turns MDM on (#12635) For #12427, and its sub-tasks #12288 and #12612 ![image](https://github.com/fleetdm/fleet/assets/4419992/b4c019dd-fbd3-4c1d-a2ad-a0bb4ebac817) --- docs/Using-Fleet/Audit-Activities.md | 4 +- frontend/interfaces/activity.ts | 1 + .../ActivityItem/ActivityItem.tests.tsx | 61 +++++++++++++++++++ .../ActivityItem/ActivityItem.tsx | 11 ++++ server/fleet/activities.go | 7 ++- server/fleet/mdm.go | 5 ++ server/service/apple_mdm.go | 1 + server/service/apple_mdm_test.go | 1 + server/service/integration_mdm_test.go | 17 +++++- server/service/microsoft_mdm.go | 24 +++++++- 10 files changed, 123 insertions(+), 9 deletions(-) diff --git a/docs/Using-Fleet/Audit-Activities.md b/docs/Using-Fleet/Audit-Activities.md index 395f7dc61a..e5c19d9d15 100644 --- a/docs/Using-Fleet/Audit-Activities.md +++ b/docs/Using-Fleet/Audit-Activities.md @@ -553,6 +553,7 @@ This activity contains the following fields: - "host_serial": Serial number of the host. - "host_display_name": Display name of the host. - "installed_from_dep": Whether the host was enrolled via DEP. +- "mdm_platform": Used to distinguish between Apple and Microsoft enrollments. Can be "apple", "microsoft" or not present. If missing, this value is treated as "apple" for backwards compatibility. #### Example @@ -560,7 +561,8 @@ This activity contains the following fields: { "host_serial": "C08VQ2AXHT96", "host_display_name": "MacBookPro16,1 (C08VQ2AXHT96)", - "installed_from_dep": true + "installed_from_dep": true, + "mdm_platform": "apple" } ``` diff --git a/frontend/interfaces/activity.ts b/frontend/interfaces/activity.ts index fcb6dcfd92..92e6afdbdd 100644 --- a/frontend/interfaces/activity.ts +++ b/frontend/interfaces/activity.ts @@ -82,6 +82,7 @@ export interface IActivityDetails { host_display_names?: string[]; host_ids?: number[]; installed_from_dep?: boolean; + mdm_platform?: "microsoft" | "apple"; minimum_version?: string; deadline?: string; profile_name?: string; diff --git a/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tests.tsx b/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tests.tsx index ec6389c781..4e6726c022 100644 --- a/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tests.tsx +++ b/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tests.tsx @@ -708,4 +708,65 @@ describe("Activity Feed", () => { expect(screen.queryByText("baz")).toBeNull(); expect(screen.getByText("Alphas", { exact: false })).toBeInTheDocument(); }); + + it("renders a 'mdm_enrolled' type for apple if mdm_platform is not provided", () => { + const activity = createMockActivity({ + type: ActivityType.MdmEnrolled, + details: { + host_serial: "ABCD", + }, + }); + render(); + + expect( + screen.getByText((content, node) => { + return ( + node?.innerHTML === + "Test User An end user turned on MDM features for a host with serial number ABCD (manual)." + ); + }) + ).toBeInTheDocument(); + }); + + it("renders a 'mdm_enrolled' type for apple with all details provided", () => { + const activity = createMockActivity({ + type: ActivityType.MdmEnrolled, + details: { + host_serial: "ABCD", + installed_from_dep: true, + mdm_platform: "apple", + }, + }); + render(); + + expect( + screen.getByText((content, node) => { + return ( + node?.innerHTML === + "Test User An end user turned on MDM features for a host with serial number ABCD (automatic)." + ); + }) + ).toBeInTheDocument(); + }); + + it("renders a 'mdm_enrolled' type activity for windows hosts.", () => { + const activity = createMockActivity({ + type: ActivityType.MdmEnrolled, + details: { + mdm_platform: "microsoft", + host_display_name: "ABCD", + }, + }); + render(); + + expect( + screen.getByText((content, node) => { + console.log(node?.innerHTML); + return ( + node?.innerHTML === + "Test User Mobile device management (MDM) was turned on for ABCD (manual)." + ); + }) + ).toBeInTheDocument(); + }); }); diff --git a/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx b/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx index b508ca5030..2d0646d290 100644 --- a/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx +++ b/frontend/pages/DashboardPage/cards/ActivityFeed/ActivityItem/ActivityItem.tsx @@ -215,6 +215,17 @@ const TAGGED_TEMPLATES = { ); }, mdmEnrolled: (activity: IActivity) => { + if (activity.details?.mdm_platform === "microsoft") { + return ( + <> + Mobile device management (MDM) was turned on for{" "} + {activity.details?.host_display_name} (manual). + + ); + } + + // note: if mdm_platform is missing, we assume this is Apple MDM for backwards + // compatibility return ( <> An end user turned on MDM features for a host with serial number{" "} diff --git a/server/fleet/activities.go b/server/fleet/activities.go index 4c6adfbcd4..86436a062d 100644 --- a/server/fleet/activities.go +++ b/server/fleet/activities.go @@ -692,6 +692,7 @@ type ActivityTypeMDMEnrolled struct { HostSerial string `json:"host_serial"` HostDisplayName string `json:"host_display_name"` InstalledFromDEP bool `json:"installed_from_dep"` + MDMPlatform string `json:"mdm_platform"` } func (a ActivityTypeMDMEnrolled) ActivityName() string { @@ -703,10 +704,12 @@ func (a ActivityTypeMDMEnrolled) Documentation() (activity string, details strin `This activity contains the following fields: - "host_serial": Serial number of the host. - "host_display_name": Display name of the host. -- "installed_from_dep": Whether the host was enrolled via DEP.`, `{ +- "installed_from_dep": Whether the host was enrolled via DEP. +- "mdm_platform": Used to distinguish between Apple and Microsoft enrollments. Can be "apple", "microsoft" or not present. If missing, this value is treated as "apple" for backwards compatibility.`, `{ "host_serial": "C08VQ2AXHT96", "host_display_name": "MacBookPro16,1 (C08VQ2AXHT96)", - "installed_from_dep": true + "installed_from_dep": true, + "mdm_platform": "apple" }` } diff --git a/server/fleet/mdm.go b/server/fleet/mdm.go index 928249ec73..cf5c2b0a29 100644 --- a/server/fleet/mdm.go +++ b/server/fleet/mdm.go @@ -7,6 +7,11 @@ import ( "time" ) +const ( + MDMPlatformApple = "apple" + MDMPlatformMicrosoft = "microsoft" +) + type AppleMDM struct { CommonName string `json:"common_name"` SerialNumber string `json:"serial_number"` diff --git a/server/service/apple_mdm.go b/server/service/apple_mdm.go index d3c5252dfb..ac3b584e4a 100644 --- a/server/service/apple_mdm.go +++ b/server/service/apple_mdm.go @@ -2211,6 +2211,7 @@ func (svc *MDMAppleCheckinAndCommandService) Authenticate(r *mdm.Request, m *mdm HostSerial: info.HardwareSerial, HostDisplayName: info.DisplayName, InstalledFromDEP: info.InstalledFromDEP, + MDMPlatform: fleet.MDMPlatformApple, }) } diff --git a/server/service/apple_mdm_test.go b/server/service/apple_mdm_test.go index a2a7eb3e03..42a3377dc6 100644 --- a/server/service/apple_mdm_test.go +++ b/server/service/apple_mdm_test.go @@ -972,6 +972,7 @@ func TestMDMAuthenticate(t *testing.T) { require.Equal(t, serial, a.HostSerial) require.Equal(t, a.HostDisplayName, fmt.Sprintf("%s (%s)", model, serial)) require.False(t, a.InstalledFromDEP) + require.Equal(t, fleet.MDMPlatformApple, a.MDMPlatform) return nil } diff --git a/server/service/integration_mdm_test.go b/server/service/integration_mdm_test.go index 3651293025..ad13153d90 100644 --- a/server/service/integration_mdm_test.go +++ b/server/service/integration_mdm_test.go @@ -925,7 +925,7 @@ func (s *integrationMDMTestSuite) TestDEPProfileAssignment() { require.JSONEq( t, fmt.Sprintf( - `{"host_serial": "%s", "host_display_name": "%s (%s)", "installed_from_dep": true}`, + `{"host_serial": "%s", "host_display_name": "%s (%s)", "installed_from_dep": true, "mdm_platform": "apple"}`, devices[0].SerialNumber, devices[0].Model, devices[0].SerialNumber, ), string(*activity.Details), @@ -1068,8 +1068,8 @@ func (s *integrationMDMTestSuite) TestAppleMDMDeviceEnrollment() { } } require.Len(t, details, 2) - require.JSONEq(t, fmt.Sprintf(`{"host_serial": "%s", "host_display_name": "%s (%s)", "installed_from_dep": false}`, mdmDeviceA.SerialNumber, mdmDeviceA.Model, mdmDeviceA.SerialNumber), string(*details[len(details)-2])) - require.JSONEq(t, fmt.Sprintf(`{"host_serial": "%s", "host_display_name": "%s (%s)", "installed_from_dep": false}`, mdmDeviceB.SerialNumber, mdmDeviceB.Model, mdmDeviceB.SerialNumber), string(*details[len(details)-1])) + require.JSONEq(t, fmt.Sprintf(`{"host_serial": "%s", "host_display_name": "%s (%s)", "installed_from_dep": false, "mdm_platform": "apple"}`, mdmDeviceA.SerialNumber, mdmDeviceA.Model, mdmDeviceA.SerialNumber), string(*details[len(details)-2])) + require.JSONEq(t, fmt.Sprintf(`{"host_serial": "%s", "host_display_name": "%s (%s)", "installed_from_dep": false, "mdm_platform": "apple"}`, mdmDeviceB.SerialNumber, mdmDeviceB.Model, mdmDeviceB.SerialNumber), string(*details[len(details)-1])) // set an enroll secret var applyResp applyEnrollSecretSpecResponse @@ -5414,6 +5414,17 @@ func (s *integrationMDMTestSuite) TestValidRequestSecurityTokenRequest() { require.True(t, s.isXMLTagContentPresent("TokenType", resSoapMsg)) require.True(t, s.isXMLTagContentPresent("RequestID", resSoapMsg)) require.True(t, s.isXMLTagContentPresent("BinarySecurityToken", resSoapMsg)) + + // Checking if an activity was created for the enrollment + s.lastActivityOfTypeMatches( + fleet.ActivityTypeMDMEnrolled{}.ActivityName(), + `{ + "mdm_platform": "microsoft", + "host_serial": "", + "installed_from_dep": false, + "host_display_name": "DESKTOP-0C89RC0" + }`, + 0) } func (s *integrationMDMTestSuite) TestInvalidRequestSecurityTokenRequestWithMissingAdditionalContext() { diff --git a/server/service/microsoft_mdm.go b/server/service/microsoft_mdm.go index 5010484939..c784b8a65c 100644 --- a/server/service/microsoft_mdm.go +++ b/server/service/microsoft_mdm.go @@ -819,10 +819,15 @@ func (svc *Service) GetMDMWindowsEnrollResponse(ctx context.Context, secTokenMsg return nil, ctxerr.Wrap(ctx, err, "creation of RequestSecurityTokenResponseCollection message") } - // RequestSecurityTokenResponseCollection message is ready - // The identity and provisioning information will be sent to the Windows MDM Enrollment Client + // RequestSecurityTokenResponseCollection message is ready. The identity + // and provisioning information will be sent to the Windows MDM + // Enrollment Client - // But before doing that, let's save the device information to the list of MDM enrolled MDM devices + // But before doing that, let's save the device information to the list + // of MDM enrolled MDM devices + // + // This method also creates the relevant enrollment activity as it has + // access to the device information. err = svc.storeWindowsMDMEnrolledDevice(ctx, secTokenMsg) if err != nil { return nil, ctxerr.Wrap(ctx, err, "enrolled device information cannot be stored") @@ -1004,6 +1009,19 @@ func (svc *Service) storeWindowsMDMEnrolledDevice(ctx context.Context, secTokenM return err } + err = svc.ds.NewActivity(ctx, nil, &fleet.ActivityTypeMDMEnrolled{ + HostDisplayName: reqDeviceName, + MDMPlatform: fleet.MDMPlatformMicrosoft, + }) + if err != nil { + // only logging, the device is enrolled at this point, and we + // wouldn't want to fail the request because there was a problem + // creating an activity feed item. + logging.WithExtras(logging.WithNoUser(ctx), + "msg", "failed to generate windows MDM enrolled activity", + ) + } + return nil }