From ce4cc9218654d06a20c360fa1e3be0a87eeafefb Mon Sep 17 00:00:00 2001 From: Jahziel Villasana-Espinoza Date: Fri, 19 Dec 2025 11:24:49 -0500 Subject: [PATCH] account for commands being dequeued in list query (#37505) **Related issue:** Resolves #36748 # 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) ## 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 --- changes/36748-win-mdm-cmd | 1 + server/datastore/mysql/mdm.go | 64 ++++++++++++++++++------- server/datastore/mysql/mdm_test.go | 41 ++++++++-------- server/datastore/mysql/microsoft_mdm.go | 2 +- 4 files changed, 68 insertions(+), 40 deletions(-) create mode 100644 changes/36748-win-mdm-cmd diff --git a/changes/36748-win-mdm-cmd b/changes/36748-win-mdm-cmd new file mode 100644 index 0000000000..b8097b03f8 --- /dev/null +++ b/changes/36748-win-mdm-cmd @@ -0,0 +1 @@ +- Updated query behind `fleetctl get mdm-commands` to correctly get completed Windows MDM commands. diff --git a/server/datastore/mysql/mdm.go b/server/datastore/mysql/mdm.go index a54aae9a90..b461748b10 100644 --- a/server/datastore/mysql/mdm.go +++ b/server/datastore/mysql/mdm.go @@ -246,12 +246,31 @@ WHERE if len(winUUIDs) > 0 { winParams = []any{winUUIDs} winStmt = ` -SELECT - mwe.host_uuid, - wq.command_uuid, - COALESCE(wcr.updated_at, wc.created_at) AS updated_at, - COALESCE(NULLIF(wcr.status_code, ''), '101') AS status, - CASE + SELECT + mwe.host_uuid, + wq.command_uuid, + wc.created_at AS updated_at, + '101' AS status, + 'pending' AS command_status, + wc.target_loc_uri AS request_type + FROM + windows_mdm_command_queue wq + JOIN mdm_windows_enrollments mwe ON mwe.id = wq.enrollment_id + JOIN windows_mdm_commands wc ON wc.command_uuid = wq.command_uuid + + WHERE + mwe.host_uuid IN (?) + + %[1]s + + UNION + + SELECT + mwe.host_uuid, + wcr.command_uuid, + COALESCE(wcr.updated_at, wc.created_at) AS updated_at, + COALESCE(NULLIF(wcr.status_code, ''), '101') AS status, + CASE WHEN COALESCE( NULLIF(wcr.status_code, ''), '101' @@ -261,7 +280,7 @@ SELECT NULLIF(wcr.status_code, ''), '101' ) AS UNSIGNED - ) BETWEEN 200 AND 399 THEN 'ran' + ) BETWEEN 200 AND 399 THEN 'ran' WHEN CAST( COALESCE( NULLIF(wcr.status_code, ''), @@ -269,17 +288,28 @@ SELECT ) AS UNSIGNED ) >= 400 THEN 'failed' END AS command_status, - wc.target_loc_uri AS request_type -FROM - windows_mdm_command_queue wq - JOIN mdm_windows_enrollments mwe ON mwe.id = wq.enrollment_id - JOIN windows_mdm_commands wc ON wc.command_uuid = wq.command_uuid - LEFT JOIN windows_mdm_command_results wcr ON wcr.command_uuid = wq.command_uuid - AND wcr.enrollment_id = wq.enrollment_id -WHERE - mwe.host_uuid IN (?)` + wc.target_loc_uri AS request_type + FROM + windows_mdm_command_results wcr + JOIN mdm_windows_enrollments mwe ON mwe.id = wcr.enrollment_id + JOIN windows_mdm_commands wc ON wc.command_uuid = wcr.command_uuid + WHERE + mwe.host_uuid IN (?) + + %[1]s + + ` + + var filterSQL string + if listOpts.Filters.RequestType != "" { + filterSQL = " AND wc.target_loc_uri = ?" + winParams = append(winParams, listOpts.Filters.RequestType, winUUIDs, listOpts.Filters.RequestType) + } else { + winParams = append(winParams, winUUIDs) + } + + winStmt = fmt.Sprintf(winStmt, filterSQL) - winStmt, winParams = addRequestTypeFilter(winStmt, &listOpts.Filters, winParams) winStmt, winParams, err = sqlx.In(winStmt, winParams...) if err != nil { return nil, nil, nil, ctxerr.Wrap(ctx, err, "prepare query to list MDM commands for Windows devices") diff --git a/server/datastore/mysql/mdm_test.go b/server/datastore/mysql/mdm_test.go index 499aa2fc3a..c280f98c12 100644 --- a/server/datastore/mysql/mdm_test.go +++ b/server/datastore/mysql/mdm_test.go @@ -188,28 +188,16 @@ func testMDMCommands(t *testing.T, ds *Datastore) { }) require.NoError(t, err) - ExecAdhocSQL(t, ds, func(tx sqlx.ExtContext) error { - res, err := tx.ExecContext( - ctx, - `INSERT INTO windows_mdm_responses (enrollment_id, raw_response) VALUES (?, ?)`, - windowsEnrollment.ID, - "", - ) - if err != nil { - return err - } - resID, _ := res.LastInsertId() - _, err = tx.ExecContext( - ctx, - `INSERT INTO windows_mdm_command_results (enrollment_id, command_uuid, raw_result, status_code, response_id) VALUES (?, ?, ?, ?, ?)`, - windowsEnrollment.ID, - winCmd.CommandUUID, - "", - "200", - resID, - ) - return err - }) + err = ds.MDMWindowsSaveResponse(ctx, windowsEnrollment.MDMDeviceID, fleet.EnrichedSyncML{ + SyncML: &fleet.SyncML{ + Raw: []byte(""), + }, + CmdRefUUIDToStatus: map[string]fleet.SyncMLCmd{winCmd.CommandUUID: { + Data: ptr.String("200"), + }}, + CmdRefUUIDs: []string{winCmd.CommandUUID}, + }, []string{}) + require.NoError(t, err) // we get both commands cmds, total, _, err = ds.ListMDMCommands( @@ -286,6 +274,7 @@ func testMDMCommands(t *testing.T, ds *Datastore) { identifier string commandStatus *fleet.MDMCommandStatusFilter expected []string + requestType string }{ { name: "windows host by hostname ambiguous with macOS host", @@ -300,6 +289,12 @@ func testMDMCommands(t *testing.T, ds *Datastore) { identifier: windowsH.UUID, expected: []string{winCmd.CommandUUID, winCmd2.CommandUUID, winCmd3.CommandUUID}, }, + { + name: "windows host by UUID, filter by request type", + identifier: windowsH.UUID, + expected: []string{winCmd2.CommandUUID}, + requestType: "./test/uri2", + }, { name: "windows host by hardware serial", identifier: windowsH.HardwareSerial, @@ -337,9 +332,11 @@ func testMDMCommands(t *testing.T, ds *Datastore) { Filters: fleet.MDMCommandFilters{ HostIdentifier: tc.identifier, CommandStatuses: commandStatuses, + RequestType: tc.requestType, }, }, ) + require.NoError(t, err) require.Len(t, cmds, len(tc.expected)) var got []string diff --git a/server/datastore/mysql/microsoft_mdm.go b/server/datastore/mysql/microsoft_mdm.go index 6995a01f87..6afc808b38 100644 --- a/server/datastore/mysql/microsoft_mdm.go +++ b/server/datastore/mysql/microsoft_mdm.go @@ -2467,7 +2467,7 @@ func (ds *Datastore) GetWindowsMDMCommandsForResending(ctx context.Context, fail return []*fleet.MDMWindowsCommand{}, nil } - stmt := `SELECT command_uuid, raw_command, target_loc_uri, created_at, updated_at + stmt := `SELECT command_uuid, raw_command, target_loc_uri, created_at, updated_at FROM windows_mdm_commands WHERE` args := []any{}