mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 09:28:54 +00:00
osquery-perf: add support for Windows MDM enrollment and session management. (#17522)
This commit is contained in:
parent
ad5c0a90be
commit
c358bde87b
3 changed files with 168 additions and 67 deletions
1
changes/16120-add-windows-mdm-support-to-osquery-perf
Normal file
1
changes/16120-add-windows-mdm-support-to-osquery-perf
Normal file
|
|
@ -0,0 +1 @@
|
|||
* Added Windows MDM support to the `osquery-perf` host-simulation command.
|
||||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"embed"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
|
|
@ -27,6 +28,7 @@ import (
|
|||
"github.com/fleetdm/fleet/v4/pkg/mdm/mdmtest"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
apple_mdm "github.com/fleetdm/fleet/v4/server/mdm/apple"
|
||||
"github.com/fleetdm/fleet/v4/server/mdm/microsoft/syncml"
|
||||
"github.com/fleetdm/fleet/v4/server/ptr"
|
||||
"github.com/fleetdm/fleet/v4/server/service"
|
||||
"github.com/google/uuid"
|
||||
|
|
@ -111,6 +113,7 @@ type Stats struct {
|
|||
osqueryEnrollments int
|
||||
orbitEnrollments int
|
||||
mdmEnrollments int
|
||||
mdmSessions int
|
||||
distributedWrites int
|
||||
mdmCommandsReceived int
|
||||
distributedReads int
|
||||
|
|
@ -152,6 +155,12 @@ func (s *Stats) IncrementMDMEnrollments() {
|
|||
s.mdmEnrollments++
|
||||
}
|
||||
|
||||
func (s *Stats) IncrementMDMSessions() {
|
||||
s.l.Lock()
|
||||
defer s.l.Unlock()
|
||||
s.mdmSessions++
|
||||
}
|
||||
|
||||
func (s *Stats) IncrementDistributedWrites() {
|
||||
s.l.Lock()
|
||||
defer s.l.Unlock()
|
||||
|
|
@ -238,7 +247,7 @@ func (s *Stats) Log() {
|
|||
defer s.l.Unlock()
|
||||
|
||||
log.Printf(
|
||||
"uptime: %s, error rate: %.2f, osquery enrolls: %d, orbit enrolls: %d, mdm enrolls: %d, distributed/reads: %d, distributed/writes: %d, config requests: %d, result log requests: %d, mdm commands received: %d, config errors: %d, distributed/read errors: %d, distributed/write errors: %d, log result errors: %d, orbit errors: %d, desktop errors: %d, mdm errors: %d, buffered logs: %d",
|
||||
"uptime: %s, error rate: %.2f, osquery enrolls: %d, orbit enrolls: %d, mdm enrolls: %d, distributed/reads: %d, distributed/writes: %d, config requests: %d, result log requests: %d, mdm sessions initiated: %d, mdm commands received: %d, config errors: %d, distributed/read errors: %d, distributed/write errors: %d, log result errors: %d, orbit errors: %d, desktop errors: %d, mdm errors: %d, buffered logs: %d",
|
||||
time.Since(s.startTime).Round(time.Second),
|
||||
float64(s.errors)/float64(s.osqueryEnrollments),
|
||||
s.osqueryEnrollments,
|
||||
|
|
@ -248,6 +257,7 @@ func (s *Stats) Log() {
|
|||
s.distributedWrites,
|
||||
s.configRequests,
|
||||
s.resultLogRequests,
|
||||
s.mdmSessions,
|
||||
s.mdmCommandsReceived,
|
||||
s.configErrors,
|
||||
s.distributedReadErrors,
|
||||
|
|
@ -345,8 +355,11 @@ type agent struct {
|
|||
deviceAuthToken *string
|
||||
orbitNodeKey *string
|
||||
|
||||
// mdmClient simulates a device running the MDM protocol (client side).
|
||||
mdmClient *mdmtest.TestAppleMDMClient
|
||||
// macMDMClient and winMDMClient simulate a device running the MDM protocol
|
||||
// (client side) against Fleet MDM.
|
||||
macMDMClient *mdmtest.TestAppleMDMClient
|
||||
winMDMClient *mdmtest.TestWindowsMDMClient
|
||||
|
||||
// isEnrolledToMDM is true when the mdmDevice has enrolled.
|
||||
isEnrolledToMDM bool
|
||||
// isEnrolledToMDMMu protects isEnrolledToMDM.
|
||||
|
|
@ -428,18 +441,46 @@ func newAgent(
|
|||
if rand.Float64() <= emptySerialProb {
|
||||
serialNumber = ""
|
||||
}
|
||||
uuid := strings.ToUpper(uuid.New().String())
|
||||
var mdmClient *mdmtest.TestAppleMDMClient
|
||||
if rand.Float64() <= mdmProb {
|
||||
mdmClient = mdmtest.NewTestMDMClientAppleDirect(mdmtest.AppleEnrollInfo{
|
||||
SCEPChallenge: mdmSCEPChallenge,
|
||||
SCEPURL: serverAddress + apple_mdm.SCEPPath,
|
||||
MDMURL: serverAddress + apple_mdm.MDMPath,
|
||||
})
|
||||
// Have the osquery agent match the MDM device serial number and UUID.
|
||||
serialNumber = mdmClient.SerialNumber
|
||||
uuid = mdmClient.UUID
|
||||
hostUUID := strings.ToUpper(uuid.New().String())
|
||||
|
||||
// determine the simulated host's OS based on the template name (see
|
||||
// validTemplateNames below for the list of possible names, the OS is always
|
||||
// the part before the underscore). Note that it is the OS and not the
|
||||
// "normalized" platform, so "ubuntu" and not "linux", "macos" and not
|
||||
// "darwin".
|
||||
agentOS := strings.TrimRight(templates.Name(), ".tmpl")
|
||||
agentOS, _, _ = strings.Cut(agentOS, "_")
|
||||
|
||||
var (
|
||||
macMDMClient *mdmtest.TestAppleMDMClient
|
||||
winMDMClient *mdmtest.TestWindowsMDMClient
|
||||
)
|
||||
if rand.Float64() < mdmProb {
|
||||
switch agentOS {
|
||||
case "macos":
|
||||
macMDMClient = mdmtest.NewTestMDMClientAppleDirect(mdmtest.AppleEnrollInfo{
|
||||
SCEPChallenge: mdmSCEPChallenge,
|
||||
SCEPURL: serverAddress + apple_mdm.SCEPPath,
|
||||
MDMURL: serverAddress + apple_mdm.MDMPath,
|
||||
})
|
||||
// Have the osquery agent match the MDM device serial number and UUID.
|
||||
serialNumber = macMDMClient.SerialNumber
|
||||
hostUUID = macMDMClient.UUID
|
||||
|
||||
case "windows":
|
||||
// windows MDM enrollment requires orbit enrollment
|
||||
if deviceAuthToken == nil {
|
||||
deviceAuthToken = ptr.String(uuid.NewString())
|
||||
}
|
||||
// creating the Windows MDM client requires the orbit node key, but we
|
||||
// only get it after orbit enrollment. So here we just set the value to a
|
||||
// placeholder (non-nil) client, the actual usable client will be created
|
||||
// after orbit enrollment, and after receiving the enrollment
|
||||
// notification.
|
||||
winMDMClient = new(mdmtest.TestWindowsMDMClient)
|
||||
}
|
||||
}
|
||||
|
||||
return &agent{
|
||||
agentIndex: agentIndex,
|
||||
serverAddress: serverAddress,
|
||||
|
|
@ -453,17 +494,18 @@ func newAgent(
|
|||
liveQueryNoResultsProb: liveQueryNoResultsProb,
|
||||
templates: templates,
|
||||
deviceAuthToken: deviceAuthToken,
|
||||
os: strings.TrimRight(templates.Name(), ".tmpl"),
|
||||
os: agentOS,
|
||||
|
||||
EnrollSecret: enrollSecret,
|
||||
ConfigInterval: configInterval,
|
||||
LogInterval: logInterval,
|
||||
QueryInterval: queryInterval,
|
||||
MDMCheckInInterval: mdmCheckInInterval,
|
||||
UUID: uuid,
|
||||
UUID: hostUUID,
|
||||
SerialNumber: serialNumber,
|
||||
|
||||
mdmClient: mdmClient,
|
||||
macMDMClient: macMDMClient,
|
||||
winMDMClient: winMDMClient,
|
||||
disableScriptExec: disableScriptExec,
|
||||
disableFleetDesktop: disableFleetDesktop,
|
||||
loggerTLSMaxLines: loggerTLSMaxLines,
|
||||
|
|
@ -498,8 +540,15 @@ func (a *agent) isOrbit() bool {
|
|||
func (a *agent) runLoop(i int, onlyAlreadyEnrolled bool) {
|
||||
if a.isOrbit() {
|
||||
if err := a.orbitEnroll(); err != nil {
|
||||
// clean-up any placeholder mdm client that depended on orbit enrollment
|
||||
// - there's no concurrency yet for a given agent instance, runLoop is
|
||||
// the place where the goroutines will be started later on.
|
||||
a.winMDMClient = nil
|
||||
return
|
||||
}
|
||||
if a.winMDMClient != nil {
|
||||
a.winMDMClient = mdmtest.NewTestMDMClientWindowsProgramatic(a.serverAddress, *a.orbitNodeKey)
|
||||
}
|
||||
}
|
||||
|
||||
if err := a.enroll(i, onlyAlreadyEnrolled); err != nil {
|
||||
|
|
@ -519,15 +568,17 @@ func (a *agent) runLoop(i int, onlyAlreadyEnrolled bool) {
|
|||
go a.runOrbitLoop()
|
||||
}
|
||||
|
||||
if a.mdmClient != nil {
|
||||
if err := a.mdmClient.Enroll(); err != nil {
|
||||
log.Printf("MDM enroll failed: %s", err)
|
||||
// NOTE: the windows MDM client enrollment is only done after receiving a
|
||||
// notification via the config in the runOrbitLoop.
|
||||
if a.macMDMClient != nil {
|
||||
if err := a.macMDMClient.Enroll(); err != nil {
|
||||
log.Printf("macOS MDM enroll failed: %s", err)
|
||||
a.stats.IncrementMDMErrors()
|
||||
return
|
||||
}
|
||||
a.setMDMEnrolled()
|
||||
a.stats.IncrementMDMEnrollments()
|
||||
go a.runMDMLoop()
|
||||
go a.runMacosMDMLoop()
|
||||
}
|
||||
|
||||
//
|
||||
|
|
@ -756,6 +807,9 @@ func (a *agent) runOrbitLoop() {
|
|||
// fleet desktop polls for policy compliance every 5 minutes
|
||||
fleetDesktopPolicyTicker := time.Tick(5 * time.Minute)
|
||||
|
||||
const windowsMDMEnrollmentAttemptFrequency = time.Hour
|
||||
var lastEnrollAttempt time.Time
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-orbitConfigTicker:
|
||||
|
|
@ -769,6 +823,20 @@ func (a *agent) runOrbitLoop() {
|
|||
// that will simulate executing them.
|
||||
go a.execScripts(cfg.Notifications.PendingScriptExecutionIDs, orbitClient)
|
||||
}
|
||||
if cfg.Notifications.NeedsProgrammaticWindowsMDMEnrollment &&
|
||||
!a.mdmEnrolled() &&
|
||||
a.winMDMClient != nil &&
|
||||
time.Since(lastEnrollAttempt) > windowsMDMEnrollmentAttemptFrequency {
|
||||
lastEnrollAttempt = time.Now()
|
||||
if err := a.winMDMClient.Enroll(); err != nil {
|
||||
log.Printf("Windows MDM enroll failed: %s", err)
|
||||
a.stats.IncrementMDMErrors()
|
||||
} else {
|
||||
a.setMDMEnrolled()
|
||||
a.stats.IncrementMDMEnrollments()
|
||||
go a.runWindowsMDMLoop()
|
||||
}
|
||||
}
|
||||
case <-orbitTokenRemoteCheckTicker:
|
||||
if !a.disableFleetDesktop && tokenRotationEnabled {
|
||||
if err := deviceClient.CheckToken(*a.deviceAuthToken); err != nil {
|
||||
|
|
@ -806,20 +874,22 @@ func (a *agent) runOrbitLoop() {
|
|||
}
|
||||
}
|
||||
|
||||
func (a *agent) runMDMLoop() {
|
||||
func (a *agent) runMacosMDMLoop() {
|
||||
mdmCheckInTicker := time.Tick(a.MDMCheckInInterval)
|
||||
|
||||
for range mdmCheckInTicker {
|
||||
mdmCommandPayload, err := a.mdmClient.Idle()
|
||||
mdmCommandPayload, err := a.macMDMClient.Idle()
|
||||
if err != nil {
|
||||
log.Printf("MDM Idle request failed: %s", err)
|
||||
a.stats.IncrementMDMErrors()
|
||||
continue
|
||||
}
|
||||
a.stats.IncrementMDMSessions()
|
||||
|
||||
INNER_FOR_LOOP:
|
||||
for mdmCommandPayload != nil {
|
||||
a.stats.IncrementMDMCommandsReceived()
|
||||
mdmCommandPayload, err = a.mdmClient.Acknowledge(mdmCommandPayload.CommandUUID)
|
||||
mdmCommandPayload, err = a.macMDMClient.Acknowledge(mdmCommandPayload.CommandUUID)
|
||||
if err != nil {
|
||||
log.Printf("MDM Acknowledge request failed: %s", err)
|
||||
a.stats.IncrementMDMErrors()
|
||||
|
|
@ -829,6 +899,48 @@ func (a *agent) runMDMLoop() {
|
|||
}
|
||||
}
|
||||
|
||||
func (a *agent) runWindowsMDMLoop() {
|
||||
mdmCheckInTicker := time.Tick(a.MDMCheckInInterval)
|
||||
|
||||
for range mdmCheckInTicker {
|
||||
cmds, err := a.winMDMClient.StartManagementSession()
|
||||
if err != nil {
|
||||
log.Printf("MDM check-in start session request failed: %s", err)
|
||||
a.stats.IncrementMDMErrors()
|
||||
continue
|
||||
}
|
||||
a.stats.IncrementMDMSessions()
|
||||
|
||||
// send a successful ack for each command
|
||||
msgID, err := a.winMDMClient.GetCurrentMsgID()
|
||||
if err != nil {
|
||||
log.Printf("MDM get current MsgID failed: %s", err)
|
||||
a.stats.IncrementMDMErrors()
|
||||
continue
|
||||
}
|
||||
|
||||
for _, c := range cmds {
|
||||
a.stats.IncrementMDMCommandsReceived()
|
||||
|
||||
status := syncml.CmdStatusOK
|
||||
a.winMDMClient.AppendResponse(fleet.SyncMLCmd{
|
||||
XMLName: xml.Name{Local: fleet.CmdStatus},
|
||||
MsgRef: &msgID,
|
||||
CmdRef: &c.Cmd.CmdID.Value,
|
||||
Cmd: ptr.String(c.Verb),
|
||||
Data: &status,
|
||||
Items: nil,
|
||||
CmdID: fleet.CmdID{Value: uuid.NewString()},
|
||||
})
|
||||
}
|
||||
if _, err := a.winMDMClient.SendResponse(); err != nil {
|
||||
log.Printf("MDM send response request failed: %s", err)
|
||||
a.stats.IncrementMDMErrors()
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *agent) execScripts(execIDs []string, orbitClient *service.OrbitClient) {
|
||||
if a.scriptExecRunning.Swap(true) {
|
||||
// if Swap returns true, the goroutine was already running, exit
|
||||
|
|
@ -1244,21 +1356,6 @@ func (a *agent) randomQueryStats() []map[string]string {
|
|||
return stats
|
||||
}
|
||||
|
||||
var possibleMDMServerURLs = []string{
|
||||
"https://kandji.com/1",
|
||||
"https://jamf.com/1",
|
||||
"https://airwatch.com/1",
|
||||
"https://microsoft.com/1",
|
||||
"https://simplemdm.com/1",
|
||||
"https://example.com/1",
|
||||
"https://kandji.com/2",
|
||||
"https://jamf.com/2",
|
||||
"https://airwatch.com/2",
|
||||
"https://microsoft.com/2",
|
||||
"https://simplemdm.com/2",
|
||||
"https://example.com/2",
|
||||
}
|
||||
|
||||
// mdmMac returns the results for the `mdm` table query.
|
||||
//
|
||||
// If the host is enrolled via MDM it will return installed_from_dep as false
|
||||
|
|
@ -1273,7 +1370,12 @@ func (a *agent) mdmMac() []map[string]string {
|
|||
}
|
||||
}
|
||||
return []map[string]string{
|
||||
{"enrolled": "true", "server_url": a.mdmClient.EnrollInfo.MDMURL, "installed_from_dep": "false"},
|
||||
{
|
||||
"enrolled": "true",
|
||||
"server_url": a.macMDMClient.EnrollInfo.MDMURL,
|
||||
"installed_from_dep": "false",
|
||||
"payload_identifier": apple_mdm.FleetPayloadIdentifier,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1292,26 +1394,20 @@ func (a *agent) setMDMEnrolled() {
|
|||
}
|
||||
|
||||
func (a *agent) mdmWindows() []map[string]string {
|
||||
autopilot := rand.Intn(2) == 1
|
||||
ix := rand.Intn(len(possibleMDMServerURLs))
|
||||
serverURL := possibleMDMServerURLs[ix]
|
||||
providerID := fleet.MDMNameFromServerURL(serverURL)
|
||||
installType := "Microsoft Workstation"
|
||||
if rand.Intn(4) == 1 {
|
||||
installType = "Microsoft Server"
|
||||
if !a.mdmEnrolled() {
|
||||
return []map[string]string{
|
||||
// empty service url means not enrolled
|
||||
{"is_federated": "0", "discovery_service_url": "", "provider_id": "", "installation_type": "Client"},
|
||||
}
|
||||
}
|
||||
|
||||
rows := []map[string]string{
|
||||
{"key": "discovery_service_url", "value": serverURL},
|
||||
{"key": "installation_type", "value": installType},
|
||||
return []map[string]string{
|
||||
{
|
||||
"is_federated": "0",
|
||||
"discovery_service_url": a.serverAddress,
|
||||
"provider_id": fleet.WellKnownMDMFleet,
|
||||
"installation_type": "Client",
|
||||
},
|
||||
}
|
||||
if providerID != "" {
|
||||
rows = append(rows, map[string]string{"key": "provider_id", "value": providerID})
|
||||
}
|
||||
if autopilot {
|
||||
rows = append(rows, map[string]string{"key": "autopilot", "value": "true"})
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
var munkiIssues = func() []string {
|
||||
|
|
@ -1482,15 +1578,19 @@ func (a *agent) processQuery(name, query string) (
|
|||
case name == hostDetailQueryPrefix+"scheduled_query_stats":
|
||||
return true, a.randomQueryStats(), &statusOK, nil, nil
|
||||
case name == hostDetailQueryPrefix+"mdm":
|
||||
ss := fleet.OsqueryStatus(rand.Intn(2))
|
||||
if ss == fleet.StatusOK {
|
||||
ss := statusOK
|
||||
if rand.Intn(10) > 0 { // 90% success
|
||||
results = a.mdmMac()
|
||||
} else {
|
||||
ss = statusNotOK
|
||||
}
|
||||
return true, results, &ss, nil, nil
|
||||
case name == hostDetailQueryPrefix+"mdm_windows":
|
||||
ss := fleet.OsqueryStatus(rand.Intn(2))
|
||||
if ss == fleet.StatusOK {
|
||||
ss := statusOK
|
||||
if rand.Intn(10) > 0 { // 90% success
|
||||
results = a.mdmWindows()
|
||||
} else {
|
||||
ss = statusNotOK
|
||||
}
|
||||
return true, results, &ss, nil, nil
|
||||
case name == hostDetailQueryPrefix+"munki_info":
|
||||
|
|
@ -1533,7 +1633,7 @@ func (a *agent) processQuery(name, query string) (
|
|||
ss := fleet.OsqueryStatus(rand.Intn(2))
|
||||
if ss == fleet.StatusOK {
|
||||
switch a.os {
|
||||
case "ubuntu_22.04":
|
||||
case "ubuntu":
|
||||
results = ubuntuSoftware
|
||||
}
|
||||
}
|
||||
|
|
@ -1779,7 +1879,7 @@ func main() {
|
|||
// osquery-perf will send log requests with results only if there are scheduled queries configured AND it's their time to run.
|
||||
logInterval = flag.Duration("logger_tls_period", 10*time.Second, "Interval for scheduled queries log requests")
|
||||
queryInterval = flag.Duration("query_interval", 10*time.Second, "Interval for live query requests")
|
||||
mdmCheckInInterval = flag.Duration("mdm_check_in_interval", 10*time.Second, "Interval for performing MDM check ins")
|
||||
mdmCheckInInterval = flag.Duration("mdm_check_in_interval", 10*time.Second, "Interval for performing MDM check-ins (applies to both macOS and Windows)")
|
||||
onlyAlreadyEnrolled = flag.Bool("only_already_enrolled", false, "Only start agents that are already enrolled")
|
||||
nodeKeyFile = flag.String("node_key_file", "", "File with node keys to use")
|
||||
|
||||
|
|
@ -1805,8 +1905,8 @@ func main() {
|
|||
osTemplates = flag.String("os_templates", "macos_14.1.2", fmt.Sprintf("Comma separated list of host OS templates to use and optionally their host count separated by ':' (any of %v, with or without the .tmpl extension)", allowedTemplateNames))
|
||||
emptySerialProb = flag.Float64("empty_serial_prob", 0.1, "Probability of a host having no serial number [0, 1]")
|
||||
|
||||
mdmProb = flag.Float64("mdm_prob", 0.0, "Probability of a host enrolling via MDM (for macOS) [0, 1]")
|
||||
mdmSCEPChallenge = flag.String("mdm_scep_challenge", "", "SCEP challenge to use when running MDM enroll")
|
||||
mdmProb = flag.Float64("mdm_prob", 0.0, "Probability of a host enrolling via Fleet MDM (applies for macOS and Windows hosts, implies orbit enrollment on Windows) [0, 1]")
|
||||
mdmSCEPChallenge = flag.String("mdm_scep_challenge", "", "SCEP challenge to use when running macOS MDM enroll")
|
||||
|
||||
liveQueryFailProb = flag.Float64("live_query_fail_prob", 0.0, "Probability of a live query failing execution in the host")
|
||||
liveQueryNoResultsProb = flag.Float64("live_query_no_results_prob", 0.2, "Probability of a live query returning no results")
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@ var hostDetailQueries = map[string]DetailQuery{
|
|||
"os_version_windows": {
|
||||
Query: `
|
||||
SELECT os.name, r.data as display_version, k.version
|
||||
FROM
|
||||
FROM
|
||||
registry r,
|
||||
os_version os,
|
||||
kernel_info k
|
||||
|
|
|
|||
Loading…
Reference in a new issue