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 {
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
}
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 {
2024-02-08 15:40:13 +00:00
startTime time . Time
errors int
osqueryEnrollments int
orbitEnrollments int
mdmEnrollments int
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
}
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 (
"uptime: %s, error rate: %.2f, osquery enrolls: %d, orbit enrolls: %d, mdm enrolls: %d, distributed/reads: %d, distributed/writes: %d, config requests: %d, result log requests: %d, mdm commands received: %d, config errors: %d, distributed/read errors: %d, distributed/write errors: %d, log result errors: %d, orbit errors: %d, desktop errors: %d, mdm errors: %d, buffered logs: %d" ,
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 ,
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 {
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
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
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
2023-10-20 13:29:59 +00:00
scheduledQueries [ ] string
scheduledQueryData [ ] scheduledQuery
2024-02-08 15:40:13 +00:00
bufferedResults [ ] json . RawMessage
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 ,
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 ( ) )
}
// #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 ,
2024-02-08 15:40:13 +00:00
mdmClient : mdmClient ,
disableScriptExec : disableScriptExec ,
disableFleetDesktop : disableFleetDesktop ,
loggerTLSMaxLines : loggerTLSMaxLines ,
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-02-08 15:40:13 +00:00
nextRun float64
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 {
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
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 ( )
}
2023-05-12 16:50:20 +00:00
if a . mdmClient != nil {
if err := a . mdmClient . Enroll ( ) ; err != nil {
2024-02-08 15:40:13 +00:00
log . Printf ( "MDM enroll failed: %s" , err )
2023-05-12 16:50:20 +00:00
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 {
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
var results [ ] json . RawMessage
now := time . Now ( ) . Unix ( )
a . scheduledQueriesMu . Lock ( )
2024-02-08 15:40:13 +00:00
prevCount := len ( a . bufferedResults )
2023-10-20 13:29:59 +00:00
for i , query := range a . scheduledQueryData {
2024-02-08 15:40:13 +00:00
if query . nextRun == 0 || now >= int64 ( query . nextRun ) {
results = append ( results , a . scheduledQueryResults ( query . packName , query . Name , int ( query . numRows ) ) )
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
}
2024-02-08 15:40:13 +00:00
a . bufferedResults = append ( a . bufferedResults , results ... )
if len ( a . bufferedResults ) > 1_000_000 { // osquery buffered_log_max is 1M
extra := len ( a . bufferedResults ) - 1_000_000
a . bufferedResults = a . bufferedResults [ extra : ]
2023-10-20 13:29:59 +00:00
}
2024-02-08 15:40:13 +00:00
a . sendLogsBatch ( )
newBufferedCount := len ( a . bufferedResults ) - prevCount
a . stats . UpdateBufferedLogs ( newBufferedCount )
a . scheduledQueriesMu . Unlock ( )
}
}
// 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
if len ( a . bufferedResults ) < batchSize {
batchSize = len ( a . bufferedResults )
}
batch := a . bufferedResults [ : batchSize ]
if err := a . submitLogs ( batch ) ; err != nil {
return
}
a . bufferedResults = a . bufferedResults [ batchSize : ]
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 {
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 )
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 )
}
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
}
}
}
}
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 {
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
}
INNER_FOR_LOOP :
for mdmCommandPayload != nil {
a . stats . IncrementMDMCommandsReceived ( )
mdmCommandPayload , err = a . mdmClient . Acknowledge ( mdmCommandPayload . CommandUUID )
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
}
}
}
}
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
}
}
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 {
2024-02-08 15:40:13 +00:00
if err != nil {
log . Printf ( "failed to run request: %s" , err )
} else { // res.StatusCode() != http.StatusOK
log . Printf ( "request failed: %d" , 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
}
2024-02-08 15:40:13 +00:00
func ( a * agent ) config ( ) error {
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
2024-02-08 15:40:13 +00:00
err := a . fastClient . Do ( req , res )
if err != nil {
return fmt . Errorf ( "config request failed to run: %w" , err )
}
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 ( )
2024-02-08 15:40:13 +00:00
statusCode := res . StatusCode ( )
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" `
} { }
if err := json . Unmarshal ( res . Body ( ) , & parsedResp ) ; err != nil {
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
}
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 {
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 )
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 ( )
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 {
"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 ( )
2024-02-08 15:40:13 +00:00
err := a . fastClient . Do ( req , res )
if err != nil {
return nil , fmt . Errorf ( "distributed/read request failed to run: %w" , err )
}
2021-10-14 13:09:58 +00:00
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 ( )
2024-02-08 15:40:13 +00:00
statusCode := res . StatusCode ( )
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
2021-10-14 13:09:58 +00:00
if err := json . Unmarshal ( res . Body ( ) , & parsedResp ) ; err != nil {
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
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 ) ,
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
}
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
}
}
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
}
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
2024-02-08 15:40:13 +00:00
err = a . fastClient . Do ( req , res )
if err != nil {
return fmt . Errorf ( "distributed/write request failed to run: %w" , err )
}
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 ( )
2024-02-08 15:40:13 +00:00
statusCode := res . StatusCode ( )
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
}
2023-10-17 14:30:59 +00:00
func ( a * agent ) scheduledQueryResults ( packName , queryName string , numResults int ) json . RawMessage {
return json . RawMessage ( ` {
2024-02-08 15:40:13 +00:00
"snapshot" : [ ` + rows(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
} ` )
}
2024-02-08 15:40:13 +00:00
func ( a * agent ) submitLogs ( results [ ] json . RawMessage ) error {
2023-10-17 14:30:59 +00:00
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 ( )
2024-02-08 15:40:13 +00:00
err = a . fastClient . Do ( req , res )
if err != nil {
return fmt . Errorf ( "log request failed to run: %w" , err )
}
2023-10-13 02:41:04 +00:00
fasthttp . ReleaseRequest ( req )
defer fasthttp . ReleaseResponse ( res )
2023-10-20 13:29:59 +00:00
a . stats . IncrementResultLogRequests ( )
2024-02-08 15:40:13 +00:00
statusCode := res . StatusCode ( )
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.
func rows ( 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" )
2024-02-08 15:40:13 +00:00
uniqueSoftwareCount = flag . Int ( "unique_software_count" , 1 , "Number of unique software installed on each host" )
2023-04-05 16:53:43 +00:00
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" )
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 ,
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 ,
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 { } )
}