mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 01:18:42 +00:00
Add "edited macos profiles" activity when applying custom settings via fleetctl (#9862)
#9587 and #9639
This commit is contained in:
parent
2e6786f8c4
commit
4a1f3988f0
9 changed files with 196 additions and 19 deletions
1
changes/issue-9587-record-activity
Normal file
1
changes/issue-9587-record-activity
Normal file
|
|
@ -0,0 +1 @@
|
|||
* Added "edited macos profiles" activity when updating a team's (or no team's) custom macOS settings via `fleetctl apply`.
|
||||
|
|
@ -600,6 +600,65 @@ This activity contains the following fields:
|
|||
}
|
||||
```
|
||||
|
||||
### Type `created_macos_profile`
|
||||
|
||||
Generated when a user adds a new macOS profile to a team (or no team).
|
||||
|
||||
This activity contains the following fields:
|
||||
- "profile_name": Name of the profile.
|
||||
- "profile_identifier": Identifier of the profile.
|
||||
- "team_id": The ID of the team that the profile applies to, null if it applies to devices that are not in a team.
|
||||
- "team_name": The name of the team that the profile applies to, null if it applies to devices that are not in a team.
|
||||
|
||||
#### Example
|
||||
|
||||
```json
|
||||
{
|
||||
"profile_name": "Custom settings 1",
|
||||
"profile_identifier": "com.my.profile",
|
||||
"team_id": 123,
|
||||
"team_name": "Workstations"
|
||||
}
|
||||
```
|
||||
|
||||
### Type `deleted_macos_profile`
|
||||
|
||||
Generated when a user deletes a macOS profile from a team (or no team).
|
||||
|
||||
This activity contains the following fields:
|
||||
- "profile_name": Name of the deleted profile.
|
||||
- "profile_identifier": Identifier of deleted the profile.
|
||||
- "team_id": The ID of the team that the profile applied to, null if it applied to devices that are not in a team.
|
||||
- "team_name": The name of the team that the profile applied to, null if it applied to devices that are not in a team.
|
||||
|
||||
#### Example
|
||||
|
||||
```json
|
||||
{
|
||||
"profile_name": "Custom settings 1",
|
||||
"profile_identifier": "com.my.profile",
|
||||
"team_id": 123,
|
||||
"team_name": "Workstations"
|
||||
}
|
||||
```
|
||||
|
||||
### Type `edited_macos_profile`
|
||||
|
||||
Generated when a user edits the macOS profiles of a team (or no team) via the fleetctl CLI.
|
||||
|
||||
This activity contains the following fields:
|
||||
- "team_id": The ID of the team that the profiles apply to, null if they apply to devices that are not in a team.
|
||||
- "team_name": The name of the team that the profiles apply to, null if they apply to devices that are not in a team.
|
||||
|
||||
#### Example
|
||||
|
||||
```json
|
||||
{
|
||||
"team_id": 123,
|
||||
"team_name": "Workstations"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
<meta name="pageOrderInSection" value="1400">
|
||||
|
|
@ -50,8 +50,8 @@ func NewService(
|
|||
// Override methods that can't be easily overriden via
|
||||
// embedding.
|
||||
svc.SetEnterpriseOverrides(fleet.EnterpriseOverrides{
|
||||
HostFeatures: eeservice.HostFeatures,
|
||||
TeamByName: eeservice.teamByName,
|
||||
HostFeatures: eeservice.HostFeatures,
|
||||
TeamByIDOrName: eeservice.teamByIDOrName,
|
||||
})
|
||||
|
||||
return eeservice, nil
|
||||
|
|
|
|||
|
|
@ -415,21 +415,33 @@ func (svc *Service) ModifyTeamEnrollSecrets(ctx context.Context, teamID uint, se
|
|||
return newSecrets, nil
|
||||
}
|
||||
|
||||
func (svc *Service) teamByName(ctx context.Context, name string) (*fleet.Team, error) {
|
||||
func (svc *Service) teamByIDOrName(ctx context.Context, id *uint, name *string) (*fleet.Team, error) {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Team{}, fleet.ActionRead); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tm, err := svc.ds.TeamByName(ctx, name)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
// this should really be handled in TeamByName so that it returns a
|
||||
// notFound error as is usually the case for this scenario, but
|
||||
// changing it causes a number of test failures that indicates this
|
||||
// might be tricky and even maybe a breaking change in some places. For
|
||||
// now, handling it here.
|
||||
return nil, notFoundError{}
|
||||
|
||||
var (
|
||||
tm *fleet.Team
|
||||
err error
|
||||
)
|
||||
if id != nil {
|
||||
tm, err = svc.ds.Team(ctx, *id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if name != nil {
|
||||
tm, err = svc.ds.TeamByName(ctx, *name)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
// this should really be handled in TeamByName so that it returns a
|
||||
// notFound error as is usually the case for this scenario, but
|
||||
// changing it causes a number of test failures that indicates this
|
||||
// might be tricky and even maybe a breaking change in some places. For
|
||||
// now, handling it here.
|
||||
return nil, notFoundError{}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return tm, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,10 @@ var ActivityDetailsList = []ActivityDetails{
|
|||
ActivityTypeEditedMacOSMinVersion{},
|
||||
|
||||
ActivityTypeReadHostDiskEncryptionKey{},
|
||||
|
||||
ActivityTypeCreatedMacosProfile{},
|
||||
ActivityTypeDeletedMacosProfile{},
|
||||
ActivityTypeEditedMacosProfile{},
|
||||
}
|
||||
|
||||
type ActivityDetails interface {
|
||||
|
|
@ -729,3 +733,72 @@ func (a ActivityTypeReadHostDiskEncryptionKey) Documentation() (activity string,
|
|||
"host_display_name": "Anna's MacBook Pro",
|
||||
}`
|
||||
}
|
||||
|
||||
type ActivityTypeCreatedMacosProfile struct {
|
||||
ProfileName string `json:"profile_name"`
|
||||
ProfileIdentifier string `json:"profile_identifier"`
|
||||
TeamID *uint `json:"team_id"`
|
||||
TeamName *string `json:"team_name"`
|
||||
}
|
||||
|
||||
func (a ActivityTypeCreatedMacosProfile) ActivityName() string {
|
||||
return "created_macos_profile"
|
||||
}
|
||||
|
||||
func (a ActivityTypeCreatedMacosProfile) Documentation() (activity, details, detailsExample string) {
|
||||
return `Generated when a user adds a new macOS profile to a team (or no team).`,
|
||||
`This activity contains the following fields:
|
||||
- "profile_name": Name of the profile.
|
||||
- "profile_identifier": Identifier of the profile.
|
||||
- "team_id": The ID of the team that the profile applies to, null if it applies to devices that are not in a team.
|
||||
- "team_name": The name of the team that the profile applies to, null if it applies to devices that are not in a team.`, `{
|
||||
"profile_name": "Custom settings 1",
|
||||
"profile_identifier": "com.my.profile",
|
||||
"team_id": 123,
|
||||
"team_name": "Workstations"
|
||||
}`
|
||||
}
|
||||
|
||||
type ActivityTypeDeletedMacosProfile struct {
|
||||
ProfileName string `json:"profile_name"`
|
||||
ProfileIdentifier string `json:"profile_identifier"`
|
||||
TeamID *uint `json:"team_id"`
|
||||
TeamName *string `json:"team_name"`
|
||||
}
|
||||
|
||||
func (a ActivityTypeDeletedMacosProfile) ActivityName() string {
|
||||
return "deleted_macos_profile"
|
||||
}
|
||||
|
||||
func (a ActivityTypeDeletedMacosProfile) Documentation() (activity, details, detailsExample string) {
|
||||
return `Generated when a user deletes a macOS profile from a team (or no team).`,
|
||||
`This activity contains the following fields:
|
||||
- "profile_name": Name of the deleted profile.
|
||||
- "profile_identifier": Identifier of deleted the profile.
|
||||
- "team_id": The ID of the team that the profile applied to, null if it applied to devices that are not in a team.
|
||||
- "team_name": The name of the team that the profile applied to, null if it applied to devices that are not in a team.`, `{
|
||||
"profile_name": "Custom settings 1",
|
||||
"profile_identifier": "com.my.profile",
|
||||
"team_id": 123,
|
||||
"team_name": "Workstations"
|
||||
}`
|
||||
}
|
||||
|
||||
type ActivityTypeEditedMacosProfile struct {
|
||||
TeamID *uint `json:"team_id"`
|
||||
TeamName *string `json:"team_name"`
|
||||
}
|
||||
|
||||
func (a ActivityTypeEditedMacosProfile) ActivityName() string {
|
||||
return "edited_macos_profile"
|
||||
}
|
||||
|
||||
func (a ActivityTypeEditedMacosProfile) Documentation() (activity, details, detailsExample string) {
|
||||
return `Generated when a user edits the macOS profiles of a team (or no team) via the fleetctl CLI.`,
|
||||
`This activity contains the following fields:
|
||||
- "team_id": The ID of the team that the profiles apply to, null if they apply to devices that are not in a team.
|
||||
- "team_name": The name of the team that the profiles apply to, null if they apply to devices that are not in a team.`, `{
|
||||
"team_id": 123,
|
||||
"team_name": "Workstations"
|
||||
}`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ import (
|
|||
//
|
||||
// TODO: find if there's a better way to accomplish this and standardize.
|
||||
type EnterpriseOverrides struct {
|
||||
HostFeatures func(context context.Context, host *Host) (*Features, error)
|
||||
TeamByName func(ctx context.Context, name string) (*Team, error)
|
||||
HostFeatures func(context context.Context, host *Host) (*Features, error)
|
||||
TeamByIDOrName func(ctx context.Context, id *uint, name *string) (*Team, error)
|
||||
}
|
||||
|
||||
type OsqueryService interface {
|
||||
|
|
|
|||
|
|
@ -1157,12 +1157,18 @@ func (svc *Service) BatchSetMDMAppleProfiles(ctx context.Context, tmID *uint, tm
|
|||
}
|
||||
|
||||
// if the team name is provided, load the corresponding team to get its id.
|
||||
if tmName != nil {
|
||||
tm, err := svc.EnterpriseOverrides.TeamByName(ctx, *tmName)
|
||||
// vice-versa, if the id is provided, load it to get the name (required for
|
||||
// the activity).
|
||||
if tmName != nil || tmID != nil {
|
||||
tm, err := svc.EnterpriseOverrides.TeamByIDOrName(ctx, tmID, tmName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tmID = &tm.ID
|
||||
if tmID == nil {
|
||||
tmID = &tm.ID
|
||||
} else {
|
||||
tmName = &tm.Name
|
||||
}
|
||||
}
|
||||
|
||||
if err := svc.authz.Authorize(ctx, &fleet.MDMAppleConfigProfile{TeamID: tmID}, fleet.ActionWrite); err != nil {
|
||||
|
|
@ -1201,7 +1207,17 @@ func (svc *Service) BatchSetMDMAppleProfiles(ctx context.Context, tmID *uint, tm
|
|||
if dryRun {
|
||||
return nil
|
||||
}
|
||||
return svc.ds.BatchSetMDMAppleProfiles(ctx, tmID, profs)
|
||||
if err := svc.ds.BatchSetMDMAppleProfiles(ctx, tmID, profs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := svc.ds.NewActivity(ctx, authz.UserFromContext(ctx), &fleet.ActivityTypeEditedMacosProfile{
|
||||
TeamID: tmID,
|
||||
TeamName: tmName,
|
||||
}); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "logging activity for edited macos profile")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
|||
|
|
@ -502,9 +502,15 @@ func TestMDMBatchSetAppleProfiles(t *testing.T) {
|
|||
ds.TeamByNameFunc = func(ctx context.Context, name string) (*fleet.Team, error) {
|
||||
return &fleet.Team{ID: 1, Name: name}, nil
|
||||
}
|
||||
ds.TeamFunc = func(ctx context.Context, id uint) (*fleet.Team, error) {
|
||||
return &fleet.Team{ID: id, Name: "team"}, nil
|
||||
}
|
||||
ds.BatchSetMDMAppleProfilesFunc = func(ctx context.Context, teamID *uint, profiles []*fleet.MDMAppleConfigProfile) error {
|
||||
return nil
|
||||
}
|
||||
ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
|
|
|
|||
|
|
@ -859,6 +859,11 @@ func (s *integrationMDMTestSuite) TestBatchSetMDMAppleProfiles() {
|
|||
|
||||
// apply an empty set to no-team
|
||||
s.Do("POST", "/api/v1/fleet/mdm/apple/profiles/batch", batchSetMDMAppleProfilesRequest{Profiles: nil}, http.StatusNoContent)
|
||||
s.lastActivityMatches(
|
||||
fleet.ActivityTypeEditedMacosProfile{}.ActivityName(),
|
||||
`{"team_id": null, "team_name": null}`,
|
||||
0,
|
||||
)
|
||||
|
||||
// apply to both team id and name
|
||||
s.Do("POST", "/api/v1/fleet/mdm/apple/profiles/batch", batchSetMDMAppleProfilesRequest{Profiles: nil},
|
||||
|
|
@ -878,6 +883,11 @@ func (s *integrationMDMTestSuite) TestBatchSetMDMAppleProfiles() {
|
|||
s.Do("POST", "/api/v1/fleet/mdm/apple/profiles/batch", batchSetMDMAppleProfilesRequest{Profiles: [][]byte{
|
||||
mobileconfigForTest("N1", "I1"),
|
||||
}}, http.StatusNoContent, "team_id", strconv.Itoa(int(tm.ID)))
|
||||
s.lastActivityMatches(
|
||||
fleet.ActivityTypeEditedMacosProfile{}.ActivityName(),
|
||||
fmt.Sprintf(`{"team_id": %d, "team_name": %q}`, tm.ID, tm.Name),
|
||||
0,
|
||||
)
|
||||
}
|
||||
|
||||
type device struct {
|
||||
|
|
|
|||
Loading…
Reference in a new issue