feat: add manual enrollment profile endpoint (#16357)
> Related issue: #16252 # Checklist for submitter If some of the following don't apply, delete the relevant line. <!-- Note that API documentation changes are now addressed by the product design team. --> - [x] Changes file added for user-visible changes in `changes/` or `orbit/changes/`. See [Changes files](https://fleetdm.com/docs/contributing/committing-changes#changes-files) for more information. - [x] Added/updated tests - [x] Manual QA for all new/changed functionality
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
- Adds a new endpoint `GET /api/v1/fleet/mdm/manual_enrollment_profile` that returns the Apple MDM manual
|
||||
enrollment profile for the organization.
|
||||
@@ -1074,3 +1074,26 @@ func (svc *Service) mdmWindowsDisableOSUpdates(ctx context.Context, teamID *uint
|
||||
err := svc.ds.DeleteMDMWindowsConfigProfileByTeamAndName(ctx, teamID, mdm.FleetWindowsOSUpdatesProfileName)
|
||||
return ctxerr.Wrap(ctx, err, "delete Windows OS updates profile")
|
||||
}
|
||||
|
||||
func (svc *Service) GetMDMManualEnrollmentProfile(ctx context.Context) ([]byte, error) {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.MDMAppleManualEnrollmentProfile{}, fleet.ActionRead); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
appConfig, err := svc.ds.AppConfig(ctx)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err)
|
||||
}
|
||||
|
||||
mobileConfig, err := apple_mdm.GenerateEnrollmentProfileMobileconfig(
|
||||
appConfig.OrgInfo.OrgName,
|
||||
appConfig.ServerSettings.ServerURL,
|
||||
svc.config.MDM.AppleSCEPChallenge,
|
||||
svc.mdmPushCertTopic,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err)
|
||||
}
|
||||
|
||||
return mobileConfig, nil
|
||||
}
|
||||
|
||||
@@ -808,6 +808,13 @@ allow {
|
||||
action == write
|
||||
}
|
||||
|
||||
# Any logged in user can read the manual enrollment profile data.
|
||||
allow {
|
||||
object.type == "mdm_apple_manual_enrollment_profile"
|
||||
not is_null(subject)
|
||||
action == read
|
||||
}
|
||||
|
||||
##
|
||||
# Cron schedules
|
||||
##
|
||||
|
||||
@@ -82,6 +82,15 @@ func (m MDMAppleEnrollmentProfile) AuthzType() string {
|
||||
return "mdm_apple_enrollment_profile"
|
||||
}
|
||||
|
||||
// MDMAppleManualEnrollmentProfile is used for authorization checks to get the standard Fleet manual
|
||||
// enrollment profile. The actual data is returned as raw bytes.
|
||||
type MDMAppleManualEnrollmentProfile struct{}
|
||||
|
||||
// AuthzType implements authz.AuthzTyper
|
||||
func (m MDMAppleManualEnrollmentProfile) AuthzType() string {
|
||||
return "mdm_apple_manual_enrollment_profile"
|
||||
}
|
||||
|
||||
// MDMAppleDEPKeyPair contains the DEP public key certificate and private key pair. Both are PEM encoded.
|
||||
type MDMAppleDEPKeyPair struct {
|
||||
PublicKey []byte `json:"public_key"`
|
||||
|
||||
@@ -791,6 +791,8 @@ type Service interface {
|
||||
// for MDM macOS migration.
|
||||
TriggerMigrateMDMDevice(ctx context.Context, host *Host) error
|
||||
|
||||
GetMDMManualEnrollmentProfile(ctx context.Context) ([]byte, error)
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// CronSchedulesService
|
||||
|
||||
|
||||
@@ -2159,6 +2159,30 @@ func (svc *Service) InitiateMDMAppleSSOCallback(ctx context.Context, auth fleet.
|
||||
return apple_mdm.FleetUISSOCallbackPath + "?error=true"
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// GET /mdm/manual_enrollment_profile
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type getManualEnrollmentProfileRequest struct{}
|
||||
|
||||
func getManualEnrollmentProfileEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
||||
profile, err := svc.GetMDMManualEnrollmentProfile(ctx)
|
||||
if err != nil {
|
||||
return getDeviceMDMManualEnrollProfileResponse{Err: err}, nil
|
||||
}
|
||||
|
||||
// Using this type to keep code DRY as it already has all the functionality we need.
|
||||
return getDeviceMDMManualEnrollProfileResponse{Profile: profile}, nil
|
||||
}
|
||||
|
||||
func (svc *Service) GetMDMManualEnrollmentProfile(ctx context.Context) ([]byte, error) {
|
||||
// skipauth: No authorization check needed due to implementation returning
|
||||
// only license error.
|
||||
svc.authz.SkipAuthorization(ctx)
|
||||
|
||||
return nil, fleet.ErrMissingLicense
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// FileVault-related free version implementation
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -252,6 +252,24 @@ func TestAppleMDMAuthorization(t *testing.T) {
|
||||
_, err = svc.NewMDMAppleDEPKeyPair(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Should work for all user types
|
||||
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)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Must be device-authenticated, should fail
|
||||
_, err = svc.GetDeviceMDMAppleEnrollmentProfile(ctx)
|
||||
checkAuthErr(t, err, true)
|
||||
|
||||
@@ -519,6 +519,7 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
|
||||
mdmAppleMW.GET("/api/_version_/fleet/mdm/apple/installers", listMDMAppleInstallersEndpoint, listMDMAppleInstallersRequest{})
|
||||
mdmAppleMW.GET("/api/_version_/fleet/mdm/apple/devices", listMDMAppleDevicesEndpoint, listMDMAppleDevicesRequest{})
|
||||
mdmAppleMW.GET("/api/_version_/fleet/mdm/apple/dep/devices", listMDMAppleDEPDevicesEndpoint, listMDMAppleDEPDevicesRequest{})
|
||||
mdmAppleMW.GET("/api/_version_/fleet/mdm/manual_enrollment_profile", getManualEnrollmentProfileEndpoint, getManualEnrollmentProfileRequest{})
|
||||
|
||||
// bootstrap-package routes
|
||||
mdmAppleMW.POST("/api/_version_/fleet/mdm/apple/bootstrap", uploadBootstrapPackageEndpoint, uploadBootstrapPackageRequest{})
|
||||
|
||||
@@ -7094,9 +7094,11 @@ func (s *integrationMDMTestSuite) downloadAndVerifyEnrollmentProfile(path string
|
||||
for _, p := range profile.PayloadContent {
|
||||
switch p.PayloadType {
|
||||
case "com.apple.security.scep":
|
||||
require.NotEmpty(t, p.PayloadContent.URL)
|
||||
require.Equal(t, s.getConfig().ServerSettings.ServerURL+apple_mdm.SCEPPath, p.PayloadContent.URL)
|
||||
require.Equal(t, s.fleetCfg.MDM.AppleSCEPChallenge, p.PayloadContent.Challenge)
|
||||
case "com.apple.mdm":
|
||||
require.NotEmpty(t, p.ServerURL)
|
||||
// Use Contains as the url may have query params
|
||||
require.Contains(t, p.ServerURL, s.getConfig().ServerSettings.ServerURL+apple_mdm.MDMPath)
|
||||
default:
|
||||
require.Failf(t, "unrecognized payload type in enrollment profile: %s", p.PayloadType)
|
||||
}
|
||||
@@ -11221,3 +11223,7 @@ func (s *integrationMDMTestSuite) TestZCustomConfigurationWebURL() {
|
||||
applyResp = applyTeamSpecsResponse{}
|
||||
s.DoJSON("POST", "/api/latest/fleet/spec/teams", teamSpecs, http.StatusUnprocessableEntity, &applyResp)
|
||||
}
|
||||
|
||||
func (s *integrationMDMTestSuite) TestGetManualEnrollmentProfile() {
|
||||
s.downloadAndVerifyEnrollmentProfile("/api/latest/fleet/mdm/manual_enrollment_profile")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user