diff --git a/cmd/fleetctl/mdm_test.go b/cmd/fleetctl/mdm_test.go index 6002a7272a..7b19850376 100644 --- a/cmd/fleetctl/mdm_test.go +++ b/cmd/fleetctl/mdm_test.go @@ -205,7 +205,11 @@ func TestMDMRunCommand(t *testing.T) { } return hosts, nil } + winCmds := map[string]struct{}{} ds.MDMWindowsInsertCommandForHostsFunc = func(ctx context.Context, deviceIDs []string, cmd *fleet.MDMWindowsCommand) error { + // every command uuid is different + require.NotContains(t, winCmds, cmd.CommandUUID) + winCmds[cmd.CommandUUID] = struct{}{} return nil } ds.GetMDMWindowsBitLockerStatusFunc = func(ctx context.Context, host *fleet.Host) (*fleet.HostMDMDiskEncryption, error) { diff --git a/server/service/client_mdm.go b/server/service/client_mdm.go index 7a50f548dc..3195106415 100644 --- a/server/service/client_mdm.go +++ b/server/service/client_mdm.go @@ -18,6 +18,7 @@ import ( "path/filepath" "strings" + "github.com/beevik/etree" "github.com/fleetdm/fleet/v4/pkg/file" "github.com/fleetdm/fleet/v4/server/fleet" "github.com/google/uuid" @@ -334,7 +335,23 @@ func (c *Client) prepareWindowsMDMCommand(rawCmd []byte) ([]byte, error) { if _, err := fleet.ParseWindowsMDMCommand(rawCmd); err != nil { return nil, err } - return rawCmd, nil + + // ensure there's a CmdID with a random UUID value, we're manipulating + // the document this way to make sure we don't introduce any unintended + // changes to the command XML. + doc := etree.NewDocument() + if err := doc.ReadFromBytes(rawCmd); err != nil { + return nil, err + } + element := doc.FindElement("//CmdID") + // if we can't find a CmdID, just add one. + if element == nil { + root := doc.Root() + element = root.CreateElement("CmdID") + } + element.SetText(uuid.NewString()) + + return doc.WriteToBytes() } func (c *Client) prepareAppleMDMCommand(rawCmd []byte) ([]byte, error) { diff --git a/server/service/client_mdm_test.go b/server/service/client_mdm_test.go new file mode 100644 index 0000000000..748bf19e58 --- /dev/null +++ b/server/service/client_mdm_test.go @@ -0,0 +1,66 @@ +package service + +import ( + "testing" + + "github.com/beevik/etree" + "github.com/stretchr/testify/assert" +) + +func TestPrepareWindowsMDMCommand(t *testing.T) { + c := &Client{} + + validXML := []byte(` + + some-id + + + `) + + invalidCmdXML := []byte(` + + some-id + + + `) + + noCmdIDXML := []byte(` + + + + `) + + t.Run("Modifies valid CmdID", func(t *testing.T) { + modified, err := c.prepareWindowsMDMCommand(validXML) + assert.Nil(t, err) + + doc := etree.NewDocument() + err = doc.ReadFromBytes(modified) + assert.Nil(t, err) + + element := doc.FindElement("//CmdID") + assert.NotNil(t, element) + assert.NotEmpty(t, element.Text()) + }) + + t.Run("Adds CmdID if missing", func(t *testing.T) { + modified, err := c.prepareWindowsMDMCommand(noCmdIDXML) + assert.Nil(t, err) + + doc := etree.NewDocument() + err = doc.ReadFromBytes(modified) + assert.Nil(t, err) + + element := doc.FindElement("//CmdID") + assert.NotNil(t, element) + assert.NotEmpty(t, element.Text()) + }) + + t.Run("Returns error on invalid XML", func(t *testing.T) { + _, err := c.prepareWindowsMDMCommand(invalidCmdXML) + assert.NotNil(t, err) + + _, err = c.prepareWindowsMDMCommand([]byte("