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


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## 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.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Nico
2026-07-31 08:43:51 -03:00
committed by GitHub
parent 9b51376f83
commit 7f1b330c90
3 changed files with 25 additions and 2 deletions
@@ -0,0 +1 @@
* Restricted deleting a fleet to global write permissions (global admin or GitOps), matching the existing restriction on creating one.
+1 -1
View File
@@ -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
}
+23 -1
View File
@@ -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) {