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