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
1003 lines
28 KiB
Go
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())
|
|
}
|