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 {