Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions internal/datagen/services.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package datagen

import "math/rand"

// ServiceStartType represents the startup type of a Windows/Linux service.
type ServiceStartType string

Expand All @@ -20,3 +22,110 @@ type ServiceIdentity struct {
Account string // "LocalSystem", "NT AUTHORITY\NETWORK SERVICE", or a UPN
SystemRef string // back-reference: hostname of owning system
}

// Service template definitions.
type serviceTemplate struct {
name string
displayName string
binaryPath string
startType ServiceStartType
account string
}

var dcServices = []serviceTemplate{
{"NTDS", "Active Directory Domain Services", `C:\Windows\System32\ntdsa.dll`, StartAutomatic, "LocalSystem"},
{"DNS", "DNS Server", `C:\Windows\System32\dns.exe`, StartAutomatic, "LocalSystem"},
{"KDC", "Kerberos Key Distribution Center", `C:\Windows\System32\lsass.exe`, StartAutomatic, "LocalSystem"},
{"Netlogon", "Net Logon", `C:\Windows\System32\netlogon.dll`, StartAutomatic, "LocalSystem"},
{"DFS", "DFS Replication", `C:\Windows\System32\dfsr.exe`, StartAutomatic, `NT AUTHORITY\NETWORK SERVICE`},
{"W32Time", "Windows Time", `C:\Windows\System32\w32time.dll`, StartAutomatic, `NT AUTHORITY\LOCAL SERVICE`},
{"EventLog", "Windows Event Log", `C:\Windows\System32\wevtsvc.dll`, StartAutomatic, `NT AUTHORITY\LOCAL SERVICE`},
{"CertSvc", "Active Directory Certificate Services", `C:\Windows\System32\certsrv.exe`, StartAutomatic, "LocalSystem"},
}

var windowsServerServices = []serviceTemplate{
{"W3SVC", "World Wide Web Publishing Service", `C:\Windows\System32\inetsrv\iisw3adm.dll`, StartAutomatic, "LocalSystem"},
{"MSSQLSERVER", "SQL Server (MSSQLSERVER)", `C:\Program Files\Microsoft SQL Server\MSSQL16.MSSQLSERVER\MSSQL\Binn\sqlservr.exe`, StartAutomatic, `NT SERVICE\MSSQLSERVER`},
{"Spooler", "Print Spooler", `C:\Windows\System32\spoolsv.exe`, StartAutomatic, "LocalSystem"},
{"WinRM", "Windows Remote Management (WS-Management)", `C:\Windows\System32\winrm.cmd`, StartAutomatic, `NT AUTHORITY\NETWORK SERVICE`},
{"WinDefend", "Windows Defender Antivirus Service", `C:\Program Files\Windows Defender\MsMpEng.exe`, StartAutomatic, "LocalSystem"},
{"EventLog", "Windows Event Log", `C:\Windows\System32\wevtsvc.dll`, StartAutomatic, `NT AUTHORITY\LOCAL SERVICE`},
{"W32Time", "Windows Time", `C:\Windows\System32\w32time.dll`, StartAutomatic, `NT AUTHORITY\LOCAL SERVICE`},
{"CryptSvc", "Cryptographic Services", `C:\Windows\System32\cryptsvc.dll`, StartAutomatic, `NT AUTHORITY\NETWORK SERVICE`},
{"wuauserv", "Windows Update", `C:\Windows\System32\wuaueng.dll`, StartAutomatic, "LocalSystem"},
{"BITS", "Background Intelligent Transfer Service", `C:\Windows\System32\qmgr.dll`, StartManual, "LocalSystem"},
}

var windowsWorkstationServices = []serviceTemplate{
{"WinDefend", "Windows Defender Antivirus Service", `C:\Program Files\Windows Defender\MsMpEng.exe`, StartAutomatic, "LocalSystem"},
{"EventLog", "Windows Event Log", `C:\Windows\System32\wevtsvc.dll`, StartAutomatic, `NT AUTHORITY\LOCAL SERVICE`},
{"AudioSrv", "Windows Audio", `C:\Windows\System32\audiosrv.dll`, StartAutomatic, `NT AUTHORITY\LOCAL SERVICE`},
{"Themes", "Themes", `C:\Windows\System32\themeservice.dll`, StartAutomatic, "LocalSystem"},
{"Schedule", "Task Scheduler", `C:\Windows\System32\schedsvc.dll`, StartAutomatic, "LocalSystem"},
}

