From 133e4a38327a510dcdb66fe28d2b44a2d4878e89 Mon Sep 17 00:00:00 2001 From: Jordan Montgomery Date: Thu, 6 Aug 2026 14:00:49 -0400 Subject: [PATCH] Tweak manual enroll profile permissions (#50688) **Related issue:** Resolves # # Checklist for submitter If some of the following don't apply, delete the relevant line. - [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. - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters. - [x] Timeouts are implemented and retries are limited to avoid infinite loops - [x] If paths of existing endpoints are modified without backwards compatibility, checked the frontend/CLI for any necessary changes ## Testing - [x] Added/updated automated tests - [x] Where appropriate, [automated tests simulate multiple hosts and test for host isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing) (updates to one hosts's records do not affect another) - [x] QA'd all new/changed functionality manually ## Summary by CodeRabbit * **Bug Fixes** * Restricted access to manual MDM enrollment profiles to global or team administrators and maintainers. * Prevented unauthenticated, roleless, observer, and GitOps users from viewing enrollment profile data. * Updated authorization documentation to reflect the required permissions. --- .../fix-manual-enrollment-profile-permissions | 1 + server/authz/policy.rego | 14 +++++++++-- server/authz/policy_test.go | 24 +++++++++++++++++++ server/service/apple_mdm_test.go | 22 +++++++++++------ 4 files changed, 52 insertions(+), 9 deletions(-) create mode 100644 changes/fix-manual-enrollment-profile-permissions diff --git a/changes/fix-manual-enrollment-profile-permissions b/changes/fix-manual-enrollment-profile-permissions new file mode 100644 index 0000000000..fa790ab146 --- /dev/null +++ b/changes/fix-manual-enrollment-profile-permissions @@ -0,0 +1 @@ +- Restricted the manual MDM enrollment profile endpoint (`GET /api/v1/fleet/enrollment_profiles/manual`) to global and fleet-scoped admins and maintainers. diff --git a/server/authz/policy.rego b/server/authz/policy.rego index c8b8e6e11c..0c3d4bd0a7 100644 --- a/server/authz/policy.rego +++ b/server/authz/policy.rego @@ -1192,10 +1192,20 @@ allow { action == write } -# Any logged in user can read the manual enrollment profile data. +# The manual enrollment profile embeds the SCEP challenge, so it is restricted +# to the same roles as enroll secrets. + +# Global admins and maintainers can read the manual enrollment profile data. allow { object.type == "mdm_apple_manual_enrollment_profile" - not is_null(subject) + subject.global_role == [admin, maintainer][_] + action == read +} + +# Team admins and maintainers can read the manual enrollment profile data. +allow { + object.type == "mdm_apple_manual_enrollment_profile" + team_role(subject, subject.teams[_].id) == [admin, maintainer][_] action == read } diff --git a/server/authz/policy_test.go b/server/authz/policy_test.go index f48c5a5f79..72505b3ba6 100644 --- a/server/authz/policy_test.go +++ b/server/authz/policy_test.go @@ -2611,6 +2611,30 @@ func TestAuthorizeMDMAppleSetupAssistant(t *testing.T) { }) } +func TestAuthorizeMDMAppleManualEnrollmentProfile(t *testing.T) { + t.Parallel() + + profile := &fleet.MDMAppleManualEnrollmentProfile{} + runTestCases(t, []authTestCase{ + {user: nil, object: profile, action: read, allow: false}, + {user: test.UserNoRoles, object: profile, action: read, allow: false}, + + {user: test.UserAdmin, object: profile, action: read, allow: true}, + {user: test.UserMaintainer, object: profile, action: read, allow: true}, + {user: test.UserObserver, object: profile, action: read, allow: false}, + {user: test.UserObserverPlus, object: profile, action: read, allow: false}, + {user: test.UserTechnician, object: profile, action: read, allow: false}, + {user: test.UserGitOps, object: profile, action: read, allow: false}, + + {user: test.UserTeamAdminTeam1, object: profile, action: read, allow: true}, + {user: test.UserTeamMaintainerTeam1, object: profile, action: read, allow: true}, + {user: test.UserTeamObserverTeam1, object: profile, action: read, allow: false}, + {user: test.UserTeamObserverPlusTeam1, object: profile, action: read, allow: false}, + {user: test.UserTeamTechnicianTeam1, object: profile, action: read, allow: false}, + {user: test.UserTeamGitOpsTeam1, object: profile, action: read, allow: false}, + }) +} + func TestAuthorizeMDMAppleBootstrapPackage(t *testing.T) { t.Parallel() diff --git a/server/service/apple_mdm_test.go b/server/service/apple_mdm_test.go index 58f6a02d09..50397464d1 100644 --- a/server/service/apple_mdm_test.go +++ b/server/service/apple_mdm_test.go @@ -356,23 +356,31 @@ func TestAppleMDMAuthorization(t *testing.T) { _, err = svc.NewMDMAppleDEPKeyPair(ctx) require.NoError(t, err) - // Should work for all user types + // The manual enrollment profile embeds the SCEP challenge, so only global + // and team admins and maintainers can read it (same roles as enroll secrets). for _, user := range []*fleet.User{ test.UserAdmin, test.UserMaintainer, - test.UserObserver, - test.UserObserverPlus, test.UserTeamAdminTeam1, - test.UserTeamGitOpsTeam1, - test.UserGitOps, test.UserTeamMaintainerTeam1, - test.UserTeamObserverTeam1, - test.UserTeamObserverPlusTeam1, } { usrctx := test.UserContext(ctx, user) _, err = svc.GetMDMManualEnrollmentProfile(usrctx, false) require.NoError(t, err) } + for _, user := range []*fleet.User{ + test.UserNoRoles, + test.UserObserver, + test.UserObserverPlus, + test.UserGitOps, + test.UserTeamObserverTeam1, + test.UserTeamObserverPlusTeam1, + test.UserTeamGitOpsTeam1, + } { + usrctx := test.UserContext(ctx, user) + _, err = svc.GetMDMManualEnrollmentProfile(usrctx, false) + checkAuthErr(t, err, true) + } // Must be device-authenticated, should fail _, err = svc.GetDeviceMDMAppleEnrollmentProfile(ctx)