delete all host MDM profiles when is unenrolled programatically through the API (#10603)

https://github.com/fleetdm/fleet/issues/10507
This commit is contained in:
Roberto Dip 2023-03-20 19:37:15 -03:00 committed by GitHub
parent d7fddb9c01
commit 09b6b8610f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 92 additions and 0 deletions

View File

@ -1060,6 +1060,14 @@ func (ds *Datastore) BulkUpsertMDMAppleHostProfiles(ctx context.Context, payload
return err
}
func (ds *Datastore) DeleteMDMAppleProfilesForHost(ctx context.Context, hostUUID string) error {
_, err := ds.writer.ExecContext(ctx, `
DELETE FROM host_mdm_apple_profiles
WHERE host_uuid = ?
`, hostUUID)
return err
}
func (ds *Datastore) UpdateOrDeleteHostMDMAppleProfile(ctx context.Context, profile *fleet.HostMDMAppleProfile) error {
if profile.OperationType == fleet.MDMAppleOperationTypeRemove &&
profile.Status != nil && (*profile.Status == fleet.MDMAppleDeliveryApplied || profile.IgnoreMDMClientError()) {

View File

@ -37,6 +37,7 @@ func TestMDMAppleConfigProfile(t *testing.T) {
{"TestMDMAppleHostsProfilesStatus", testMDMAppleHostsProfilesStatus},
{"TestMDMAppleInsertIdPAccount", testMDMAppleInsertIdPAccount},
{"TestIgnoreMDMClientError", testIgnoreMDMClientError},
{"TestDeleteMDMAppleProfilesForHost", testDeleteMDMAppleProfilesForHost},
}
for _, c := range cases {
@ -1577,3 +1578,38 @@ func testIgnoreMDMClientError(t *testing.T, ds *Datastore) {
require.Equal(t, fleet.MDMAppleDeliveryFailed, *cps[0].Status)
require.Equal(t, "MDMClientError (96): Cannot replace profile 'p2' because it was not installed by the MDM server.", cps[0].Detail)
}
func testDeleteMDMAppleProfilesForHost(t *testing.T, ds *Datastore) {
ctx := context.Background()
h, err := ds.NewHost(ctx, &fleet.Host{
DetailUpdatedAt: time.Now(),
LabelUpdatedAt: time.Now(),
PolicyUpdatedAt: time.Now(),
SeenTime: time.Now(),
OsqueryHostID: ptr.String("host0-osquery-id"),
NodeKey: ptr.String("host0-node-key"),
UUID: "host0-test-mdm-profiles",
Hostname: "hostname0",
})
require.NoError(t, err)
require.NoError(t, ds.BulkUpsertMDMAppleHostProfiles(ctx, []*fleet.MDMAppleBulkUpsertHostProfilePayload{{
ProfileID: uint(1),
ProfileIdentifier: "p1",
ProfileName: "name1",
HostUUID: h.UUID,
CommandUUID: "c1",
OperationType: fleet.MDMAppleOperationTypeRemove,
Status: &fleet.MDMAppleDeliveryPending,
}}))
gotProfs, err := ds.GetHostMDMProfiles(ctx, h.UUID)
require.NoError(t, err)
require.Len(t, gotProfs, 1)
err = ds.DeleteMDMAppleProfilesForHost(ctx, h.UUID)
require.NoError(t, err)
gotProfs, err = ds.GetHostMDMProfiles(ctx, h.UUID)
require.NoError(t, err)
require.Nil(t, gotProfs)
}

View File

@ -817,6 +817,9 @@ type Datastore interface {
// and the status is "applied" (i.e. successfully removed).
UpdateOrDeleteHostMDMAppleProfile(ctx context.Context, profile *HostMDMAppleProfile) error
// DeleteMDMAppleProfilesForHost deletes all MDM profiles for a host
DeleteMDMAppleProfilesForHost(ctx context.Context, hostUUID string) error
// GetMDMAppleCommandRequest type returns the request type for the given command
GetMDMAppleCommandRequestType(ctx context.Context, commandUUID string) (string, error)

View File

@ -566,6 +566,8 @@ type GetMDMAppleProfilesContentsFunc func(ctx context.Context, profileIDs []uint
type UpdateOrDeleteHostMDMAppleProfileFunc func(ctx context.Context, profile *fleet.HostMDMAppleProfile) error
type DeleteMDMAppleProfilesForHostFunc func(ctx context.Context, hostUUID string) error
type GetMDMAppleCommandRequestTypeFunc func(ctx context.Context, commandUUID string) (string, error)
type GetMDMAppleHostsProfilesSummaryFunc func(ctx context.Context, teamID *uint) (*fleet.MDMAppleHostsProfilesSummary, error)
@ -1398,6 +1400,9 @@ type DataStore struct {
UpdateOrDeleteHostMDMAppleProfileFunc UpdateOrDeleteHostMDMAppleProfileFunc
UpdateOrDeleteHostMDMAppleProfileFuncInvoked bool
DeleteMDMAppleProfilesForHostFunc DeleteMDMAppleProfilesForHostFunc
DeleteMDMAppleProfilesForHostFuncInvoked bool
GetMDMAppleCommandRequestTypeFunc GetMDMAppleCommandRequestTypeFunc
GetMDMAppleCommandRequestTypeFuncInvoked bool
@ -3335,6 +3340,13 @@ func (s *DataStore) UpdateOrDeleteHostMDMAppleProfile(ctx context.Context, profi
return s.UpdateOrDeleteHostMDMAppleProfileFunc(ctx, profile)
}
func (s *DataStore) DeleteMDMAppleProfilesForHost(ctx context.Context, hostUUID string) error {
s.mu.Lock()
s.DeleteMDMAppleProfilesForHostFuncInvoked = true
s.mu.Unlock()
return s.DeleteMDMAppleProfilesForHostFunc(ctx, hostUUID)
}
func (s *DataStore) GetMDMAppleCommandRequestType(ctx context.Context, commandUUID string) (string, error) {
s.mu.Lock()
s.GetMDMAppleCommandRequestTypeFuncInvoked = true

View File

@ -1103,6 +1103,14 @@ func (svc *Service) EnqueueMDMAppleCommandRemoveEnrollmentProfile(ctx context.Co
return ctxerr.Wrap(ctx, err, "enqueuing mdm apple remove profile command")
}
// Since the host is unenrolled, delete all profiles assigned to the
// host manually, the device won't Acknowledge any more requests (eg:
// to delete profiles) and profiles are automatically removed on
// unenrollment.
if err := svc.ds.DeleteMDMAppleProfilesForHost(ctx, h.UUID); err != nil {
return ctxerr.Wrap(ctx, err, "removing all profiles from host")
}
if err := svc.ds.NewActivity(ctx, authz.UserFromContext(ctx), &fleet.ActivityTypeMDMUnenrolled{
HostSerial: h.HardwareSerial,
HostDisplayName: h.DisplayName(),

View File

@ -662,6 +662,10 @@ func TestMDMCommandAuthz(t *testing.T) {
return nil
}
ds.DeleteMDMAppleProfilesForHostFunc = func(ctx context.Context, hostUUID string) error {
return nil
}
var mdmEnabled atomic.Bool
ds.GetNanoMDMEnrollmentStatusFunc = func(ctx context.Context, hostUUID string) (bool, error) {
// This function is called twice during EnqueueMDMAppleCommandRemoveEnrollmentProfile.

View File

@ -778,6 +778,21 @@ func (s *integrationMDMTestSuite) TestMDMAppleUnenroll() {
require.Len(t, listHostsRes.Hosts, 1)
h := listHostsRes.Hosts[0]
// assign profiles to the host
s.Do("POST", "/api/v1/fleet/mdm/apple/profiles/batch", batchSetMDMAppleProfilesRequest{Profiles: [][]byte{
mobileconfigForTest("N1", "I1"),
mobileconfigForTest("N2", "I2"),
mobileconfigForTest("N3", "I3"),
}}, http.StatusNoContent)
// trigger a sync and verify that there are profiles assigned to the host
_, err = s.profileSchedule.Trigger()
require.NoError(t, err)
var hostResp getHostResponse
s.DoJSON("GET", fmt.Sprintf("/api/v1/fleet/hosts/%d", h.ID), getHostRequest{}, http.StatusOK, &hostResp)
require.Len(t, *hostResp.Host.MDM.Profiles, 3)
// try to unenroll the host, fails since the host doesn't respond
s.Do("PATCH", fmt.Sprintf("/api/latest/fleet/mdm/hosts/%d/unenroll", h.ID), nil, http.StatusGatewayTimeout)
@ -815,6 +830,12 @@ func (s *integrationMDMTestSuite) TestMDMAppleUnenroll() {
return res, err
}
s.Do("PATCH", fmt.Sprintf("/api/latest/fleet/mdm/hosts/%d/unenroll", h.ID), nil, http.StatusOK)
// profiles are removed and the host is no longer enrolled
hostResp = getHostResponse{}
s.DoJSON("GET", fmt.Sprintf("/api/v1/fleet/hosts/%d", h.ID), getHostRequest{}, http.StatusOK, &hostResp)
require.Nil(t, hostResp.Host.MDM.Profiles)
require.Equal(t, "", hostResp.Host.MDM.Name)
}
func (s *integrationMDMTestSuite) TestMDMAppleGetEncryptionKey() {