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"
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"
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"
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"
2021-10-14 13:09:58 +00:00
"github.com/valyala/fasthttp"
2021-09-22 20:18:55 +00:00
)
2023-07-14 16:06:34 +00:00
var (
//go:embed *.tmpl
templatesFS embed . FS
2021-10-14 13:09:58 +00:00
2023-07-14 16:06:34 +00:00
//go:embed *.software
macOSVulnerableSoftwareFS embed . FS
2022-01-28 13:05:11 +00:00
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
2023-07-14 16:06:34 +00:00
macosVulnerableSoftware [ ] fleet . Software
windowsSoftware [ ] map [ string ] string
ubuntuSoftware [ ] map [ string ] string
)
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 {
fmt . Println ( "skipping" , string ( line ) )
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" ,
} )
}
2023-07-14 16:06:34 +00:00
log . Printf ( "Loaded %d vulnerable macOS software\n" , len ( macosVulnerableSoftware ) )
}
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 ( )
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 {
2023-10-20 13:29:59 +00:00
startTime time . Time
2023-05-12 16:50:20 +00:00
errors int
osqueryEnrollments int
orbitEnrollments int
mdmEnrollments int
distributedWrites int
mdmCommandsReceived int
2023-10-20 13:29:59 +00:00
distributedReads int
configRequests int
resultLogRequests int
2023-05-12 16:50:20 +00:00
orbitErrors int
mdmErrors int
desktopErrors 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
}
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 ++
}
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
}
func ( s * Stats ) Log ( ) {
s . l . Lock ( )
defer s . l . Unlock ( )
fmt . Printf (
2023-10-20 13:29:59 +00:00
"%s :: 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, orbit errors: %d, desktop errors: %d, mdm errors: %d\n" ,
2023-05-12 16:50:20 +00:00
time . Now ( ) . Format ( "2006-01-02T15:04:05Z" ) ,
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 ,
2023-05-12 16:50:20 +00:00
s . mdmCommandsReceived ,
2022-10-28 17:27:21 +00:00
s . orbitErrors ,
s . desktopErrors ,
2023-05-12 16:50:20 +00:00
s . mdmErrors ,
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 {
fmt . Println ( "WARNING (ignore if creating a new node key file): error loading nodekey file:" , err )
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.
2021-10-14 13:09:58 +00:00
fmt . Printf ( "loaded %d node keys\n" , len ( n . nodekeys ) )
}
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 {
fmt . Println ( "error opening nodekey file:" , err . Error ( ) )
return
}
defer f . Close ( )
if _ , err := f . WriteString ( nodekey + "\n" ) ; err != nil {
fmt . Println ( "error writing nodekey file:" , err )
}
}
2021-11-01 18:23:31 +00:00
type agent struct {
2023-05-25 11:12:10 +00:00
agentIndex int
softwareCount softwareEntityCount
userCount entityCount
policyPassProb float64
munkiIssueProb float64
munkiIssueCount int
liveQueryFailProb float64
liveQueryNoResultsProb float64
strings map [ string ] string
serverAddress string
fastClient fasthttp . Client
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
2023-05-12 16:50:20 +00:00
// mdmClient simulates a device running the MDM protocol (client side).
2023-11-01 14:13:12 +00:00
mdmClient * mdmtest . TestAppleMDMClient
2023-05-12 16:50:20 +00:00
// isEnrolledToMDM is true when the mdmDevice has enrolled.
isEnrolledToMDM bool
// isEnrolledToMDMMu protects isEnrolledToMDM.
isEnrolledToMDMMu sync . Mutex
2023-08-23 22:31:47 +00:00
disableScriptExec bool
// atomic boolean is set to true when executing scripts, so that only a
// single goroutine at a time can execute scripts.
scriptExecRunning atomic . Bool
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
scheduledQueriesMu sync . Mutex
scheduledQueries [ ] string
scheduledQueryData [ ] scheduledQuery
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
}
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 ,
2023-05-12 16:50:20 +00:00
softwareCount softwareEntityCount ,
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 ,
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 ( ) )
}
// #nosec (osquery-perf is only used for testing)
tlsConfig := & tls . Config {
InsecureSkipVerify : true ,
}
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 = ""
}
uuid := strings . ToUpper ( uuid . New ( ) . String ( ) )
2023-11-01 14:13:12 +00:00
var mdmClient * mdmtest . TestAppleMDMClient
2023-05-12 16:50:20 +00:00
if rand . Float64 ( ) <= mdmProb {
2023-11-01 14:13:12 +00:00
mdmClient = mdmtest . NewTestMDMClientAppleDirect ( mdmtest . AppleEnrollInfo {
2023-05-12 16:50:20 +00:00
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
2023-02-28 17:55:04 +00:00
}
2021-11-01 18:23:31 +00:00
return & agent {
2023-05-25 11:12:10 +00:00
agentIndex : agentIndex ,
serverAddress : serverAddress ,
softwareCount : softwareCount ,
userCount : userCount ,
strings : make ( map [ string ] string ) ,
policyPassProb : policyPassProb ,
munkiIssueProb : munkiIssueProb ,
munkiIssueCount : munkiIssueCount ,
liveQueryFailProb : liveQueryFailProb ,
liveQueryNoResultsProb : liveQueryNoResultsProb ,
2021-12-09 21:05:32 +00:00
fastClient : fasthttp . Client {
2022-05-31 13:15:58 +00:00
TLSConfig : tlsConfig ,
2021-12-09 21:05:32 +00:00
} ,
2022-05-31 13:15:58 +00:00
templates : templates ,
deviceAuthToken : deviceAuthToken ,
2022-08-29 16:34:40 +00:00
os : strings . TrimRight ( templates . Name ( ) , ".tmpl" ) ,
2021-12-09 21:05:32 +00:00
2023-05-12 16:50:20 +00:00
EnrollSecret : enrollSecret ,
ConfigInterval : configInterval ,
2023-10-17 14:30:59 +00:00
LogInterval : logInterval ,
2023-05-12 16:50:20 +00:00
QueryInterval : queryInterval ,
MDMCheckInInterval : mdmCheckInInterval ,
UUID : uuid ,
SerialNumber : serialNumber ,
2023-08-23 22:31:47 +00:00
mdmClient : mdmClient ,
disableScriptExec : disableScriptExec ,
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" `
NextRun float64
NumResults uint
PackName string
}
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 {
return
}
}
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
2021-11-01 18:23:31 +00:00
a . config ( )
2021-09-22 20:18:55 +00:00
resp , err := a . DistributedRead ( )
if err != nil {
log . Println ( err )
} else {
if len ( resp . Queries ) > 0 {
a . DistributedWrite ( resp . Queries )
}
}
2022-10-28 17:27:21 +00:00
if a . isOrbit ( ) {
go a . runOrbitLoop ( )
}
2023-05-12 16:50:20 +00:00
if a . mdmClient != nil {
if err := a . mdmClient . Enroll ( ) ; err != nil {
log . Printf ( "MDM enroll failed: %s\n" , err )
a . stats . IncrementMDMErrors ( )
return
}
a . setMDMEnrolled ( )
a . stats . IncrementMDMEnrollments ( )
go a . runMDMLoop ( )
}
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 {
resp , err := a . DistributedRead ( )
if err != nil {
log . Println ( err )
} else if 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 {
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
var results [ ] json . RawMessage
now := time . Now ( ) . Unix ( )
a . scheduledQueriesMu . Lock ( )
for i , query := range a . scheduledQueryData {
if query . NextRun == 0 || now >= int64 ( query . NextRun ) {
results = append ( results , a . scheduledQueryResults ( query . PackName , query . Name , int ( query . NumResults ) ) )
a . scheduledQueryData [ i ] . NextRun = float64 ( now + int64 ( query . ScheduleInterval ) )
2023-10-17 14:30:59 +00:00
}
2021-09-22 20:18:55 +00:00
}
2023-10-20 13:29:59 +00:00
a . scheduledQueriesMu . Unlock ( )
if len ( results ) > 0 {
a . SubmitLogs ( results )
}
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" ) ,
} ,
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 {
log . Println ( "creating device client: " , err )
}
// orbit does a config check when it starts
if _ , err := orbitClient . GetConfig ( ) ; err != nil {
a . stats . IncrementOrbitErrors ( )
log . Println ( "orbitClient.GetConfig: " , err )
}
tokenRotationEnabled := orbitClient . GetServerCapabilities ( ) . Has ( fleet . CapabilityOrbitEndpoints ) &&
orbitClient . GetServerCapabilities ( ) . Has ( fleet . CapabilityTokenRotation )
// 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 )
}
if err := deviceClient . CheckToken ( * a . deviceAuthToken ) ; err != nil {
a . stats . IncrementOrbitErrors ( )
log . Println ( "deviceClient.CheckToken: " , err )
}
}
// 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
}
}
}
// fleet desktop performs a burst of check token requests when it's initialized
checkToken ( )
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 )
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 ( )
log . Println ( "orbitClient.GetConfig: " , err )
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 )
}
2022-10-28 17:27:21 +00:00
case <- orbitTokenRemoteCheckTicker :
if tokenRotationEnabled {
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 :
if tokenRotationEnabled {
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 ( )
log . Println ( "orbitClient.Ping: " , err )
2023-10-04 12:10:16 +00:00
continue
2022-10-28 17:27:21 +00:00
}
case <- fleetDesktopPolicyTicker :
2023-05-15 20:00:52 +00:00
if _ , err := deviceClient . DesktopSummary ( * a . deviceAuthToken ) ; err != nil {
2022-10-28 17:27:21 +00:00
a . stats . IncrementDesktopErrors ( )
log . Println ( "deviceClient.NumberOfFailingPolicies: " , err )
2023-10-04 12:10:16 +00:00
continue
2022-10-28 17:27:21 +00:00
}
}
}
}
2023-05-12 16:50:20 +00:00
func ( a * agent ) runMDMLoop ( ) {
mdmCheckInTicker := time . Tick ( a . MDMCheckInInterval )
for range mdmCheckInTicker {
mdmCommandPayload , err := a . mdmClient . Idle ( )
if err != nil {
log . Printf ( "MDM Idle request failed: %s\n" , err )
a . stats . IncrementMDMErrors ( )
continue
}
INNER_FOR_LOOP :
for mdmCommandPayload != nil {
a . stats . IncrementMDMCommandsReceived ( )
mdmCommandPayload , err = a . mdmClient . Acknowledge ( mdmCommandPayload . CommandUUID )
if err != nil {
log . Printf ( "MDM Acknowledge request failed: %s\n" , err )
a . stats . IncrementMDMErrors ( )
break INNER_FOR_LOOP
}
}
}
}
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 )
log . Printf ( "running scripts: %v\n" , execIDs )
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
}
log . Printf ( "did save disabled host script result: id=%s\n" , execID )
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
}
log . Printf ( "did exec and save host script result: id=%s, output size=%d, runtime=%d, exit code=%d\n" , execID , base64 . StdEncoding . EncodedLen ( n ) , runtime , exitCode )
}
}
2021-11-01 18:23:31 +00:00
func ( a * agent ) waitingDo ( req * fasthttp . Request , res * fasthttp . Response ) {
2021-12-09 21:05:32 +00:00
err := a . fastClient . Do ( req , res )
2021-10-14 13:09:58 +00:00
for err != nil || res . StatusCode ( ) != http . StatusOK {
fmt . Println ( err , res . StatusCode ( ) )
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 )
2021-12-09 21:05:32 +00:00
err = a . fastClient . Do ( req , res )
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
}
2023-02-28 17:55:04 +00:00
req := fasthttp . AcquireRequest ( )
2022-10-28 17:27:21 +00:00
req . SetBody ( jsonBytes )
req . Header . SetMethod ( "POST" )
req . Header . SetContentType ( "application/json" )
req . Header . SetRequestURI ( a . serverAddress + "/api/fleet/orbit/enroll" )
resp := fasthttp . AcquireResponse ( )
a . waitingDo ( req , resp )
2023-02-28 17:55:04 +00:00
fasthttp . ReleaseRequest ( req )
2022-10-28 17:27:21 +00:00
defer fasthttp . ReleaseResponse ( resp )
if resp . StatusCode ( ) != http . StatusOK {
log . Println ( "orbit enroll status:" , resp . StatusCode ( ) )
return fmt . Errorf ( "status code: %d" , resp . StatusCode ( ) )
}
var parsedResp service . EnrollOrbitResponse
if err := json . Unmarshal ( resp . Body ( ) , & parsedResp ) ; err != nil {
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
}
2021-10-14 13:09:58 +00:00
req := fasthttp . AcquireRequest ( )
req . SetBody ( body . Bytes ( ) )
req . Header . SetMethod ( "POST" )
req . Header . SetContentType ( "application/json" )
2021-09-22 20:18:55 +00:00
req . Header . Add ( "User-Agent" , "osquery/4.6.0" )
2022-04-05 15:35:53 +00:00
req . SetRequestURI ( a . serverAddress + "/api/osquery/enroll" )
2021-10-14 13:09:58 +00:00
res := fasthttp . AcquireResponse ( )
2021-09-22 20:18:55 +00:00
2021-10-14 13:09:58 +00:00
a . waitingDo ( req , res )
2021-09-22 20:18:55 +00:00
2021-10-14 13:09:58 +00:00
fasthttp . ReleaseRequest ( req )
defer fasthttp . ReleaseResponse ( res )
if res . StatusCode ( ) != http . StatusOK {
log . Println ( "enroll status:" , res . StatusCode ( ) )
return fmt . Errorf ( "status code: %d" , res . StatusCode ( ) )
2021-09-22 20:18:55 +00:00
}
var parsedResp enrollResponse
2021-10-14 13:09:58 +00:00
if err := json . Unmarshal ( res . Body ( ) , & 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
}
2021-11-01 18:23:31 +00:00
func ( a * agent ) config ( ) {
2021-12-09 21:05:32 +00:00
body := bytes . NewBufferString ( ` { "node_key": " ` + a . nodeKey + ` "} ` )
2021-09-22 20:18:55 +00:00
2021-10-14 13:09:58 +00:00
req := fasthttp . AcquireRequest ( )
req . SetBody ( body . Bytes ( ) )
req . Header . SetMethod ( "POST" )
req . Header . SetContentType ( "application/json" )
2021-09-22 20:18:55 +00:00
req . Header . Add ( "User-Agent" , "osquery/4.6.0" )
2022-04-05 15:35:53 +00:00
req . SetRequestURI ( a . serverAddress + "/api/osquery/config" )
2021-10-14 13:09:58 +00:00
res := fasthttp . AcquireResponse ( )
2021-09-22 20:18:55 +00:00
2021-10-14 13:09:58 +00:00
a . waitingDo ( req , res )
2021-09-22 20:18:55 +00:00
2021-10-14 13:09:58 +00:00
fasthttp . ReleaseRequest ( req )
defer fasthttp . ReleaseResponse ( res )
2023-10-20 13:29:59 +00:00
a . stats . IncrementConfigRequests ( )
2021-10-14 13:09:58 +00:00
if res . StatusCode ( ) != http . StatusOK {
log . Println ( "config status:" , res . StatusCode ( ) )
2021-09-22 20:18:55 +00:00
return
}
2021-12-09 20:20:32 +00:00
parsedResp := struct {
Packs map [ string ] struct {
Queries map [ string ] interface { } ` json:"queries" `
} ` json:"packs" `
} { }
if err := json . Unmarshal ( res . Body ( ) , & parsedResp ) ; err != nil {
log . Println ( "json parse at config:" , err )
return
}
var scheduledQueries [ ] string
2023-10-13 02:41:04 +00:00
var scheduledQueryData [ ] 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 {
2021-12-09 20:20:32 +00:00
scheduledQueries = append ( scheduledQueries , packName + "_" + queryName )
2023-10-13 02:41:04 +00:00
m , ok := query . ( map [ string ] interface { } )
if ! ok {
log . Fatalf ( "processing scheduled query failed: %v\n" , query )
}
q := scheduledQuery { }
q . PackName = packName
q . Name = queryName
q . NumResults = 1
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
}
q . NumResults = uint ( num )
}
q . ScheduleInterval = m [ "interval" ] . ( float64 )
q . Query = m [ "query" ] . ( string )
scheduledQueryData = append ( scheduledQueryData , q )
2021-12-09 20:20:32 +00:00
}
}
2023-10-20 13:29:59 +00:00
a . scheduledQueriesMu . Lock ( )
2021-12-09 20:20:32 +00:00
a . scheduledQueries = scheduledQueries
2023-10-13 02:41:04 +00:00
a . scheduledQueryData = scheduledQueryData
2023-10-20 13:29:59 +00:00
a . scheduledQueriesMu . Unlock ( )
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 {
"name" : fmt . Sprintf ( "Common_%d" , i ) ,
"version" : "0.0.1" ,
"bundle_identifier" : "com.fleetdm.osquery-perf" ,
"source" : "osquery-perf" ,
"last_opened_at" : lastOpenedAt ,
2023-05-17 18:49:09 +00:00
"installed_path" : fmt . Sprintf ( "/some/path/Common_%d" , 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 {
"name" : fmt . Sprintf ( "Unique_%s_%d" , a . CachedString ( "hostname" ) , i ) ,
"version" : "1.1.1" ,
"bundle_identifier" : "com.fleetdm.osquery-perf" ,
"source" : "osquery-perf" ,
"last_opened_at" : lastOpenedAt ,
2023-05-17 18:49:09 +00:00
"installed_path" : fmt . Sprintf ( "/some/path/Unique_%s_%d" , 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
}
func ( a * agent ) DistributedRead ( ) ( * distributedReadResponse , error ) {
2021-10-14 13:09:58 +00:00
req := fasthttp . AcquireRequest ( )
2021-12-09 21:05:32 +00:00
req . SetBody ( [ ] byte ( ` { "node_key": " ` + a . nodeKey + ` "} ` ) )
2021-10-14 13:09:58 +00:00
req . Header . SetMethod ( "POST" )
req . Header . SetContentType ( "application/json" )
req . Header . Add ( "User-Agent" , "osquery/4.6.0" )
2022-04-05 15:35:53 +00:00
req . SetRequestURI ( a . serverAddress + "/api/osquery/distributed/read" )
2021-10-14 13:09:58 +00:00
res := fasthttp . AcquireResponse ( )
a . waitingDo ( req , res )
fasthttp . ReleaseRequest ( req )
defer fasthttp . ReleaseResponse ( res )
2021-09-22 20:18:55 +00:00
2023-10-20 13:29:59 +00:00
a . stats . IncrementDistributedReads ( )
2021-09-22 20:18:55 +00:00
var parsedResp distributedReadResponse
2021-10-14 13:09:58 +00:00
if err := json . Unmarshal ( res . Body ( ) , & parsedResp ) ; err != nil {
log . Println ( "json parse:" , err )
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
for _ , scheduledQuery := range a . scheduledQueries {
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 ) ,
"wall_time" : fmt . Sprint ( rand . Intn ( 4000 ) + 10 ) ,
} )
}
return stats
}
2022-11-15 13:24:40 +00:00
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" ,
}
2022-08-10 19:15:01 +00:00
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 {
2023-05-12 16:50:20 +00:00
{ "enrolled" : "true" , "server_url" : a . mdmClient . EnrollInfo . MDMURL , "installed_from_dep" : "false" } ,
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 {
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"
}
rows := [ ] map [ string ] string {
{ "key" : "discovery_service_url" , "value" : serverURL } ,
{ "key" : "installation_type" , "value" : installType } ,
}
if providerID != "" {
rows = append ( rows , map [ string ] string { "key" : "provider_id" , "value" : providerID } )
2021-12-21 12:37:58 +00:00
}
2022-11-15 13:24:40 +00:00
if autopilot {
rows = append ( rows , map [ string ] string { "key" : "autopilot" , "value" : "true" } )
}
return rows
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 )
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" :
ss := fleet . OsqueryStatus ( rand . Intn ( 2 ) )
if ss == fleet . StatusOK {
2022-11-15 13:24:40 +00:00
results = a . mdmMac ( )
}
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" :
ss := fleet . OsqueryStatus ( rand . Intn ( 2 ) )
if ss == fleet . StatusOK {
results = a . mdmWindows ( )
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" :
ss := fleet . OsqueryStatus ( rand . Intn ( 2 ) )
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" :
ss := fleet . OsqueryStatus ( rand . Intn ( 2 ) )
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" :
ss := fleet . OsqueryStatus ( rand . Intn ( 2 ) )
if ss == fleet . StatusOK {
switch a . os {
case "ubuntu_22.04" :
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
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
}
}
func ( a * agent ) DistributedWrite ( queries map [ string ] string ) {
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
}
2021-10-14 13:09:58 +00:00
req := fasthttp . AcquireRequest ( )
2021-11-01 18:23:31 +00:00
req . SetBody ( body )
2021-10-14 13:09:58 +00:00
req . Header . SetMethod ( "POST" )
req . Header . SetContentType ( "application/json" )
2021-12-21 12:37:58 +00:00
req . Header . Add ( "User-Agent" , "osquery/5.0.1" )
2022-04-05 15:35:53 +00:00
req . SetRequestURI ( a . serverAddress + "/api/osquery/distributed/write" )
2021-10-14 13:09:58 +00:00
res := fasthttp . AcquireResponse ( )
2021-09-22 20:18:55 +00:00
2021-10-14 13:09:58 +00:00
a . waitingDo ( req , res )
2021-09-22 20:18:55 +00:00
2021-10-14 13:09:58 +00:00
fasthttp . ReleaseRequest ( req )
defer fasthttp . ReleaseResponse ( res )
2022-10-28 17:27:21 +00:00
a . stats . IncrementDistributedWrites ( )
2021-09-22 20:18:55 +00:00
// No need to read the distributed write body
}
2023-10-17 14:30:59 +00:00
func ( a * agent ) scheduledQueryResults ( packName , queryName string , numResults int ) json . RawMessage {
return json . RawMessage ( ` {
"snapshot" : [ ` + results(numResults, a.UUID) + `
2023-10-13 02:41:04 +00:00
] ,
"action" : "snapshot" ,
"name" : "pack/` + packName + `/` + queryName + `" ,
"hostIdentifier" : "` + a.UUID + `" ,
"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" ,
"hostname" : "` + a.CachedString(" hostname ") + `"
}
2023-10-17 14:30:59 +00:00
} ` )
}
func ( a * agent ) SubmitLogs ( results [ ] json . RawMessage ) {
jsonResults , err := json . Marshal ( results )
if err != nil {
panic ( err )
}
type submitLogsRequest struct {
NodeKey string ` json:"node_key" `
LogType string ` json:"log_type" `
Data json . RawMessage ` json:"data" `
}
r := submitLogsRequest {
NodeKey : a . nodeKey ,
LogType : "result" ,
Data : jsonResults ,
2023-10-13 02:41:04 +00:00
}
body , err := json . Marshal ( r )
if err != nil {
panic ( err )
}
req := fasthttp . AcquireRequest ( )
req . SetBody ( body )
req . Header . SetMethod ( "POST" )
req . Header . SetContentType ( "application/json" )
req . Header . Add ( "User-Agent" , "osquery/5.0.1" )
req . SetRequestURI ( a . serverAddress + "/api/osquery/log" )
res := fasthttp . AcquireResponse ( )
a . waitingDo ( req , res )
fasthttp . ReleaseRequest ( req )
defer fasthttp . ReleaseResponse ( res )
2023-10-20 13:29:59 +00:00
a . stats . IncrementResultLogRequests ( )
2023-10-13 02:41:04 +00:00
}
// Creates a set of results for use in tests for Query Results.
2023-10-17 14:30:59 +00:00
func results ( num int , hostUUID string ) 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" ,
2023-10-17 14:30:59 +00:00
"uuid" : "` + hostUUID + `" ,
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 ( ) {
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" )
2023-05-12 16:50:20 +00:00
mdmCheckInInterval = flag . Duration ( "mdm_check_in_interval" , 10 * time . Second , "Interval for performing MDM check ins" )
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" )
commonSoftwareCount = flag . Int ( "common_software_count" , 10 , "Number of common installed applications reported to fleet" )
commonSoftwareUninstallCount = flag . Int ( "common_software_uninstall_count" , 1 , "Number of common software to uninstall" )
commonSoftwareUninstallProb = flag . Float64 ( "common_software_uninstall_prob" , 0.1 , "Probability of uninstalling common_software_uninstall_count unique software/s" )
uniqueSoftwareCount = flag . Int ( "unique_software_count" , 10 , "Number of uninstalls " )
uniqueSoftwareUninstallCount = flag . Int ( "unique_software_uninstall_count" , 1 , "Number of unique software to uninstall" )
uniqueSoftwareUninstallProb = flag . Float64 ( "unique_software_uninstall_prob" , 0.1 , "Probability of uninstalling unique_software_uninstall_count common software/s" )
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
2023-05-25 11:12:10 +00:00
mdmProb = flag . Float64 ( "mdm_prob" , 0.0 , "Probability of a host enrolling via MDM (for macOS) [0, 1]" )
2023-05-12 16:50:20 +00:00
mdmSCEPChallenge = flag . String ( "mdm_scep_challenge" , "" , "SCEP challenge to use when running 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" )
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
}
2023-04-05 16:53:43 +00:00
if * commonSoftwareUninstallCount >= * commonSoftwareCount {
log . Fatalf ( "Argument common_software_uninstall_count cannot be bigger than common_software_count" )
}
if * uniqueSoftwareUninstallCount >= * uniqueSoftwareCount {
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 ,
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 ,
2022-05-31 13:15:58 +00:00
} , entityCount {
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 ,
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 )
}
fmt . Println ( "Agents running. Kill with C-c." )
<- make ( chan struct { } )
}