mirror of
https://github.com/j3ssie/osmedeus.git
synced 2026-08-28 18:59:46 +02:00
- Implement cloud provider infrastructure (DigitalOcean, AWS, GCP, Linode, Azure) with Pulumi integration for distributed scanning - Add nmap and tmux utility functions for port scanning results processing and long-running background session management - Introduce webhook-triggered run execution with unique UUID and authentication key support for external integrations
283 lines
6.8 KiB
Go
283 lines
6.8 KiB
Go
package functions
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/j3ssie/osmedeus/v5/internal/json"
|
|
)
|
|
|
|
func TestParseNmapXML(t *testing.T) {
|
|
testdataPath := filepath.Join("..", "..", "test", "testdata", "sample-jsonl-output", "sample-nmap-result-1.xml")
|
|
|
|
// Check if test file exists
|
|
if _, err := os.Stat(testdataPath); os.IsNotExist(err) {
|
|
t.Skipf("Test file not found: %s", testdataPath)
|
|
return
|
|
}
|
|
|
|
results, err := parseNmapXML(testdataPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to parse XML: %v", err)
|
|
}
|
|
|
|
if len(results) == 0 {
|
|
t.Error("Expected at least 1 host, got 0")
|
|
return
|
|
}
|
|
|
|
// Verify first host
|
|
host := results[0]
|
|
if host.HostIP == "" {
|
|
t.Error("Expected non-empty host IP")
|
|
}
|
|
|
|
if host.AssetType != "ip" {
|
|
t.Errorf("Expected asset type 'ip', got '%s'", host.AssetType)
|
|
}
|
|
|
|
if len(host.Ports) == 0 {
|
|
t.Error("Expected ports, got none")
|
|
}
|
|
|
|
// Verify JSONL serialization
|
|
data, err := json.Marshal(host)
|
|
if err != nil {
|
|
t.Errorf("Failed to marshal to JSON: %v", err)
|
|
}
|
|
|
|
if len(data) == 0 {
|
|
t.Error("Expected non-empty JSON output")
|
|
}
|
|
|
|
// Verify it's valid JSON
|
|
var unmarshal AssetOutput
|
|
if err := json.Unmarshal(data, &unmarshal); err != nil {
|
|
t.Errorf("Failed to unmarshal JSON: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestParseNmapGrepable(t *testing.T) {
|
|
testdataPath := filepath.Join("..", "..", "test", "testdata", "sample-jsonl-output", "sample-nmap-result-1.gnmap")
|
|
|
|
// Check if test file exists
|
|
if _, err := os.Stat(testdataPath); os.IsNotExist(err) {
|
|
t.Skipf("Test file not found: %s", testdataPath)
|
|
return
|
|
}
|
|
|
|
results, err := parseNmapGrepable(testdataPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to parse grepable: %v", err)
|
|
}
|
|
|
|
if len(results) == 0 {
|
|
t.Error("Expected at least 1 host, got 0")
|
|
return
|
|
}
|
|
|
|
// Verify first host
|
|
host := results[0]
|
|
if host.HostIP == "" {
|
|
t.Error("Expected non-empty host IP")
|
|
}
|
|
|
|
if host.AssetType != "ip" {
|
|
t.Errorf("Expected asset type 'ip', got '%s'", host.AssetType)
|
|
}
|
|
|
|
if len(host.Ports) == 0 {
|
|
t.Error("Expected ports, got none")
|
|
}
|
|
}
|
|
|
|
func TestParseNmapXMLMultipleHosts(t *testing.T) {
|
|
testdataPath := filepath.Join("..", "..", "test", "testdata", "sample-jsonl-output", "sample-nmap-result-2.xml")
|
|
|
|
// Check if test file exists
|
|
if _, err := os.Stat(testdataPath); os.IsNotExist(err) {
|
|
t.Skipf("Test file not found: %s", testdataPath)
|
|
return
|
|
}
|
|
|
|
results, err := parseNmapXML(testdataPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to parse XML: %v", err)
|
|
}
|
|
|
|
if len(results) == 0 {
|
|
t.Error("Expected at least 1 host, got 0")
|
|
}
|
|
|
|
// Verify all hosts have required fields
|
|
for i, host := range results {
|
|
if host.HostIP == "" {
|
|
t.Errorf("Host %d: Expected non-empty host IP", i)
|
|
}
|
|
if host.AssetValue == "" {
|
|
t.Errorf("Host %d: Expected non-empty asset value", i)
|
|
}
|
|
if host.AssetType != "ip" {
|
|
t.Errorf("Host %d: Expected asset type 'ip', got '%s'", i, host.AssetType)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNmapToJSONLIntegration(t *testing.T) {
|
|
testdataPath := filepath.Join("..", "..", "test", "testdata", "sample-jsonl-output", "sample-nmap-result-1.xml")
|
|
|
|
// Check if test file exists
|
|
if _, err := os.Stat(testdataPath); os.IsNotExist(err) {
|
|
t.Skipf("Test file not found: %s", testdataPath)
|
|
return
|
|
}
|
|
|
|
// Create temp output file
|
|
tmpDir := t.TempDir()
|
|
outputPath := filepath.Join(tmpDir, "output.jsonl")
|
|
|
|
// Parse and write
|
|
results, err := parseNmapXML(testdataPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to parse XML: %v", err)
|
|
}
|
|
|
|
// Write JSONL
|
|
outFile, err := os.Create(outputPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create output file: %v", err)
|
|
}
|
|
|
|
for _, asset := range results {
|
|
data, err := json.Marshal(asset)
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal asset: %v", err)
|
|
}
|
|
if _, err := outFile.Write(data); err != nil {
|
|
t.Fatalf("Failed to write data: %v", err)
|
|
}
|
|
if _, err := outFile.WriteString("\n"); err != nil {
|
|
t.Fatalf("Failed to write newline: %v", err)
|
|
}
|
|
}
|
|
if err := outFile.Close(); err != nil {
|
|
t.Fatalf("Failed to close file: %v", err)
|
|
}
|
|
|
|
// Verify output file exists and is not empty
|
|
stat, err := os.Stat(outputPath)
|
|
if err != nil {
|
|
t.Fatalf("Output file not found: %v", err)
|
|
}
|
|
|
|
if stat.Size() == 0 {
|
|
t.Error("Output file is empty")
|
|
}
|
|
|
|
// Read and verify JSONL format
|
|
content, err := os.ReadFile(outputPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read output file: %v", err)
|
|
}
|
|
|
|
lines := strings.Split(strings.TrimSpace(string(content)), "\n")
|
|
if len(lines) == 0 {
|
|
t.Error("Expected at least one line in JSONL output")
|
|
}
|
|
|
|
// Verify each line is valid JSON
|
|
for i, line := range lines {
|
|
if line == "" {
|
|
continue
|
|
}
|
|
var asset AssetOutput
|
|
if err := json.Unmarshal([]byte(line), &asset); err != nil {
|
|
t.Errorf("Line %d is not valid JSON: %v", i+1, err)
|
|
}
|
|
|
|
// Verify required fields
|
|
if asset.HostIP == "" {
|
|
t.Errorf("Line %d: Missing host_ip", i+1)
|
|
}
|
|
if asset.AssetValue == "" {
|
|
t.Errorf("Line %d: Missing asset_value", i+1)
|
|
}
|
|
if asset.AssetType == "" {
|
|
t.Errorf("Line %d: Missing asset_type", i+1)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseNmapXMLEmptyFile(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
emptyFile := filepath.Join(tmpDir, "empty.xml")
|
|
|
|
if err := os.WriteFile(emptyFile, []byte(""), 0644); err != nil {
|
|
t.Fatalf("Failed to create empty file: %v", err)
|
|
}
|
|
|
|
_, err := parseNmapXML(emptyFile)
|
|
if err == nil {
|
|
t.Error("Expected error for empty file, got nil")
|
|
}
|
|
}
|
|
|
|
func TestParseNmapXMLInvalidXML(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
invalidFile := filepath.Join(tmpDir, "invalid.xml")
|
|
|
|
if err := os.WriteFile(invalidFile, []byte("not valid xml"), 0644); err != nil {
|
|
t.Fatalf("Failed to create invalid file: %v", err)
|
|
}
|
|
|
|
_, err := parseNmapXML(invalidFile)
|
|
if err == nil {
|
|
t.Error("Expected error for invalid XML, got nil")
|
|
}
|
|
}
|
|
|
|
func TestParseNmapGrepableInvalidFile(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
invalidFile := filepath.Join(tmpDir, "invalid.gnmap")
|
|
|
|
if err := os.WriteFile(invalidFile, []byte("not a grepable format"), 0644); err != nil {
|
|
t.Fatalf("Failed to create invalid file: %v", err)
|
|
}
|
|
|
|
results, err := parseNmapGrepable(invalidFile)
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
|
|
// Should return empty results for invalid format
|
|
if len(results) != 0 {
|
|
t.Errorf("Expected 0 results for invalid format, got %d", len(results))
|
|
}
|
|
}
|
|
|
|
func TestSanitizeTargetForPath(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
expected string
|
|
}{
|
|
{"192.168.1.0/24", "192-168-1-0-24"},
|
|
{"10.0.0.1", "10-0-0-1"},
|
|
{"example.com", "example-com"},
|
|
{"192.168.1.1:8080", "192-168-1-1-8080"},
|
|
{"targets.txt", "targets"},
|
|
{"scan.xml", "scan"},
|
|
{"http://example.com", "http---example-com"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.input, func(t *testing.T) {
|
|
result := sanitizeTargetForPath(tt.input)
|
|
if result != tt.expected {
|
|
t.Errorf("sanitizeTargetForPath(%q) = %q, expected %q", tt.input, result, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|