From 7f1b330c90d807050a563138511e2f6e773228cb Mon Sep 17 00:00:00 2001 From: Nico <32375741+nulmete@users.noreply.github.com> Date: Fri, 31 Jul 2026 08:43:51 -0300 Subject: [PATCH] Restrict deleting a fleet to global admins (#50271) # Checklist for submitter - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files) for more information. ## Testing - [x] Added/updated automated tests - [x] QA'd all new/changed functionality manually ## Summary by CodeRabbit * **Bug Fixes** * Restricted fleet deletion to users with global write permissions, including global administrators and GitOps. * Corrected team deletion authorization to require global write access. * Prevented global technicians, team technicians, and observer-level users from deleting teams. * Updated authorization behavior to consistently enforce the required access level. --- changes/restrict-delete-fleet-to-global-admin | 1 + ee/server/service/teams.go | 2 +- server/service/teams_test.go | 24 ++++++++++++++++++- 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 changes/restrict-delete-fleet-to-global-admin diff --git a/changes/restrict-delete-fleet-to-global-admin b/changes/restrict-delete-fleet-to-global-admin new file mode 100644 index 0000000000..3a105283d5 --- /dev/null +++ b/changes/restrict-delete-fleet-to-global-admin @@ -0,0 +1 @@ +* Restricted deleting a fleet to global write permissions (global admin or GitOps), matching the existing restriction on creating one. diff --git a/ee/server/service/teams.go b/ee/server/service/teams.go index 48373dcfd0..c5e1fedf31 100644 --- a/ee/server/service/teams.go +++ b/ee/server/service/teams.go @@ -919,7 +919,7 @@ func (svc *Service) ListAvailableTeamsForUser(ctx context.Context, user *fleet.U } func (svc *Service) DeleteTeam(ctx context.Context, teamID uint) error { - if err := svc.authz.Authorize(ctx, &fleet.Team{ID: teamID}, fleet.ActionWrite); err != nil { + if err := svc.authz.Authorize(ctx, &fleet.Team{}, fleet.ActionWrite); err != nil { return err } diff --git a/server/service/teams_test.go b/server/service/teams_test.go index 59546a7e22..98d3ebcfff 100644 --- a/server/service/teams_test.go +++ b/server/service/teams_test.go @@ -232,7 +232,7 @@ func TestTeamAuth(t *testing.T) { checkAuthErr(t, tt.shouldFailRead, err) err = svc.DeleteTeam(ctx, 1) - checkAuthErr(t, tt.shouldFailTeamWrite, err) + checkAuthErr(t, tt.shouldFailGlobalWrite, err) _, err = svc.TeamEnrollSecrets(ctx, 1) checkAuthErr(t, tt.shouldFailRead, err) @@ -246,6 +246,28 @@ func TestTeamAuth(t *testing.T) { } } +func TestDeleteTeamRejectsNonAdminNonGitOpsRoles(t *testing.T) { + ds := new(mock.Store) + license := &fleet.LicenseInfo{Tier: fleet.TierPremium, Expiration: time.Now().Add(24 * time.Hour)} + svc, ctx := newTestService(t, ds, nil, nil, &TestServerOpts{License: license, SkipCreateTestUsers: true}) + + for _, tt := range []struct { + name string + user *fleet.User + }{ + {"global technician", &fleet.User{GlobalRole: new(fleet.RoleTechnician)}}, + {"team technician, belongs to team", &fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleTechnician}}}}, + {"global observer_plus", &fleet.User{GlobalRole: new(fleet.RoleObserverPlus)}}, + {"team observer_plus, belongs to team", &fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleObserverPlus}}}}, + } { + t.Run(tt.name, func(t *testing.T) { + ctx := viewer.NewContext(ctx, viewer.Viewer{User: tt.user}) + err := svc.DeleteTeam(ctx, 1) + checkAuthErr(t, true, err) + }) + } +} + // TestGitOpsCannotManageTeamMembers verifies that a team gitops user cannot // add or remove team members (including self-promotion to admin). func TestGitOpsCannotManageTeamMembers(t *testing.T) {