var linuxServerServices = []serviceTemplate{
{"sshd", "OpenSSH server daemon", "/usr/sbin/sshd", StartAutomatic, "root"},
{"nginx", "A high performance web server", "/usr/sbin/nginx", StartAutomatic, "www-data"},
{"postgresql", "PostgreSQL RDBMS", "/usr/lib/postgresql/16/bin/postgres", StartAutomatic, "postgres"},
{"docker", "Docker Application Container Engine", "/usr/bin/dockerd", StartAutomatic, "root"},
{"cron", "Regular background program processing daemon", "/usr/sbin/cron", StartAutomatic, "root"},
{"rsyslog", "System Logging Service", "/usr/sbin/rsyslogd", StartAutomatic, "syslog"},
{"systemd-journald", "Journal Service", "/lib/systemd/systemd-journald", StartAutomatic, "root"},
{"prometheus-node-exporter", "Prometheus Node Exporter", "/usr/bin/prometheus-node-exporter", StartAutomatic, "prometheus"},
}

var linuxWorkstationServices = []serviceTemplate{
{"sshd", "OpenSSH server daemon", "/usr/sbin/sshd", StartAutomatic, "root"},
{"cron", "Regular background program processing daemon", "/usr/sbin/cron", StartAutomatic, "root"},
{"NetworkManager", "Network Manager", "/usr/sbin/NetworkManager", StartAutomatic, "root"},
}

// GenerateServicesForSystem returns services appropriate for the given OS and role.
func GenerateServicesForSystem(r *rand.Rand, os OSType, role SystemRole, hostname string) []*ServiceIdentity {
var templates []serviceTemplate

switch {
case role == RoleDC:
templates = dcServices
case os == OSWindows && role == RoleServer:
templates = windowsServerServices
case os == OSWindows && role == RoleWorkstation:
templates = windowsWorkstationServices
case os == OSLinux && role == RoleServer:
templates = linuxServerServices
case os == OSLinux && role == RoleWorkstation:
templates = linuxWorkstationServices
default:
// Router or macOS — minimal services
templates = []serviceTemplate{
{"sshd", "OpenSSH server daemon", "/usr/sbin/sshd", StartAutomatic, "root"},
}
}

// Pick a random subset (at least 3, up to all)
count := len(templates)
if count > 5 {
count = 3 + r.Intn(count-2) // #nosec G404
if count > len(templates) {
count = len(templates)
}
}

// Shuffle and take first count
indices := r.Perm(len(templates))
services := make([]*ServiceIdentity, count)
for i := 0; i < count; i++ {
tmpl := templates[indices[i]]
services[i] = &ServiceIdentity{
Name: tmpl.name,
DisplayName: tmpl.displayName,
BinaryPath: tmpl.binaryPath,
StartType: tmpl.startType,
Account: tmpl.account,
SystemRef: hostname,
}
}

return services
}
72 changes: 72 additions & 0 deletions internal/datagen/services_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package datagen

import (
"math/rand"
"testing"
)

func TestGenerateServicesForSystem(t *testing.T) {
r := rand.New(rand.NewSource(42))

t.Run("windows server gets windows services", func(t *testing.T) {
services := GenerateServicesForSystem(r, OSWindows, RoleServer, "MARS-WEB01")
if len(services) < 3 {
t.Errorf("expected at least 3 services, got %d", len(services))
}
for _, s := range services {
if s.SystemRef != "MARS-WEB01" {
t.Errorf("expected SystemRef 'MARS-WEB01', got %q", s.SystemRef)
}
if s.Name == "" {
t.Error("service Name should not be empty")
}
if s.DisplayName == "" {
t.Error("service DisplayName should not be empty")
}
}
})

t.Run("linux server gets linux services", func(t *testing.T) {
services := GenerateServicesForSystem(r, OSLinux, RoleServer, "thor-web-01")
if len(services) < 3 {
t.Errorf("expected at least 3 services, got %d", len(services))
}
})

t.Run("DC gets DC-specific services", func(t *testing.T) {
services := GenerateServicesForSystem(r, OSWindows, RoleDC, "ZEUS-DC01")
hasNTDS := false
for _, s := range services {
if s.Name == "NTDS" {
hasNTDS = true
}
}
if !hasNTDS {
t.Error("DC should have NTDS service")
}
})

t.Run("workstation gets fewer services", func(t *testing.T) {
wsServices := GenerateServicesForSystem(r, OSWindows, RoleWorkstation, "WS01")
srvServices := GenerateServicesForSystem(r, OSWindows, RoleServer, "SRV01")
if len(wsServices) >= len(srvServices) {
t.Errorf("workstation should have fewer services (%d) than server (%d)",
len(wsServices), len(srvServices))
}
})

t.Run("deterministic", func(t *testing.T) {
r1 := rand.New(rand.NewSource(99))
r2 := rand.New(rand.NewSource(99))
s1 := GenerateServicesForSystem(r1, OSWindows, RoleServer, "TEST")
s2 := GenerateServicesForSystem(r2, OSWindows, RoleServer, "TEST")
if len(s1) != len(s2) {
t.Fatalf("different lengths: %d vs %d", len(s1), len(s2))
}
for i := range s1 {
if s1[i].Name != s2[i].Name {
t.Errorf("service[%d]: %q vs %q", i, s1[i].Name, s2[i].Name)
}
}
})
}
Loading