mirror of
https://github.com/fleetdm/fleet
synced 2026-05-23 08:58:41 +00:00
Adding temporary MS-MDM implementation (#12852)
This is the prototype implementation for MS-MDM. Most of the code here will change in the upcoming sprints once https://github.com/fleetdm/fleet/issues/12839, https://github.com/fleetdm/fleet/issues/12840, https://github.com/fleetdm/fleet/issues/12841 get implemented. - [ ] Changes file added for user-visible changes in `changes/` or `orbit/changes/`. See [Changes files](https://fleetdm.com/docs/contributing/committing-changes#changes-files) for more information. - [X] Added/updated tests - [X] Manual QA for all new/changed functionality
This commit is contained in:
parent
d6f51f893c
commit
2c02ab3be5
6 changed files with 521 additions and 2 deletions
|
|
@ -823,3 +823,79 @@ type MDMWindowsEnrolledDevice struct {
|
|||
func (e MDMWindowsEnrolledDevice) AuthzType() string {
|
||||
return "mdm_windows"
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
/// Microsoft MS-MDM message
|
||||
|
||||
type SyncMLMessage struct {
|
||||
XMLinfo string `xml:"xmlns,attr"`
|
||||
Header SyncMLHeader `xml:"SyncHdr"`
|
||||
Body SyncMLBody `xml:"SyncBody"`
|
||||
}
|
||||
|
||||
// SyncML XML Parsing Types - This needs to be improved
|
||||
type SyncMLHeader struct {
|
||||
DTD string `xml:"VerDTD"`
|
||||
Version string `xml:"VerProto"`
|
||||
SessionID int `xml:"SessionID"`
|
||||
MsgID int `xml:"MsgID"`
|
||||
Target string `xml:"Target>LocURI"`
|
||||
Source string `xml:"Source>LocURI"`
|
||||
MaxMsgSize int `xml:"Meta>A:MaxMsgSize"`
|
||||
}
|
||||
|
||||
type SyncMLCommandMeta struct {
|
||||
XMLinfo string `xml:"xmlns,attr"`
|
||||
Type string `xml:"Type"`
|
||||
}
|
||||
|
||||
type SyncMLCommandItem struct {
|
||||
Meta SyncMLCommandMeta `xml:"Meta"`
|
||||
Source string `xml:"Source>LocURI"`
|
||||
Data string `xml:"Data"`
|
||||
}
|
||||
|
||||
type SyncMLCommand struct {
|
||||
XMLName xml.Name
|
||||
CmdID int `xml:",omitempty"`
|
||||
MsgRef string `xml:",omitempty"`
|
||||
CmdRef string `xml:",omitempty"`
|
||||
Cmd string `xml:",omitempty"`
|
||||
Target string `xml:"Target>LocURI"`
|
||||
Source string `xml:"Source>LocURI"`
|
||||
Data string `xml:",omitempty"`
|
||||
Item []SyncMLCommandItem `xml:",any"`
|
||||
}
|
||||
|
||||
type SyncMLBody struct {
|
||||
Item []SyncMLCommand `xml:",any"`
|
||||
}
|
||||
|
||||
// IsValidSyncMLMsg checks for required fields in the SyncML message
|
||||
func (req *SyncMLMessage) IsValidSyncMLMsg() error {
|
||||
if req == nil {
|
||||
return errors.New("invalid SyncML message: nil")
|
||||
}
|
||||
|
||||
if len(req.Header.Version) == 0 {
|
||||
return errors.New("invalid SyncML message: Version")
|
||||
}
|
||||
|
||||
if len(req.Header.Target) == 0 {
|
||||
return errors.New("invalid SyncML message: Target")
|
||||
}
|
||||
|
||||
if req.Header.SessionID == 0 {
|
||||
return errors.New("invalid SyncML message: SessionID")
|
||||
}
|
||||
|
||||
if req.Header.MsgID == 0 {
|
||||
return errors.New("invalid SyncML message: SessionID")
|
||||
}
|
||||
|
||||
if len(req.Body.Item) == 0 {
|
||||
return errors.New("invalid SyncML message: Item")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -777,4 +777,7 @@ type Service interface {
|
|||
// SignMDMMicrosoftClientCSR returns a signed certificate from the client certificate signing request and the
|
||||
// certificate fingerprint. The certificate common name should be passed in the subject parameter.
|
||||
SignMDMMicrosoftClientCSR(ctx context.Context, subject string, csr *x509.CertificateRequest) ([]byte, string, error)
|
||||
|
||||
// GetMDMWindowsManagementResponse returns a valid SyncML response message
|
||||
GetMDMWindowsManagementResponse(ctx context.Context, reqSyncML *SyncMLMessage) (*string, error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,6 +135,9 @@ const (
|
|||
// HTTP Content Type for SOAP responses
|
||||
SoapContentType = "application/soap+xml; charset=utf-8"
|
||||
|
||||
// HTTP Content Type for SyncML MDM responses
|
||||
SyncMLContentType = "application/vnd.syncml.dm+xml"
|
||||
|
||||
// Minimal Key Length for SHA1WithRSA encryption
|
||||
PolicyMinKeyLength = "2048"
|
||||
|
||||
|
|
@ -228,6 +231,10 @@ const (
|
|||
|
||||
// Login related query param expected by STS Auth endpoint
|
||||
STSLoginHint = "login_hint"
|
||||
|
||||
// Alert Command IDs
|
||||
DeviceUnenrollmentID = "1226"
|
||||
HostInitMessageID = "1201"
|
||||
)
|
||||
|
||||
func ResolveWindowsMDMDiscovery(serverURL string) (string, error) {
|
||||
|
|
|
|||
|
|
@ -610,6 +610,10 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
|
|||
// This endpoint is authenticated using the BinarySecurityToken header field
|
||||
neWindowsMDM.POST(microsoft_mdm.MDE2EnrollPath, mdmMicrosoftEnrollEndpoint, SoapRequestContainer{})
|
||||
|
||||
// This endpoint is unauthenticated for now
|
||||
// It should be authenticated through TLS headers once proper implementation is in place
|
||||
neWindowsMDM.POST(microsoft_mdm.MDE2ManagementPath, mdmMicrosoftManagementEndpoint, SyncMLReqMsgContainer{})
|
||||
|
||||
ne.POST("/api/fleet/orbit/enroll", enrollOrbitEndpoint, EnrollOrbitRequest{})
|
||||
|
||||
// For some reason osquery does not provide a node key with the block data.
|
||||
|
|
|
|||
|
|
@ -6170,6 +6170,36 @@ func (s *integrationMDMTestSuite) TestInvalidGetAuthRequest() {
|
|||
require.Contains(t, resContent, "forbidden")
|
||||
}
|
||||
|
||||
func (s *integrationMDMTestSuite) TestValidSyncMLRequestNoAuth() {
|
||||
t := s.T()
|
||||
|
||||
// Target Endpoint URL for the management endpoint
|
||||
targetEndpointURL := microsoft_mdm.MDE2ManagementPath
|
||||
|
||||
// Preparing the SyncML request
|
||||
requestBytes, err := s.newSyncMLSessionMsg(targetEndpointURL)
|
||||
require.NoError(t, err)
|
||||
|
||||
resp := s.DoRaw("POST", targetEndpointURL, requestBytes, http.StatusOK)
|
||||
|
||||
resBytes, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Contains(t, resp.Header["Content-Type"], microsoft_mdm.SyncMLContentType)
|
||||
|
||||
// Checking if SyncML response can be unmarshalled to an golang type
|
||||
var xmlType interface{}
|
||||
err = xml.Unmarshal(resBytes, &xmlType)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Checking if SOAP response contains a valid RequestSecurityTokenResponseCollection message
|
||||
resSoapMsg := string(resBytes)
|
||||
require.True(t, s.isXMLTagPresent("SyncHdr", resSoapMsg))
|
||||
require.True(t, s.isXMLTagPresent("SyncBody", resSoapMsg))
|
||||
require.True(t, s.isXMLTagContentPresent("Exec", resSoapMsg))
|
||||
require.True(t, s.isXMLTagContentPresent("Add", resSoapMsg))
|
||||
}
|
||||
|
||||
// ///////////////////////////////////////////////////////////////////////////
|
||||
// Common helpers
|
||||
|
||||
|
|
@ -6356,3 +6386,76 @@ func (s *integrationMDMTestSuite) newSecurityTokenMsg(encodedBinToken string, de
|
|||
|
||||
return requestBytes, nil
|
||||
}
|
||||
|
||||
// TODO: Add support to add custom DeviceID when DeviceAuth is in place
|
||||
func (s *integrationMDMTestSuite) newSyncMLSessionMsg(managementUrl string) ([]byte, error) {
|
||||
if len(managementUrl) == 0 {
|
||||
return nil, errors.New("managementUrl is empty")
|
||||
}
|
||||
|
||||
return []byte(`
|
||||
<SyncML xmlns="SYNCML:SYNCML1.2">
|
||||
<SyncHdr>
|
||||
<VerDTD>1.2</VerDTD>
|
||||
<VerProto>DM/1.2</VerProto>
|
||||
<SessionID>1</SessionID>
|
||||
<MsgID>1</MsgID>
|
||||
<Target>
|
||||
<LocURI>` + managementUrl + `</LocURI>
|
||||
</Target>
|
||||
<Source>
|
||||
<LocURI>DB257C3A08778F4FB61E2749066C1F27</LocURI>
|
||||
</Source>
|
||||
</SyncHdr>
|
||||
<SyncBody>
|
||||
<Alert>
|
||||
<CmdID>2</CmdID>
|
||||
<Data>1201</Data>
|
||||
</Alert>
|
||||
<Alert>
|
||||
<CmdID>3</CmdID>
|
||||
<Data>1224</Data>
|
||||
<Item>
|
||||
<Meta>
|
||||
<Type xmlns="syncml:metinf">com.microsoft/MDM/LoginStatus</Type>
|
||||
</Meta>
|
||||
<Data>user</Data>
|
||||
</Item>
|
||||
</Alert>
|
||||
<Replace>
|
||||
<CmdID>4</CmdID>
|
||||
<Item>
|
||||
<Source>
|
||||
<LocURI>./DevInfo/DevId</LocURI>
|
||||
</Source>
|
||||
<Data>DB257C3A08778F4FB61E2749066C1F27</Data>
|
||||
</Item>
|
||||
<Item>
|
||||
<Source>
|
||||
<LocURI>./DevInfo/Man</LocURI>
|
||||
</Source>
|
||||
<Data>VMware, Inc.</Data>
|
||||
</Item>
|
||||
<Item>
|
||||
<Source>
|
||||
<LocURI>./DevInfo/Mod</LocURI>
|
||||
</Source>
|
||||
<Data>VMware7,1</Data>
|
||||
</Item>
|
||||
<Item>
|
||||
<Source>
|
||||
<LocURI>./DevInfo/DmV</LocURI>
|
||||
</Source>
|
||||
<Data>1.3</Data>
|
||||
</Item>
|
||||
<Item>
|
||||
<Source>
|
||||
<LocURI>./DevInfo/Lang</LocURI>
|
||||
</Source>
|
||||
<Data>en-US</Data>
|
||||
</Item>
|
||||
</Replace>
|
||||
<Final/>
|
||||
</SyncBody>
|
||||
</SyncML>`), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,11 +9,13 @@ import (
|
|||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html"
|
||||
"html/template"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
||||
|
|
@ -67,7 +69,7 @@ func (r SoapResponseContainer) error() error { return r.Err }
|
|||
func (r SoapResponseContainer) hijackRender(ctx context.Context, w http.ResponseWriter) {
|
||||
xmlRes, err := xml.MarshalIndent(r.Data, "", "\t")
|
||||
if err != nil {
|
||||
logging.WithExtras(ctx, "Windows MDM SoapResponseContainer", err)
|
||||
logging.WithExtras(ctx, "error with SoapResponseContainer", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
|
@ -82,6 +84,56 @@ func (r SoapResponseContainer) hijackRender(ctx context.Context, w http.Response
|
|||
}
|
||||
}
|
||||
|
||||
type SyncMLReqMsgContainer struct {
|
||||
Data *fleet.SyncMLMessage
|
||||
Params url.Values
|
||||
Err error
|
||||
}
|
||||
|
||||
// MDM SOAP request decoder
|
||||
func (req *SyncMLReqMsgContainer) DecodeBody(ctx context.Context, r io.Reader, u url.Values) error {
|
||||
// Reading the request bytes
|
||||
reqBytes, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "reading SyncML message request")
|
||||
}
|
||||
|
||||
// Set the request parameters
|
||||
req.Params = u
|
||||
|
||||
// Handle empty body scenario
|
||||
req.Data = &fleet.SyncMLMessage{}
|
||||
|
||||
if len(reqBytes) != 0 {
|
||||
// Unmarshal the XML data from the request into the SoapRequest struct
|
||||
err = xml.Unmarshal(reqBytes, &req.Data)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "unmarshalling SyncML message request")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type SyncMLResponseMsgContainer struct {
|
||||
Data *string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (r SyncMLResponseMsgContainer) error() error { return r.Err }
|
||||
|
||||
// hijackRender writes the response header and the RAW HTML output
|
||||
func (r SyncMLResponseMsgContainer) hijackRender(ctx context.Context, w http.ResponseWriter) {
|
||||
resData := []byte(*r.Data + "\n")
|
||||
|
||||
w.Header().Set("Content-Type", mdm.SyncMLContentType)
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(resData)))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if n, err := w.Write(resData); err != nil {
|
||||
logging.WithExtras(ctx, "err", err, "written", n)
|
||||
}
|
||||
}
|
||||
|
||||
type MDMAuthContainer struct {
|
||||
Data *string
|
||||
Err error
|
||||
|
|
@ -764,6 +816,33 @@ func mdmMicrosoftEnrollEndpoint(ctx context.Context, request interface{}, svc fl
|
|||
}, nil
|
||||
}
|
||||
|
||||
// mdmMicrosoftManagementEndpoint handles the OMA DM management sessions
|
||||
// It receives a SyncML message with protocol commands, it process the commands and responds with a
|
||||
// SyncML message with protocol commands results and more protocol commands for the calling host
|
||||
// Note: This logic needs to be improved with better SyncML message parsing, better message tracking
|
||||
// and better security authentication (done through TLS and in-message hash)
|
||||
func mdmMicrosoftManagementEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (errorer, error) {
|
||||
reqSyncML := request.(*SyncMLReqMsgContainer).Data
|
||||
|
||||
// Checking first if incoming SyncML message is valid and returning error if this is not the case
|
||||
if err := reqSyncML.IsValidSyncMLMsg(); err != nil {
|
||||
soapFault := svc.GetAuthorizedSoapFault(ctx, mdm.SoapErrorMessageFormat, mdm_types.MDEFault, err)
|
||||
return getSoapResponseFault(strconv.Itoa(reqSyncML.Header.MsgID), soapFault), nil
|
||||
}
|
||||
|
||||
// Getting the RequestSecurityTokenResponseCollection message
|
||||
resSyncML, err := svc.GetMDMWindowsManagementResponse(ctx, reqSyncML)
|
||||
if err != nil {
|
||||
soapFault := svc.GetAuthorizedSoapFault(ctx, mdm.SoapErrorMessageFormat, mdm_types.MDEEnrollment, err)
|
||||
return getSoapResponseFault(strconv.Itoa(reqSyncML.Header.MsgID), soapFault), nil
|
||||
}
|
||||
|
||||
return SyncMLResponseMsgContainer{
|
||||
Data: resSyncML,
|
||||
Err: nil,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// authBinarySecurityToken checks if the provided token is valid
|
||||
func (svc *Service) authBinarySecurityToken(ctx context.Context, authToken *fleet.HeaderBinarySecurityToken) (string, error) {
|
||||
if authToken == nil {
|
||||
|
|
@ -974,7 +1053,7 @@ func (svc *Service) GetMDMWindowsEnrollResponse(ctx context.Context, secTokenMsg
|
|||
return nil, ctxerr.Wrap(ctx, err, "device enroll check")
|
||||
}
|
||||
|
||||
// Getting the the device provisioning information in the form of a WapProvisioningDoc
|
||||
// Getting the device provisioning information in the form of a WapProvisioningDoc
|
||||
deviceProvisioning, err := svc.getDeviceProvisioningInformation(ctx, secTokenMsg)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "device provisioning information")
|
||||
|
|
@ -1006,6 +1085,152 @@ func (svc *Service) GetMDMWindowsEnrollResponse(ctx context.Context, secTokenMsg
|
|||
return &secTokenResponseCollectionMsg, nil
|
||||
}
|
||||
|
||||
// GetMDMWindowsManagementResponse returns a valid SyncML response message
|
||||
func (svc *Service) GetMDMWindowsManagementResponse(ctx context.Context, reqSyncML *fleet.SyncMLMessage) (*string, error) {
|
||||
if reqSyncML == nil {
|
||||
return nil, fleet.NewInvalidArgumentError("syncml req message", "message is not present")
|
||||
}
|
||||
|
||||
// TODO: The following logic should happen here
|
||||
// - TLS based auth
|
||||
// - Device auth based on Source/LocURI DeviceID information
|
||||
// (this should be present on Enrollment DB)
|
||||
// - Processing of incoming protocol commands (Alerts mostly
|
||||
// - MS-MDM session management
|
||||
// - Inclusion of queued protocol commands should be performed here
|
||||
// - Tracking of message acknowledgements through Message queue
|
||||
|
||||
// Getting the management response message
|
||||
resSyncMLmsg, err := svc.getManagementResponse(ctx, reqSyncML)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "device provisioning information")
|
||||
}
|
||||
|
||||
// Token is authorized
|
||||
svc.authz.SkipAuthorization(ctx)
|
||||
|
||||
return resSyncMLmsg, nil
|
||||
}
|
||||
|
||||
func (svc *Service) getManagementResponse(ctx context.Context, reqSyncML *fleet.SyncMLMessage) (*string, error) {
|
||||
if reqSyncML == nil {
|
||||
return nil, fleet.NewInvalidArgumentError("syncml req message", "message is not present")
|
||||
}
|
||||
|
||||
// cmdID tracks the command sequence
|
||||
cmdID := 0
|
||||
|
||||
// Retrieve the MessageID from the syncml req body
|
||||
deviceID := reqSyncML.Header.Source
|
||||
|
||||
// Retrieve the sessionID from the syncml req body
|
||||
sessionID := reqSyncML.Header.SessionID
|
||||
|
||||
// Retrieve the msgID from the syncml req body
|
||||
msgID := reqSyncML.Header.MsgID
|
||||
|
||||
// Getting the management URL message content
|
||||
appCfg, err := svc.ds.AppConfig(ctx)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err)
|
||||
}
|
||||
|
||||
urlManagementEndpoint, err := mdm.ResolveWindowsMDMManagement(appCfg.ServerSettings.ServerURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Checking the SyncML message types
|
||||
var response string
|
||||
if isSessionInitializationMessage(reqSyncML.Body) {
|
||||
// Create response payload - MDM SyncML configuration profiles commands will be enforced here
|
||||
response = `
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<SyncML xmlns="SYNCML:SYNCML1.2">
|
||||
<SyncHdr>
|
||||
<VerDTD>1.2</VerDTD>
|
||||
<VerProto>DM/1.2</VerProto>
|
||||
<SessionID>` + strconv.Itoa(sessionID) + `</SessionID>
|
||||
<MsgID>` + strconv.Itoa(msgID) + `</MsgID>
|
||||
<Target>
|
||||
<LocURI>` + deviceID + `</LocURI>
|
||||
</Target>
|
||||
<Source>
|
||||
<LocURI>` + urlManagementEndpoint + `</LocURI>
|
||||
</Source>
|
||||
</SyncHdr>
|
||||
<SyncBody>
|
||||
<Status>
|
||||
<CmdID>` + getNextCmdID(&cmdID) + `</CmdID>
|
||||
<MsgRef>` + strconv.Itoa(msgID) + `</MsgRef>
|
||||
<CmdRef>0</CmdRef>
|
||||
<Cmd>SyncHdr</Cmd>
|
||||
<Data>200</Data>
|
||||
</Status>
|
||||
<Status>
|
||||
<CmdID>` + getNextCmdID(&cmdID) + `</CmdID>
|
||||
<MsgRef>` + strconv.Itoa(msgID) + `</MsgRef>
|
||||
<CmdRef>2</CmdRef>
|
||||
<Cmd>Alert</Cmd>
|
||||
<Data>200</Data>
|
||||
</Status>
|
||||
<Status>
|
||||
<CmdID>` + getNextCmdID(&cmdID) + `</CmdID>
|
||||
<MsgRef>` + strconv.Itoa(msgID) + `</MsgRef>
|
||||
<CmdRef>3</CmdRef>
|
||||
<Cmd>Alert</Cmd>
|
||||
<Data>200</Data>
|
||||
</Status>
|
||||
<Status>
|
||||
<CmdID>` + getNextCmdID(&cmdID) + `</CmdID>
|
||||
<MsgRef>` + strconv.Itoa(msgID) + `</MsgRef>
|
||||
<CmdRef>4</CmdRef>
|
||||
<Cmd>Replace</Cmd>
|
||||
<Data>200</Data>
|
||||
</Status>
|
||||
` + svc.getConfigProfilesToEnforce(ctx, &cmdID) + `
|
||||
<Final />
|
||||
</SyncBody>
|
||||
</SyncML>`
|
||||
} else {
|
||||
// Acknowledge SyncML messages sent by host
|
||||
response = `
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<SyncML xmlns="SYNCML:SYNCML1.2">
|
||||
<SyncHdr>
|
||||
<VerDTD>1.2</VerDTD>
|
||||
<VerProto>DM/1.2</VerProto>
|
||||
<SessionID>` + strconv.Itoa(sessionID) + `</SessionID>
|
||||
<MsgID>` + strconv.Itoa(msgID) + `</MsgID>
|
||||
<Target>
|
||||
<LocURI>` + deviceID + `</LocURI>
|
||||
</Target>
|
||||
<Source>
|
||||
<LocURI>` + urlManagementEndpoint + `</LocURI>
|
||||
</Source>
|
||||
</SyncHdr>
|
||||
<SyncBody>
|
||||
<Status>
|
||||
<CmdID>` + getNextCmdID(&cmdID) + `</CmdID>
|
||||
<MsgRef>` + strconv.Itoa(msgID) + `</MsgRef>
|
||||
<CmdRef>0</CmdRef>
|
||||
<Cmd>SyncHdr</Cmd>
|
||||
<Data>200</Data>
|
||||
</Status>
|
||||
<Final />
|
||||
</SyncBody>
|
||||
</SyncML>`
|
||||
}
|
||||
|
||||
// Create a replacer to replace both "\n" and "\t"
|
||||
replacer := strings.NewReplacer("\n", "", "\t", "")
|
||||
|
||||
// Use the replacer on the string representation of xmlContent
|
||||
responseRaw := replacer.Replace(response)
|
||||
|
||||
return &responseRaw, nil
|
||||
}
|
||||
|
||||
// removeWindowsDeviceIfAlreadyMDMEnrolled removes the device if already MDM enrolled
|
||||
// HW DeviceID is used to check the list of enrolled devices
|
||||
func (svc *Service) removeWindowsDeviceIfAlreadyMDMEnrolled(ctx context.Context, secTokenMsg *fleet.RequestSecurityToken) error {
|
||||
|
|
@ -1230,3 +1455,104 @@ func (svc *Service) SignMDMMicrosoftClientCSR(ctx context.Context, subject strin
|
|||
|
||||
return cert, fpHex, nil
|
||||
}
|
||||
|
||||
func (svc *Service) getConfigProfilesToEnforce(ctx context.Context, commandID *int) string {
|
||||
// fleetctl package
|
||||
// --fleet-url=https://dashboard.fleetdm.ngrok.dev
|
||||
// --enroll-secret=6EM269jFhXlEcWn9nr/kCQGNa5sIh3GM
|
||||
|
||||
// Getting the management URL
|
||||
appCfg, _ := svc.ds.AppConfig(ctx)
|
||||
fleetEnrollUrl := appCfg.ServerSettings.ServerURL
|
||||
|
||||
// Getting the global enrollment secret
|
||||
var globalEnrollSecret string
|
||||
secrets, err := svc.ds.GetEnrollSecrets(ctx, nil)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, secret := range secrets {
|
||||
if secret.TeamID == nil {
|
||||
globalEnrollSecret = secret.Secret
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// keeping the same GUID will prevent the MSI to be installed multiple times - it will be
|
||||
// installed only the first time the message is issued.
|
||||
// FleetURL and FleetSecret properties are passed to the Fleet MSI
|
||||
// See here for more information: https://learn.microsoft.com/en-us/windows/win32/msi/command-line-options
|
||||
installCommandPayload := `<MsiInstallJob id="{f5645004-3214-46ea-92c2-48835689da06}">
|
||||
<Product Version="1.0.0.0">
|
||||
<Download>
|
||||
<ContentURLList>
|
||||
<ContentURL>https://download.fleetdm.com/fleetd-base.msi</ContentURL>
|
||||
</ContentURLList>
|
||||
</Download>
|
||||
<Validation>
|
||||
<FileHash>7D127BA8F8CC5937DB3052E2632D672120217D910E271A58565BBA780ED8F05C</FileHash>
|
||||
</Validation>
|
||||
<Enforcement>
|
||||
<CommandLine>/quiet FleetURL="` + fleetEnrollUrl + `" FleetSecret="` + globalEnrollSecret + `"</CommandLine>
|
||||
<TimeOut>10</TimeOut>
|
||||
<RetryCount>1</RetryCount>
|
||||
<RetryInterval>5</RetryInterval>
|
||||
</Enforcement>
|
||||
</Product>
|
||||
</MsiInstallJob>`
|
||||
|
||||
newCmds := `<Add>
|
||||
<CmdID>` + getNextCmdID(commandID) + `</CmdID>
|
||||
<Item>
|
||||
<Target>
|
||||
<LocURI>./Device/Vendor/MSFT/EnterpriseDesktopAppManagement/MSI/%7Bf5645004-3214-46ea-92c2-48835689da06%7D/DownloadInstall</LocURI>
|
||||
</Target>
|
||||
</Item>
|
||||
</Add>
|
||||
<Exec>
|
||||
<CmdID>` + getNextCmdID(commandID) + `</CmdID>
|
||||
<Item>
|
||||
<Target>
|
||||
<LocURI>./Device/Vendor/MSFT/EnterpriseDesktopAppManagement/MSI/%7Bf5645004-3214-46ea-92c2-48835689da06%7D/DownloadInstall</LocURI>
|
||||
</Target>
|
||||
<Data>` + html.EscapeString(installCommandPayload) + `</Data>
|
||||
<Meta>
|
||||
<Type xmlns="syncml:metinf">text/plain</Type>
|
||||
<Format xmlns="syncml:metinf">xml</Format>
|
||||
</Meta>
|
||||
</Item>
|
||||
</Exec>`
|
||||
|
||||
return newCmds
|
||||
}
|
||||
|
||||
// getNextCmdID returns the next command ID
|
||||
func getNextCmdID(i *int) string {
|
||||
*i++
|
||||
return strconv.Itoa(*i)
|
||||
}
|
||||
|
||||
// Checks if body contains a DM device unrollment SyncML message
|
||||
func isDeviceUnenrollmentMessage(body fleet.SyncMLBody) bool {
|
||||
for _, element := range body.Item {
|
||||
if element.Data == mdm.DeviceUnenrollmentID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Checks if body contains a DM session initialization SyncML message sent by device
|
||||
func isSessionInitializationMessage(body fleet.SyncMLBody) bool {
|
||||
isUnenrollMessage := isDeviceUnenrollmentMessage(body)
|
||||
|
||||
for _, element := range body.Item {
|
||||
if element.Data == mdm.HostInitMessageID && !isUnenrollMessage {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue