Add JSON and HTML writers for reporting Hunter findings
This commit is contained in:
@@ -1 +1,120 @@
|
||||
package writer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"rmm-hunter/internal/pkg"
|
||||
"rmm-hunter/internal/pkg/writer/disposition"
|
||||
"rmm-hunter/internal/suspicious"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type HTMLReportData struct {
|
||||
ReportName string
|
||||
GeneratedAt string
|
||||
RiskRating *disposition.Disposition
|
||||
Findings interface{}
|
||||
}
|
||||
|
||||
// WriteHTMLReport generates an HTML report from Hunter findings
|
||||
func WriteHTMLReport(sus *suspicious.Suspicious, opts *pkg.RunOptions) error {
|
||||
if opts == nil {
|
||||
opts = &pkg.RunOptions{Name: "rmm-hunter-report"}
|
||||
}
|
||||
|
||||
if opts.Name == "" {
|
||||
opts.Name = "rmm-hunter-report"
|
||||
}
|
||||
|
||||
if sus == nil {
|
||||
return fmt.Errorf("suspicious instance is nil")
|
||||
}
|
||||
|
||||
// Calculate risk disposition
|
||||
riskRating := disposition.CalculateDisposition(sus)
|
||||
|
||||
// Prepare template data
|
||||
data := HTMLReportData{
|
||||
ReportName: opts.Name,
|
||||
GeneratedAt: time.Now().UTC().Format("2006-01-02 15:04:05 UTC"),
|
||||
RiskRating: riskRating,
|
||||
Findings: sus,
|
||||
}
|
||||
|
||||
// Parse template
|
||||
tmpl, err := template.New("report").Funcs(templateFuncs()).Parse(htmlTemplate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse HTML template: %w", err)
|
||||
}
|
||||
|
||||
// Ensure output directory exists
|
||||
filename := fmt.Sprintf("%s.html", opts.Name)
|
||||
if err := ensureDir(filepath.Dir(filename)); err != nil {
|
||||
return fmt.Errorf("failed to create output directory: %w", err)
|
||||
}
|
||||
|
||||
// Create output file
|
||||
file, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create HTML file: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Execute template
|
||||
if err := tmpl.Execute(file, data); err != nil {
|
||||
return fmt.Errorf("failed to execute HTML template: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("[+] HTML report written to: %s\n", filename)
|
||||
return nil
|
||||
}
|
||||
|
||||
// templateFuncs provides helper functions for the HTML template
|
||||
func templateFuncs() template.FuncMap {
|
||||
return template.FuncMap{
|
||||
"safeHTML": func(s string) template.HTML {
|
||||
return template.HTML(s)
|
||||
},
|
||||
"riskColor": func(rating string) string {
|
||||
switch strings.ToLower(rating) {
|
||||
case "low":
|
||||
return "#28a745"
|
||||
case "medium":
|
||||
return "#ffc107"
|
||||
case "high":
|
||||
return "#dc3545"
|
||||
default:
|
||||
return "#6c757d"
|
||||
}
|
||||
},
|
||||
"len": func(v interface{}) int {
|
||||
if v == nil {
|
||||
return 0
|
||||
}
|
||||
switch val := v.(type) {
|
||||
case []interface{}:
|
||||
return len(val)
|
||||
case []string:
|
||||
return len(val)
|
||||
case []*suspicious.Service:
|
||||
return len(val)
|
||||
case []suspicious.Process:
|
||||
return len(val)
|
||||
case []suspicious.NetworkConnection:
|
||||
return len(val)
|
||||
case []*suspicious.ScheduledTask:
|
||||
return len(val)
|
||||
case []suspicious.AutoRun:
|
||||
return len(val)
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
},
|
||||
"mul": func(a, b float64) float64 {
|
||||
return a * b
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user