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(`
Target: %s
Scan Time: %s
Duration: %v
Total Vulnerabilities: %d
Critical: %d
High: %d
Medium: %d
Low: %d
Risk Score: %.1f/100
Severity: %s
Package: %s
Description: %s
Status: %s
Message: %s