debian-forge-composer/internal/security/security_scanner.go
robojerk 4eeaa43c39
Some checks failed
Tests / 🛃 Unit tests (push) Failing after 13s
Tests / 🗄 DB tests (push) Failing after 19s
Tests / 🐍 Lint python scripts (push) Failing after 1s
Tests / ⌨ Golang Lint (push) Failing after 1s
Tests / 📦 Packit config lint (push) Failing after 1s
Tests / 🔍 Check source preparation (push) Failing after 1s
Tests / 🔍 Check for valid snapshot urls (push) Failing after 1s
Tests / 🔍 Check for missing or unused runner repos (push) Failing after 1s
Tests / 🐚 Shellcheck (push) Failing after 1s
Tests / 📦 RPMlint (push) Failing after 1s
Tests / Gitlab CI trigger helper (push) Failing after 1s
Tests / 🎀 kube-linter (push) Failing after 1s
Tests / 🧹 cloud-cleaner-is-enabled (push) Successful in 3s
Tests / 🔍 Check spec file osbuild/images dependencies (push) Failing after 1s
did stuff
2025-08-26 10:34:42 -07:00

1003 lines
28 KiB
Go

package security
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/sirupsen/logrus"
)
type SecurityScanner struct {
logger *logrus.Logger
config *SecurityConfig
scanners map[string]SecurityTool
policies map[string]SecurityPolicy
reporters map[string]SecurityReporter
}
type SecurityConfig struct {
EnabledScanners []string `json:"enabled_scanners"`
PolicyFile string `json:"policy_file"`
OutputDir string `json:"output_dir"`
SeverityLevels []string `json:"severity_levels"`
MaxCVEs int `json:"max_cves"`
Timeout int `json:"timeout"`
Metadata map[string]string `json:"metadata"`
}
type SecurityTool struct {
Name string `json:"name"`
Description string `json:"description"`
Command string `json:"command"`
Args []string `json:"args"`
Enabled bool `json:"enabled"`
Config map[string]interface{} `json:"config"`
}
type SecurityPolicy struct {
Name string `json:"name"`
Description string `json:"description"`
Rules []SecurityRule `json:"rules"`
Severity string `json:"severity"`
Enabled bool `json:"enabled"`
}
type SecurityRule struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Type string `json:"type"`
Condition string `json:"condition"`
Action string `json:"action"`
Severity string `json:"severity"`
Metadata map[string]interface{} `json:"metadata"`
}
type SecurityReporter struct {
Name string `json:"name"`
Description string `json:"description"`
Format string `json:"format"`
Output string `json:"output"`
Enabled bool `json:"enabled"`
}
type SecurityScanResult struct {
ID string `json:"id"`
Target string `json:"target"`
TargetType string `json:"target_type"`
ScanTime time.Time `json:"scan_time"`
Duration time.Duration `json:"duration"`
Vulnerabilities []Vulnerability `json:"vulnerabilities"`
PolicyResults []PolicyResult `json:"policy_results"`
Summary SecuritySummary `json:"summary"`
Metadata map[string]interface{} `json:"metadata"`
}
type Vulnerability struct {
ID string `json:"id"`
CVE string `json:"cve"`
Severity string `json:"severity"`
Title string `json:"title"`
Description string `json:"description"`
Package string `json:"package"`
Version string `json:"version"`
FixedIn string `json:"fixed_in"`
References []string `json:"references"`
CVSS CVSSScore `json:"cvss"`
Metadata map[string]interface{} `json:"metadata"`
}
type CVSSScore struct {
Version string `json:"version"`
BaseScore float64 `json:"base_score"`
TemporalScore float64 `json:"temporal_score"`
EnvironmentalScore float64 `json:"environmental_score"`
Vector string `json:"vector"`
}
type PolicyResult struct {
PolicyID string `json:"policy_id"`
RuleID string `json:"rule_id"`
Status string `json:"status"`
Message string `json:"message"`
Severity string `json:"severity"`
Violations []PolicyViolation `json:"violations"`
Metadata map[string]interface{} `json:"metadata"`
}
type PolicyViolation struct {
RuleID string `json:"rule_id"`
Message string `json:"message"`
Severity string `json:"severity"`
Location string `json:"location"`
Details map[string]interface{} `json:"details"`
}
type SecuritySummary struct {
TotalVulnerabilities int `json:"total_vulnerabilities"`
CriticalCount int `json:"critical_count"`
HighCount int `json:"high_count"`
MediumCount int `json:"medium_count"`
LowCount int `json:"low_count"`
PolicyViolations int `json:"policy_violations"`
RiskScore float64 `json:"risk_score"`
Recommendations []string `json:"recommendations"`
Metadata map[string]interface{} `json:"metadata"`
}
func NewSecurityScanner(config *SecurityConfig, logger *logrus.Logger) *SecurityScanner {
scanner := &SecurityScanner{
logger: logger,
config: config,
scanners: make(map[string]SecurityTool),
policies: make(map[string]SecurityPolicy),
reporters: make(map[string]SecurityReporter),
}
// Initialize security tools
scanner.initializeScanners()
// Initialize security policies
scanner.initializePolicies()
// Initialize reporters
scanner.initializeReporters()
return scanner
}
func (ss *SecurityScanner) initializeScanners() {
// Trivy scanner for container and image vulnerabilities
ss.scanners["trivy"] = SecurityTool{
Name: "trivy",
Description: "Comprehensive vulnerability scanner for containers and images",
Command: "trivy",
Args: []string{"--format", "json", "--severity", "CRITICAL,HIGH,MEDIUM,LOW"},
Enabled: true,
Config: map[string]interface{}{
"timeout": 300,
"cache": true,
},
}
// Grype scanner for package vulnerabilities
ss.scanners["grype"] = SecurityTool{
Name: "grype",
Description: "Vulnerability scanner for packages and dependencies",
Command: "grype",
Args: []string{"--output", "json"},
Enabled: true,
Config: map[string]interface{}{
"timeout": 180,
"scope": "all-layers",
},
}
// Clair scanner for container layer analysis
ss.scanners["clair"] = SecurityTool{
Name: "clair",
Description: "Static analysis tool for container layers",
Command: "clair",
Args: []string{"--log-level", "info"},
Enabled: true,
Config: map[string]interface{}{
"timeout": 240,
"workers": 4,
},
}
// OWASP ZAP for web application security
ss.scanners["zap"] = SecurityTool{
Name: "zap",
Description: "Web application security scanner",
Command: "zap-baseline",
Args: []string{"-t", "http://localhost:8080"},
Enabled: false, // Disabled by default
Config: map[string]interface{}{
"timeout": 600,
"rules": "baseline",
},
}
}
func (ss *SecurityScanner) initializePolicies() {
// Debian Security Policy
ss.policies["debian_security"] = SecurityPolicy{
Name: "debian_security",
Description: "Debian Security Team compliance policy",
Severity: "high",
Enabled: true,
Rules: []SecurityRule{
{
ID: "DS001",
Name: "No Critical CVEs",
Description: "No critical severity CVEs allowed",
Type: "vulnerability",
Condition: "critical_cve_count == 0",
Action: "fail",
Severity: "critical",
},
{
ID: "DS002",
Name: "No High CVEs",
Description: "No high severity CVEs allowed",
Type: "vulnerability",
Condition: "high_cve_count == 0",
Action: "fail",
Severity: "high",
},
{
ID: "DS003",
Name: "Package Signature Verification",
Description: "All packages must be properly signed",
Type: "signature",
Condition: "all_packages_signed == true",
Action: "fail",
Severity: "high",
},
{
ID: "DS004",
Name: "Container Image Signing",
Description: "Container images must be signed",
Type: "signature",
Condition: "image_signed == true",
Action: "fail",
Severity: "medium",
},
},
}
// Debian Policy Compliance
ss.policies["debian_policy"] = SecurityPolicy{
Name: "debian_policy",
Description: "Debian Policy compliance checking",
Severity: "medium",
Enabled: true,
Rules: []SecurityRule{
{
ID: "DP001",
Name: "Filesystem Hierarchy",
Description: "Files must be in correct locations",
Type: "filesystem",
Condition: "filesystem_compliant == true",
Action: "warn",
Severity: "medium",
},
{
ID: "DP002",
Name: "Package Dependencies",
Description: "Package dependencies must be correct",
Type: "dependency",
Condition: "dependencies_valid == true",
Action: "fail",
Severity: "high",
},
},
}
// Security Hardening Guidelines
ss.policies["security_hardening"] = SecurityPolicy{
Name: "security_hardening",
Description: "Security hardening guidelines implementation",
Severity: "medium",
Enabled: true,
Rules: []SecurityRule{
{
ID: "SH001",
Name: "No Root Login",
Description: "Root login should be disabled",
Type: "configuration",
Condition: "root_login_disabled == true",
Action: "warn",
Severity: "medium",
},
{
ID: "SH002",
Name: "SSH Hardening",
Description: "SSH should be properly configured",
Type: "configuration",
Condition: "ssh_hardened == true",
Action: "warn",
Severity: "medium",
},
},
}
}
func (ss *SecurityScanner) initializeReporters() {
// JSON reporter
ss.reporters["json"] = SecurityReporter{
Name: "json",
Description: "JSON format security report",
Format: "json",
Output: "security-report.json",
Enabled: true,
}
// HTML reporter
ss.reporters["html"] = SecurityReporter{
Name: "html",
Description: "HTML format security report",
Format: "html",
Output: "security-report.html",
Enabled: true,
}
// SARIF reporter for CI/CD integration
ss.reporters["sarif"] = SecurityReporter{
Name: "sarif",
Description: "SARIF format for CI/CD tools",
Format: "sarif",
Output: "security-report.sarif",
Enabled: true,
}
}
func (ss *SecurityScanner) ScanTarget(target string, targetType string) (*SecurityScanResult, error) {
ss.logger.Infof("Starting security scan for target: %s (type: %s)", target, targetType)
startTime := time.Now()
// Create scan result
result := &SecurityScanResult{
ID: generateScanID(),
Target: target,
TargetType: targetType,
ScanTime: startTime,
Metadata: make(map[string]interface{}),
}
// Run vulnerability scans
if err := ss.runVulnerabilityScans(result); err != nil {
ss.logger.Warnf("Vulnerability scan failed: %v", err)
}
// Run policy checks
if err := ss.runPolicyChecks(result); err != nil {
ss.logger.Warnf("Policy check failed: %v", err)
}
// Calculate duration
result.Duration = time.Since(startTime)
// Generate summary
ss.generateSummary(result)
// Generate reports
if err := ss.generateReports(result); err != nil {
ss.logger.Warnf("Report generation failed: %v", err)
}
ss.logger.Infof("Security scan completed in %v", result.Duration)
return result, nil
}
func (ss *SecurityScanner) runVulnerabilityScans(result *SecurityScanResult) error {
for name, scanner := range ss.scanners {
if !scanner.Enabled {
continue
}
ss.logger.Infof("Running %s vulnerability scan", name)
switch name {
case "trivy":
if err := ss.runTrivyScan(result, scanner); err != nil {
ss.logger.Warnf("Trivy scan failed: %v", err)
}
case "grype":
if err := ss.runGrypeScan(result, scanner); err != nil {
ss.logger.Warnf("Grype scan failed: %v", err)
}
case "clair":
if err := ss.runClairScan(result, scanner); err != nil {
ss.logger.Warnf("Clair scan failed: %v", err)
}
}
}
return nil
}
func (ss *SecurityScanner) runTrivyScan(result *SecurityScanResult, scanner SecurityTool) error {
args := append(scanner.Args, result.Target)
cmd := exec.Command(scanner.Command, args...)
output, err := cmd.Output()
if err != nil {
return fmt.Errorf("trivy scan failed: %w", err)
}
// Parse Trivy output
var trivyResult map[string]interface{}
if err := json.Unmarshal(output, &trivyResult); err != nil {
return fmt.Errorf("failed to parse trivy output: %w", err)
}
// Extract vulnerabilities
if vulnerabilities, ok := trivyResult["Vulnerabilities"].([]interface{}); ok {
for _, vuln := range vulnerabilities {
if vulnMap, ok := vuln.(map[string]interface{}); ok {
vulnerability := ss.parseTrivyVulnerability(vulnMap)
result.Vulnerabilities = append(result.Vulnerabilities, vulnerability)
}
}
}
return nil
}
func (ss *SecurityScanner) runGrypeScan(result *SecurityScanResult, scanner SecurityTool) error {
args := append(scanner.Args, result.Target)
cmd := exec.Command(scanner.Command, args...)
output, err := cmd.Output()
if err != nil {
return fmt.Errorf("grype scan failed: %w", err)
}
// Parse Grype output
var grypeResult map[string]interface{}
if err := json.Unmarshal(output, &grypeResult); err != nil {
return fmt.Errorf("failed to parse grype output: %w", err)
}
// Extract vulnerabilities
if matches, ok := grypeResult["matches"].([]interface{}); ok {
for _, match := range matches {
if matchMap, ok := match.(map[string]interface{}); ok {
vulnerability := ss.parseGrypeVulnerability(matchMap)
result.Vulnerabilities = append(result.Vulnerabilities, vulnerability)
}
}
}
return nil
}
func (ss *SecurityScanner) runClairScan(result *SecurityScanResult, scanner SecurityTool) error {
// Clair requires a different approach - it analyzes container layers
// This is a simplified implementation
ss.logger.Info("Running Clair layer analysis")
// Placeholder for Clair implementation
// In production, this would use Clair's API or CLI
return nil
}
func (ss *SecurityScanner) parseTrivyVulnerability(vulnMap map[string]interface{}) Vulnerability {
vulnerability := Vulnerability{
Metadata: make(map[string]interface{}),
}
if cve, ok := vulnMap["VulnerabilityID"].(string); ok {
vulnerability.CVE = cve
vulnerability.ID = cve
}
if severity, ok := vulnMap["Severity"].(string); ok {
vulnerability.Severity = severity
}
if title, ok := vulnMap["Title"].(string); ok {
vulnerability.Title = title
}
if description, ok := vulnMap["Description"].(string); ok {
vulnerability.Description = description
}
if pkg, ok := vulnMap["PkgName"].(string); ok {
vulnerability.Package = pkg
}
if version, ok := vulnMap["InstalledVersion"].(string); ok {
vulnerability.Version = version
}
if fixedIn, ok := vulnMap["FixedVersion"].(string); ok {
vulnerability.FixedIn = fixedIn
}
// Parse CVSS if available
if cvss, ok := vulnMap["CVSS"].(map[string]interface{}); ok {
vulnerability.CVSS = ss.parseCVSS(cvss)
}
return vulnerability
}
func (ss *SecurityScanner) parseGrypeVulnerability(matchMap map[string]interface{}) Vulnerability {
vulnerability := Vulnerability{
Metadata: make(map[string]interface{}),
}
if vuln, ok := matchMap["vulnerability"].(map[string]interface{}); ok {
if id, ok := vuln["id"].(string); ok {
vulnerability.ID = id
vulnerability.CVE = id
}
if severity, ok := vuln["severity"].(string); ok {
vulnerability.Severity = severity
}
if description, ok := vuln["description"].(string); ok {
vulnerability.Description = description
}
}
if artifact, ok := matchMap["artifact"].(map[string]interface{}); ok {
if name, ok := artifact["name"].(string); ok {
vulnerability.Package = name
}
if version, ok := artifact["version"].(string); ok {
vulnerability.Version = version
}
}
return vulnerability
}
func (ss *SecurityScanner) parseCVSS(cvssMap map[string]interface{}) CVSSScore {
cvss := CVSSScore{}
if version, ok := cvssMap["V3Vector"].(string); ok {
cvss.Vector = version
cvss.Version = "3.1"
}
if score, ok := cvssMap["V3Score"].(float64); ok {
cvss.BaseScore = score
}
return cvss
}
func (ss *SecurityScanner) runPolicyChecks(result *SecurityScanResult) error {
for name, policy := range ss.policies {
if !policy.Enabled {
continue
}
ss.logger.Infof("Running policy check: %s", name)
for _, rule := range policy.Rules {
result := ss.evaluateRule(rule, result)
result.PolicyResults = append(result.PolicyResults, result)
}
}
return nil
}
func (ss *SecurityScanner) evaluateRule(rule SecurityRule, scanResult *SecurityScanResult) PolicyResult {
policyResult := PolicyResult{
PolicyID: rule.ID,
RuleID: rule.ID,
Status: "passed",
Message: "Rule passed",
Severity: rule.Severity,
Metadata: make(map[string]interface{}),
}
// Evaluate rule based on type
switch rule.Type {
case "vulnerability":
policyResult = ss.evaluateVulnerabilityRule(rule, scanResult)
case "signature":
policyResult = ss.evaluateSignatureRule(rule, scanResult)
case "filesystem":
policyResult = ss.evaluateFilesystemRule(rule, scanResult)
case "dependency":
policyResult = ss.evaluateDependencyRule(rule, scanResult)
case "configuration":
policyResult = ss.evaluateConfigurationRule(rule, scanResult)
}
return policyResult
}
func (ss *SecurityScanner) evaluateVulnerabilityRule(rule SecurityRule, scanResult *SecurityScanResult) PolicyResult {
policyResult := PolicyResult{
PolicyID: rule.ID,
RuleID: rule.ID,
Status: "passed",
Message: "Vulnerability rule passed",
Severity: rule.Severity,
Metadata: make(map[string]interface{}),
}
// Count vulnerabilities by severity
criticalCount := 0
highCount := 0
for _, vuln := range scanResult.Vulnerabilities {
switch vuln.Severity {
case "CRITICAL":
criticalCount++
case "HIGH":
highCount++
}
}
// Check rule conditions
switch rule.ID {
case "DS001": // No Critical CVEs
if criticalCount > 0 {
policyResult.Status = "failed"
policyResult.Message = fmt.Sprintf("Found %d critical CVEs", criticalCount)
policyResult.Violations = append(policyResult.Violations, PolicyViolation{
RuleID: rule.ID,
Message: fmt.Sprintf("Critical CVE count: %d", criticalCount),
Severity: "critical",
})
}
case "DS002": // No High CVEs
if highCount > 0 {
policyResult.Status = "failed"
policyResult.Message = fmt.Sprintf("Found %d high CVEs", highCount)
policyResult.Violations = append(policyResult.Violations, PolicyViolation{
RuleID: rule.ID,
Message: fmt.Sprintf("High CVE count: %d", highCount),
Severity: "high",
})
}
}
return policyResult
}
func (ss *SecurityScanner) evaluateSignatureRule(rule SecurityRule, scanResult *SecurityScanResult) PolicyResult {
// Placeholder for signature verification
// In production, this would check GPG signatures and cosign verification
return PolicyResult{
PolicyID: rule.ID,
RuleID: rule.ID,
Status: "skipped",
Message: "Signature verification not implemented",
Severity: rule.Severity,
}
}
func (ss *SecurityScanner) evaluateFilesystemRule(rule SecurityRule, scanResult *SecurityScanResult) PolicyResult {
// Placeholder for filesystem compliance checking
return PolicyResult{
PolicyID: rule.ID,
RuleID: rule.ID,
Status: "skipped",
Message: "Filesystem compliance not implemented",
Severity: rule.Severity,
}
}
func (ss *SecurityScanner) evaluateDependencyRule(rule SecurityRule, scanResult *SecurityScanResult) PolicyResult {
// Placeholder for dependency validation
return PolicyResult{
PolicyID: rule.ID,
RuleID: rule.ID,
Status: "skipped",
Message: "Dependency validation not implemented",
Severity: rule.Severity,
}
}
func (ss *SecurityScanner) evaluateConfigurationRule(rule SecurityRule, scanResult *SecurityScanResult) PolicyResult {
// Placeholder for configuration checking
return PolicyResult{
PolicyID: rule.ID,
RuleID: rule.ID,
Status: "skipped",
Message: "Configuration checking not implemented",
Severity: rule.Severity,
}
}
func (ss *SecurityScanner) generateSummary(result *SecurityScanResult) {
summary := &SecuritySummary{
Metadata: make(map[string]interface{}),
}
// Count vulnerabilities by severity
for _, vuln := range result.Vulnerabilities {
switch vuln.Severity {
case "CRITICAL":
summary.CriticalCount++
case "HIGH":
summary.HighCount++
case "MEDIUM":
summary.MediumCount++
case "LOW":
summary.LowCount++
}
}
summary.TotalVulnerabilities = len(result.Vulnerabilities)
// Count policy violations
for _, policyResult := range result.PolicyResults {
if policyResult.Status == "failed" {
summary.PolicyViolations++
}
}
// Calculate risk score (0-100)
summary.RiskScore = ss.calculateRiskScore(summary)
// Generate recommendations
summary.Recommendations = ss.generateRecommendations(summary)
result.Summary = *summary
}
func (ss *SecurityScanner) calculateRiskScore(summary *SecuritySummary) float64 {
score := 0.0
// Critical vulnerabilities have highest weight
score += float64(summary.CriticalCount) * 25.0
// High vulnerabilities
score += float64(summary.HighCount) * 15.0
// Medium vulnerabilities
score += float64(summary.MediumCount) * 5.0
// Low vulnerabilities
score += float64(summary.LowCount) * 1.0
// Policy violations
score += float64(summary.PolicyViolations) * 10.0
// Cap at 100
if score > 100.0 {
score = 100.0
}
return score
}
func (ss *SecurityScanner) generateRecommendations(summary *SecuritySummary) []string {
var recommendations []string
if summary.CriticalCount > 0 {
recommendations = append(recommendations, "Immediately address critical vulnerabilities")
}
if summary.HighCount > 0 {
recommendations = append(recommendations, "Address high severity vulnerabilities within 24 hours")
}
if summary.MediumCount > 0 {
recommendations = append(recommendations, "Plan to address medium severity vulnerabilities")
}
if summary.PolicyViolations > 0 {
recommendations = append(recommendations, "Review and fix policy violations")
}
if summary.RiskScore > 50 {
recommendations = append(recommendations, "Overall risk score is high - review security posture")
}
return recommendations
}
func (ss *SecurityScanner) generateReports(result *SecurityScanResult) error {
for name, reporter := range ss.reporters {
if !reporter.Enabled {
continue
}
ss.logger.Infof("Generating %s report", name)
switch reporter.Format {
case "json":
if err := ss.generateJSONReport(result, reporter); err != nil {
ss.logger.Warnf("JSON report generation failed: %v", err)
}
case "html":
if err := ss.generateHTMLReport(result, reporter); err != nil {
ss.logger.Warnf("HTML report generation failed: %v", err)
}
case "sarif":
if err := ss.generateSARIFReport(result, reporter); err != nil {
ss.logger.Warnf("SARIF report generation failed: %v", err)
}
}
}
return nil
}
func (ss *SecurityScanner) generateJSONReport(result *SecurityScanResult, reporter SecurityReporter) error {
outputPath := filepath.Join(ss.config.OutputDir, reporter.Output)
jsonData, err := json.MarshalIndent(result, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal JSON: %w", err)
}
if err := os.WriteFile(outputPath, jsonData, 0644); err != nil {
return fmt.Errorf("failed to write JSON report: %w", err)
}
return nil
}
func (ss *SecurityScanner) generateHTMLReport(result *SecurityScanResult, reporter SecurityReporter) error {
// Generate HTML report template
htmlContent := ss.generateHTMLTemplate(result)
outputPath := filepath.Join(ss.config.OutputDir, reporter.Output)
if err := os.WriteFile(outputPath, []byte(htmlContent), 0644); err != nil {
return fmt.Errorf("failed to write HTML report: %w", err)
}
return nil
}
func (ss *SecurityScanner) generateHTMLTemplate(result *SecurityScanResult) string {
// Simple HTML template for security report
return fmt.Sprintf(`<!DOCTYPE html>
<html>
<head>
<title>Security Scan Report - %s</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.header { background: #f0f0f0; padding: 20px; border-radius: 5px; }
.summary { background: #e8f5e8; padding: 15px; margin: 20px 0; border-radius: 5px; }
.vulnerability { background: #fff3cd; padding: 10px; margin: 10px 0; border-radius: 3px; }
.critical { border-left: 5px solid #dc3545; }
.high { border-left: 5px solid #fd7e14; }
.medium { border-left: 5px solid #ffc107; }
.low { border-left: 5px solid #28a745; }
</style>
</head>
<body>
<div class="header">
<h1>Security Scan Report</h1>
<p><strong>Target:</strong> %s</p>
<p><strong>Scan Time:</strong> %s</p>
<p><strong>Duration:</strong> %v</p>
</div>
<div class="summary">
<h2>Summary</h2>
<p><strong>Total Vulnerabilities:</strong> %d</p>
<p><strong>Critical:</strong> %d</p>
<p><strong>High:</strong> %d</p>
<p><strong>Medium:</strong> %d</p>
<p><strong>Low:</strong> %d</p>
<p><strong>Risk Score:</strong> %.1f/100</p>
</div>
<h2>Vulnerabilities</h2>
%s
<h2>Policy Results</h2>
%s
</body>
</html>`,
result.Target, result.Target, result.ScanTime.Format("2006-01-02 15:04:05"),
result.Duration, result.Summary.TotalVulnerabilities, result.Summary.CriticalCount,
result.Summary.HighCount, result.Summary.MediumCount, result.Summary.LowCount,
result.Summary.RiskScore, ss.generateVulnerabilitiesHTML(result), ss.generatePolicyResultsHTML(result))
}
func (ss *SecurityScanner) generateVulnerabilitiesHTML(result *SecurityScanResult) string {
var html strings.Builder
for _, vuln := range result.Vulnerabilities {
severityClass := strings.ToLower(vuln.Severity)
html.WriteString(fmt.Sprintf(`<div class="vulnerability %s">
<h3>%s</h3>
<p><strong>Severity:</strong> %s</p>
<p><strong>Package:</strong> %s</p>
<p><strong>Description:</strong> %s</p>
</div>`, severityClass, vuln.CVE, vuln.Severity, vuln.Package, vuln.Description))
}
return html.String()
}
func (ss *SecurityScanner) generatePolicyResultsHTML(result *SecurityScanResult) string {
var html strings.Builder
for _, policyResult := range result.PolicyResults {
statusColor := "green"
if policyResult.Status == "failed" {
statusColor = "red"
}
html.WriteString(fmt.Sprintf(`<div style="border-left: 5px solid %s; padding: 10px; margin: 10px 0;">
<h3>%s</h3>
<p><strong>Status:</strong> %s</p>
<p><strong>Message:</strong> %s</p>
</div>`, statusColor, policyResult.RuleID, policyResult.Status, policyResult.Message))
}
return html.String()
}
func (ss *SecurityScanner) generateSARIFReport(result *SecurityScanResult, reporter SecurityReporter) error {
// Generate SARIF format report for CI/CD integration
sarifReport := ss.generateSARIFTemplate(result)
outputPath := filepath.Join(ss.config.OutputDir, reporter.Output)
if err := os.WriteFile(outputPath, []byte(sarifReport), 0644); err != nil {
return fmt.Errorf("failed to write SARIF report: %w", err)
}
return nil
}
func (ss *SecurityScanner) generateSARIFTemplate(result *SecurityScanResult) string {
// SARIF format for CI/CD tools
return fmt.Sprintf(`{
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
"version": "2.1.0",
"runs": [
{
"tool": {
"driver": {
"name": "Debian Forge Security Scanner",
"version": "1.0.0"
}
},
"results": [
%s
]
}
]
}`, ss.generateSARIFResults(result))
}
func (ss *SecurityScanner) generateSARIFResults(result *SecurityScanResult) string {
var results []string
for _, vuln := range result.Vulnerabilities {
level := "warning"
switch vuln.Severity {
case "CRITICAL":
level = "error"
case "HIGH":
level = "error"
case "MEDIUM":
level = "warning"
case "LOW":
level = "note"
}
result := fmt.Sprintf(`{
"ruleId": "%s",
"level": "%s",
"message": {
"text": "%s"
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "%s"
}
}
}
]
}`, vuln.CVE, level, vuln.Description, result.Target)
results = append(results, result)
}
return strings.Join(results, ",")
}
// Helper functions
func generateScanID() string {
return fmt.Sprintf("scan-%d", time.Now().UnixNano())
}