2021-09-22 20:18:55 +00:00
package main
import (
"bytes"
2022-06-23 20:44:45 +00:00
"compress/bzip2"
2023-08-23 22:31:47 +00:00
cryptorand "crypto/rand"
2021-09-22 20:18:55 +00:00
"crypto/tls"
2021-10-14 13:09:58 +00:00
"embed"
2023-08-23 22:31:47 +00:00
"encoding/base64"
2021-09-22 20:18:55 +00:00
"encoding/json"
2024-03-13 13:29:25 +00:00
"encoding/xml"
2021-11-24 20:56:54 +00:00
"errors"
2021-09-22 20:18:55 +00:00
"flag"
"fmt"
2023-08-23 22:31:47 +00:00
"io"
2021-09-22 20:18:55 +00:00
"log"
"math/rand"
"net/http"
2024-02-12 22:06:58 +00:00
_ "net/http/pprof"
2021-10-14 13:09:58 +00:00
"os"
2022-06-28 18:11:49 +00:00
"strconv"
2021-09-22 20:18:55 +00:00
"strings"
2021-10-14 13:09:58 +00:00
"sync"
2023-08-23 22:31:47 +00:00
"sync/atomic"
2021-09-22 20:18:55 +00:00
"text/template"
"time"
2023-05-12 16:50:20 +00:00
"github.com/fleetdm/fleet/v4/pkg/mdm/mdmtest"
2021-11-01 18:23:31 +00:00
"github.com/fleetdm/fleet/v4/server/fleet"
2023-05-12 16:50:20 +00:00
apple_mdm "github.com/fleetdm/fleet/v4/server/mdm/apple"
2024-03-13 13:29:25 +00:00
"github.com/fleetdm/fleet/v4/server/mdm/microsoft/syncml"
2022-05-31 13:15:58 +00:00
"github.com/fleetdm/fleet/v4/server/ptr"
2021-11-01 18:23:31 +00:00
"github.com/fleetdm/fleet/v4/server/service"
2021-09-22 20:18:55 +00:00
"github.com/google/uuid"
)
2023-07-14 16:06:34 +00:00
var (
//go:embed *.tmpl
templatesFS embed . FS
2021-10-14 13:09:58 +00:00
2024-03-14 19:33:12 +00:00
//go:embed macos_vulnerable.software
2023-07-14 16:06:34 +00:00
macOSVulnerableSoftwareFS embed . FS
2022-01-28 13:05:11 +00:00
2024-03-14 19:33:12 +00:00
//go:embed vscode_extensions_vulnerable.software
vsCodeExtensionsVulnerableSoftwareFS embed . FS
2023-07-14 16:06:34 +00:00
//go:embed ubuntu_2204-software.json.bz2
ubuntuSoftwareFS embed . FS
//go:embed windows_11-software.json.bz2
windowsSoftwareFS embed . FS
2022-01-28 13:05:11 +00:00
2024-03-14 19:33:12 +00:00
macosVulnerableSoftware [ ] fleet . Software
vsCodeExtensionsVulnerableSoftware [ ] fleet . Software
windowsSoftware [ ] map [ string ] string
ubuntuSoftware [ ] map [ string ] string
2023-07-14 16:06:34 +00:00
)
func loadMacOSVulnerableSoftware ( ) {
macOSVulnerableSoftwareData , err := macOSVulnerableSoftwareFS . ReadFile ( "macos_vulnerable.software" )
2022-01-28 13:05:11 +00:00
if err != nil {
2023-07-14 16:06:34 +00:00
log . Fatal ( "reading vulnerable macOS software file: " , err )
2022-01-28 13:05:11 +00:00
}
2023-07-14 16:06:34 +00:00
lines := bytes . Split ( macOSVulnerableSoftwareData , [ ] byte ( "\n" ) )
2022-01-28 13:05:11 +00:00
for _ , line := range lines {
parts := bytes . Split ( line , [ ] byte ( "##" ) )
if len ( parts ) < 2 {
2024-02-08 15:40:13 +00:00
log . Println ( "skipping" , string ( line ) )
2022-01-28 13:05:11 +00:00
continue
}
2023-07-14 16:06:34 +00:00
macosVulnerableSoftware = append ( macosVulnerableSoftware , fleet . Software {
2022-02-14 15:14:26 +00:00
Name : strings . TrimSpace ( string ( parts [ 0 ] ) ) ,
Version : strings . TrimSpace ( string ( parts [ 1 ] ) ) ,
2022-01-28 13:05:11 +00:00
Source : "apps" ,
} )
}
2024-02-08 15:40:13 +00:00
log . Printf ( "Loaded %d vulnerable macOS software" , len ( macosVulnerableSoftware ) )
2023-07-14 16:06:34 +00:00
}
2024-03-14 19:33:12 +00:00
func loadExtraVulnerableSoftware ( ) {
vsCodeExtensionsVulnerableSoftwareData , err := vsCodeExtensionsVulnerableSoftwareFS . ReadFile ( "vscode_extensions_vulnerable.software" )
if err != nil {
log . Fatal ( "reading vulnerable vscode_extensions software file: " , err )
}
lines := bytes . Split ( vsCodeExtensionsVulnerableSoftwareData , [ ] byte ( "\n" ) )
for _ , line := range lines {
parts := bytes . Split ( line , [ ] byte ( "##" ) )
if len ( parts ) < 3 {
log . Println ( "skipping" , string ( line ) )
continue
}
vsCodeExtensionsVulnerableSoftware = append ( vsCodeExtensionsVulnerableSoftware , fleet . Software {
Vendor : strings . TrimSpace ( string ( parts [ 0 ] ) ) ,
Name : strings . TrimSpace ( string ( parts [ 1 ] ) ) ,
Version : strings . TrimSpace ( string ( parts [ 2 ] ) ) ,
Source : "vscode_extensions" ,
} )
}
log . Printf ( "Loaded %d vulnerable vscode_extensions software" , len ( vsCodeExtensionsVulnerableSoftware ) )
}
2023-07-14 16:06:34 +00:00
func loadSoftwareItems ( fs embed . FS , path string ) [ ] map [ string ] string {
bz2 , err := fs . Open ( path )
if err != nil {
panic ( err )
}
type softwareJSON struct {
Name string ` json:"name" `
Version string ` json:"version" `
Release string ` json:"release,omitempty" `
Arch string ` json:"arch,omitempty" `
}
var softwareList [ ] softwareJSON
// ignoring "G110: Potential DoS vulnerability via decompression bomb", as this is test code.
if err := json . NewDecoder ( bzip2 . NewReader ( bz2 ) ) . Decode ( & softwareList ) ; err != nil { //nolint:gosec
panic ( err )
}
softwareRows := make ( [ ] map [ string ] string , 0 , len ( softwareList ) )
for _ , s := range softwareList {
softwareRows = append ( softwareRows , map [ string ] string {
"name" : s . Name ,
"version" : s . Version ,
"source" : "programs" ,
} )
}
return softwareRows
}
func init ( ) {
loadMacOSVulnerableSoftware ( )
2024-03-14 19:33:12 +00:00
loadExtraVulnerableSoftware ( )
2023-07-14 16:06:34 +00:00
windowsSoftware = loadSoftwareItems ( windowsSoftwareFS , "windows_11-software.json.bz2" )
ubuntuSoftware = loadSoftwareItems ( ubuntuSoftwareFS , "ubuntu_2204-software.json.bz2" )
2022-01-28 13:05:11 +00:00
}
2021-10-14 13:09:58 +00:00
type Stats struct {
2024-02-08 15:40:13 +00:00
startTime time . Time
errors int
osqueryEnrollments int
orbitEnrollments int
mdmEnrollments int
2024-03-13 13:29:25 +00:00
mdmSessions int
2024-02-08 15:40:13 +00:00
distributedWrites int
mdmCommandsReceived int
distributedReads int
configRequests int
configErrors int
resultLogRequests int
orbitErrors int
mdmErrors int
desktopErrors int
distributedReadErrors int
distributedWriteErrors int
resultLogErrors int
bufferedLogs int
2021-10-14 13:09:58 +00:00
l sync . Mutex
}
2022-10-28 17:27:21 +00:00
func ( s * Stats ) IncrementErrors ( errors int ) {
2021-10-14 13:09:58 +00:00
s . l . Lock ( )
defer s . l . Unlock ( )
s . errors += errors
2022-10-28 17:27:21 +00:00
}
func ( s * Stats ) IncrementEnrollments ( ) {
s . l . Lock ( )
defer s . l . Unlock ( )
2023-05-12 16:50:20 +00:00
s . osqueryEnrollments ++
2022-10-28 17:27:21 +00:00
}
func ( s * Stats ) IncrementOrbitEnrollments ( ) {
s . l . Lock ( )
defer s . l . Unlock ( )
2023-05-12 16:50:20 +00:00
s . orbitEnrollments ++
}
func ( s * Stats ) IncrementMDMEnrollments ( ) {
s . l . Lock ( )
defer s . l . Unlock ( )
s . mdmEnrollments ++
2022-10-28 17:27:21 +00:00
}
2024-03-13 13:29:25 +00:00
func ( s * Stats ) IncrementMDMSessions ( ) {
s . l . Lock ( )
defer s . l . Unlock ( )
s . mdmSessions ++
}
2022-10-28 17:27:21 +00:00
func ( s * Stats ) IncrementDistributedWrites ( ) {
s . l . Lock ( )
defer s . l . Unlock ( )
2023-05-12 16:50:20 +00:00
s . distributedWrites ++
}
func ( s * Stats ) IncrementMDMCommandsReceived ( ) {
s . l . Lock ( )
defer s . l . Unlock ( )
s . mdmCommandsReceived ++
2022-10-28 17:27:21 +00:00
}
2023-10-20 13:29:59 +00:00
func ( s * Stats ) IncrementDistributedReads ( ) {
s . l . Lock ( )
defer s . l . Unlock ( )
s . distributedReads ++
}
func ( s * Stats ) IncrementConfigRequests ( ) {
s . l . Lock ( )
defer s . l . Unlock ( )
s . configRequests ++
}
2024-02-08 15:40:13 +00:00
func ( s * Stats ) IncrementConfigErrors ( ) {
s . l . Lock ( )
defer s . l . Unlock ( )
s . configErrors ++
}
2023-10-20 13:29:59 +00:00
func ( s * Stats ) IncrementResultLogRequests ( ) {
s . l . Lock ( )
defer s . l . Unlock ( )
s . resultLogRequests ++
}
2022-10-28 17:27:21 +00:00
func ( s * Stats ) IncrementOrbitErrors ( ) {
s . l . Lock ( )
defer s . l . Unlock ( )
s . orbitErrors ++
}
2023-05-12 16:50:20 +00:00
func ( s * Stats ) IncrementMDMErrors ( ) {
s . l . Lock ( )
defer s . l . Unlock ( )
s . mdmErrors ++
}
2022-10-28 17:27:21 +00:00
func ( s * Stats ) IncrementDesktopErrors ( ) {
s . l . Lock ( )
defer s . l . Unlock ( )
s . desktopErrors ++
2021-10-14 13:09:58 +00:00
}
2024-02-08 15:40:13 +00:00
func ( s * Stats ) IncrementDistributedReadErrors ( ) {
s . l . Lock ( )
defer s . l . Unlock ( )
s . distributedReadErrors ++
}
func ( s * Stats ) IncrementDistributedWriteErrors ( ) {
s . l . Lock ( )
defer s . l . Unlock ( )
s . distributedWriteErrors ++
}
func ( s * Stats ) IncrementResultLogErrors ( ) {
s . l . Lock ( )
defer s . l . Unlock ( )
s . resultLogErrors ++
}
func ( s * Stats ) UpdateBufferedLogs ( v int ) {
s . l . Lock ( )
defer s . l . Unlock ( )
s . bufferedLogs += v
if s . bufferedLogs < 0 {
s . bufferedLogs = 0
}
}
2021-10-14 13:09:58 +00:00
func ( s * Stats ) Log ( ) {
s . l . Lock ( )
defer s . l . Unlock ( )
2024-02-08 15:40:13 +00:00
log . Printf (
2024-03-13 13:29:25 +00:00
"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" ,
2023-10-20 13:29:59 +00:00
time . Since ( s . startTime ) . Round ( time . Second ) ,
2023-05-12 16:50:20 +00:00
float64 ( s . errors ) / float64 ( s . osqueryEnrollments ) ,
s . osqueryEnrollments ,
s . orbitEnrollments ,
s . mdmEnrollments ,
2023-10-20 13:29:59 +00:00
s . distributedReads ,
2023-05-12 16:50:20 +00:00
s . distributedWrites ,
2023-10-20 13:29:59 +00:00
s . configRequests ,
s . resultLogRequests ,
2024-03-13 13:29:25 +00:00
s . mdmSessions ,
2023-05-12 16:50:20 +00:00
s . mdmCommandsReceived ,
2024-02-08 15:40:13 +00:00
s . configErrors ,
s . distributedReadErrors ,
s . distributedWriteErrors ,
s . resultLogErrors ,
2022-10-28 17:27:21 +00:00
s . orbitErrors ,
s . desktopErrors ,
2023-05-12 16:50:20 +00:00
s . mdmErrors ,
2024-02-08 15:40:13 +00:00
s . bufferedLogs ,
2021-10-14 13:09:58 +00:00
)
}
func ( s * Stats ) runLoop ( ) {
ticker := time . Tick ( 10 * time . Second )
for range ticker {
s . Log ( )
}
}
2021-11-01 18:23:31 +00:00
type nodeKeyManager struct {
2021-10-14 13:09:58 +00:00
filepath string
l sync . Mutex
nodekeys [ ] string
}
2021-11-01 18:23:31 +00:00
func ( n * nodeKeyManager ) LoadKeys ( ) {
2021-10-14 13:09:58 +00:00
if n . filepath == "" {
return
}
n . l . Lock ( )
defer n . l . Unlock ( )
data , err := os . ReadFile ( n . filepath )
if err != nil {
2024-02-08 15:40:13 +00:00
log . Println ( "WARNING (ignore if creating a new node key file): error loading nodekey file:" , err )
2021-10-14 13:09:58 +00:00
return
}
n . nodekeys = strings . Split ( string ( data ) , "\n" )
2021-12-09 21:05:32 +00:00
n . nodekeys = n . nodekeys [ : len ( n . nodekeys ) - 1 ] // remove last empty node key due to new line.
2024-02-08 15:40:13 +00:00
log . Printf ( "loaded %d node keys" , len ( n . nodekeys ) )
2021-10-14 13:09:58 +00:00
}
2021-11-01 18:23:31 +00:00
func ( n * nodeKeyManager ) Get ( i int ) string {
2021-10-14 13:09:58 +00:00
n . l . Lock ( )
defer n . l . Unlock ( )
if len ( n . nodekeys ) > i {
return n . nodekeys [ i ]
}
return ""
}
2021-11-01 18:23:31 +00:00
func ( n * nodeKeyManager ) Add ( nodekey string ) {
2021-10-14 13:09:58 +00:00
if n . filepath == "" {
return
}
// we lock just to make sure we write one at a time
n . l . Lock ( )
defer n . l . Unlock ( )
2022-05-31 13:15:58 +00:00
f , err := os . OpenFile ( n . filepath , os . O_APPEND | os . O_CREATE | os . O_WRONLY , 0 o600 )
2021-10-14 13:09:58 +00:00
if err != nil {
2024-02-08 15:40:13 +00:00
log . Printf ( "error opening nodekey file: %s" , err . Error ( ) )
2021-10-14 13:09:58 +00:00
return
}
defer f . Close ( )
if _ , err := f . WriteString ( nodekey + "\n" ) ; err != nil {
2024-02-08 15:40:13 +00:00
log . Printf ( "error writing nodekey file: %s" , err )
2021-10-14 13:09:58 +00:00
}
}
2021-11-01 18:23:31 +00:00
type agent struct {
2024-03-14 19:33:12 +00:00
agentIndex int
softwareCount softwareEntityCount
softwareVSCodeExtensionsCount softwareExtraEntityCount
userCount entityCount
policyPassProb float64
munkiIssueProb float64
munkiIssueCount int
liveQueryFailProb float64
liveQueryNoResultsProb float64
strings map [ string ] string
serverAddress string
stats * Stats
nodeKeyManager * nodeKeyManager
nodeKey string
templates * template . Template
os string
2022-05-31 13:15:58 +00:00
// deviceAuthToken holds Fleet Desktop device authentication token.
//
// Non-nil means the agent is identified as orbit osquery,
// nil means the agent is identified as vanilla osquery.
deviceAuthToken * string
2022-10-28 17:27:21 +00:00
orbitNodeKey * string
2021-12-09 21:05:32 +00:00
2024-03-13 13:29:25 +00:00
// macMDMClient and winMDMClient simulate a device running the MDM protocol
// (client side) against Fleet MDM.
macMDMClient * mdmtest . TestAppleMDMClient
winMDMClient * mdmtest . TestWindowsMDMClient
2023-05-12 16:50:20 +00:00
// isEnrolledToMDM is true when the mdmDevice has enrolled.
isEnrolledToMDM bool
// isEnrolledToMDMMu protects isEnrolledToMDM.
isEnrolledToMDMMu sync . Mutex
2024-02-08 15:40:13 +00:00
disableScriptExec bool
disableFleetDesktop bool
loggerTLSMaxLines int
2023-08-23 22:31:47 +00:00
// atomic boolean is set to true when executing scripts, so that only a
// single goroutine at a time can execute scripts.
scriptExecRunning atomic . Bool
2024-03-14 19:33:12 +00:00
softwareVSCodeExtensionsProb float64
softwareVSCodeExtensionsFailProb float64
2023-05-12 16:50:20 +00:00
//
2021-12-09 21:05:32 +00:00
// The following are exported to be used by the templates.
2023-05-12 16:50:20 +00:00
//
2021-12-09 21:05:32 +00:00
2023-02-08 14:49:42 +00:00
EnrollSecret string
UUID string
2023-02-17 20:10:49 +00:00
SerialNumber string
2023-02-08 14:49:42 +00:00
ConfigInterval time . Duration
2023-10-17 14:30:59 +00:00
LogInterval time . Duration
2023-02-08 14:49:42 +00:00
QueryInterval time . Duration
2023-05-12 16:50:20 +00:00
MDMCheckInInterval time . Duration
2023-02-08 14:49:42 +00:00
DiskEncryptionEnabled bool
2023-10-20 13:29:59 +00:00
2024-02-08 15:40:13 +00:00
scheduledQueriesMu sync . Mutex // protects the below members
2024-03-14 15:59:57 +00:00
scheduledQueryData map [ string ] scheduledQuery
2024-03-04 18:10:10 +00:00
// bufferedResults contains result logs that are buffered when
// /api/v1/osquery/log requests to the Fleet server fail.
//
// NOTE: We use a map instead of a slice to prevent the data structure to
// increase indefinitely (we sacrifice accuracy of logs but that's
// a-ok for osquery-perf and load testing).
bufferedResults map [ resultLog ] int
2021-12-09 21:05:32 +00:00
}
2021-12-09 20:20:32 +00:00
2021-12-09 21:24:48 +00:00
type entityCount struct {
2021-12-09 21:05:32 +00:00
common int
unique int
2021-09-22 20:18:55 +00:00
}
2022-01-28 13:05:11 +00:00
type softwareEntityCount struct {
entityCount
2023-04-05 16:53:43 +00:00
vulnerable int
withLastOpened int
lastOpenedProb float64
commonSoftwareUninstallCount int
commonSoftwareUninstallProb float64
uniqueSoftwareUninstallCount int
uniqueSoftwareUninstallProb float64
2022-01-28 13:05:11 +00:00
}
2024-03-14 19:33:12 +00:00
type softwareExtraEntityCount struct {
entityCount
commonSoftwareUninstallCount int
commonSoftwareUninstallProb float64
uniqueSoftwareUninstallCount int
uniqueSoftwareUninstallProb float64
}
2022-01-28 13:05:11 +00:00
2021-11-19 11:50:25 +00:00
func newAgent (
2021-12-09 21:05:32 +00:00
agentIndex int ,
2023-05-12 16:50:20 +00:00
serverAddress , enrollSecret string ,
templates * template . Template ,
2023-10-17 14:30:59 +00:00
configInterval , logInterval , queryInterval , mdmCheckInInterval time . Duration ,
2024-03-14 19:33:12 +00:00
softwareQueryFailureProb float64 ,
softwareVSCodeExtensionsQueryFailureProb float64 ,
2023-05-12 16:50:20 +00:00
softwareCount softwareEntityCount ,
2024-03-14 19:33:12 +00:00
softwareVSCodeExtensionsCount softwareExtraEntityCount ,
2023-05-12 16:50:20 +00:00
userCount entityCount ,
2021-11-19 11:50:25 +00:00
policyPassProb float64 ,
2022-05-31 13:15:58 +00:00
orbitProb float64 ,
2022-08-29 18:40:16 +00:00
munkiIssueProb float64 , munkiIssueCount int ,
2023-02-28 17:55:04 +00:00
emptySerialProb float64 ,
2023-05-12 16:50:20 +00:00
mdmProb float64 ,
mdmSCEPChallenge string ,
2023-05-25 11:12:10 +00:00
liveQueryFailProb float64 ,
liveQueryNoResultsProb float64 ,
2023-08-23 22:31:47 +00:00
disableScriptExec bool ,
2024-02-08 15:40:13 +00:00
disableFleetDesktop bool ,
loggerTLSMaxLines int ,
2021-11-19 11:50:25 +00:00
) * agent {
2022-05-31 13:15:58 +00:00
var deviceAuthToken * string
if rand . Float64 ( ) <= orbitProb {
deviceAuthToken = ptr . String ( uuid . NewString ( ) )
}
2023-05-12 16:50:20 +00:00
serialNumber := mdmtest . RandSerialNumber ( )
2023-02-28 17:55:04 +00:00
if rand . Float64 ( ) <= emptySerialProb {
2023-05-12 16:50:20 +00:00
serialNumber = ""
}
2024-03-13 13:29:25 +00:00
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 )
}
2023-02-28 17:55:04 +00:00
}
2024-03-13 13:29:25 +00:00
2021-11-01 18:23:31 +00:00
return & agent {
2024-03-14 19:33:12 +00:00
agentIndex : agentIndex ,
serverAddress : serverAddress ,
softwareCount : softwareCount ,
softwareVSCodeExtensionsCount : softwareVSCodeExtensionsCount ,
userCount : userCount ,
strings : make ( map [ string ] string ) ,
policyPassProb : policyPassProb ,
munkiIssueProb : munkiIssueProb ,
munkiIssueCount : munkiIssueCount ,
liveQueryFailProb : liveQueryFailProb ,
liveQueryNoResultsProb : liveQueryNoResultsProb ,
templates : templates ,
deviceAuthToken : deviceAuthToken ,
os : agentOS ,
EnrollSecret : enrollSecret ,
ConfigInterval : configInterval ,
LogInterval : logInterval ,
QueryInterval : queryInterval ,
MDMCheckInInterval : mdmCheckInInterval ,
UUID : hostUUID ,
SerialNumber : serialNumber ,
softwareVSCodeExtensionsProb : softwareQueryFailureProb ,
softwareVSCodeExtensionsFailProb : softwareVSCodeExtensionsQueryFailureProb ,
macMDMClient : macMDMClient ,
winMDMClient : winMDMClient ,
2024-02-08 15:40:13 +00:00
disableScriptExec : disableScriptExec ,
disableFleetDesktop : disableFleetDesktop ,
loggerTLSMaxLines : loggerTLSMaxLines ,
2024-03-04 18:10:10 +00:00
bufferedResults : make ( map [ resultLog ] int ) ,
2021-09-22 20:18:55 +00:00
}
}
type enrollResponse struct {
NodeKey string ` json:"node_key" `
}
type distributedReadResponse struct {
Queries map [ string ] string ` json:"queries" `
}
2023-10-13 02:41:04 +00:00
type scheduledQuery struct {
Query string ` json:"query" `
Name string ` json:"name" `
ScheduleInterval float64 ` json:"interval" `
Platform string ` json:"platform" `
Version string ` json:"version" `
Snapshot bool ` json:"snapshot" `
2024-03-14 15:59:57 +00:00
lastRun int64
numRows uint
packName string
2023-10-13 02:41:04 +00:00
}
2022-10-28 17:27:21 +00:00
func ( a * agent ) isOrbit ( ) bool {
return a . deviceAuthToken != nil
}
2021-11-01 18:23:31 +00:00
func ( a * agent ) runLoop ( i int , onlyAlreadyEnrolled bool ) {
2022-10-28 17:27:21 +00:00
if a . isOrbit ( ) {
if err := a . orbitEnroll ( ) ; err != nil {
2024-03-13 13:29:25 +00:00
// 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
2022-10-28 17:27:21 +00:00
return
}
2024-03-13 13:29:25 +00:00
if a . winMDMClient != nil {
a . winMDMClient = mdmtest . NewTestMDMClientWindowsProgramatic ( a . serverAddress , * a . orbitNodeKey )
}
2022-10-28 17:27:21 +00:00
}
2021-11-01 18:23:31 +00:00
if err := a . enroll ( i , onlyAlreadyEnrolled ) ; err != nil {
2021-10-14 13:09:58 +00:00
return
}
2021-09-22 20:18:55 +00:00
2024-02-08 15:40:13 +00:00
_ = a . config ( )
2021-09-22 20:18:55 +00:00
resp , err := a . DistributedRead ( )
2024-02-08 15:40:13 +00:00
if err == nil {
2021-09-22 20:18:55 +00:00
if len ( resp . Queries ) > 0 {
2024-02-08 15:40:13 +00:00
_ = a . DistributedWrite ( resp . Queries )
2021-09-22 20:18:55 +00:00
}
}
2022-10-28 17:27:21 +00:00
if a . isOrbit ( ) {
go a . runOrbitLoop ( )
}
2024-03-13 13:29:25 +00:00
// 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 )
2023-05-12 16:50:20 +00:00
a . stats . IncrementMDMErrors ( )
return
}
a . setMDMEnrolled ( )
a . stats . IncrementMDMEnrollments ( )
2024-03-13 13:29:25 +00:00
go a . runMacosMDMLoop ( )
2023-05-12 16:50:20 +00:00
}
2023-10-17 14:30:59 +00:00
//
// osquery runs three separate independent threads,
// - a thread for getting, running and submitting results for distributed queries (distributed).
// - a thread for getting configuration from a remote server (config).
// - a thread for submitting log results (logger).
//
// Thus we try to simulate that as much as we can.
2023-10-20 13:29:59 +00:00
// (1) distributed thread:
2023-10-17 14:30:59 +00:00
go func ( ) {
2023-10-20 13:29:59 +00:00
liveQueryTicker := time . NewTicker ( a . QueryInterval )
defer liveQueryTicker . Stop ( )
for range liveQueryTicker . C {
2024-02-08 15:40:13 +00:00
if resp , err := a . DistributedRead ( ) ; err == nil && len ( resp . Queries ) > 0 {
_ = a . DistributedWrite ( resp . Queries )
2023-10-17 14:30:59 +00:00
}
}
} ( )
2023-10-20 13:29:59 +00:00
// (2) config thread:
2023-10-17 14:30:59 +00:00
go func ( ) {
2023-10-20 13:29:59 +00:00
configTicker := time . NewTicker ( a . ConfigInterval )
defer configTicker . Stop ( )
for range configTicker . C {
2024-02-08 15:40:13 +00:00
_ = a . config ( )
2023-10-17 14:30:59 +00:00
}
} ( )
2023-10-20 13:29:59 +00:00
// (3) logger thread:
logTicker := time . NewTicker ( a . LogInterval )
defer logTicker . Stop ( )
for range logTicker . C {
// check if we have any scheduled queries that should be returning results
2024-02-12 22:06:58 +00:00
var results [ ] resultLog
2023-10-20 13:29:59 +00:00
now := time . Now ( ) . Unix ( )
a . scheduledQueriesMu . Lock ( )
2024-03-04 18:10:10 +00:00
prevCount := a . countBuffered ( )
2024-03-14 15:59:57 +00:00
for queryName , query := range a . scheduledQueryData {
if query . lastRun == 0 || now >= ( query . lastRun + int64 ( query . ScheduleInterval ) ) {
2024-02-12 22:06:58 +00:00
results = append ( results , resultLog {
packName : query . packName ,
queryName : query . Name ,
numRows : int ( query . numRows ) ,
} )
2024-03-14 15:59:57 +00:00
// Update lastRun
v := a . scheduledQueryData [ queryName ]
v . lastRun = now
a . scheduledQueryData [ queryName ] = v
2023-10-17 14:30:59 +00:00
}
2021-09-22 20:18:55 +00:00
}
2024-03-04 18:10:10 +00:00
if prevCount + len ( results ) < 1_000_000 { // osquery buffered_log_max is 1M
a . addToBuffer ( results )
2023-10-20 13:29:59 +00:00
}
2024-02-08 15:40:13 +00:00
a . sendLogsBatch ( )
2024-03-04 18:10:10 +00:00
newBufferedCount := a . countBuffered ( ) - prevCount
2024-02-08 15:40:13 +00:00
a . stats . UpdateBufferedLogs ( newBufferedCount )
a . scheduledQueriesMu . Unlock ( )
}
}
2024-03-04 18:10:10 +00:00
func ( a * agent ) countBuffered ( ) int {
var total int
for _ , count := range a . bufferedResults {
total += count
}
return total
}
func ( a * agent ) addToBuffer ( results [ ] resultLog ) {
for _ , result := range results {
a . bufferedResults [ result ] += 1
}
}
// getBatch returns a random set of logs from the buffered logs.
// NOTE: We sacrifice some accuracy in the name of CPU and memory efficiency.
func ( a * agent ) getBatch ( batchSize int ) [ ] resultLog {
results := make ( [ ] resultLog , 0 , batchSize )
for result , count := range a . bufferedResults {
left := batchSize - len ( results )
if left <= 0 {
return results
}
if count > left {
count = left
}
for i := 0 ; i < count ; i ++ {
results = append ( results , result )
}
}
return results
}
2024-02-12 22:06:58 +00:00
type resultLog struct {
packName string
queryName string
numRows int
}
2024-03-04 18:10:10 +00:00
func ( r resultLog ) emit ( ) [ ] byte {
2024-02-12 22:06:58 +00:00
return scheduledQueryResults ( r . packName , r . queryName , r . numRows )
}
2024-02-08 15:40:13 +00:00
// sendLogsBatch sends up to loggerTLSMaxLines logs and updates the buffer.
func ( a * agent ) sendLogsBatch ( ) {
if len ( a . bufferedResults ) == 0 {
return
2021-09-22 20:18:55 +00:00
}
2024-02-08 15:40:13 +00:00
batchSize := a . loggerTLSMaxLines
2024-03-04 18:10:10 +00:00
if count := a . countBuffered ( ) ; count < batchSize {
batchSize = count
2024-02-12 22:06:58 +00:00
}
2024-03-04 18:10:10 +00:00
batch := a . getBatch ( batchSize )
if err := a . submitLogs ( batch ) ; err != nil {
2024-02-08 15:40:13 +00:00
return
}
2024-03-04 18:10:10 +00:00
a . removeBuffered ( batchSize )
}
// removeBuffered removes a random set of logs from the buffered logs.
// NOTE: We sacrifice some accuracy in the name of CPU and memory efficiency.
func ( a * agent ) removeBuffered ( batchSize int ) {
for b := batchSize ; b > 0 ; {
for result , count := range a . bufferedResults {
if count > b {
a . bufferedResults [ result ] -= b
return
}
delete ( a . bufferedResults , result )
b -= count
}
}
2021-09-22 20:18:55 +00:00
}
2022-10-28 17:27:21 +00:00
func ( a * agent ) runOrbitLoop ( ) {
orbitClient , err := service . NewOrbitClient (
"" ,
a . serverAddress ,
"" ,
true ,
a . EnrollSecret ,
2023-04-27 11:44:39 +00:00
nil ,
2023-03-13 21:54:18 +00:00
fleet . OrbitHostInfo {
HardwareUUID : a . UUID ,
HardwareSerial : a . SerialNumber ,
Hostname : a . CachedString ( "hostname" ) ,
} ,
2024-02-21 18:36:15 +00:00
nil ,
2022-10-28 17:27:21 +00:00
)
if err != nil {
log . Println ( "creating orbit client: " , err )
}
orbitClient . TestNodeKey = * a . orbitNodeKey
2023-04-27 11:44:39 +00:00
deviceClient , err := service . NewDeviceClient ( a . serverAddress , true , "" , nil , "" )
2022-10-28 17:27:21 +00:00
if err != nil {
2024-02-08 15:40:13 +00:00
log . Fatal ( "creating device client: " , err )
2022-10-28 17:27:21 +00:00
}
// orbit does a config check when it starts
if _ , err := orbitClient . GetConfig ( ) ; err != nil {
a . stats . IncrementOrbitErrors ( )
}
2024-02-08 15:40:13 +00:00
tokenRotationEnabled := false
if ! a . disableFleetDesktop {
tokenRotationEnabled = orbitClient . GetServerCapabilities ( ) . Has ( fleet . CapabilityOrbitEndpoints ) &&
orbitClient . GetServerCapabilities ( ) . Has ( fleet . CapabilityTokenRotation )
2022-10-28 17:27:21 +00:00
2024-02-08 15:40:13 +00:00
// it also writes and checks the device token
if tokenRotationEnabled {
if err := orbitClient . SetOrUpdateDeviceToken ( * a . deviceAuthToken ) ; err != nil {
a . stats . IncrementOrbitErrors ( )
log . Println ( "orbitClient.SetOrUpdateDeviceToken: " , err )
}
2022-10-28 17:27:21 +00:00
2024-02-08 15:40:13 +00:00
if err := deviceClient . CheckToken ( * a . deviceAuthToken ) ; err != nil {
a . stats . IncrementOrbitErrors ( )
log . Println ( "deviceClient.CheckToken: " , err )
}
2022-10-28 17:27:21 +00:00
}
}
// checkToken is used to simulate Fleet Desktop polling until a token is
// valid, we make a random number of requests to properly emulate what
// happens in the real world as there are delays that are not accounted by
// the way this simulation is arranged.
checkToken := func ( ) {
min := 1
max := 5
numberOfRequests := rand . Intn ( max - min + 1 ) + min
ticker := time . NewTicker ( 5 * time . Second )
defer ticker . Stop ( )
for {
<- ticker . C
numberOfRequests --
if err := deviceClient . CheckToken ( * a . deviceAuthToken ) ; err != nil {
log . Println ( "deviceClient.CheckToken: " , err )
}
if numberOfRequests == 0 {
break
}
}
}
2024-02-08 15:40:13 +00:00
// Fleet Desktop performs a burst of check token requests when it's initialized
if ! a . disableFleetDesktop {
checkToken ( )
}
2022-10-28 17:27:21 +00:00
2023-08-23 22:31:47 +00:00
// orbit makes a call to check the config and update the CLI flags every 30
2022-10-28 17:27:21 +00:00
// seconds
orbitConfigTicker := time . Tick ( 30 * time . Second )
// orbit makes a call every 5 minutes to check the validity of the device
// token on the server
orbitTokenRemoteCheckTicker := time . Tick ( 5 * time . Minute )
// orbit pings the server every 1 hour to rotate the device token
orbitTokenRotationTicker := time . Tick ( 1 * time . Hour )
// orbit polls the /orbit/ping endpoint every 5 minutes to check if the
// server capabilities have changed
capabilitiesCheckerTicker := time . Tick ( 5 * time . Minute )
// fleet desktop polls for policy compliance every 5 minutes
fleetDesktopPolicyTicker := time . Tick ( 5 * time . Minute )
2024-03-13 13:29:25 +00:00
const windowsMDMEnrollmentAttemptFrequency = time . Hour
var lastEnrollAttempt time . Time
2022-10-28 17:27:21 +00:00
for {
select {
case <- orbitConfigTicker :
2023-08-23 22:31:47 +00:00
cfg , err := orbitClient . GetConfig ( )
if err != nil {
2022-10-28 17:27:21 +00:00
a . stats . IncrementOrbitErrors ( )
2023-10-04 12:10:16 +00:00
continue
2022-10-28 17:27:21 +00:00
}
2023-08-23 22:31:47 +00:00
if len ( cfg . Notifications . PendingScriptExecutionIDs ) > 0 {
// there are pending scripts to execute on this host, start a goroutine
// that will simulate executing them.
go a . execScripts ( cfg . Notifications . PendingScriptExecutionIDs , orbitClient )
}
2024-03-13 13:29:25 +00:00
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 ( )
}
}
2022-10-28 17:27:21 +00:00
case <- orbitTokenRemoteCheckTicker :
2024-02-08 15:40:13 +00:00
if ! a . disableFleetDesktop && tokenRotationEnabled {
2022-10-28 17:27:21 +00:00
if err := deviceClient . CheckToken ( * a . deviceAuthToken ) ; err != nil {
a . stats . IncrementOrbitErrors ( )
log . Println ( "deviceClient.CheckToken: " , err )
2023-10-04 12:10:16 +00:00
continue
2022-10-28 17:27:21 +00:00
}
}
case <- orbitTokenRotationTicker :
2024-02-08 15:40:13 +00:00
if ! a . disableFleetDesktop && tokenRotationEnabled {
2022-10-28 17:27:21 +00:00
newToken := ptr . String ( uuid . NewString ( ) )
if err := orbitClient . SetOrUpdateDeviceToken ( * newToken ) ; err != nil {
a . stats . IncrementOrbitErrors ( )
log . Println ( "orbitClient.SetOrUpdateDeviceToken: " , err )
2023-10-04 12:10:16 +00:00
continue
2022-10-28 17:27:21 +00:00
}
a . deviceAuthToken = newToken
// fleet desktop performs a burst of check token requests after a token is rotated
checkToken ( )
}
case <- capabilitiesCheckerTicker :
if err := orbitClient . Ping ( ) ; err != nil {
a . stats . IncrementOrbitErrors ( )
2023-10-04 12:10:16 +00:00
continue
2022-10-28 17:27:21 +00:00
}
case <- fleetDesktopPolicyTicker :
2024-02-08 15:40:13 +00:00
if ! a . disableFleetDesktop {
if _ , err := deviceClient . DesktopSummary ( * a . deviceAuthToken ) ; err != nil {
a . stats . IncrementDesktopErrors ( )
log . Println ( "deviceClient.NumberOfFailingPolicies: " , err )
continue
}
2022-10-28 17:27:21 +00:00
}
}
}
}
2024-03-13 13:29:25 +00:00
func ( a * agent ) runMacosMDMLoop ( ) {
2023-05-12 16:50:20 +00:00
mdmCheckInTicker := time . Tick ( a . MDMCheckInInterval )
for range mdmCheckInTicker {
2024-03-13 13:29:25 +00:00
mdmCommandPayload , err := a . macMDMClient . Idle ( )
2023-05-12 16:50:20 +00:00
if err != nil {
2024-02-08 15:40:13 +00:00
log . Printf ( "MDM Idle request failed: %s" , err )
2023-05-12 16:50:20 +00:00
a . stats . IncrementMDMErrors ( )
continue
}
2024-03-13 13:29:25 +00:00
a . stats . IncrementMDMSessions ( )
2023-05-12 16:50:20 +00:00
INNER_FOR_LOOP :
for mdmCommandPayload != nil {
a . stats . IncrementMDMCommandsReceived ( )
2024-03-13 13:29:25 +00:00
mdmCommandPayload , err = a . macMDMClient . Acknowledge ( mdmCommandPayload . CommandUUID )
2023-05-12 16:50:20 +00:00
if err != nil {
2024-02-08 15:40:13 +00:00
log . Printf ( "MDM Acknowledge request failed: %s" , err )
2023-05-12 16:50:20 +00:00
a . stats . IncrementMDMErrors ( )
break INNER_FOR_LOOP
}
}
}
}
2024-03-13 13:29:25 +00:00
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
}
}
}
2023-08-23 22:31:47 +00:00
func ( a * agent ) execScripts ( execIDs [ ] string , orbitClient * service . OrbitClient ) {
if a . scriptExecRunning . Swap ( true ) {
// if Swap returns true, the goroutine was already running, exit
return
}
defer a . scriptExecRunning . Store ( false )
2024-02-08 15:40:13 +00:00
log . Printf ( "running scripts: %v" , execIDs )
2023-08-23 22:31:47 +00:00
for _ , execID := range execIDs {
if a . disableScriptExec {
// send a no-op result without executing if script exec is disabled
if err := orbitClient . SaveHostScriptResult ( & fleet . HostScriptResultPayload {
ExecutionID : execID ,
2023-08-31 14:08:50 +00:00
Output : "Scripts are disabled" ,
2023-08-23 22:31:47 +00:00
Runtime : 0 ,
2023-08-30 18:02:44 +00:00
ExitCode : - 2 ,
2023-08-23 22:31:47 +00:00
} ) ; err != nil {
log . Println ( "save disabled host script result:" , err )
return
}
2024-02-08 15:40:13 +00:00
log . Printf ( "did save disabled host script result: id=%s" , execID )
2023-08-23 22:31:47 +00:00
continue
}
script , err := orbitClient . GetHostScript ( execID )
if err != nil {
log . Println ( "get host script:" , err )
return
}
// simulate script execution
outputLen := rand . Intn ( 11000 ) // base64 encoding will make the actual output a bit bigger
buf := make ( [ ] byte , outputLen )
n , _ := io . ReadFull ( cryptorand . Reader , buf )
exitCode := rand . Intn ( 2 )
runtime := rand . Intn ( 5 )
time . Sleep ( time . Duration ( runtime ) * time . Second )
if err := orbitClient . SaveHostScriptResult ( & fleet . HostScriptResultPayload {
HostID : script . HostID ,
ExecutionID : script . ExecutionID ,
Output : base64 . StdEncoding . EncodeToString ( buf [ : n ] ) ,
Runtime : runtime ,
ExitCode : exitCode ,
} ) ; err != nil {
log . Println ( "save host script result:" , err )
return
}
2024-02-08 15:40:13 +00:00
log . Printf ( "did exec and save host script result: id=%s, output size=%d, runtime=%d, exit code=%d" , execID , base64 . StdEncoding . EncodedLen ( n ) , runtime , exitCode )
2023-08-23 22:31:47 +00:00
}
}
2024-03-04 18:10:10 +00:00
func ( a * agent ) waitingDo ( fn func ( ) * http . Request ) * http . Response {
response , err := http . DefaultClient . Do ( fn ( ) )
2024-02-19 14:33:19 +00:00
for err != nil || response . StatusCode != http . StatusOK {
2024-02-08 15:40:13 +00:00
if err != nil {
log . Printf ( "failed to run request: %s" , err )
} else { // res.StatusCode() != http.StatusOK
2024-03-04 18:10:10 +00:00
response . Body . Close ( )
2024-02-19 14:33:19 +00:00
log . Printf ( "request failed: %d" , response . StatusCode )
2024-02-08 15:40:13 +00:00
}
2022-10-28 17:27:21 +00:00
a . stats . IncrementErrors ( 1 )
2021-10-14 13:09:58 +00:00
<- time . Tick ( time . Duration ( rand . Intn ( 120 ) + 1 ) * time . Second )
2024-03-04 18:10:10 +00:00
response , err = http . DefaultClient . Do ( fn ( ) )
2021-09-22 20:18:55 +00:00
}
2024-02-19 14:33:19 +00:00
return response
2021-09-22 20:18:55 +00:00
}
2022-10-28 17:27:21 +00:00
// TODO: add support to `alreadyEnrolled` akin to the `enroll` function. for
// now, we assume that the agent is not already enrolled, if you kill the agent
// process then those Orbit node keys are gone.
func ( a * agent ) orbitEnroll ( ) error {
2023-02-28 17:55:04 +00:00
params := service . EnrollOrbitRequest {
EnrollSecret : a . EnrollSecret ,
HardwareUUID : a . UUID ,
HardwareSerial : a . SerialNumber ,
}
2022-10-28 17:27:21 +00:00
jsonBytes , err := json . Marshal ( params )
if err != nil {
log . Println ( "orbit json marshall:" , err )
return err
}
2024-03-04 18:10:10 +00:00
response := a . waitingDo ( func ( ) * http . Request {
request , err := http . NewRequest ( "POST" , a . serverAddress + "/api/fleet/orbit/enroll" , bytes . NewReader ( jsonBytes ) )
if err != nil {
panic ( err )
}
request . Header . Add ( "Content-type" , "application/json" )
return request
} )
2024-02-19 14:33:19 +00:00
defer response . Body . Close ( )
2022-10-28 17:27:21 +00:00
var parsedResp service . EnrollOrbitResponse
2024-02-19 14:33:19 +00:00
if err := json . NewDecoder ( response . Body ) . Decode ( & parsedResp ) ; err != nil {
2022-10-28 17:27:21 +00:00
log . Println ( "orbit json parse:" , err )
return err
}
a . orbitNodeKey = & parsedResp . OrbitNodeKey
a . stats . IncrementOrbitEnrollments ( )
return nil
}
2021-11-01 18:23:31 +00:00
func ( a * agent ) enroll ( i int , onlyAlreadyEnrolled bool ) error {
2021-12-09 21:05:32 +00:00
a . nodeKey = a . nodeKeyManager . Get ( i )
if a . nodeKey != "" {
2022-10-28 17:27:21 +00:00
a . stats . IncrementEnrollments ( )
2021-10-14 13:09:58 +00:00
return nil
}
if onlyAlreadyEnrolled {
2021-11-24 20:56:54 +00:00
return errors . New ( "not enrolled" )
2021-09-22 20:18:55 +00:00
}
var body bytes . Buffer
2021-12-09 21:05:32 +00:00
if err := a . templates . ExecuteTemplate ( & body , "enroll" , a ) ; err != nil {
2021-09-22 20:18:55 +00:00
log . Println ( "execute template:" , err )
2021-10-14 13:09:58 +00:00
return err
2021-09-22 20:18:55 +00:00
}
2024-03-04 18:10:10 +00:00
response := a . waitingDo ( func ( ) * http . Request {
request , err := http . NewRequest ( "POST" , a . serverAddress + "/api/osquery/enroll" , & body )
if err != nil {
panic ( err )
}
request . Header . Add ( "Content-type" , "application/json" )
return request
} )
2024-02-19 14:33:19 +00:00
defer response . Body . Close ( )
2021-10-14 13:09:58 +00:00
2024-02-19 14:33:19 +00:00
if response . StatusCode != http . StatusOK {
log . Println ( "enroll status:" , response . StatusCode )
return fmt . Errorf ( "status code: %d" , response . StatusCode )
2021-09-22 20:18:55 +00:00
}
var parsedResp enrollResponse
2024-02-19 14:33:19 +00:00
if err := json . NewDecoder ( response . Body ) . Decode ( & parsedResp ) ; err != nil {
2021-09-22 20:18:55 +00:00
log . Println ( "json parse:" , err )
2021-10-14 13:09:58 +00:00
return err
2021-09-22 20:18:55 +00:00
}
2021-12-09 21:05:32 +00:00
a . nodeKey = parsedResp . NodeKey
2022-10-28 17:27:21 +00:00
a . stats . IncrementEnrollments ( )
2021-10-14 13:09:58 +00:00
2021-12-09 21:05:32 +00:00
a . nodeKeyManager . Add ( a . nodeKey )
2021-10-14 13:09:58 +00:00
return nil
2021-09-22 20:18:55 +00:00
}
2024-02-08 15:40:13 +00:00
func ( a * agent ) config ( ) error {
2024-02-19 14:33:19 +00:00
request , err := http . NewRequest ( "POST" , a . serverAddress + "/api/osquery/config" , bytes . NewReader ( [ ] byte ( ` { "node_key": " ` + a . nodeKey + ` "} ` ) ) )
if err != nil {
return err
}
request . Header . Add ( "Content-type" , "application/json" )
2021-10-14 13:09:58 +00:00
2024-02-19 14:33:19 +00:00
response , err := http . DefaultClient . Do ( request )
2024-02-12 22:06:58 +00:00
if err != nil {
return fmt . Errorf ( "config request failed to run: %w" , err )
}
2024-02-19 14:33:19 +00:00
defer response . Body . Close ( )
2024-02-12 22:06:58 +00:00
2023-10-20 13:29:59 +00:00
a . stats . IncrementConfigRequests ( )
2024-02-19 14:33:19 +00:00
statusCode := response . StatusCode
2024-02-08 15:40:13 +00:00
if statusCode != http . StatusOK {
a . stats . IncrementConfigErrors ( )
return fmt . Errorf ( "config request failed: %d" , statusCode )
2021-09-22 20:18:55 +00:00
}
2021-12-09 20:20:32 +00:00
parsedResp := struct {
Packs map [ string ] struct {
Queries map [ string ] interface { } ` json:"queries" `
} ` json:"packs" `
} { }
2024-02-19 14:33:19 +00:00
if err := json . NewDecoder ( response . Body ) . Decode ( & parsedResp ) ; err != nil {
2024-03-04 18:10:10 +00:00
a . stats . IncrementConfigErrors ( )
2024-02-08 15:40:13 +00:00
return fmt . Errorf ( "json parse at config: %w" , err )
2021-12-09 20:20:32 +00:00
}
2024-03-14 15:59:57 +00:00
scheduledQueryData := make ( map [ string ] scheduledQuery )
2021-12-09 20:20:32 +00:00
for packName , pack := range parsedResp . Packs {
2023-10-13 02:41:04 +00:00
for queryName , query := range pack . Queries {
m , ok := query . ( map [ string ] interface { } )
if ! ok {
2024-02-08 15:40:13 +00:00
return fmt . Errorf ( "processing scheduled query failed: %v" , query )
2023-10-13 02:41:04 +00:00
}
q := scheduledQuery { }
2024-02-08 15:40:13 +00:00
q . packName = packName
2023-10-13 02:41:04 +00:00
q . Name = queryName
2024-02-08 15:40:13 +00:00
q . numRows = 1
2023-10-13 02:41:04 +00:00
parts := strings . Split ( q . Name , "_" )
2023-10-13 17:26:46 +00:00
if len ( parts ) == 2 {
2023-10-13 02:41:04 +00:00
num , err := strconv . ParseInt ( parts [ 1 ] , 10 , 32 )
if err != nil {
num = 1
}
2024-02-08 15:40:13 +00:00
q . numRows = uint ( num )
2023-10-13 02:41:04 +00:00
}
q . ScheduleInterval = m [ "interval" ] . ( float64 )
q . Query = m [ "query" ] . ( string )
2024-03-14 15:59:57 +00:00
scheduledQueryName := packName + "_" + queryName
if existingEntry , ok := a . scheduledQueryData [ scheduledQueryName ] ; ok {
// Keep lastRun if the query is already scheduled.
q . lastRun = existingEntry . lastRun
}
scheduledQueryData [ scheduledQueryName ] = q
2021-12-09 20:20:32 +00:00
}
}
2023-10-20 13:29:59 +00:00
a . scheduledQueriesMu . Lock ( )
2023-10-13 02:41:04 +00:00
a . scheduledQueryData = scheduledQueryData
2023-10-20 13:29:59 +00:00
a . scheduledQueriesMu . Unlock ( )
2024-02-08 15:40:13 +00:00
return nil
2021-09-22 20:18:55 +00:00
}
2021-10-14 13:09:58 +00:00
const stringVals = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_."
2021-09-22 20:18:55 +00:00
2022-08-29 18:40:16 +00:00
func randomString ( n int ) string {
2021-10-14 13:09:58 +00:00
sb := strings . Builder { }
sb . Grow ( n )
for i := 0 ; i < n ; i ++ {
sb . WriteByte ( stringVals [ rand . Int63 ( ) % int64 ( len ( stringVals ) ) ] )
2021-09-22 20:18:55 +00:00
}
2021-10-14 13:09:58 +00:00
return sb . String ( )
}
2021-09-22 20:18:55 +00:00
2021-11-01 18:23:31 +00:00
func ( a * agent ) CachedString ( key string ) string {
2021-10-14 13:09:58 +00:00
if val , ok := a . strings [ key ] ; ok {
return val
2021-09-22 20:18:55 +00:00
}
2022-08-29 18:40:16 +00:00
val := randomString ( 12 )
2021-10-14 13:09:58 +00:00
a . strings [ key ] = val
return val
}
2021-09-22 20:18:55 +00:00
2022-11-15 13:24:40 +00:00
func ( a * agent ) hostUsers ( ) [ ] map [ string ] string {
2021-12-09 21:24:48 +00:00
groupNames := [ ] string { "staff" , "nobody" , "wheel" , "tty" , "daemon" }
shells := [ ] string { "/bin/zsh" , "/bin/sh" , "/usr/bin/false" , "/bin/bash" }
2022-08-29 16:34:40 +00:00
commonUsers := make ( [ ] map [ string ] string , a . userCount . common )
2021-12-09 21:24:48 +00:00
for i := 0 ; i < len ( commonUsers ) ; i ++ {
2022-08-29 16:34:40 +00:00
commonUsers [ i ] = map [ string ] string {
"uid" : fmt . Sprint ( i ) ,
"username" : fmt . Sprintf ( "Common_%d" , i ) ,
"type" : "" , // Empty for macOS.
"groupname" : groupNames [ i % len ( groupNames ) ] ,
"shell" : shells [ i % len ( shells ) ] ,
2021-12-09 21:24:48 +00:00
}
}
2022-08-29 16:34:40 +00:00
uniqueUsers := make ( [ ] map [ string ] string , a . userCount . unique )
2021-12-09 21:24:48 +00:00
for i := 0 ; i < len ( uniqueUsers ) ; i ++ {
2022-08-29 16:34:40 +00:00
uniqueUsers [ i ] = map [ string ] string {
"uid" : fmt . Sprint ( i ) ,
"username" : fmt . Sprintf ( "Unique_%d_%d" , a . agentIndex , i ) ,
"type" : "" , // Empty for macOS.
"groupname" : groupNames [ i % len ( groupNames ) ] ,
"shell" : shells [ i % len ( shells ) ] ,
2021-12-09 21:24:48 +00:00
}
}
users := append ( commonUsers , uniqueUsers ... )
rand . Shuffle ( len ( users ) , func ( i , j int ) {
users [ i ] , users [ j ] = users [ j ] , users [ i ]
} )
return users
}
2022-08-29 16:34:40 +00:00
func ( a * agent ) softwareMacOS ( ) [ ] map [ string ] string {
2022-04-26 18:16:59 +00:00
var lastOpenedCount int
2022-08-29 16:34:40 +00:00
commonSoftware := make ( [ ] map [ string ] string , a . softwareCount . common )
2021-12-09 21:05:32 +00:00
for i := 0 ; i < len ( commonSoftware ) ; i ++ {
2022-08-29 16:34:40 +00:00
var lastOpenedAt string
if l := a . genLastOpenedAt ( & lastOpenedCount ) ; l != nil {
lastOpenedAt = l . Format ( time . UnixDate )
}
commonSoftware [ i ] = map [ string ] string {
2024-03-14 19:33:12 +00:00
"name" : fmt . Sprintf ( "Common_%d.app" , i ) ,
2022-08-29 16:34:40 +00:00
"version" : "0.0.1" ,
2024-03-14 19:33:12 +00:00
"bundle_identifier" : fmt . Sprintf ( "com.fleetdm.osquery-perf.common_%s_%d" , a . CachedString ( "hostname" ) , i ) ,
"source" : "apps" ,
2022-08-29 16:34:40 +00:00
"last_opened_at" : lastOpenedAt ,
2024-03-14 19:33:12 +00:00
"installed_path" : fmt . Sprintf ( "/some/path/Common_%d.app" , i ) ,
2021-11-01 18:23:31 +00:00
}
}
2023-04-05 16:53:43 +00:00
if a . softwareCount . commonSoftwareUninstallProb > 0.0 && rand . Float64 ( ) <= a . softwareCount . commonSoftwareUninstallProb {
rand . Shuffle ( len ( commonSoftware ) , func ( i , j int ) {
commonSoftware [ i ] , commonSoftware [ j ] = commonSoftware [ j ] , commonSoftware [ i ]
} )
commonSoftware = commonSoftware [ : a . softwareCount . common - a . softwareCount . commonSoftwareUninstallCount ]
}
2022-08-29 16:34:40 +00:00
uniqueSoftware := make ( [ ] map [ string ] string , a . softwareCount . unique )
2021-12-09 21:05:32 +00:00
for i := 0 ; i < len ( uniqueSoftware ) ; i ++ {
2022-08-29 16:34:40 +00:00
var lastOpenedAt string
if l := a . genLastOpenedAt ( & lastOpenedCount ) ; l != nil {
lastOpenedAt = l . Format ( time . UnixDate )
}
uniqueSoftware [ i ] = map [ string ] string {
2024-03-14 19:33:12 +00:00
"name" : fmt . Sprintf ( "Unique_%s_%d.app" , a . CachedString ( "hostname" ) , i ) ,
2022-08-29 16:34:40 +00:00
"version" : "1.1.1" ,
2024-03-14 19:33:12 +00:00
"bundle_identifier" : fmt . Sprintf ( "com.fleetdm.osquery-perf.unique_%s_%d" , a . CachedString ( "hostname" ) , i ) ,
"source" : "apps" ,
2022-08-29 16:34:40 +00:00
"last_opened_at" : lastOpenedAt ,
2024-03-14 19:33:12 +00:00
"installed_path" : fmt . Sprintf ( "/some/path/Unique_%s_%d.app" , a . CachedString ( "hostname" ) , i ) ,
2021-12-09 21:05:32 +00:00
}
}
2023-04-05 16:53:43 +00:00
if a . softwareCount . uniqueSoftwareUninstallProb > 0.0 && rand . Float64 ( ) <= a . softwareCount . uniqueSoftwareUninstallProb {
rand . Shuffle ( len ( uniqueSoftware ) , func ( i , j int ) {
uniqueSoftware [ i ] , uniqueSoftware [ j ] = uniqueSoftware [ j ] , uniqueSoftware [ i ]
} )
uniqueSoftware = uniqueSoftware [ : a . softwareCount . unique - a . softwareCount . uniqueSoftwareUninstallCount ]
}
2022-08-29 16:34:40 +00:00
randomVulnerableSoftware := make ( [ ] map [ string ] string , a . softwareCount . vulnerable )
2022-01-28 13:05:11 +00:00
for i := 0 ; i < len ( randomVulnerableSoftware ) ; i ++ {
2023-07-14 16:06:34 +00:00
sw := macosVulnerableSoftware [ rand . Intn ( len ( macosVulnerableSoftware ) ) ]
2022-08-29 16:34:40 +00:00
var lastOpenedAt string
if l := a . genLastOpenedAt ( & lastOpenedCount ) ; l != nil {
lastOpenedAt = l . Format ( time . UnixDate )
}
randomVulnerableSoftware [ i ] = map [ string ] string {
"name" : sw . Name ,
"version" : sw . Version ,
"bundle_identifier" : sw . BundleIdentifier ,
"source" : sw . Source ,
"last_opened_at" : lastOpenedAt ,
2023-05-17 18:49:09 +00:00
"installed_path" : fmt . Sprintf ( "/some/path/%s" , sw . Name ) ,
2022-08-29 16:34:40 +00:00
}
2022-01-28 13:05:11 +00:00
}
2021-12-09 21:05:32 +00:00
software := append ( commonSoftware , uniqueSoftware ... )
2022-01-28 13:05:11 +00:00
software = append ( software , randomVulnerableSoftware ... )
2021-12-09 21:05:32 +00:00
rand . Shuffle ( len ( software ) , func ( i , j int ) {
software [ i ] , software [ j ] = software [ j ] , software [ i ]
} )
2021-11-01 18:23:31 +00:00
return software
}
2024-03-14 19:33:12 +00:00
func ( a * agent ) softwareVSCodeExtensions ( ) [ ] map [ string ] string {
commonVSCodeExtensionsSoftware := make ( [ ] map [ string ] string , a . softwareVSCodeExtensionsCount . common )
for i := 0 ; i < len ( commonVSCodeExtensionsSoftware ) ; i ++ {
commonVSCodeExtensionsSoftware [ i ] = map [ string ] string {
"name" : fmt . Sprintf ( "common.extension_%d" , i ) ,
"version" : "0.0.1" ,
"source" : "vscode_extensions" ,
}
}
if a . softwareVSCodeExtensionsCount . commonSoftwareUninstallProb > 0.0 && rand . Float64 ( ) <= a . softwareCount . commonSoftwareUninstallProb {
rand . Shuffle ( len ( commonVSCodeExtensionsSoftware ) , func ( i , j int ) {
commonVSCodeExtensionsSoftware [ i ] , commonVSCodeExtensionsSoftware [ j ] = commonVSCodeExtensionsSoftware [ j ] , commonVSCodeExtensionsSoftware [ i ]
} )
commonVSCodeExtensionsSoftware = commonVSCodeExtensionsSoftware [ : a . softwareVSCodeExtensionsCount . common - a . softwareVSCodeExtensionsCount . commonSoftwareUninstallCount ]
}
uniqueVSCodeExtensionsSoftware := make ( [ ] map [ string ] string , a . softwareVSCodeExtensionsCount . unique )
for i := 0 ; i < len ( uniqueVSCodeExtensionsSoftware ) ; i ++ {
uniqueVSCodeExtensionsSoftware [ i ] = map [ string ] string {
"name" : fmt . Sprintf ( "unique.extension_%s_%d" , a . CachedString ( "hostname" ) , i ) ,
"version" : "1.1.1" ,
"source" : "vscode_extensions" ,
}
}
if a . softwareVSCodeExtensionsCount . uniqueSoftwareUninstallProb > 0.0 && rand . Float64 ( ) <= a . softwareVSCodeExtensionsCount . uniqueSoftwareUninstallProb {
rand . Shuffle ( len ( uniqueVSCodeExtensionsSoftware ) , func ( i , j int ) {
uniqueVSCodeExtensionsSoftware [ i ] , uniqueVSCodeExtensionsSoftware [ j ] = uniqueVSCodeExtensionsSoftware [ j ] , uniqueVSCodeExtensionsSoftware [ i ]
} )
uniqueVSCodeExtensionsSoftware = uniqueVSCodeExtensionsSoftware [ : a . softwareVSCodeExtensionsCount . unique - a . softwareVSCodeExtensionsCount . uniqueSoftwareUninstallCount ]
}
var vulnerableVSCodeExtensionsSoftware [ ] map [ string ] string
for _ , vsCodeExtension := range vsCodeExtensionsVulnerableSoftware {
vulnerableVSCodeExtensionsSoftware = append ( vulnerableVSCodeExtensionsSoftware , map [ string ] string {
"name" : vsCodeExtension . Name ,
"version" : vsCodeExtension . Version ,
"vendor" : vsCodeExtension . Vendor ,
"source" : vsCodeExtension . Source ,
} )
}
software := append ( commonVSCodeExtensionsSoftware , uniqueVSCodeExtensionsSoftware ... )
software = append ( software , vulnerableVSCodeExtensionsSoftware ... )
rand . Shuffle ( len ( software ) , func ( i , j int ) {
software [ i ] , software [ j ] = software [ j ] , software [ i ]
} )
return software
}
2021-11-01 18:23:31 +00:00
func ( a * agent ) DistributedRead ( ) ( * distributedReadResponse , error ) {
2024-02-19 14:33:19 +00:00
request , err := http . NewRequest ( "POST" , a . serverAddress + "/api/osquery/distributed/read" , bytes . NewReader ( [ ] byte ( ` { "node_key": " ` + a . nodeKey + ` "} ` ) ) )
if err != nil {
return nil , err
}
request . Header . Add ( "Content-type" , "application/json" )
2021-09-22 20:18:55 +00:00
2024-02-19 14:33:19 +00:00
response , err := http . DefaultClient . Do ( request )
2024-02-12 22:06:58 +00:00
if err != nil {
return nil , fmt . Errorf ( "distributed/read request failed to run: %w" , err )
}
2024-02-19 14:33:19 +00:00
defer response . Body . Close ( )
2024-02-12 22:06:58 +00:00
2023-10-20 13:29:59 +00:00
a . stats . IncrementDistributedReads ( )
2024-02-19 14:33:19 +00:00
statusCode := response . StatusCode
2024-02-08 15:40:13 +00:00
if statusCode != http . StatusOK {
a . stats . IncrementDistributedReadErrors ( )
return nil , fmt . Errorf ( "distributed/read request failed: %d" , statusCode )
}
2021-09-22 20:18:55 +00:00
var parsedResp distributedReadResponse
2024-02-19 14:33:19 +00:00
if err := json . NewDecoder ( response . Body ) . Decode ( & parsedResp ) ; err != nil {
2024-03-04 18:10:10 +00:00
a . stats . IncrementDistributedReadErrors ( )
2024-02-08 15:40:13 +00:00
log . Printf ( "json parse: %s" , err )
2021-10-14 13:09:58 +00:00
return nil , err
2021-09-22 20:18:55 +00:00
}
return & parsedResp , nil
}
2021-11-01 18:23:31 +00:00
var defaultQueryResult = [ ] map [ string ] string {
{ "foo" : "bar" } ,
2021-09-22 20:18:55 +00:00
}
2022-04-26 18:16:59 +00:00
func ( a * agent ) genLastOpenedAt ( count * int ) * time . Time {
if * count >= a . softwareCount . withLastOpened {
return nil
}
* count ++
if rand . Float64 ( ) <= a . softwareCount . lastOpenedProb {
now := time . Now ( )
return & now
}
return nil
}
2021-11-19 11:50:25 +00:00
func ( a * agent ) runPolicy ( query string ) [ ] map [ string ] string {
if rand . Float64 ( ) <= a . policyPassProb {
return [ ] map [ string ] string {
{ "1" : "1" } ,
}
}
2022-09-12 19:37:38 +00:00
return [ ] map [ string ] string { }
2021-11-19 11:50:25 +00:00
}
2021-12-09 20:20:32 +00:00
func ( a * agent ) randomQueryStats ( ) [ ] map [ string ] string {
2023-10-20 13:29:59 +00:00
a . scheduledQueriesMu . Lock ( )
defer a . scheduledQueriesMu . Unlock ( )
2021-12-09 20:20:32 +00:00
var stats [ ] map [ string ] string
2024-03-14 15:59:57 +00:00
for scheduledQuery := range a . scheduledQueryData {
2021-12-09 20:20:32 +00:00
stats = append ( stats , map [ string ] string {
"name" : scheduledQuery ,
"delimiter" : "_" ,
"average_memory" : fmt . Sprint ( rand . Intn ( 200 ) + 10 ) ,
"denylisted" : "false" ,
"executions" : fmt . Sprint ( rand . Intn ( 100 ) + 1 ) ,
"interval" : fmt . Sprint ( rand . Intn ( 100 ) + 1 ) ,
"last_executed" : fmt . Sprint ( time . Now ( ) . Unix ( ) ) ,
"output_size" : fmt . Sprint ( rand . Intn ( 100 ) + 1 ) ,
"system_time" : fmt . Sprint ( rand . Intn ( 4000 ) + 10 ) ,
"user_time" : fmt . Sprint ( rand . Intn ( 4000 ) + 10 ) ,
2024-02-12 13:58:40 +00:00
"wall_time" : fmt . Sprint ( rand . Intn ( 4 ) + 1 ) ,
"wall_time_ms" : fmt . Sprint ( rand . Intn ( 4000 ) + 10 ) ,
2021-12-09 20:20:32 +00:00
} )
}
return stats
}
2023-05-12 16:50:20 +00:00
// mdmMac returns the results for the `mdm` table query.
//
// If the host is enrolled via MDM it will return installed_from_dep as false
// (which means the host will be identified as manually enrolled).
//
// NOTE: To support proper DEP simulation in a loadtest environment
// we may need to implement a mocked Apple DEP endpoint.
2022-11-15 13:24:40 +00:00
func ( a * agent ) mdmMac ( ) [ ] map [ string ] string {
2023-05-12 16:50:20 +00:00
if ! a . mdmEnrolled ( ) {
return [ ] map [ string ] string {
{ "enrolled" : "false" , "server_url" : "" , "installed_from_dep" : "false" } ,
}
2021-12-21 12:37:58 +00:00
}
return [ ] map [ string ] string {
2024-03-13 13:29:25 +00:00
{
"enrolled" : "true" ,
"server_url" : a . macMDMClient . EnrollInfo . MDMURL ,
"installed_from_dep" : "false" ,
"payload_identifier" : apple_mdm . FleetPayloadIdentifier ,
} ,
2022-11-15 13:24:40 +00:00
}
}
2023-05-12 16:50:20 +00:00
func ( a * agent ) mdmEnrolled ( ) bool {
a . isEnrolledToMDMMu . Lock ( )
defer a . isEnrolledToMDMMu . Unlock ( )
return a . isEnrolledToMDM
}
func ( a * agent ) setMDMEnrolled ( ) {
a . isEnrolledToMDMMu . Lock ( )
defer a . isEnrolledToMDMMu . Unlock ( )
a . isEnrolledToMDM = true
}
2022-11-15 13:24:40 +00:00
func ( a * agent ) mdmWindows ( ) [ ] map [ string ] string {
2024-03-13 13:29:25 +00:00
if ! a . mdmEnrolled ( ) {
return [ ] map [ string ] string {
// empty service url means not enrolled
{ "is_federated" : "0" , "discovery_service_url" : "" , "provider_id" : "" , "installation_type" : "Client" } ,
}
2021-12-21 12:37:58 +00:00
}
2024-03-13 13:29:25 +00:00
return [ ] map [ string ] string {
{
"is_federated" : "0" ,
"discovery_service_url" : a . serverAddress ,
"provider_id" : fleet . WellKnownMDMFleet ,
"installation_type" : "Client" ,
} ,
2022-11-15 13:24:40 +00:00
}
2021-12-21 12:37:58 +00:00
}
2022-08-29 18:40:16 +00:00
var munkiIssues = func ( ) [ ] string {
// generate a list of random munki issues (messages)
issues := make ( [ ] string , 1000 )
for i := range issues {
// message size: between 60 and 200, with spaces between each 10-char word so
// that it can still make a bit of sense for UI tests.
numParts := rand . Intn ( 15 ) + 6 // number between 0-14, add 6 to get between 6-20
var sb strings . Builder
for j := 0 ; j < numParts ; j ++ {
if j > 0 {
sb . WriteString ( " " )
}
sb . WriteString ( randomString ( 10 ) )
}
issues [ i ] = sb . String ( )
}
return issues
} ( )
2021-12-21 12:37:58 +00:00
func ( a * agent ) munkiInfo ( ) [ ] map [ string ] string {
2022-08-29 18:40:16 +00:00
var errors , warnings [ ] string
if rand . Float64 ( ) <= a . munkiIssueProb {
for i := 0 ; i < a . munkiIssueCount ; i ++ {
if rand . Intn ( 2 ) == 1 {
errors = append ( errors , munkiIssues [ rand . Intn ( len ( munkiIssues ) ) ] )
} else {
warnings = append ( warnings , munkiIssues [ rand . Intn ( len ( munkiIssues ) ) ] )
}
}
}
errList := strings . Join ( errors , ";" )
warnList := strings . Join ( warnings , ";" )
2021-12-21 12:37:58 +00:00
return [ ] map [ string ] string {
2022-08-29 18:40:16 +00:00
{ "version" : "1.2.3" , "errors" : errList , "warnings" : warnList } ,
2021-12-21 12:37:58 +00:00
}
}
2021-12-21 20:36:19 +00:00
func ( a * agent ) googleChromeProfiles ( ) [ ] map [ string ] string {
count := rand . Intn ( 5 ) // return between 0 and 4 emails
result := make ( [ ] map [ string ] string , count )
for i := range result {
email := fmt . Sprintf ( "user%d@example.com" , i )
if i == len ( result ) - 1 {
// if the maximum number of emails is returned, set a random domain name
// so that we have email addresses that match a lot of hosts, and some
// that match few hosts.
domainRand := rand . Intn ( 10 )
email = fmt . Sprintf ( "user%d@example%d.com" , i , domainRand )
}
result [ i ] = map [ string ] string { "email" : email }
}
return result
}
2022-06-28 18:11:49 +00:00
func ( a * agent ) batteries ( ) [ ] map [ string ] string {
count := rand . Intn ( 3 ) // return between 0 and 2 batteries
result := make ( [ ] map [ string ] string , count )
for i := range result {
health := "Good"
cycleCount := rand . Intn ( 2000 )
switch {
case cycleCount > 1500 :
health = "Poor"
case cycleCount > 1000 :
health = "Fair"
}
result [ i ] = map [ string ] string {
"serial_number" : fmt . Sprintf ( "%04d" , i ) ,
"cycle_count" : strconv . Itoa ( cycleCount ) ,
"health" : health ,
}
}
return result
}
2022-09-21 19:16:31 +00:00
func ( a * agent ) diskSpace ( ) [ ] map [ string ] string {
// between 1-100 gigs, between 0-99 percentage available
gigs := rand . Intn ( 100 )
gigs ++
pct := rand . Intn ( 100 )
2023-12-22 18:46:33 +00:00
available := gigs * pct / 100
2022-09-21 19:16:31 +00:00
return [ ] map [ string ] string {
2023-12-22 18:46:33 +00:00
{
"percent_disk_space_available" : strconv . Itoa ( pct ) ,
"gigs_disk_space_available" : strconv . Itoa ( available ) ,
"gigs_total_disk_space" : strconv . Itoa ( gigs ) ,
} ,
2022-09-21 19:16:31 +00:00
}
}
2022-11-02 19:44:02 +00:00
func ( a * agent ) diskEncryption ( ) [ ] map [ string ] string {
// 50% of results have encryption enabled
2023-02-08 14:49:42 +00:00
a . DiskEncryptionEnabled = rand . Intn ( 2 ) == 1
if a . DiskEncryptionEnabled {
2022-11-02 19:44:02 +00:00
return [ ] map [ string ] string { { "1" : "1" } }
}
return [ ] map [ string ] string { }
}
2022-12-19 13:01:59 +00:00
func ( a * agent ) diskEncryptionLinux ( ) [ ] map [ string ] string {
// 50% of results have encryption enabled
2023-02-08 14:49:42 +00:00
a . DiskEncryptionEnabled = rand . Intn ( 2 ) == 1
if a . DiskEncryptionEnabled {
2022-12-19 13:01:59 +00:00
return [ ] map [ string ] string {
{ "path" : "/etc" , "encrypted" : "0" } ,
{ "path" : "/tmp" , "encrypted" : "0" } ,
{ "path" : "/" , "encrypted" : "1" } ,
}
}
return [ ] map [ string ] string {
{ "path" : "/etc" , "encrypted" : "0" } ,
{ "path" : "/tmp" , "encrypted" : "0" } ,
}
}
2023-12-13 20:46:59 +00:00
func ( a * agent ) runLiveQuery ( query string ) ( results [ ] map [ string ] string , status * fleet . OsqueryStatus , message * string , stats * fleet . Stats ) {
2023-05-25 11:12:10 +00:00
if a . liveQueryFailProb > 0.0 && rand . Float64 ( ) <= a . liveQueryFailProb {
ss := fleet . OsqueryStatus ( 1 )
2023-12-13 20:46:59 +00:00
return [ ] map [ string ] string { } , & ss , ptr . String ( "live query failed with error foobar" ) , nil
2023-05-25 11:12:10 +00:00
}
ss := fleet . OsqueryStatus ( 0 )
if a . liveQueryNoResultsProb > 0.0 && rand . Float64 ( ) <= a . liveQueryNoResultsProb {
2023-12-13 20:46:59 +00:00
return [ ] map [ string ] string { } , & ss , nil , nil
2023-05-25 11:12:10 +00:00
}
2024-01-29 18:11:49 +00:00
return [ ] map [ string ] string {
{
"admindir" : "/var/lib/dpkg" ,
"arch" : "amd64" ,
"maintainer" : "foobar" ,
"name" : "netconf" ,
"priority" : "optional" ,
"revision" : "" ,
"section" : "default" ,
"size" : "112594" ,
"source" : "" ,
"status" : "install ok installed" ,
"version" : "20230224000000" ,
} ,
2023-12-13 20:46:59 +00:00
} , & ss , nil , & fleet . Stats {
WallTimeMs : uint64 ( rand . Intn ( 1000 ) * 1000 ) ,
UserTime : uint64 ( rand . Intn ( 1000 ) ) ,
SystemTime : uint64 ( rand . Intn ( 1000 ) ) ,
Memory : uint64 ( rand . Intn ( 1000 ) ) ,
}
2023-05-25 11:12:10 +00:00
}
2023-12-13 20:46:59 +00:00
func ( a * agent ) processQuery ( name , query string ) (
handled bool , results [ ] map [ string ] string , status * fleet . OsqueryStatus , message * string , stats * fleet . Stats ,
) {
2022-05-31 13:15:58 +00:00
const (
hostPolicyQueryPrefix = "fleet_policy_query_"
hostDetailQueryPrefix = "fleet_detail_query_"
2023-05-25 11:12:10 +00:00
liveQueryPrefix = "fleet_distributed_query_"
2022-05-31 13:15:58 +00:00
)
2022-06-01 16:57:44 +00:00
statusOK := fleet . StatusOK
2022-09-26 19:39:39 +00:00
statusNotOK := fleet . OsqueryStatus ( 1 )
2024-03-14 19:33:12 +00:00
results = [ ] map [ string ] string { } // if a query fails, osquery returns empty results array
2022-05-31 13:15:58 +00:00
switch {
2023-05-25 11:12:10 +00:00
case strings . HasPrefix ( name , liveQueryPrefix ) :
2023-12-13 20:46:59 +00:00
results , status , message , stats = a . runLiveQuery ( query )
return true , results , status , message , stats
2022-05-31 13:15:58 +00:00
case strings . HasPrefix ( name , hostPolicyQueryPrefix ) :
2023-12-13 20:46:59 +00:00
return true , a . runPolicy ( query ) , & statusOK , nil , nil
2022-05-31 13:15:58 +00:00
case name == hostDetailQueryPrefix + "scheduled_query_stats" :
2023-12-13 20:46:59 +00:00
return true , a . randomQueryStats ( ) , & statusOK , nil , nil
2022-05-31 13:15:58 +00:00
case name == hostDetailQueryPrefix + "mdm" :
2024-03-13 13:29:25 +00:00
ss := statusOK
if rand . Intn ( 10 ) > 0 { // 90% success
2022-11-15 13:24:40 +00:00
results = a . mdmMac ( )
2024-03-13 13:29:25 +00:00
} else {
ss = statusNotOK
2022-11-15 13:24:40 +00:00
}
2023-12-13 20:46:59 +00:00
return true , results , & ss , nil , nil
2022-11-15 13:24:40 +00:00
case name == hostDetailQueryPrefix + "mdm_windows" :
2024-03-13 13:29:25 +00:00
ss := statusOK
if rand . Intn ( 10 ) > 0 { // 90% success
2022-11-15 13:24:40 +00:00
results = a . mdmWindows ( )
2024-03-13 13:29:25 +00:00
} else {
ss = statusNotOK
2021-12-21 12:37:58 +00:00
}
2023-12-13 20:46:59 +00:00
return true , results , & ss , nil , nil
2022-05-31 13:15:58 +00:00
case name == hostDetailQueryPrefix + "munki_info" :
ss := fleet . OsqueryStatus ( rand . Intn ( 2 ) )
if ss == fleet . StatusOK {
results = a . munkiInfo ( )
2021-12-21 12:37:58 +00:00
}
2023-12-13 20:46:59 +00:00
return true , results , & ss , nil , nil
2022-05-31 13:15:58 +00:00
case name == hostDetailQueryPrefix + "google_chrome_profiles" :
ss := fleet . OsqueryStatus ( rand . Intn ( 2 ) )
if ss == fleet . StatusOK {
results = a . googleChromeProfiles ( )
2021-12-21 20:36:19 +00:00
}
2023-12-13 20:46:59 +00:00
return true , results , & ss , nil , nil
2022-06-28 18:11:49 +00:00
case name == hostDetailQueryPrefix + "battery" :
ss := fleet . OsqueryStatus ( rand . Intn ( 2 ) )
if ss == fleet . StatusOK {
results = a . batteries ( )
}
2023-12-13 20:46:59 +00:00
return true , results , & ss , nil , nil
2022-08-29 16:34:40 +00:00
case name == hostDetailQueryPrefix + "users" :
ss := fleet . OsqueryStatus ( rand . Intn ( 2 ) )
if ss == fleet . StatusOK {
2022-11-15 13:24:40 +00:00
results = a . hostUsers ( )
2022-08-29 16:34:40 +00:00
}
2023-12-13 20:46:59 +00:00
return true , results , & ss , nil , nil
2022-08-29 16:34:40 +00:00
case name == hostDetailQueryPrefix + "software_macos" :
2024-03-14 19:33:12 +00:00
ss := fleet . StatusOK
if a . softwareVSCodeExtensionsProb > 0.0 && rand . Float64 ( ) <= a . softwareVSCodeExtensionsProb {
ss = fleet . OsqueryStatus ( 1 )
}
2022-08-29 16:34:40 +00:00
if ss == fleet . StatusOK {
results = a . softwareMacOS ( )
}
2023-12-13 20:46:59 +00:00
return true , results , & ss , nil , nil
2022-08-29 16:34:40 +00:00
case name == hostDetailQueryPrefix + "software_windows" :
2024-03-14 19:33:12 +00:00
ss := fleet . StatusOK
if a . softwareVSCodeExtensionsProb > 0.0 && rand . Float64 ( ) <= a . softwareVSCodeExtensionsProb {
ss = fleet . OsqueryStatus ( 1 )
}
2022-08-29 16:34:40 +00:00
if ss == fleet . StatusOK {
2023-07-14 16:06:34 +00:00
results = windowsSoftware
2022-08-29 16:34:40 +00:00
}
2023-12-13 20:46:59 +00:00
return true , results , & ss , nil , nil
2022-08-29 16:34:40 +00:00
case name == hostDetailQueryPrefix + "software_linux" :
2024-03-14 19:33:12 +00:00
ss := fleet . StatusOK
if a . softwareVSCodeExtensionsProb > 0.0 && rand . Float64 ( ) <= a . softwareVSCodeExtensionsProb {
ss = fleet . OsqueryStatus ( 1 )
}
2022-08-29 16:34:40 +00:00
if ss == fleet . StatusOK {
switch a . os {
2024-03-13 13:29:25 +00:00
case "ubuntu" :
2023-07-14 16:06:34 +00:00
results = ubuntuSoftware
2022-08-29 16:34:40 +00:00
}
}
2023-12-13 20:46:59 +00:00
return true , results , & ss , nil , nil
2024-03-14 19:33:12 +00:00
case name == hostDetailQueryPrefix + "software_vscode_extensions" :
ss := fleet . StatusOK
if a . softwareVSCodeExtensionsFailProb > 0.0 && rand . Float64 ( ) <= a . softwareVSCodeExtensionsFailProb {
ss = fleet . OsqueryStatus ( 1 )
}
if ss == fleet . StatusOK {
results = a . softwareVSCodeExtensions ( )
}
return true , results , & ss , nil , nil
2022-09-21 19:16:31 +00:00
case name == hostDetailQueryPrefix + "disk_space_unix" || name == hostDetailQueryPrefix + "disk_space_windows" :
ss := fleet . OsqueryStatus ( rand . Intn ( 2 ) )
if ss == fleet . StatusOK {
results = a . diskSpace ( )
}
2023-12-13 20:46:59 +00:00
return true , results , & ss , nil , nil
2022-12-19 13:01:59 +00:00
case strings . HasPrefix ( name , hostDetailQueryPrefix + "disk_encryption_linux" ) :
ss := fleet . OsqueryStatus ( rand . Intn ( 2 ) )
if ss == fleet . StatusOK {
results = a . diskEncryptionLinux ( )
}
2023-12-13 20:46:59 +00:00
return true , results , & ss , nil , nil
2023-02-08 14:49:42 +00:00
case name == hostDetailQueryPrefix + "disk_encryption_darwin" ||
name == hostDetailQueryPrefix + "disk_encryption_windows" :
2022-11-02 19:44:02 +00:00
ss := fleet . OsqueryStatus ( rand . Intn ( 2 ) )
if ss == fleet . StatusOK {
results = a . diskEncryption ( )
}
2023-12-13 20:46:59 +00:00
return true , results , & ss , nil , nil
2022-09-26 19:39:39 +00:00
case name == hostDetailQueryPrefix + "kubequery_info" && a . os != "kubequery" :
// Real osquery running on hosts would return no results if it was not
// running kubequery (due to discovery query). Returning true here so that
// the caller knows it is handled, will not try to return lorem-ipsum-style
// results.
2023-12-13 20:46:59 +00:00
return true , nil , & statusNotOK , nil , nil
2022-05-31 13:15:58 +00:00
default :
// Look for results in the template file.
2021-12-09 21:05:32 +00:00
if t := a . templates . Lookup ( name ) ; t == nil {
2023-12-13 20:46:59 +00:00
return false , nil , nil , nil , nil
2021-09-22 20:18:55 +00:00
}
2021-11-01 18:23:31 +00:00
var ni bytes . Buffer
2021-12-09 21:05:32 +00:00
err := a . templates . ExecuteTemplate ( & ni , name , a )
2021-11-01 18:23:31 +00:00
if err != nil {
panic ( err )
}
2022-05-31 13:15:58 +00:00
err = json . Unmarshal ( ni . Bytes ( ) , & results )
2021-11-01 18:23:31 +00:00
if err != nil {
panic ( err )
2021-09-22 20:18:55 +00:00
}
2022-08-26 18:55:03 +00:00
2023-12-13 20:46:59 +00:00
return true , results , & statusOK , nil , nil
2022-05-31 13:15:58 +00:00
}
}
2024-02-08 15:40:13 +00:00
func ( a * agent ) DistributedWrite ( queries map [ string ] string ) error {
2022-05-31 13:15:58 +00:00
r := service . SubmitDistributedQueryResultsRequest {
Results : make ( fleet . OsqueryDistributedQueryResults ) ,
Statuses : make ( map [ string ] fleet . OsqueryStatus ) ,
2023-05-25 11:12:10 +00:00
Messages : make ( map [ string ] string ) ,
2023-12-13 20:46:59 +00:00
Stats : make ( map [ string ] * fleet . Stats ) ,
2022-05-31 13:15:58 +00:00
}
r . NodeKey = a . nodeKey
for name , query := range queries {
2023-12-13 20:46:59 +00:00
handled , results , status , message , stats := a . processQuery ( name , query )
2022-06-01 16:57:44 +00:00
if ! handled {
// If osquery-perf does not handle the incoming query,
// always return status OK and the default query result.
r . Results [ name ] = defaultQueryResult
r . Statuses [ name ] = fleet . StatusOK
} else {
if results != nil {
r . Results [ name ] = results
}
if status != nil {
r . Statuses [ name ] = * status
}
2023-05-25 11:12:10 +00:00
if message != nil {
r . Messages [ name ] = * message
}
2023-12-13 20:46:59 +00:00
if stats != nil {
r . Stats [ name ] = stats
}
2022-05-31 13:15:58 +00:00
}
2021-11-01 18:23:31 +00:00
}
body , err := json . Marshal ( r )
if err != nil {
panic ( err )
2021-09-22 20:18:55 +00:00
}
2024-02-19 14:33:19 +00:00
request , err := http . NewRequest ( "POST" , a . serverAddress + "/api/osquery/distributed/write" , bytes . NewReader ( body ) )
if err != nil {
return err
}
request . Header . Add ( "Content-type" , "application/json" )
2021-10-14 13:09:58 +00:00
2024-02-19 14:33:19 +00:00
response , err := http . DefaultClient . Do ( request )
2024-02-12 22:06:58 +00:00
if err != nil {
return fmt . Errorf ( "distributed/write request failed to run: %w" , err )
}
2024-02-19 14:33:19 +00:00
defer response . Body . Close ( )
2024-02-12 22:06:58 +00:00
2022-10-28 17:27:21 +00:00
a . stats . IncrementDistributedWrites ( )
2024-02-08 15:40:13 +00:00
2024-02-19 14:33:19 +00:00
statusCode := response . StatusCode
2024-02-08 15:40:13 +00:00
if statusCode != http . StatusOK {
a . stats . IncrementDistributedWriteErrors ( )
return fmt . Errorf ( "distributed/write request failed: %d" , statusCode )
}
2021-09-22 20:18:55 +00:00
// No need to read the distributed write body
2024-02-08 15:40:13 +00:00
return nil
2021-09-22 20:18:55 +00:00
}
2024-03-04 18:10:10 +00:00
func scheduledQueryResults ( packName , queryName string , numResults int ) [ ] byte {
return [ ] byte ( ` {
2024-02-12 22:06:58 +00:00
"snapshot" : [ ` + rows(numResults) + `
2023-10-13 02:41:04 +00:00
] ,
"action" : "snapshot" ,
"name" : "pack/` + packName + `/` + queryName + `" ,
2024-02-12 22:06:58 +00:00
"hostIdentifier" : "EF9595F0-CE81-493A-9B06-D8A9D2CCB952" ,
2023-10-13 02:41:04 +00:00
"calendarTime" : "Fri Oct 6 18:13:04 2023 UTC" ,
"unixTime" : 1696615984 ,
"epoch" : 0 ,
"counter" : 0 ,
"numerics" : false ,
"decorations" : {
"host_uuid" : "187c4d56-8e45-1a9d-8513-ac17efd2f0fd" ,
2024-02-12 22:06:58 +00:00
"hostname" : "osquery-perf"
2023-10-13 02:41:04 +00:00
}
2023-10-17 14:30:59 +00:00
} ` )
}
2024-03-04 18:10:10 +00:00
func ( a * agent ) connCheck ( ) error {
request , err := http . NewRequest ( "GET" , a . serverAddress + "/version" , nil )
2024-02-19 14:33:19 +00:00
if err != nil {
2024-03-04 18:10:10 +00:00
panic ( err )
2024-02-19 14:33:19 +00:00
}
2024-03-04 18:10:10 +00:00
response , err := http . DefaultClient . Do ( request )
2023-10-17 14:30:59 +00:00
if err != nil {
2024-03-04 18:10:10 +00:00
return err
2023-10-17 14:30:59 +00:00
}
2024-03-04 18:10:10 +00:00
defer response . Body . Close ( )
if response . StatusCode != http . StatusOK {
return errors . New ( http . StatusText ( response . StatusCode ) )
2023-10-17 14:30:59 +00:00
}
2024-03-04 18:10:10 +00:00
return nil
}
func ( a * agent ) submitLogs ( results [ ] resultLog ) error {
// Connection check to prevent unnecessary JSON marshaling when the server is down.
if err := a . connCheck ( ) ; err != nil {
return fmt . Errorf ( "/version check failed: %w" , err )
2023-10-13 02:41:04 +00:00
}
2024-03-04 18:10:10 +00:00
var resultLogs [ ] byte
for i , result := range results {
if i > 0 {
resultLogs = append ( resultLogs , ',' )
}
resultLogs = append ( resultLogs , result . emit ( ) ... )
2023-10-13 02:41:04 +00:00
}
2024-03-04 18:10:10 +00:00
body := [ ] byte ( ` { "node_key": " ` + a . nodeKey + ` ", "log_type": "result", "data": [ ` + string ( resultLogs ) + ` ]} ` )
2024-02-19 14:33:19 +00:00
request , err := http . NewRequest ( "POST" , a . serverAddress + "/api/osquery/log" , bytes . NewReader ( body ) )
if err != nil {
return err
}
request . Header . Add ( "Content-type" , "application/json" )
2023-10-20 13:29:59 +00:00
2024-02-19 14:33:19 +00:00
response , err := http . DefaultClient . Do ( request )
2024-02-12 22:06:58 +00:00
if err != nil {
return fmt . Errorf ( "log request failed to run: %w" , err )
}
2024-02-19 14:33:19 +00:00
defer response . Body . Close ( )
2024-02-12 22:06:58 +00:00
2023-10-20 13:29:59 +00:00
a . stats . IncrementResultLogRequests ( )
2024-02-08 15:40:13 +00:00
2024-02-19 14:33:19 +00:00
statusCode := response . StatusCode
2024-02-08 15:40:13 +00:00
if statusCode != http . StatusOK {
a . stats . IncrementResultLogErrors ( )
return fmt . Errorf ( "log request failed: %d" , statusCode )
}
return nil
2023-10-13 02:41:04 +00:00
}
2024-02-08 15:40:13 +00:00
// rows returns a set of rows for use in tests for query results.
2024-02-12 22:06:58 +00:00
func rows ( num int ) string {
2023-10-13 02:41:04 +00:00
b := strings . Builder { }
for i := 0 ; i < num ; i ++ {
b . WriteString ( ` {
"build_distro" : "centos7" ,
"build_platform" : "linux" ,
"config_hash" : "eed0d8296e5f90b790a23814a9db7a127b13498d" ,
"config_valid" : "1" ,
"extensions" : "active" ,
"instance_id" : "e5799132-85ab-4cfa-89f3-03e0dd3c509a" ,
"pid" : "3574" ,
"platform_mask" : "9" ,
"start_time" : "1696502961" ,
2024-02-12 22:06:58 +00:00
"uuid" : "EF9595F0-CE81-493A-9B06-D8A9D2CCB95" ,
2023-10-13 02:41:04 +00:00
"version" : "5.9.2" ,
"watcher" : "3570"
} ` )
if i != num - 1 {
b . WriteString ( "," )
}
}
return b . String ( )
}
2021-09-22 20:18:55 +00:00
func main ( ) {
2024-02-12 22:06:58 +00:00
// Start HTTP server for pprof. See https://pkg.go.dev/net/http/pprof.
go func ( ) {
log . Println ( http . ListenAndServe ( "localhost:6060" , nil ) )
} ( )
2024-02-19 14:33:19 +00:00
// #nosec (osquery-perf is only used for testing)
tlsConfig := & tls . Config {
InsecureSkipVerify : true ,
}
tr := http . DefaultTransport . ( * http . Transport ) . Clone ( )
tr . TLSClientConfig = tlsConfig
http . DefaultClient . Transport = tr
2022-11-15 13:24:40 +00:00
validTemplateNames := map [ string ] bool {
2024-01-29 18:11:49 +00:00
"macos_13.6.2.tmpl" : true ,
"macos_14.1.2.tmpl" : true ,
"windows_11.tmpl" : true ,
"windows_11_22H2_2861.tmpl" : true ,
"windows_11_22H2_3007.tmpl" : true ,
"ubuntu_22.04.tmpl" : true ,
2022-11-15 13:24:40 +00:00
}
allowedTemplateNames := make ( [ ] string , 0 , len ( validTemplateNames ) )
for k := range validTemplateNames {
allowedTemplateNames = append ( allowedTemplateNames , k )
}
var (
2023-10-17 14:30:59 +00:00
serverURL = flag . String ( "server_url" , "https://localhost:8080" , "URL (with protocol and port of osquery server)" )
enrollSecret = flag . String ( "enroll_secret" , "" , "Enroll secret to authenticate enrollment" )
hostCount = flag . Int ( "host_count" , 10 , "Number of hosts to start (default 10)" )
randSeed = flag . Int64 ( "seed" , time . Now ( ) . UnixNano ( ) , "Seed for random generator (default current time)" )
startPeriod = flag . Duration ( "start_period" , 10 * time . Second , "Duration to spread start of hosts over" )
configInterval = flag . Duration ( "config_interval" , 1 * time . Minute , "Interval for config requests" )
// Flag logger_tls_period defines how often to check for sending scheduled query results.
// 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" )
2023-04-05 16:53:43 +00:00
queryInterval = flag . Duration ( "query_interval" , 10 * time . Second , "Interval for live query requests" )
2024-03-13 13:29:25 +00:00
mdmCheckInInterval = flag . Duration ( "mdm_check_in_interval" , 10 * time . Second , "Interval for performing MDM check-ins (applies to both macOS and Windows)" )
2023-04-05 16:53:43 +00:00
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" )
2024-03-14 19:33:12 +00:00
softwareQueryFailureProb = flag . Float64 ( "software_query_fail_prob" , 0.5 , "Probability of the software query failing" )
softwareVSCodeExtensionsQueryFailureProb = flag . Float64 ( "software_vscode_extensions_query_fail_prob" , 0.5 , "Probability of the software vscode_extensions query failing" )
commonSoftwareCount = flag . Int ( "common_software_count" , 10 , "Number of common installed applications reported to fleet" )
commonVSCodeExtensionsSoftwareCount = flag . Int ( "common_vscode_extensions_software_count" , 5 , "Number of common vscode_extensions installed applications reported to fleet" )
commonSoftwareUninstallCount = flag . Int ( "common_software_uninstall_count" , 1 , "Number of common software to uninstall" )
commonVSCodeExtensionsSoftwareUninstallCount = flag . Int ( "common_vscode_extensions_software_uninstall_count" , 1 , "Number of common vscode_extensions software to uninstall" )
commonSoftwareUninstallProb = flag . Float64 ( "common_software_uninstall_prob" , 0.1 , "Probability of uninstalling common_software_uninstall_count unique software/s" )
commonVSCodeExtensionsSoftwareUninstallProb = flag . Float64 ( "common_vscode_extensions_software_uninstall_prob" , 0.1 , "Probability of uninstalling vscode_extensions common_software_uninstall_count unique software/s" )
2023-04-05 16:53:43 +00:00
2024-03-14 19:33:12 +00:00
uniqueSoftwareCount = flag . Int ( "unique_software_count" , 1 , "Number of unique software installed on each host" )
uniqueVSCodeExtensionsSoftwareCount = flag . Int ( "unique_vscode_extensions_software_count" , 1 , "Number of unique vscode_extensions software installed on each host" )
uniqueSoftwareUninstallCount = flag . Int ( "unique_software_uninstall_count" , 1 , "Number of unique software to uninstall" )
uniqueVSCodeExtensionsSoftwareUninstallCount = flag . Int ( "unique_vscode_extensions_software_uninstall_count" , 1 , "Number of unique vscode_extensions software to uninstall" )
uniqueSoftwareUninstallProb = flag . Float64 ( "unique_software_uninstall_prob" , 0.1 , "Probability of uninstalling unique_software_uninstall_count common software/s" )
uniqueVSCodeExtensionsSoftwareUninstallProb = flag . Float64 ( "unique_vscode_extensions_software_uninstall_prob" , 0.1 , "Probability of uninstalling unique_vscode_extensions_software_uninstall_count common software/s" )
2023-04-05 16:53:43 +00:00
2022-11-15 13:24:40 +00:00
vulnerableSoftwareCount = flag . Int ( "vulnerable_software_count" , 10 , "Number of vulnerable installed applications reported to fleet" )
withLastOpenedSoftwareCount = flag . Int ( "with_last_opened_software_count" , 10 , "Number of applications that may report a last opened timestamp to fleet" )
lastOpenedChangeProb = flag . Float64 ( "last_opened_change_prob" , 0.1 , "Probability of last opened timestamp to be reported as changed [0, 1]" )
commonUserCount = flag . Int ( "common_user_count" , 10 , "Number of common host users reported to fleet" )
uniqueUserCount = flag . Int ( "unique_user_count" , 10 , "Number of unique host users reported to fleet" )
policyPassProb = flag . Float64 ( "policy_pass_prob" , 1.0 , "Probability of policies to pass [0, 1]" )
orbitProb = flag . Float64 ( "orbit_prob" , 0.5 , "Probability of a host being identified as orbit install [0, 1]" )
munkiIssueProb = flag . Float64 ( "munki_issue_prob" , 0.5 , "Probability of a host having munki issues (note that ~50% of hosts have munki installed) [0, 1]" )
munkiIssueCount = flag . Int ( "munki_issue_count" , 10 , "Number of munki issues reported by hosts identified to have munki issues" )
2023-08-31 13:58:50 +00:00
// E.g. when running with `-host_count=10`, you can set host count for each template the following way:
2024-01-29 18:11:49 +00:00
// `-os_templates=windows_11.tmpl:3,macos_14.1.2.tmpl:4,ubuntu_22.04.tmpl:3`
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 ) )
2023-08-31 13:58:50 +00:00
emptySerialProb = flag . Float64 ( "empty_serial_prob" , 0.1 , "Probability of a host having no serial number [0, 1]" )
2023-05-12 16:50:20 +00:00
2024-03-13 13:29:25 +00:00
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" )
2023-05-25 11:12:10 +00:00
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" )
2023-08-23 22:31:47 +00:00
disableScriptExec = flag . Bool ( "disable_script_exec" , false , "Disable script execution support" )
2024-02-08 15:40:13 +00:00
disableFleetDesktop = flag . Bool ( "disable_fleet_desktop" , false , "Disable Fleet Desktop" )
// logger_tls_max_lines is simulating the osquery setting with the same name.
loggerTLSMaxLines = flag . Int ( "" , 1024 , "Maximum number of buffered result log lines to send on every log request" )
2022-11-15 13:24:40 +00:00
)
2021-09-22 20:18:55 +00:00
flag . Parse ( )
rand . Seed ( * randSeed )
2022-11-15 13:24:40 +00:00
if * onlyAlreadyEnrolled {
// Orbit enrollment does not support the "already enrolled" mode at the
// moment (see TODO in this file).
* orbitProb = 0
2022-06-08 01:09:47 +00:00
}
2024-02-08 15:40:13 +00:00
if * commonSoftwareUninstallCount > * commonSoftwareCount {
2023-04-05 16:53:43 +00:00
log . Fatalf ( "Argument common_software_uninstall_count cannot be bigger than common_software_count" )
}
2024-02-08 15:40:13 +00:00
if * uniqueSoftwareUninstallCount > * uniqueSoftwareCount {
2023-04-05 16:53:43 +00:00
log . Fatalf ( "Argument unique_software_uninstall_count cannot be bigger than unique_software_count" )
}
2023-08-31 13:58:50 +00:00
tmplsm := make ( map [ * template . Template ] int )
2022-11-15 13:24:40 +00:00
requestedTemplates := strings . Split ( * osTemplates , "," )
2023-08-31 13:58:50 +00:00
tmplsTotalHostCount := 0
2022-11-15 13:24:40 +00:00
for _ , nm := range requestedTemplates {
2023-08-31 13:58:50 +00:00
numberOfHosts := 0
if strings . Contains ( nm , ":" ) {
parts := strings . Split ( nm , ":" )
nm = parts [ 0 ]
hc , err := strconv . ParseInt ( parts [ 1 ] , 10 , 64 )
if err != nil {
log . Fatalf ( "Invalid template host count: %s" , parts [ 1 ] )
}
numberOfHosts = int ( hc )
}
2022-11-15 13:24:40 +00:00
if ! strings . HasSuffix ( nm , ".tmpl" ) {
nm += ".tmpl"
}
if ! validTemplateNames [ nm ] {
log . Fatalf ( "Invalid template name: %s (accepted values: %v)" , nm , allowedTemplateNames )
}
tmpl , err := template . ParseFS ( templatesFS , nm )
2022-06-08 01:09:47 +00:00
if err != nil {
log . Fatal ( "parse templates: " , err )
}
2023-08-31 13:58:50 +00:00
tmplsm [ tmpl ] = numberOfHosts
tmplsTotalHostCount += numberOfHosts
}
if tmplsTotalHostCount != 0 && tmplsTotalHostCount != * hostCount {
log . Fatalf ( "Invalid host count in templates: total=%d vs host_count=%d" , tmplsTotalHostCount , * hostCount )
2021-09-22 20:18:55 +00:00
}
2021-11-01 18:23:31 +00:00
// Spread starts over the interval to prevent thundering herd
2021-09-22 20:18:55 +00:00
sleepTime := * startPeriod / time . Duration ( * hostCount )
2021-10-14 13:09:58 +00:00
2023-10-20 13:29:59 +00:00
stats := & Stats {
startTime : time . Now ( ) ,
}
2021-10-14 13:09:58 +00:00
go stats . runLoop ( )
2021-11-01 18:23:31 +00:00
nodeKeyManager := & nodeKeyManager { }
2021-10-14 13:09:58 +00:00
if nodeKeyFile != nil {
nodeKeyManager . filepath = * nodeKeyFile
nodeKeyManager . LoadKeys ( )
}
2023-08-31 13:58:50 +00:00
var tmplss [ ] * template . Template
for tmpl := range tmplsm {
tmplss = append ( tmplss , tmpl )
}
2021-09-22 20:18:55 +00:00
for i := 0 ; i < * hostCount ; i ++ {
2023-08-31 13:58:50 +00:00
var tmpl * template . Template
if tmplsTotalHostCount > 0 {
for tmpl_ , hostCount := range tmplsm {
if hostCount > 0 {
tmpl = tmpl_
tmplsm [ tmpl_ ] = tmplsm [ tmpl_ ] - 1
break
}
}
if tmpl == nil {
log . Fatalf ( "Failed to determine template for host: %d" , i )
}
} else {
tmpl = tmplss [ i % len ( tmplss ) ]
}
2023-05-12 16:50:20 +00:00
a := newAgent ( i + 1 ,
* serverURL ,
* enrollSecret ,
tmpl ,
* configInterval ,
2023-10-17 14:30:59 +00:00
* logInterval ,
2023-05-12 16:50:20 +00:00
* queryInterval ,
* mdmCheckInInterval ,
2024-03-14 19:33:12 +00:00
* softwareQueryFailureProb ,
* softwareVSCodeExtensionsQueryFailureProb ,
2022-05-31 13:15:58 +00:00
softwareEntityCount {
entityCount : entityCount {
common : * commonSoftwareCount ,
unique : * uniqueSoftwareCount ,
} ,
2023-04-05 16:53:43 +00:00
vulnerable : * vulnerableSoftwareCount ,
withLastOpened : * withLastOpenedSoftwareCount ,
lastOpenedProb : * lastOpenedChangeProb ,
commonSoftwareUninstallCount : * commonSoftwareUninstallCount ,
commonSoftwareUninstallProb : * commonSoftwareUninstallProb ,
uniqueSoftwareUninstallCount : * uniqueSoftwareUninstallCount ,
uniqueSoftwareUninstallProb : * uniqueSoftwareUninstallProb ,
2024-03-14 19:33:12 +00:00
} ,
softwareExtraEntityCount {
entityCount : entityCount {
common : * commonVSCodeExtensionsSoftwareCount ,
unique : * uniqueVSCodeExtensionsSoftwareCount ,
} ,
commonSoftwareUninstallCount : * commonVSCodeExtensionsSoftwareUninstallCount ,
commonSoftwareUninstallProb : * commonVSCodeExtensionsSoftwareUninstallProb ,
uniqueSoftwareUninstallCount : * uniqueVSCodeExtensionsSoftwareUninstallCount ,
uniqueSoftwareUninstallProb : * uniqueVSCodeExtensionsSoftwareUninstallProb ,
} ,
entityCount {
2022-05-31 13:15:58 +00:00
common : * commonUserCount ,
unique : * uniqueUserCount ,
2022-01-28 13:05:11 +00:00
} ,
2022-05-31 13:15:58 +00:00
* policyPassProb ,
* orbitProb ,
2022-08-29 18:40:16 +00:00
* munkiIssueProb ,
* munkiIssueCount ,
2023-02-28 17:55:04 +00:00
* emptySerialProb ,
2023-05-12 16:50:20 +00:00
* mdmProb ,
* mdmSCEPChallenge ,
2023-05-25 11:12:10 +00:00
* liveQueryFailProb ,
* liveQueryNoResultsProb ,
2023-08-23 22:31:47 +00:00
* disableScriptExec ,
2024-02-08 15:40:13 +00:00
* disableFleetDesktop ,
* loggerTLSMaxLines ,
2022-05-31 13:15:58 +00:00
)
2021-12-09 21:05:32 +00:00
a . stats = stats
a . nodeKeyManager = nodeKeyManager
2022-11-15 13:24:40 +00:00
go a . runLoop ( i , * onlyAlreadyEnrolled )
2021-09-22 20:18:55 +00:00
time . Sleep ( sleepTime )
}
2024-02-08 15:40:13 +00:00
log . Println ( "Agents running. Kill with C-c." )
2021-09-22 20:18:55 +00:00
<- make ( chan struct { } )
}