2021-09-22 20:18:55 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"crypto/tls"
|
2021-10-14 13:09:58 +00:00
|
|
|
"embed"
|
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"
|
|
|
|
"log"
|
|
|
|
"math/rand"
|
|
|
|
"net/http"
|
2021-10-14 13:09:58 +00:00
|
|
|
"os"
|
2021-09-22 20:18:55 +00:00
|
|
|
"strings"
|
2021-10-14 13:09:58 +00:00
|
|
|
"sync"
|
2021-09-22 20:18:55 +00:00
|
|
|
"text/template"
|
|
|
|
"time"
|
|
|
|
|
2021-11-01 18:23:31 +00:00
|
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
|
|
"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
|
|
|
)
|
|
|
|
|
2021-10-14 13:09:58 +00:00
|
|
|
//go:embed *.tmpl
|
|
|
|
var templatesFS embed.FS
|
|
|
|
|
|
|
|
type Stats struct {
|
|
|
|
errors int
|
|
|
|
enrollments int
|
|
|
|
distributedwrites int
|
|
|
|
|
|
|
|
l sync.Mutex
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Stats) RecordStats(errors int, enrollments int, distributedwrites int) {
|
|
|
|
s.l.Lock()
|
|
|
|
defer s.l.Unlock()
|
|
|
|
|
|
|
|
s.errors += errors
|
|
|
|
s.enrollments += enrollments
|
|
|
|
s.distributedwrites += distributedwrites
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Stats) Log() {
|
|
|
|
s.l.Lock()
|
|
|
|
defer s.l.Unlock()
|
|
|
|
|
|
|
|
fmt.Printf(
|
|
|
|
"%s :: error rate: %.2f \t enrollments: %d \t writes: %d\n",
|
|
|
|
time.Now().String(),
|
|
|
|
float64(s.errors)/float64(s.enrollments),
|
|
|
|
s.enrollments,
|
|
|
|
s.distributedwrites,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
f, err := os.OpenFile(n.filepath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
|
|
|
|
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 {
|
2021-12-09 21:05:32 +00:00
|
|
|
agentIndex int
|
2021-12-09 21:24:48 +00:00
|
|
|
softwareCount entityCount
|
|
|
|
userCount entityCount
|
2021-12-09 21:05:32 +00:00
|
|
|
policyPassProb float64
|
|
|
|
strings map[string]string
|
|
|
|
serverAddress string
|
|
|
|
fastClient fasthttp.Client
|
|
|
|
stats *Stats
|
|
|
|
nodeKeyManager *nodeKeyManager
|
|
|
|
nodeKey string
|
|
|
|
templates *template.Template
|
|
|
|
|
|
|
|
scheduledQueries []string
|
|
|
|
|
|
|
|
// The following are exported to be used by the templates.
|
|
|
|
|
2021-09-22 20:18:55 +00:00
|
|
|
EnrollSecret string
|
|
|
|
UUID string
|
|
|
|
ConfigInterval time.Duration
|
|
|
|
QueryInterval time.Duration
|
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
|
|
|
}
|
|
|
|
|
2021-11-19 11:50:25 +00:00
|
|
|
func newAgent(
|
2021-12-09 21:05:32 +00:00
|
|
|
agentIndex int,
|
2021-11-19 11:50:25 +00:00
|
|
|
serverAddress, enrollSecret string, templates *template.Template,
|
2021-12-09 21:24:48 +00:00
|
|
|
configInterval, queryInterval time.Duration, softwareCount, userCount entityCount,
|
2021-11-19 11:50:25 +00:00
|
|
|
policyPassProb float64,
|
|
|
|
) *agent {
|
2021-09-22 20:18:55 +00:00
|
|
|
transport := http.DefaultTransport.(*http.Transport).Clone()
|
|
|
|
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
|
|
|
transport.DisableCompression = true
|
2021-11-01 18:23:31 +00:00
|
|
|
return &agent{
|
2021-12-09 21:05:32 +00:00
|
|
|
agentIndex: agentIndex,
|
|
|
|
serverAddress: serverAddress,
|
|
|
|
softwareCount: softwareCount,
|
2021-12-09 21:24:48 +00:00
|
|
|
userCount: userCount,
|
2021-12-09 21:05:32 +00:00
|
|
|
strings: make(map[string]string),
|
|
|
|
policyPassProb: policyPassProb,
|
|
|
|
fastClient: fasthttp.Client{
|
|
|
|
TLSConfig: &tls.Config{InsecureSkipVerify: true},
|
|
|
|
},
|
|
|
|
templates: templates,
|
|
|
|
|
2021-09-22 20:18:55 +00:00
|
|
|
EnrollSecret: enrollSecret,
|
|
|
|
ConfigInterval: configInterval,
|
|
|
|
QueryInterval: queryInterval,
|
|
|
|
UUID: uuid.New().String(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type enrollResponse struct {
|
|
|
|
NodeKey string `json:"node_key"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type distributedReadResponse struct {
|
|
|
|
Queries map[string]string `json:"queries"`
|
|
|
|
}
|
|
|
|
|
2021-11-01 18:23:31 +00:00
|
|
|
func (a *agent) runLoop(i int, onlyAlreadyEnrolled bool) {
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
configTicker := time.Tick(a.ConfigInterval)
|
|
|
|
liveQueryTicker := time.Tick(a.QueryInterval)
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-configTicker:
|
2021-11-01 18:23:31 +00:00
|
|
|
a.config()
|
2021-09-22 20:18:55 +00:00
|
|
|
case <-liveQueryTicker:
|
|
|
|
resp, err := a.DistributedRead()
|
|
|
|
if err != nil {
|
|
|
|
log.Println(err)
|
|
|
|
} else {
|
|
|
|
if len(resp.Queries) > 0 {
|
|
|
|
a.DistributedWrite(resp.Queries)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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())
|
2021-12-09 21:05:32 +00:00
|
|
|
a.stats.RecordStats(1, 0, 0)
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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 != "" {
|
|
|
|
a.stats.RecordStats(0, 1, 0)
|
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")
|
2021-12-09 21:05:32 +00:00
|
|
|
req.SetRequestURI(a.serverAddress + "/api/v1/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
|
|
|
|
a.stats.RecordStats(0, 1, 0)
|
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")
|
2021-12-09 21:05:32 +00:00
|
|
|
req.SetRequestURI(a.serverAddress + "/api/v1/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)
|
|
|
|
|
|
|
|
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
|
|
|
|
for packName, pack := range parsedResp.Packs {
|
|
|
|
for queryName := range pack.Queries {
|
|
|
|
scheduledQueries = append(scheduledQueries, packName+"_"+queryName)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
a.scheduledQueries = scheduledQueries
|
|
|
|
|
2021-09-22 20:18:55 +00:00
|
|
|
// No need to read the config body
|
|
|
|
}
|
|
|
|
|
2021-10-14 13:09:58 +00:00
|
|
|
const stringVals = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_."
|
2021-09-22 20:18:55 +00:00
|
|
|
|
2021-11-01 18:23:31 +00:00
|
|
|
func (a *agent) 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
|
|
|
}
|
2021-10-14 13:09:58 +00:00
|
|
|
val := a.randomString(12)
|
|
|
|
a.strings[key] = val
|
|
|
|
return val
|
|
|
|
}
|
2021-09-22 20:18:55 +00:00
|
|
|
|
2021-12-09 21:24:48 +00:00
|
|
|
func (a *agent) HostUsersMacOS() []fleet.HostUser {
|
|
|
|
groupNames := []string{"staff", "nobody", "wheel", "tty", "daemon"}
|
|
|
|
shells := []string{"/bin/zsh", "/bin/sh", "/usr/bin/false", "/bin/bash"}
|
|
|
|
commonUsers := make([]fleet.HostUser, a.userCount.common)
|
|
|
|
for i := 0; i < len(commonUsers); i++ {
|
|
|
|
commonUsers[i] = fleet.HostUser{
|
|
|
|
Uid: uint(i),
|
|
|
|
Username: fmt.Sprintf("Common_%d", i),
|
|
|
|
Type: "", // Empty for macOS.
|
|
|
|
GroupName: groupNames[i%len(groupNames)],
|
|
|
|
Shell: shells[i%len(shells)],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
uniqueUsers := make([]fleet.HostUser, a.userCount.unique)
|
|
|
|
for i := 0; i < len(uniqueUsers); i++ {
|
|
|
|
uniqueUsers[i] = fleet.HostUser{
|
|
|
|
Uid: uint(i),
|
|
|
|
Username: fmt.Sprintf("Unique_%d_%d", a.agentIndex, i),
|
|
|
|
Type: "", // Empty for macOS.
|
|
|
|
GroupName: groupNames[i%len(groupNames)],
|
|
|
|
Shell: shells[i%len(shells)],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
users := append(commonUsers, uniqueUsers...)
|
|
|
|
rand.Shuffle(len(users), func(i, j int) {
|
|
|
|
users[i], users[j] = users[j], users[i]
|
|
|
|
})
|
|
|
|
return users
|
|
|
|
}
|
|
|
|
|
2021-11-01 18:23:31 +00:00
|
|
|
func (a *agent) SoftwareMacOS() []fleet.Software {
|
2021-12-09 21:05:32 +00:00
|
|
|
commonSoftware := make([]fleet.Software, a.softwareCount.common)
|
|
|
|
for i := 0; i < len(commonSoftware); i++ {
|
|
|
|
commonSoftware[i] = fleet.Software{
|
|
|
|
Name: fmt.Sprintf("Common_%d", i),
|
2021-11-01 18:23:31 +00:00
|
|
|
Version: "0.0.1",
|
|
|
|
BundleIdentifier: "com.fleetdm.osquery-perf",
|
|
|
|
Source: "osquery-perf",
|
|
|
|
}
|
|
|
|
}
|
2021-12-09 21:05:32 +00:00
|
|
|
uniqueSoftware := make([]fleet.Software, a.softwareCount.unique)
|
|
|
|
for i := 0; i < len(uniqueSoftware); i++ {
|
|
|
|
uniqueSoftware[i] = fleet.Software{
|
|
|
|
Name: fmt.Sprintf("Unique_%d_%d", a.agentIndex, i),
|
|
|
|
Version: "1.1.1",
|
|
|
|
BundleIdentifier: "com.fleetdm.osquery-perf",
|
|
|
|
Source: "osquery-perf",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
software := append(commonSoftware, uniqueSoftware...)
|
|
|
|
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")
|
2021-12-09 21:05:32 +00:00
|
|
|
req.SetRequestURI(a.serverAddress + "/api/v1/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
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
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"},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-12-09 20:20:32 +00:00
|
|
|
func (a *agent) randomQueryStats() []map[string]string {
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2021-12-21 12:37:58 +00:00
|
|
|
func (a *agent) mdm() []map[string]string {
|
|
|
|
enrolled := "true"
|
|
|
|
if rand.Intn(2) == 1 {
|
|
|
|
enrolled = "false"
|
|
|
|
}
|
|
|
|
installedFromDep := "true"
|
|
|
|
if rand.Intn(2) == 1 {
|
|
|
|
installedFromDep = "false"
|
|
|
|
}
|
|
|
|
return []map[string]string{
|
|
|
|
{"enrolled": enrolled, "server_url": "http://some.url/mdm", "installed_from_dep": installedFromDep},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *agent) munkiInfo() []map[string]string {
|
|
|
|
return []map[string]string{
|
|
|
|
{"version": "1.2.3"},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2021-11-01 18:23:31 +00:00
|
|
|
func (a *agent) DistributedWrite(queries map[string]string) {
|
|
|
|
r := service.SubmitDistributedQueryResultsRequest{
|
|
|
|
Results: make(fleet.OsqueryDistributedQueryResults),
|
|
|
|
Statuses: make(map[string]fleet.OsqueryStatus),
|
|
|
|
}
|
2021-12-09 21:05:32 +00:00
|
|
|
r.NodeKey = a.nodeKey
|
2021-11-19 11:50:25 +00:00
|
|
|
const hostPolicyQueryPrefix = "fleet_policy_query_"
|
2021-12-09 20:20:32 +00:00
|
|
|
const hostDetailQueryPrefix = "fleet_detail_query_"
|
2021-11-01 18:23:31 +00:00
|
|
|
for name := range queries {
|
|
|
|
r.Results[name] = defaultQueryResult
|
|
|
|
r.Statuses[name] = fleet.StatusOK
|
2021-11-19 11:50:25 +00:00
|
|
|
if strings.HasPrefix(name, hostPolicyQueryPrefix) {
|
|
|
|
r.Results[name] = a.runPolicy(queries[name])
|
|
|
|
continue
|
|
|
|
}
|
2021-12-09 20:20:32 +00:00
|
|
|
if name == hostDetailQueryPrefix+"scheduled_query_stats" {
|
|
|
|
r.Results[name] = a.randomQueryStats()
|
|
|
|
continue
|
|
|
|
}
|
2021-12-21 12:37:58 +00:00
|
|
|
if name == hostDetailQueryPrefix+"mdm" {
|
|
|
|
r.Statuses[name] = fleet.OsqueryStatus(rand.Intn(2))
|
|
|
|
r.Results[name] = nil
|
|
|
|
if r.Statuses[name] == fleet.StatusOK {
|
|
|
|
r.Results[name] = a.mdm()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if name == hostDetailQueryPrefix+"munki_info" {
|
|
|
|
r.Statuses[name] = fleet.OsqueryStatus(rand.Intn(2))
|
|
|
|
r.Results[name] = nil
|
|
|
|
if r.Statuses[name] == fleet.StatusOK {
|
|
|
|
r.Results[name] = a.munkiInfo()
|
|
|
|
}
|
|
|
|
}
|
2021-12-21 20:36:19 +00:00
|
|
|
if name == hostDetailQueryPrefix+"google_chrome_profiles" {
|
|
|
|
r.Statuses[name] = fleet.OsqueryStatus(rand.Intn(2))
|
|
|
|
r.Results[name] = nil
|
|
|
|
if r.Statuses[name] == fleet.StatusOK {
|
|
|
|
r.Results[name] = a.googleChromeProfiles()
|
|
|
|
}
|
|
|
|
}
|
2021-12-09 21:05:32 +00:00
|
|
|
if t := a.templates.Lookup(name); t == nil {
|
2021-11-01 18:23:31 +00:00
|
|
|
continue
|
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)
|
|
|
|
}
|
|
|
|
var m []map[string]string
|
|
|
|
err = json.Unmarshal(ni.Bytes(), &m)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
2021-09-22 20:18:55 +00:00
|
|
|
}
|
2021-11-01 18:23:31 +00:00
|
|
|
r.Results[name] = m
|
|
|
|
}
|
|
|
|
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")
|
2021-12-09 21:05:32 +00:00
|
|
|
req.SetRequestURI(a.serverAddress + "/api/v1/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)
|
|
|
|
|
2021-12-09 21:05:32 +00:00
|
|
|
a.stats.RecordStats(0, 0, 1)
|
2021-09-22 20:18:55 +00:00
|
|
|
// No need to read the distributed write body
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
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")
|
|
|
|
queryInterval := flag.Duration("query_interval", 10*time.Second, "Interval for live query requests")
|
2021-10-14 13:09:58 +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")
|
2021-12-09 21:05:32 +00:00
|
|
|
commonSoftwareCount := flag.Int("common_software_count", 10, "Number of common of installed applications reported to fleet")
|
|
|
|
uniqueSoftwareCount := flag.Int("unique_software_count", 10, "Number of unique installed applications reported to fleet")
|
2021-12-09 21:24:48 +00:00
|
|
|
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")
|
2021-12-09 21:05:32 +00:00
|
|
|
policyPassProb := flag.Float64("policy_pass_prob", 1.0, "Probability of policies to pass [0, 1]")
|
2021-09-22 20:18:55 +00:00
|
|
|
|
|
|
|
flag.Parse()
|
|
|
|
|
|
|
|
rand.Seed(*randSeed)
|
|
|
|
|
2021-11-01 18:23:31 +00:00
|
|
|
// Currently all hosts will be macOS.
|
|
|
|
tmpl, err := template.ParseFS(templatesFS, "mac10.14.6.tmpl")
|
2021-09-22 20:18:55 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Fatal("parse templates: ", err)
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
|
|
|
stats := &Stats{}
|
|
|
|
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()
|
|
|
|
}
|
|
|
|
|
2021-09-22 20:18:55 +00:00
|
|
|
for i := 0; i < *hostCount; i++ {
|
2021-12-09 21:24:48 +00:00
|
|
|
a := newAgent(i+1, *serverURL, *enrollSecret, tmpl, *configInterval, *queryInterval, entityCount{
|
2021-12-09 21:05:32 +00:00
|
|
|
common: *commonSoftwareCount,
|
|
|
|
unique: *uniqueSoftwareCount,
|
2021-12-09 21:24:48 +00:00
|
|
|
}, entityCount{
|
|
|
|
common: *commonUserCount,
|
|
|
|
unique: *uniqueUserCount,
|
2021-12-09 21:05:32 +00:00
|
|
|
}, *policyPassProb)
|
|
|
|
a.stats = stats
|
|
|
|
a.nodeKeyManager = nodeKeyManager
|
2021-10-14 13:09:58 +00:00
|
|
|
go a.runLoop(i, onlyAlreadyEnrolled != nil && *onlyAlreadyEnrolled)
|
2021-09-22 20:18:55 +00:00
|
|
|
time.Sleep(sleepTime)
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Println("Agents running. Kill with C-c.")
|
|
|
|
<-make(chan struct{})
|
|
|
|
}
|