diff --git a/changes/19870-vpp-activities-backend b/changes/19870-vpp-activities-backend
new file mode 100644
index 0000000000..115f92e1fd
--- /dev/null
+++ b/changes/19870-vpp-activities-backend
@@ -0,0 +1 @@
+- Adds global activity support for VPP related activities.
\ No newline at end of file
diff --git a/docs/Using Fleet/Audit-logs.md b/docs/Using Fleet/Audit-logs.md
index ab16e6970d..cbffea6455 100644
--- a/docs/Using Fleet/Audit-logs.md
+++ b/docs/Using Fleet/Audit-logs.md
@@ -1158,7 +1158,7 @@ Generated when a software installer is deleted from Fleet.
This activity contains the following fields:
- "software_title": Name of the software.
- "software_package": Filename of the installer.
-- "team_name": Name of the team to which this software was added. `null if it was added to no team.
+- "team_name": Name of the team to which this software was added. `null` if it was added to no team.
- "team_id": The ID of the team to which this software was added. `null` if it was added to no team.
- "self_service": Whether the software was available for installation by the end user.
@@ -1174,6 +1174,83 @@ This activity contains the following fields:
}
```
+## vpp_enabled
+
+Generated when the VPP feature is enabled in Fleet.
+
+
+
+## vpp_disabled
+
+Generated when the VPP feature is disabled in Fleet.
+
+
+
+## added_app_store_app
+
+Generated when an App Store app is added to Fleet.
+
+This activity contains the following fields:
+- "software_title": Name of the App Store app.
+- "app_store_id": ID of the app on the Apple App Store.
+- "team_name": Name of the team to which this App Store app was added, or `null` if it was added to no team.
+- "team_id": ID of the team to which this App Store app was added, or `null`if it was added to no team.
+
+#### Example
+
+```json
+{
+ "software_title": "Logic Pro",
+ "app_store_id": "1234567",
+ "team_name": "Workstations",
+ "team_id": 1
+}
+```
+
+## deleted_app_store_app
+
+Generated when an App Store app is deleted from Fleet.
+
+This activity contains the following fields:
+- "software_title": Name of the App Store app.
+- "app_store_id": ID of the app on the Apple App Store.
+- "team_name": Name of the team from which this App Store app was deleted, or `null` if it was deleted from no team.
+- "team_id": ID of the team from which this App Store app was deleted, or `null`if it was deleted from no team.
+
+#### Example
+
+```json
+{
+ "software_title": "Logic Pro",
+ "app_store_id": "1234567",
+ "team_name": "Workstations",
+ "team_id": 1
+}
+```
+
+## installed_app_store_app
+
+Generated when an App Store app is installed on a device.
+
+This activity contains the following fields:
+- host_id: ID of the host on which the app was installed.
+- host_display_name: Display name of the host.
+- software_title: Name of the App Store app.
+- app_store_id: ID of the app on the Apple App Store.
+- command_uuid: UUID of the MDM command used to install the app.
+
+#### Example
+
+```json
+{
+ "host_id": 42,
+ "host_display_name": "Anna's MacBook Pro",
+ "software_title": "Logic Pro",
+ "app_store_id": "1234567",
+ "command_uuid": "98765432-1234-1234-1234-1234567890ab"
+}
+```
+
diff --git a/ee/server/service/vpp.go b/ee/server/service/vpp.go
index ef2bb4a130..eb3ba1ad1b 100644
--- a/ee/server/service/vpp.go
+++ b/ee/server/service/vpp.go
@@ -7,6 +7,7 @@ import (
"fmt"
"net/http"
+ "github.com/fleetdm/fleet/v4/server/authz"
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/mdm/apple/itunes"
@@ -159,5 +160,15 @@ func (svc *Service) AddAppStoreApp(ctx context.Context, teamID *uint, adamID str
return ctxerr.Wrap(ctx, err, "writing VPP app to db")
}
+ act := fleet.ActivityAddedAppStoreApp{
+ AppStoreID: app.AdamID,
+ TeamName: &teamName,
+ SoftwareTitle: app.Name,
+ TeamID: teamID,
+ }
+ if err := svc.NewActivity(ctx, authz.UserFromContext(ctx), act); err != nil {
+ return ctxerr.Wrap(ctx, err, "create activity for add app store app")
+ }
+
return nil
}
diff --git a/server/fleet/activities.go b/server/fleet/activities.go
index 56d312faac..849a78953b 100644
--- a/server/fleet/activities.go
+++ b/server/fleet/activities.go
@@ -99,6 +99,11 @@ var ActivityDetailsList = []ActivityDetails{
ActivityTypeInstalledSoftware{},
ActivityTypeAddedSoftware{},
ActivityTypeDeletedSoftware{},
+ ActivityEnabledVPP{},
+ ActivityDisabledVPP{},
+ ActivityAddedAppStoreApp{},
+ ActivityDeletedAppStoreApp{},
+ ActivityInstalledAppStoreApp{},
}
type ActivityDetails interface {
@@ -1507,7 +1512,7 @@ func (a ActivityTypeDeletedSoftware) Documentation() (string, string, string) {
return `Generated when a software installer is deleted from Fleet.`, `This activity contains the following fields:
- "software_title": Name of the software.
- "software_package": Filename of the installer.
-- "team_name": Name of the team to which this software was added.` + " `null " + `if it was added to no team.
+- "team_name": Name of the team to which this software was added.` + " `null` " + `if it was added to no team.
- "team_id": The ID of the team to which this software was added.` + " `null` " + `if it was added to no team.
- "self_service": Whether the software was available for installation by the end user.`, `{
"software_title": "Falcon.app",
@@ -1598,3 +1603,97 @@ func LogRoleChangeActivities(
}
return nil
}
+
+type ActivityEnabledVPP struct{}
+
+func (a ActivityEnabledVPP) ActivityName() string {
+ return "vpp_enabled"
+}
+
+func (a ActivityEnabledVPP) Documentation() (activity string, details string, detailsExample string) {
+ return "Generated when the VPP feature is enabled in Fleet.", "", ""
+}
+
+type ActivityDisabledVPP struct{}
+
+func (a ActivityDisabledVPP) ActivityName() string {
+ return "vpp_disabled"
+}
+
+func (a ActivityDisabledVPP) Documentation() (activity string, details string, detailsExample string) {
+ return "Generated when the VPP feature is disabled in Fleet.", "", ""
+}
+
+type ActivityAddedAppStoreApp struct {
+ SoftwareTitle string `json:"software_title"`
+ AppStoreID string `json:"app_store_id"`
+ TeamName *string `json:"team_name"`
+ TeamID *uint `json:"team_id"`
+}
+
+func (a ActivityAddedAppStoreApp) ActivityName() string {
+ return "added_app_store_app"
+}
+
+func (a ActivityAddedAppStoreApp) Documentation() (activity string, details string, detailsExample string) {
+ return "Generated when an App Store app is added to Fleet.", `This activity contains the following fields:
+- "software_title": Name of the App Store app.
+- "app_store_id": ID of the app on the Apple App Store.
+- "team_name": Name of the team to which this App Store app was added, or ` + "`null`" + ` if it was added to no team.
+- "team_id": ID of the team to which this App Store app was added, or ` + "`null`" + `if it was added to no team.`, `{
+ "software_title": "Logic Pro",
+ "app_store_id": "1234567",
+ "team_name": "Workstations",
+ "team_id": 1
+}`
+}
+
+type ActivityDeletedAppStoreApp struct {
+ SoftwareTitle string `json:"software_title"`
+ AppStoreID string `json:"app_store_id"`
+ TeamName string `json:"team_name"`
+}
+
+func (a ActivityDeletedAppStoreApp) ActivityName() string {
+ return "deleted_app_store_app"
+}
+
+func (a ActivityDeletedAppStoreApp) Documentation() (activity string, details string, detailsExample string) {
+ return "Generated when an App Store app is deleted from Fleet.", `This activity contains the following fields:
+- "software_title": Name of the App Store app.
+- "app_store_id": ID of the app on the Apple App Store.
+- "team_name": Name of the team from which this App Store app was deleted, or ` + "`null`" + ` if it was deleted from no team.
+- "team_id": ID of the team from which this App Store app was deleted, or ` + "`null`" + `if it was deleted from no team.`, `{
+ "software_title": "Logic Pro",
+ "app_store_id": "1234567",
+ "team_name": "Workstations",
+ "team_id": 1
+}`
+}
+
+type ActivityInstalledAppStoreApp struct {
+ HostID int `json:"host_id"`
+ HostDisplayName string `json:"host_display_name"`
+ SoftwareTitle string `json:"software_title"`
+ AppStoreID int `json:"app_store_id"`
+ CommandUUID string `json:"command_uuid"`
+}
+
+func (a ActivityInstalledAppStoreApp) ActivityName() string {
+ return "installed_app_store_app"
+}
+
+func (a ActivityInstalledAppStoreApp) Documentation() (string, string, string) {
+ return "Generated when an App Store app is installed on a device.", `This activity contains the following fields:
+- host_id: ID of the host on which the app was installed.
+- host_display_name: Display name of the host.
+- software_title: Name of the App Store app.
+- app_store_id: ID of the app on the Apple App Store.
+- command_uuid: UUID of the MDM command used to install the app.`, `{
+ "host_id": 42,
+ "host_display_name": "Anna's MacBook Pro",
+ "software_title": "Logic Pro",
+ "app_store_id": "1234567",
+ "command_uuid": "98765432-1234-1234-1234-1234567890ab"
+}`
+}
diff --git a/server/service/integration_enterprise_test.go b/server/service/integration_enterprise_test.go
index 60def3cf75..a25be64736 100644
--- a/server/service/integration_enterprise_test.go
+++ b/server/service/integration_enterprise_test.go
@@ -10702,6 +10702,8 @@ func (s *integrationMDMTestSuite) TestVPPApps() {
t.Setenv("FLEET_DEV_VPP_URL", s.appleVPPConfigSrv.URL)
s.uploadDataViaForm("/api/latest/fleet/mdm/apple/vpp_token", "token", "token.vpptoken", []byte(base64.StdEncoding.EncodeToString([]byte(tokenJSON))), http.StatusAccepted, "")
+ s.lastActivityMatches(fleet.ActivityEnabledVPP{}.ActivityName(), "", 0)
+
// Get the token
var resp getMDMAppleVPPTokenResponse
s.DoJSON("GET", "/api/latest/fleet/vpp", &getMDMAppleVPPTokenRequest{}, http.StatusOK, &resp)
@@ -10755,6 +10757,7 @@ func (s *integrationMDMTestSuite) TestVPPApps() {
// Add an app store app to team 1
var addAppResp addAppStoreAppResponse
s.DoJSON("POST", "/api/latest/fleet/software/app_store_apps", &addAppStoreAppRequest{TeamID: &team.ID, AppStoreID: appResp.AppStoreApps[0].AdamID}, http.StatusOK, &addAppResp)
+ s.lastActivityMatches(fleet.ActivityAddedAppStoreApp{}.ActivityName(), fmt.Sprintf(`{"team_name": "%s", "software_title": "%s", "app_store_id": "%s", "team_id": %d}`, team.Name, appResp.AppStoreApps[0].Name, appResp.AppStoreApps[0].AdamID, team.ID), 0)
// Add an app store app to non-existent team
s.DoJSON("POST", "/api/latest/fleet/software/app_store_apps", &addAppStoreAppRequest{TeamID: ptr.Uint(9999), AppStoreID: appResp.AppStoreApps[0].AdamID}, http.StatusNotFound, &addAppResp)
@@ -10777,6 +10780,7 @@ func (s *integrationMDMTestSuite) TestVPPApps() {
// Delete VPP token and check that it's not appearing anymore
s.Do("DELETE", "/api/latest/fleet/mdm/apple/vpp_token", &deleteMDMAppleVPPTokenRequest{}, http.StatusNoContent)
s.DoJSON("GET", "/api/latest/fleet/vpp", &getMDMAppleVPPTokenRequest{}, http.StatusNotFound, &resp)
+ s.lastActivityMatches(fleet.ActivityDisabledVPP{}.ActivityName(), "", 0)
}
// 1. software title uploaded doesn't match existing title
diff --git a/server/service/mdm.go b/server/service/mdm.go
index f9d21b8fa9..545df4e6d5 100644
--- a/server/service/mdm.go
+++ b/server/service/mdm.go
@@ -2646,6 +2646,11 @@ func (svc *Service) UploadMDMAppleVPPToken(ctx context.Context, token io.ReadSee
return ctxerr.Wrap(ctx, err, "writing VPP token to db")
}
+ act := fleet.ActivityEnabledVPP{}
+ if err := svc.NewActivity(ctx, authz.UserFromContext(ctx), act); err != nil {
+ return ctxerr.Wrap(ctx, err, "create activity for upload VPP token")
+ }
+
return nil
}
@@ -2729,5 +2734,14 @@ func (svc *Service) DeleteMDMAppleVPPToken(ctx context.Context) error {
return err
}
- return svc.ds.DeleteMDMConfigAssetsByName(ctx, []fleet.MDMAssetName{fleet.MDMAssetVPPToken})
+ if err := svc.ds.DeleteMDMConfigAssetsByName(ctx, []fleet.MDMAssetName{fleet.MDMAssetVPPToken}); err != nil {
+ return ctxerr.Wrap(ctx, err, "delete VPP token")
+ }
+
+ act := fleet.ActivityDisabledVPP{}
+ if err := svc.NewActivity(ctx, authz.UserFromContext(ctx), act); err != nil {
+ return ctxerr.Wrap(ctx, err, "create activity for delete VPP token")
+ }
+
+ return nil
}
diff --git a/server/service/mdm_test.go b/server/service/mdm_test.go
index 41941020e9..92e3b3db31 100644
--- a/server/service/mdm_test.go
+++ b/server/service/mdm_test.go
@@ -119,6 +119,10 @@ func TestMDMAppleAuthorization(t *testing.T) {
return nil
}
+ ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails, details []byte, createdAt time.Time) error {
+ return nil
+ }
+
ds.DeleteMDMConfigAssetsByNameFunc = func(ctx context.Context, assetNames []fleet.MDMAssetName) error { return nil }
// use a custom implementation of checkAuthErr as the service call will fail