diff --git a/v2/tools/generate-coverage-matrices.go b/v2/tools/generate-coverage-matrices.go new file mode 100644 index 00000000..45043cd7 --- /dev/null +++ b/v2/tools/generate-coverage-matrices.go @@ -0,0 +1,112 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + "sort" + + "github.com/datadog/stratus-red-team/v2/pkg/stratus" +) + +func GenerateCoverageMatrices(index map[stratus.Platform]map[string][]*stratus.AttackTechnique, docsDirectory string) error { + // Process each platform in the index + for platform, tacticsMap := range index { + // Initialize the HTML content + htmlContent := fmt.Sprintf(` + + + + %s Coverage Matrix + + + + +

Stratus Red Team

+ + +`, platform, platform) + + // Extract unique tactics + tacticsSet := make(map[string]struct{}) + for tactic := range tacticsMap { + tacticsSet[tactic] = struct{}{} + } + tactics := make([]string, 0, len(tacticsSet)) + for tactic := range tacticsSet { + tactics = append(tactics, tactic) + } + sort.Strings(tactics) + + // Add header row + htmlContent += "" + for _, tactic := range tactics { + htmlContent += fmt.Sprintf("", tactic) + } + htmlContent += "\n\n" + + rows := make([][]string, 0) + maxRows := 0 + + // Map tactic to techniques + tacticToTechniques := make(map[string][]string) + for _, tactic := range tactics { + techniques := tacticsMap[tactic] + for _, technique := range techniques { + tacticToTechniques[tactic] = append(tacticToTechniques[tactic], technique.FriendlyName) + } + if len(tacticToTechniques[tactic]) > maxRows { + maxRows = len(tacticToTechniques[tactic]) + } + } + + // Fill rows with Stratus techniques for each ATT&CK tactic + for i := 0; i < maxRows; i++ { + row := make([]string, len(tactics)) + for j, tactic := range tactics { + if i < len(tacticToTechniques[tactic]) { + row[j] = tacticToTechniques[tactic][i] + } else { + row[j] = "" + } + } + rows = append(rows, row) + } + + // Add rows to the HTML table + for _, row := range rows { + htmlContent += "" + for _, cell := range row { + if cell != "" { + htmlContent += fmt.Sprintf("", cell) + } else { + htmlContent += "" + } + } + htmlContent += "\n" + } + + // Close table and HTML document + htmlContent += ` + +
Coverage Matrix for %s
%s
%s
+ +` + filePath := filepath.Join(docsDirectory, fmt.Sprintf("%s.html", platform)) + if err := os.WriteFile(filePath, []byte(htmlContent), 0644); err != nil { + return fmt.Errorf("failed to write HTML file for platform %s: %w", platform, err) + } + fmt.Printf("Generated coverage matrix for platform: %s\n", platform) + } + + return nil +} \ No newline at end of file diff --git a/v2/tools/generate-docs.go b/v2/tools/generate-docs.go index cfbd1fb7..f1c79f77 100644 --- a/v2/tools/generate-docs.go +++ b/v2/tools/generate-docs.go @@ -28,6 +28,13 @@ func main() { os.Exit(1) } + // Generate HTML files for each platform + if err := GenerateCoverageMatrices(index, docsDirectory); err != nil { + fmt.Fprintln(os.Stderr, "Could not generate coverage matrices") + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } + // Write a single index file with all techniques. File is enconded in YAML. yamlIndex := filepath.Join(docsDirectory, "index.yaml") if err := GenerateYAML(yamlIndex, index); err != nil {