From ecdcb7c2fb5772ff23fd0269699e5a6a61a89ce2 Mon Sep 17 00:00:00 2001 From: Sarah Gillespie <73313222+gillespi314@users.noreply.github.com> Date: Mon, 15 Apr 2024 14:18:09 -0500 Subject: [PATCH] Add activity item for resend configuration profile (#18271) --- docs/Using Fleet/Audit-logs.md | 19 +++++++++++++++++++ server/fleet/activities.go | 24 ++++++++++++++++++++++++ server/service/integration_mdm_test.go | 18 +++++++++++++++--- server/service/mdm.go | 12 +++++++++++- server/service/mdm_test.go | 3 +++ 5 files changed, 72 insertions(+), 4 deletions(-) diff --git a/docs/Using Fleet/Audit-logs.md b/docs/Using Fleet/Audit-logs.md index 700f2e869a..d0bda3d634 100644 --- a/docs/Using Fleet/Audit-logs.md +++ b/docs/Using Fleet/Audit-logs.md @@ -1108,6 +1108,25 @@ This activity contains the following fields: } ``` +## resent_configuration_profile + +Generated when a user resends an MDM configuration profile to a host. + +This activity contains the following fields: +- "host_id": The ID of the host. +- "host_display_name": The display name of the host. +- "profile_name": The name of the configuration profile. + +#### Example + +```json +{ + "host_id": 1, + "host_display_name": "Anna's MacBook Pro", + "profile_name": "Passcode requirements" +} +``` + diff --git a/server/fleet/activities.go b/server/fleet/activities.go index f299222c24..00a2964455 100644 --- a/server/fleet/activities.go +++ b/server/fleet/activities.go @@ -88,6 +88,8 @@ var ActivityDetailsList = []ActivityDetails{ ActivityTypeCreatedDeclarationProfile{}, ActivityTypeDeletedDeclarationProfile{}, ActivityTypeEditedDeclarationProfile{}, + + ActivityTypeResentConfigurationProfile{}, } type ActivityDetails interface { @@ -1390,6 +1392,28 @@ func (a ActivityTypeEditedDeclarationProfile) Documentation() (activity string, }` } +type ActivityTypeResentConfigurationProfile struct { + HostID *uint `json:"host_id"` + HostDisplayName *string `json:"host_display_name"` + ProfileName string `json:"profile_name"` +} + +func (a ActivityTypeResentConfigurationProfile) ActivityName() string { + return "resent_configuration_profile" +} + +func (a ActivityTypeResentConfigurationProfile) Documentation() (activity string, details string, detailsExample string) { + return `Generated when a user resends an MDM configuration profile to a host.`, + `This activity contains the following fields: +- "host_id": The ID of the host. +- "host_display_name": The display name of the host. +- "profile_name": The name of the configuration profile.`, `{ + "host_id": 1, + "host_display_name": "Anna's MacBook Pro", + "profile_name": "Passcode requirements" +}` +} + // LogRoleChangeActivities logs activities for each role change, globally and one for each change in teams. func LogRoleChangeActivities(ctx context.Context, ds Datastore, adminUser *User, oldGlobalRole *string, oldTeamRoles []UserTeam, user *User) error { if user.GlobalRole != nil && (oldGlobalRole == nil || *oldGlobalRole != *user.GlobalRole) { diff --git a/server/service/integration_mdm_test.go b/server/service/integration_mdm_test.go index ff68f1f013..d1a0e4ddc2 100644 --- a/server/service/integration_mdm_test.go +++ b/server/service/integration_mdm_test.go @@ -791,14 +791,18 @@ func (s *integrationMDMTestSuite) TestAppleProfileManagement() { require.Equal(t, prof, installs[0]) require.Empty(t, removes) s.checkMDMProfilesSummaries(t, &tm.ID, fleet.MDMProfilesSummary{Verifying: 1}, nil) + s.lastActivityMatches( + fleet.ActivityTypeResentConfigurationProfile{}.ActivityName(), + fmt.Sprintf(`{"host_id": %d, "host_display_name": %q, "profile_name": %q}`, host.ID, host.DisplayName(), "name-"+mcUUID), + 0) // add a declaration to the team - declIdent := "decl-ident-" + t.Name() + declIdent := "decl-ident-" + uuid.NewString() fields := map[string][]string{ "team_id": {fmt.Sprintf("%d", tm.ID)}, } body, headers := generateNewProfileMultipartRequest( - t, declIdent+".json", declarationForTest(declIdent), s.token, fields, + t, "some-declaration.json", declarationForTest(declIdent), s.token, fields, ) res = s.DoRawWithHeaders("POST", "/api/latest/fleet/configuration_profiles", body.Bytes(), http.StatusOK, headers) var resp newMDMConfigProfileResponse @@ -837,6 +841,10 @@ func (s *integrationMDMTestSuite) TestAppleProfileManagement() { _ = s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/configuration_profiles/resend/%s", host.ID, declUUID), nil, http.StatusAccepted) checkDDMSync(mdmDevice) s.checkMDMProfilesSummaries(t, &tm.ID, fleet.MDMProfilesSummary{Verifying: 1}, nil) + s.lastActivityMatches( + fleet.ActivityTypeResentConfigurationProfile{}.ActivityName(), + fmt.Sprintf(`{"host_id": %d, "host_display_name": %q, "profile_name": "some-declaration"}`, host.ID, host.DisplayName()), + 0) // transfer the host to the global team err = s.ds.AddHostsToTeam(ctx, nil, []uint{host.ID}) @@ -11007,7 +11015,11 @@ func (s *integrationMDMTestSuite) TestWindowsProfileManagement() { // can resend a profile after it has failed res = s.DoRaw("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/configuration_profiles/resend/%s", host.ID, teamProfiles[0]), nil, http.StatusAccepted) verifyProfiles(mdmDevice, 1, false) // trigger a profile sync, device gets the profile resent - checkHostProfileStatus(t, host.UUID, teamProfiles[0], fleet.MDMDeliveryVerifying) // profile was resent, so it back to verifying + checkHostProfileStatus(t, host.UUID, teamProfiles[0], fleet.MDMDeliveryVerifying) // profile was resent, so back to verifying + s.lastActivityMatches( + fleet.ActivityTypeResentConfigurationProfile{}.ActivityName(), + fmt.Sprintf(`{"host_id": %d, "host_display_name": %q, "profile_name": %q}`, host.ID, host.DisplayName(), "name-"+teamProfiles[0]), + 0) // add a macOS profile to the team mcUUID := "a" + uuid.NewString() diff --git a/server/service/mdm.go b/server/service/mdm.go index 2035bfa319..322298d02b 100644 --- a/server/service/mdm.go +++ b/server/service/mdm.go @@ -2025,6 +2025,7 @@ func (svc *Service) ResendHostMDMProfile(ctx context.Context, hostID uint, profi } var profileTeamID *uint + var profileName string switch { case strings.HasPrefix(profileUUID, fleet.MDMAppleProfileUUIDPrefix): if err := svc.VerifyMDMAppleConfigured(ctx); err != nil { @@ -2038,6 +2039,7 @@ func (svc *Service) ResendHostMDMProfile(ctx context.Context, hostID uint, profi return ctxerr.Wrap(ctx, err, "getting apple config profile") } profileTeamID = prof.TeamID + profileName = prof.Name case strings.HasPrefix(profileUUID, fleet.MDMAppleDeclarationUUIDPrefix): if err := svc.VerifyMDMAppleConfigured(ctx); err != nil { @@ -2051,6 +2053,7 @@ func (svc *Service) ResendHostMDMProfile(ctx context.Context, hostID uint, profi return ctxerr.Wrap(ctx, err, "getting apple declaration") } profileTeamID = decl.TeamID + profileName = decl.Name case strings.HasPrefix(profileUUID, fleet.MDMWindowsProfileUUIDPrefix): if err := svc.VerifyMDMWindowsConfigured(ctx); err != nil { @@ -2064,6 +2067,7 @@ func (svc *Service) ResendHostMDMProfile(ctx context.Context, hostID uint, profi return ctxerr.Wrap(ctx, err, "getting windows config profile") } profileTeamID = prof.TeamID + profileName = prof.Name default: return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("HostMDMProfile", "Invalid profile UUID prefix.").WithStatus(http.StatusNotFound), "check profile UUID prefix") @@ -2093,7 +2097,13 @@ func (svc *Service) ResendHostMDMProfile(ctx context.Context, hostID uint, profi return ctxerr.Wrap(ctx, err, "resending host mdm profile") } - // TODO: log activity? + if err := svc.ds.NewActivity(ctx, authz.UserFromContext(ctx), &fleet.ActivityTypeResentConfigurationProfile{ + HostID: &host.ID, + HostDisplayName: ptr.String(host.DisplayName()), + ProfileName: profileName, + }); err != nil { + return ctxerr.Wrap(ctx, err, "logging activity for resend config profile") + } return nil } diff --git a/server/service/mdm_test.go b/server/service/mdm_test.go index aeca6c87e3..013571ff31 100644 --- a/server/service/mdm_test.go +++ b/server/service/mdm_test.go @@ -1708,6 +1708,9 @@ func TestMDMResendConfigProfileAuthz(t *testing.T) { ds.ResendHostMDMProfileFunc = func(ctx context.Context, hostUUID, profUUID string) error { return nil } + ds.NewActivityFunc = func(context.Context, *fleet.User, fleet.ActivityDetails) error { + return nil + } checkShouldFail := func(t *testing.T, err error, shouldFail bool) { if !shouldFail {