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
99 changes: 99 additions & 0 deletions internal/datagen/applications.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package datagen

import "math/rand"

// ApplicationIdentity represents installed software on a system.
type ApplicationIdentity struct {
Name string // "Microsoft SQL Server 2019"
Expand All @@ -9,3 +11,100 @@ type ApplicationIdentity struct {
InstallDate string // "2024-06-15"
SystemRef string // back-reference: hostname of owning system
}

// appTemplate defines an application template.
type appTemplate struct {
name string
version string
vendor string
installPath string
}

var windowsServerApps = []appTemplate{
{"Internet Information Services", "10.0.20348", "Microsoft Corporation", `C:\Windows\System32\inetsrv`},
{"Microsoft SQL Server 2019", "15.0.4322.2", "Microsoft Corporation", `C:\Program Files\Microsoft SQL Server`},
{".NET Runtime 8.0", "8.0.1", "Microsoft Corporation", `C:\Program Files\dotnet`},
{"Microsoft Exchange Server 2019", "15.2.1118.40", "Microsoft Corporation", `C:\Program Files\Microsoft\Exchange Server`},
{"Microsoft SharePoint Server 2019", "16.0.10396.20000", "Microsoft Corporation", `C:\Program Files\Microsoft Office Servers`},
{"Windows Server Backup", "10.0.20348", "Microsoft Corporation", `C:\Windows\System32`},
{"Microsoft Visual C++ 2019 Redistributable", "14.29.30133", "Microsoft Corporation", `C:\Program Files\Microsoft Visual Studio`},
}

var linuxServerApps = []appTemplate{
{"nginx", "1.24.0", "Nginx Inc.", "/usr/sbin/nginx"},
{"Apache HTTP Server", "2.4.58", "Apache Software Foundation", "/usr/sbin/apache2"},
{"PostgreSQL", "16.1", "PostgreSQL Global Development Group", "/usr/lib/postgresql/16"},
{"MySQL", "8.0.35", "Oracle Corporation", "/usr/sbin/mysqld"},
{"Docker Engine", "24.0.7", "Docker Inc.", "/usr/bin/docker"},
{"OpenJDK Runtime Environment", "17.0.9", "Eclipse Adoptium", "/usr/lib/jvm/java-17-openjdk"},
{"Node.js", "20.10.0", "OpenJS Foundation", "/usr/bin/node"},
{"Redis", "7.2.3", "Redis Ltd.", "/usr/bin/redis-server"},
}

var workstationApps = []appTemplate{
{"Microsoft Office 365 ProPlus", "16.0.17126.20132", "Microsoft Corporation", `C:\Program Files\Microsoft Office`},
{"Google Chrome", "120.0.6099.130", "Google LLC", `C:\Program Files\Google\Chrome`},
{"Mozilla Firefox", "121.0", "Mozilla Foundation", `C:\Program Files\Mozilla Firefox`},
{"Visual Studio Code", "1.85.1", "Microsoft Corporation", `C:\Users\AppData\Local\Programs\Microsoft VS Code`},
{"Slack", "4.35.126", "Slack Technologies", `C:\Users\AppData\Local\slack`},
{"Microsoft Teams", "1.6.00.36062", "Microsoft Corporation", `C:\Users\AppData\Local\Microsoft\Teams`},
{"Zoom Workplace", "5.17.0", "Zoom Video Communications", `C:\Program Files\Zoom`},
{"Adobe Acrobat Reader", "23.008.20470", "Adobe Inc.", `C:\Program Files\Adobe\Acrobat Reader DC`},
}

var dcApps = []appTemplate{
{"Active Directory Domain Services", "10.0.20348", "Microsoft Corporation", `C:\Windows\System32`},
{"Active Directory Certificate Services", "10.0.20348", "Microsoft Corporation", `C:\Windows\System32\certsrv`},
{"DNS Server", "10.0.20348", "Microsoft Corporation", `C:\Windows\System32\dns.exe`},
{"DHCP Server", "10.0.20348", "Microsoft Corporation", `C:\Windows\System32`},
{"Remote Server Administration Tools", "10.0.20348", "Microsoft Corporation", `C:\Windows\System32`},
{"Group Policy Management Console", "10.0.20348", "Microsoft Corporation", `C:\Windows\System32`},
}

// installDates provides deterministic install date strings.
var installDates = NewPool(
"2024-01-15", "2024-02-20", "2024-03-10", "2024-04-05",
"2024-05-18", "2024-06-22", "2024-07-30", "2024-08-14",
"2024-09-01", "2024-10-12", "2024-11-25", "2024-12-03",
)

// GenerateApplicationsForSystem returns applications appropriate for the given OS and role.
func GenerateApplicationsForSystem(r *rand.Rand, os OSType, role SystemRole, hostname string) []*ApplicationIdentity {
var templates []appTemplate

switch {
case role == RoleDC:
templates = dcApps
case os == OSWindows && role == RoleServer:
templates = windowsServerApps
case os == OSLinux && (role == RoleServer || role == RoleRouter):
templates = linuxServerApps
default:
templates = workstationApps
}

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

indices := r.Perm(len(templates))
apps := make([]*ApplicationIdentity, count)
for i := 0; i < count; i++ {
tmpl := templates[indices[i]]
apps[i] = &ApplicationIdentity{
Name: tmpl.name,
Version: tmpl.version,
Vendor: tmpl.vendor,
InstallPath: tmpl.installPath,
InstallDate: installDates.Random(r),
SystemRef: hostname,
}
}

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

import (
"math/rand"
"testing"
)

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

t.Run("windows server apps", func(t *testing.T) {
apps := GenerateApplicationsForSystem(r, OSWindows, RoleServer, "SRV01")
if len(apps) < 2 {
t.Errorf("expected at least 2 apps, got %d", len(apps))
}
for _, a := range apps {
if a.SystemRef != "SRV01" {
t.Errorf("expected SystemRef 'SRV01', got %q", a.SystemRef)
}
if a.Name == "" {
t.Error("app Name should not be empty")
}
if a.Version == "" {
t.Error("app Version should not be empty")
}
if a.Vendor == "" {
t.Error("app Vendor should not be empty")
}
}
})

t.Run("linux server apps", func(t *testing.T) {
apps := GenerateApplicationsForSystem(r, OSLinux, RoleServer, "srv01")
if len(apps) < 2 {
t.Errorf("expected at least 2 apps, got %d", len(apps))
}
})

t.Run("DC apps", func(t *testing.T) {
apps := GenerateApplicationsForSystem(r, OSWindows, RoleDC, "DC01")
if len(apps) < 2 {
t.Errorf("expected at least 2 apps, got %d", len(apps))
}
})

t.Run("workstation apps", func(t *testing.T) {
apps := GenerateApplicationsForSystem(r, OSWindows, RoleWorkstation, "WS01")
if len(apps) < 2 {
t.Errorf("expected at least 2 apps, got %d", len(apps))
}
})

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