Fix stale pending remove apple declarations if host was offline for add and remove declaration (#30981)

Fixes: #29824 

This PR fixes a situtation where Apple Declarations could be lingering
around for hosts, if they were offline when the decl. was added and
removed, and no further declaration config is pushed to force a status
update.

It tackles it by deleting the pending and failed installs from the
table, before setting the remaining (verified and verifying) to be
remove operation, as those have hit the host.

I couldn't come up with a way that would auto-fix the hosts we see in
dogfood, as those have the same declaration identifier and token for
both an install row and pending remove row. Those needs to manually be
adjusted and then it should be good.

# 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/`,
`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)
- [x] Added/updated automated tests
- [x] Manual QA for all new/changed functionality
This commit is contained in:
Magnus Jensen
2025-07-22 11:22:04 +02:00
committed by GitHub
parent 32b8e55eab
commit 6147a12ece
4 changed files with 47 additions and 4 deletions
@@ -0,0 +1 @@
* Fix stale pending remove apple declarations, if the host was offline while adding and removing the same declaration.
+4 -2
View File
@@ -643,10 +643,10 @@ func cancelAppleHostInstallsForDeletedMDMDeclarations(ctx context.Context, tx sq
host_mdm_apple_declarations
WHERE
declaration_uuid IN (?) AND
status IS NULL AND
( status IS NULL OR status IN (?) ) AND
operation_type = ?`
stmt, args, err := sqlx.In(delStmt, declUUIDs, fleet.MDMOperationTypeInstall)
stmt, args, err := sqlx.In(delStmt, declUUIDs, []fleet.MDMDeliveryStatus{fleet.MDMDeliveryPending, fleet.MDMDeliveryFailed}, fleet.MDMOperationTypeInstall)
if err != nil {
return ctxerr.Wrap(ctx, err, "building in statement")
}
@@ -5888,6 +5888,8 @@ ON DUPLICATE KEY UPDATE
}
}
// TODO: Do we want to take action if an update coming from the host is not found in the current "expected" declarations?
err := ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
if len(args) != 0 {
stmt := fmt.Sprintf(updateHostDeclarationsStmt, strings.TrimSuffix(insertVals.String(), ","))
+40
View File
@@ -101,6 +101,7 @@ func TestMDMApple(t *testing.T) {
{"SetMDMAppleProfilesWithVariables", testSetMDMAppleProfilesWithVariables},
{"GetNanoMDMEnrollmentTimes", testGetNanoMDMEnrollmentTimes},
{"GetNanoMDMUserEnrollment", testGetNanoMDMUserEnrollment},
{"TestDeleteMDMAppleDeclarationWithPendingInstalls", testDeleteMDMAppleDeclarationWithPendingInstalls},
}
for _, c := range cases {
@@ -5526,6 +5527,45 @@ func testSetOrUpdateMDMAppleDDMDeclaration(t *testing.T, ds *Datastore) {
require.Equal(t, d1tm1B.DeclarationUUID, d1tm1.DeclarationUUID)
}
func testDeleteMDMAppleDeclarationWithPendingInstalls(t *testing.T, ds *Datastore) {
ctx := t.Context()
decl, err := ds.NewMDMAppleDeclaration(ctx, &fleet.MDMAppleDeclaration{
Identifier: "decl-1",
Name: "decl-1",
})
require.NoError(t, err)
host, err := ds.NewHost(ctx, &fleet.Host{
Hostname: "test-host1-name",
OsqueryHostID: ptr.String("1337"),
NodeKey: ptr.String("1337"),
UUID: "test-uuid-1",
TeamID: nil,
Platform: "darwin",
})
require.NoError(t, err)
nanoEnroll(t, ds, host, true)
_, err = ds.BulkSetPendingMDMHostProfiles(ctx, nil, nil, []string{decl.DeclarationUUID}, nil)
require.NoError(t, err)
// verify the correct state of the declaration for the host
profs, err := ds.GetHostMDMAppleProfiles(ctx, host.UUID)
require.NoError(t, err)
require.Len(t, profs, 1)
require.Equal(t, decl.DeclarationUUID, profs[0].ProfileUUID)
require.Equal(t, fleet.MDMDeliveryPending, *profs[0].Status)
require.Equal(t, fleet.MDMOperationTypeInstall, profs[0].OperationType)
err = ds.DeleteMDMAppleDeclaration(ctx, decl.DeclarationUUID)
require.NoError(t, err)
profs, err = ds.GetHostMDMAppleProfiles(ctx, host.UUID)
require.NoError(t, err)
require.Len(t, profs, 0)
}
func TestMDMAppleProfileVerification(t *testing.T) {
ds := CreateMySQLDS(t)
ctx := t.Context()
+2 -2
View File
@@ -470,7 +470,8 @@ func (s *integrationMDMTestSuite) TestAppleDDMSecretVariables() {
_, mdmDevice := createHostThenEnrollMDM(s.ds, s.server.URL, t)
checkDeclarationItemsResp := func(t *testing.T, r fleet.MDMAppleDDMDeclarationItemsResponse, expectedDeclTok string,
expectedDeclsByToken map[string]fleet.MDMAppleDeclaration) {
expectedDeclsByToken map[string]fleet.MDMAppleDeclaration,
) {
require.Equal(t, expectedDeclTok, r.DeclarationsToken)
require.NotEmpty(t, r.Declarations.Activations)
require.Empty(t, r.Declarations.Assets)
@@ -1065,7 +1066,6 @@ func (s *integrationMDMTestSuite) TestAppleDDMStatusReport() {
require.NoError(t, err)
assertHostDeclarations(mdmHost.UUID, []*fleet.MDMAppleHostDeclaration{
{Identifier: "I1", Status: &fleet.MDMDeliveryVerified, OperationType: fleet.MDMOperationTypeInstall},
{Identifier: "I2", Status: &fleet.MDMDeliveryPending, OperationType: fleet.MDMOperationTypeRemove},
})
// host sends a report, declaration I2 is removed from the hosts_* table