Skip to content

Commit 18d830a

Browse files
author
Roberto Dip
authored
allow to verify profiles that are pending (#15911)
for #15678
1 parent 6c84209 commit 18d830a

File tree

4 files changed

+170
-34
lines changed

4 files changed

+170
-34
lines changed

changes/profile-verification

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* Improve profile verification logic to verify 'pending' profiles.

server/datastore/mysql/apple_mdm_test.go

+35-32
Original file line numberDiff line numberDiff line change
@@ -3464,11 +3464,13 @@ func testSetVerifiedMacOSProfiles(t *testing.T, ds *Datastore) {
34643464
require.NoError(t, err)
34653465
cp3, err := ds.NewMDMAppleConfigProfile(ctx, *configProfileForTest(t, "name3", "cp3", "uuid3"))
34663466
require.NoError(t, err)
3467+
cp4, err := ds.NewMDMAppleConfigProfile(ctx, *configProfileForTest(t, "name4", "cp4", "uuid4"))
3468+
require.NoError(t, err)
34673469

34683470
// list config profiles for no team
34693471
cps, err := ds.ListMDMAppleConfigProfiles(ctx, nil)
34703472
require.NoError(t, err)
3471-
require.Len(t, cps, 3)
3473+
require.Len(t, cps, 4)
34723474
storedByIdentifier := make(map[string]*fleet.MDMAppleConfigProfile)
34733475
for _, cp := range cps {
34743476
storedByIdentifier[cp.Identifier] = cp
@@ -3484,6 +3486,7 @@ func testSetVerifiedMacOSProfiles(t *testing.T, ds *Datastore) {
34843486
cp1.Identifier: fleet.MDMDeliveryPending,
34853487
cp2.Identifier: fleet.MDMDeliveryVerifying,
34863488
cp3.Identifier: fleet.MDMDeliveryVerified,
3489+
cp4.Identifier: fleet.MDMDeliveryPending,
34873490
}
34883491
}
34893492

@@ -3497,12 +3500,12 @@ func testSetVerifiedMacOSProfiles(t *testing.T, ds *Datastore) {
34973500
for _, h := range hosts {
34983501
gotProfs, err := ds.GetHostMDMAppleProfiles(ctx, h.UUID)
34993502
require.NoError(t, err)
3500-
require.Len(t, gotProfs, 3)
3503+
require.Len(t, gotProfs, 4)
35013504
for _, p := range gotProfs {
35023505
s, ok := expectedHostMDMStatus[h.ID][p.Identifier]
35033506
require.True(t, ok)
35043507
require.NotNil(t, p.Status)
3505-
require.Equal(t, s, *p.Status)
3508+
require.Equalf(t, s, *p.Status, "profile identifier %s", p.Identifier)
35063509
}
35073510
}
35083511
}
@@ -3520,23 +3523,13 @@ func testSetVerifiedMacOSProfiles(t *testing.T, ds *Datastore) {
35203523
upsertHostCPs(hosts, []*fleet.MDMAppleConfigProfile{storedByIdentifier[cp1.Identifier]}, fleet.MDMOperationTypeInstall, &fleet.MDMDeliveryPending, ctx, ds, t)
35213524
upsertHostCPs(hosts, []*fleet.MDMAppleConfigProfile{storedByIdentifier[cp2.Identifier]}, fleet.MDMOperationTypeInstall, &fleet.MDMDeliveryVerifying, ctx, ds, t)
35223525
upsertHostCPs(hosts, []*fleet.MDMAppleConfigProfile{storedByIdentifier[cp3.Identifier]}, fleet.MDMOperationTypeInstall, &fleet.MDMDeliveryVerified, ctx, ds, t)
3526+
upsertHostCPs(hosts, []*fleet.MDMAppleConfigProfile{storedByIdentifier[cp4.Identifier]}, fleet.MDMOperationTypeInstall, &fleet.MDMDeliveryPending, ctx, ds, t)
35233527
checkHostMDMProfileStatuses()
35243528

35253529
// statuses don't change during the grace period if profiles are missing (i.e. not installed)
35263530
require.NoError(t, apple_mdm.VerifyHostMDMProfiles(ctx, ds, hosts[0], map[string]*fleet.HostMacOSProfile{}))
35273531
checkHostMDMProfileStatuses()
35283532

3529-
// only "verifying" status can change to "verified" so status of cp1 doesn't change (it
3530-
// remains "pending")
3531-
require.NoError(t, apple_mdm.VerifyHostMDMProfiles(ctx, ds, hosts[0], map[string]*fleet.HostMacOSProfile{
3532-
cp1.Identifier: {
3533-
Identifier: cp1.Identifier,
3534-
DisplayName: cp1.Name,
3535-
InstallDate: time.Now(),
3536-
},
3537-
}))
3538-
checkHostMDMProfileStatuses()
3539-
35403533
// if install date is before the updated at timestamp of the profile, statuses don't change
35413534
// during the grace period
35423535
require.NoError(t, apple_mdm.VerifyHostMDMProfiles(ctx, ds, hosts[1], profilesByIdentifier([]*fleet.HostMacOSProfile{
@@ -3555,17 +3548,17 @@ func testSetVerifiedMacOSProfiles(t *testing.T, ds *Datastore) {
35553548
DisplayName: cp3.Name,
35563549
InstallDate: storedByIdentifier[cp3.Identifier].UpdatedAt.Add(-1 * time.Hour),
35573550
},
3551+
{
3552+
Identifier: cp4.Identifier,
3553+
DisplayName: cp4.Name,
3554+
InstallDate: storedByIdentifier[cp4.Identifier].UpdatedAt.Add(-1 * time.Hour),
3555+
},
35583556
})))
35593557
checkHostMDMProfileStatuses()
35603558

3561-
// if install date is on or after the updated at timestamp of the profile, "verifying" status
3562-
// changes to "verified"
3559+
// if install date is on or after the updated at timestamp of the profile, "verifying" or "pending" status
3560+
// changes to "verified". Any "pending" profiles not reported are not changed
35633561
require.NoError(t, apple_mdm.VerifyHostMDMProfiles(ctx, ds, hosts[2], profilesByIdentifier([]*fleet.HostMacOSProfile{
3564-
{
3565-
Identifier: cp1.Identifier,
3566-
DisplayName: cp1.Name,
3567-
InstallDate: storedByIdentifier[cp1.Identifier].UpdatedAt,
3568-
},
35693562
{
35703563
Identifier: cp2.Identifier,
35713564
DisplayName: cp2.Name,
@@ -3576,17 +3569,18 @@ func testSetVerifiedMacOSProfiles(t *testing.T, ds *Datastore) {
35763569
DisplayName: cp3.Name,
35773570
InstallDate: storedByIdentifier[cp3.Identifier].UpdatedAt,
35783571
},
3572+
{
3573+
Identifier: cp4.Identifier,
3574+
DisplayName: cp4.Name,
3575+
InstallDate: storedByIdentifier[cp4.Identifier].UpdatedAt,
3576+
},
35793577
})))
35803578
expectedHostMDMStatus[hosts[2].ID][cp2.Identifier] = fleet.MDMDeliveryVerified
3579+
expectedHostMDMStatus[hosts[2].ID][cp4.Identifier] = fleet.MDMDeliveryVerified
35813580
checkHostMDMProfileStatuses()
35823581

35833582
// repeated call doesn't change statuses
35843583
require.NoError(t, apple_mdm.VerifyHostMDMProfiles(ctx, ds, hosts[2], profilesByIdentifier([]*fleet.HostMacOSProfile{
3585-
{
3586-
Identifier: cp1.Identifier,
3587-
DisplayName: cp1.Name,
3588-
InstallDate: storedByIdentifier[cp1.Identifier].UpdatedAt,
3589-
},
35903584
{
35913585
Identifier: cp2.Identifier,
35923586
DisplayName: cp2.Name,
@@ -3597,15 +3591,20 @@ func testSetVerifiedMacOSProfiles(t *testing.T, ds *Datastore) {
35973591
DisplayName: cp3.Name,
35983592
InstallDate: storedByIdentifier[cp3.Identifier].UpdatedAt,
35993593
},
3594+
{
3595+
Identifier: cp4.Identifier,
3596+
DisplayName: cp4.Name,
3597+
InstallDate: storedByIdentifier[cp4.Identifier].UpdatedAt,
3598+
},
36003599
})))
36013600
checkHostMDMProfileStatuses()
36023601

36033602
// simulate expired grace period by setting updated_at timestamp of profiles back by 24 hours
36043603
ExecAdhocSQL(t, ds, func(tx sqlx.ExtContext) error {
36053604
_, err := tx.ExecContext(ctx,
3606-
`UPDATE mdm_apple_configuration_profiles SET updated_at = ? WHERE profile_uuid IN(?, ?, ?)`,
3605+
`UPDATE mdm_apple_configuration_profiles SET updated_at = ? WHERE profile_uuid IN(?, ?, ?, ?)`,
36073606
time.Now().Add(-24*time.Hour),
3608-
cp1.ProfileUUID, cp2.ProfileUUID, cp3.ProfileUUID,
3607+
cp1.ProfileUUID, cp2.ProfileUUID, cp3.ProfileUUID, cp4.ProfileUUID,
36093608
)
36103609
return err
36113610
})
@@ -3623,11 +3622,14 @@ func testSetVerifiedMacOSProfiles(t *testing.T, ds *Datastore) {
36233622
InstallDate: time.Now(),
36243623
},
36253624
})))
3626-
expectedHostMDMStatus[hosts[2].ID][cp3.Identifier] = fleet.MDMDeliveryPending // first retry for cp3
3625+
expectedHostMDMStatus[hosts[2].ID][cp1.Identifier] = fleet.MDMDeliveryVerified //cp1 can go from pending to verified
3626+
expectedHostMDMStatus[hosts[2].ID][cp3.Identifier] = fleet.MDMDeliveryPending // first retry for cp3
3627+
expectedHostMDMStatus[hosts[2].ID][cp4.Identifier] = fleet.MDMDeliveryPending // first retry for cp4
36273628
checkHostMDMProfileStatuses()
36283629
// simulate retry command acknowledged by setting status to "verifying"
36293630
adHocSetVerifying(hosts[2].UUID, cp3.Identifier)
3630-
// report osquery results again with cp3 still missing
3631+
adHocSetVerifying(hosts[2].UUID, cp4.Identifier)
3632+
// report osquery results again with cp3 and cp4 still missing
36313633
require.NoError(t, apple_mdm.VerifyHostMDMProfiles(ctx, ds, hosts[2], profilesByIdentifier([]*fleet.HostMacOSProfile{
36323634
{
36333635
Identifier: cp1.Identifier,
@@ -3641,6 +3643,7 @@ func testSetVerifiedMacOSProfiles(t *testing.T, ds *Datastore) {
36413643
},
36423644
})))
36433645
expectedHostMDMStatus[hosts[2].ID][cp3.Identifier] = fleet.MDMDeliveryFailed // still missing after retry so expect cp3 to fail
3646+
expectedHostMDMStatus[hosts[2].ID][cp4.Identifier] = fleet.MDMDeliveryFailed // still missing after retry so expect cp4 to fail
36443647
checkHostMDMProfileStatuses()
36453648

36463649
// after the grace period and one retry attempt, status changes to "failed" if a profile is outdated (i.e. installed
@@ -4471,7 +4474,7 @@ func TestMDMAppleProfileVerification(t *testing.T) {
44714474
{
44724475
name: "PendingThenFoundExpected",
44734476
initialStatus: fleet.MDMDeliveryPending,
4474-
expectedStatus: fleet.MDMDeliveryPending, // no change
4477+
expectedStatus: fleet.MDMDeliveryVerified, // pending can go to verified if found
44754478
expectedDetail: "",
44764479
},
44774480
{
@@ -4534,7 +4537,7 @@ func TestMDMAppleProfileVerification(t *testing.T) {
45344537
{
45354538
name: "PendingThenFoundExpectedAndUnexpected",
45364539
initialStatus: fleet.MDMDeliveryPending,
4537-
expectedStatus: fleet.MDMDeliveryPending, // no change
4540+
expectedStatus: fleet.MDMDeliveryVerified, // profile can go from pending to verified
45384541
expectedDetail: "",
45394542
},
45404543
{

server/datastore/mysql/mdm.go

+9-2
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,10 @@ WHERE
435435
fleet.HostMDMProfileDetailFailedWasVerified,
436436
fleet.MDMDeliveryFailed,
437437
host.UUID,
438-
[]interface{}{fleet.MDMDeliveryVerifying, fleet.MDMDeliveryVerified},
438+
[]interface{}{
439+
fleet.MDMDeliveryVerifying,
440+
fleet.MDMDeliveryVerified,
441+
},
439442
fleet.MDMOperationTypeInstall,
440443
identifiersOrNames,
441444
}
@@ -482,7 +485,11 @@ WHERE
482485
args := []interface{}{
483486
fleet.MDMDeliveryVerified,
484487
host.UUID,
485-
[]interface{}{fleet.MDMDeliveryVerifying, fleet.MDMDeliveryFailed},
488+
[]interface{}{
489+
fleet.MDMDeliveryPending,
490+
fleet.MDMDeliveryVerifying,
491+
fleet.MDMDeliveryFailed,
492+
},
486493
fleet.MDMOperationTypeInstall,
487494
identifiersOrNames,
488495
}
+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package apple_mdm
2+
3+
import (
4+
"context"
5+
"errors"
6+
"testing"
7+
"time"
8+
9+
"github.com/fleetdm/fleet/v4/server/fleet"
10+
"github.com/fleetdm/fleet/v4/server/mock"
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
func TestVerifyHostMDMProfiles(t *testing.T) {
15+
ctx := context.Background()
16+
host := &fleet.Host{
17+
DetailUpdatedAt: time.Now(),
18+
}
19+
ds := new(mock.Store)
20+
21+
tests := []struct {
22+
name string
23+
expectedProfiles map[string]*fleet.ExpectedMDMProfile
24+
retryCounts []fleet.HostMDMProfileRetryCount
25+
installed map[string]*fleet.HostMacOSProfile
26+
toVerify []string
27+
toFail []string
28+
toRetry []string
29+
expectErr bool
30+
}{
31+
{
32+
name: "error on getting expected profiles",
33+
expectErr: true,
34+
},
35+
{
36+
name: "all profiles verified",
37+
expectedProfiles: map[string]*fleet.ExpectedMDMProfile{"profile1": {}},
38+
installed: map[string]*fleet.HostMacOSProfile{"profile1": {}},
39+
toVerify: []string{"profile1"},
40+
},
41+
{
42+
name: "profiles missing, not within grace period, no retries yet",
43+
expectedProfiles: map[string]*fleet.ExpectedMDMProfile{
44+
"profile1": {},
45+
"profile2": {EarliestInstallDate: host.DetailUpdatedAt.Add(-24 * time.Hour)},
46+
},
47+
installed: map[string]*fleet.HostMacOSProfile{"profile1": {}},
48+
toVerify: []string{"profile1"},
49+
toRetry: []string{"profile2"},
50+
},
51+
{
52+
name: "profiles missing, with and without retries, not within grace period",
53+
expectedProfiles: map[string]*fleet.ExpectedMDMProfile{"profile1": {}, "profile2": {}},
54+
retryCounts: []fleet.HostMDMProfileRetryCount{
55+
{ProfileIdentifier: "profile1", Retries: 0},
56+
{ProfileIdentifier: "profile2", Retries: 1},
57+
},
58+
installed: map[string]*fleet.HostMacOSProfile{},
59+
toRetry: []string{"profile1"},
60+
toFail: []string{"profile2"},
61+
},
62+
{
63+
name: "host profile installed prior to uploading profile to Fleet",
64+
expectedProfiles: map[string]*fleet.ExpectedMDMProfile{
65+
"profile1": {EarliestInstallDate: time.Now().Add(-2 * time.Hour)},
66+
},
67+
installed: map[string]*fleet.HostMacOSProfile{
68+
"profile1": {InstallDate: time.Now().Add(-24 * time.Hour)},
69+
},
70+
toRetry: []string{"profile1"},
71+
},
72+
{
73+
name: "host profile installed prior to uploading profile to Fleet with max retries",
74+
expectedProfiles: map[string]*fleet.ExpectedMDMProfile{
75+
"profile1": {EarliestInstallDate: time.Now().Add(-2 * time.Hour)},
76+
},
77+
installed: map[string]*fleet.HostMacOSProfile{
78+
"profile1": {InstallDate: time.Now().Add(-24 * time.Hour)},
79+
},
80+
retryCounts: []fleet.HostMDMProfileRetryCount{
81+
{ProfileIdentifier: "profile1", Retries: 1},
82+
},
83+
toFail: []string{"profile1"},
84+
},
85+
}
86+
87+
for _, tc := range tests {
88+
t.Run(tc.name, func(t *testing.T) {
89+
// setup mocks
90+
ds.GetHostMDMProfilesExpectedForVerificationFunc = func(ctx context.Context, host *fleet.Host) (map[string]*fleet.ExpectedMDMProfile, error) {
91+
if tc.expectErr {
92+
return nil, errors.New("error")
93+
}
94+
return tc.expectedProfiles, nil
95+
}
96+
97+
ds.GetHostMDMProfilesRetryCountsFunc = func(ctx context.Context, host *fleet.Host) ([]fleet.HostMDMProfileRetryCount, error) {
98+
return tc.retryCounts, nil
99+
}
100+
101+
ds.UpdateHostMDMProfilesVerificationFunc = func(ctx context.Context, host *fleet.Host, verified, toFail, toRetry []string) error {
102+
require.ElementsMatch(t, tc.toVerify, verified, "verified profiles do not match")
103+
require.ElementsMatch(t, tc.toFail, toFail, "failed profiles do not match")
104+
require.ElementsMatch(t, tc.toRetry, toRetry, "retried profiles do not match")
105+
return nil
106+
}
107+
108+
// run the test
109+
err := VerifyHostMDMProfiles(ctx, ds, host, tc.installed)
110+
if tc.expectErr {
111+
require.Error(t, err)
112+
require.False(
113+
t,
114+
ds.UpdateHostMDMProfilesVerificationFuncInvoked,
115+
"UpdateHostMDMProfilesVerificationFunc should not have been called",
116+
)
117+
} else {
118+
require.NoError(t, err)
119+
require.True(t, ds.UpdateHostMDMProfilesVerificationFuncInvoked, "UpdateHostMDMProfilesVerificationFunc should have been called")
120+
}
121+
122+
ds.UpdateHostMDMProfilesVerificationFuncInvoked = false
123+
})
124+
}
125+
}

0 commit comments

Comments
 (0)