debian-forge-composer/internal/security/compliance_checker.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

1167 lines
33 KiB
Go

package security
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/sirupsen/logrus"
)
type ComplianceChecker struct {
logger *logrus.Logger
config *ComplianceConfig
standards map[string]ComplianceStandard
checkers map[string]ComplianceChecker
reporters map[string]ComplianceReporter
}
type ComplianceConfig struct {
StandardsPath string `json:"standards_path"`
PolicyPath string `json:"policy_path"`
OutputDir string `json:"output_dir"`
SeverityLevels []string `json:"severity_levels"`
AutoFix bool `json:"auto_fix"`
Metadata map[string]string `json:"metadata"`
}
type ComplianceStandard struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Version string `json:"version"`
Category string `json:"category"`
Rules []ComplianceRule `json:"rules"`
Enabled bool `json:"enabled"`
Metadata map[string]interface{} `json:"metadata"`
}
type ComplianceRule struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Category string `json:"category"`
Severity string `json:"severity"`
Check string `json:"check"`
Fix string `json:"fix"`
References []string `json:"references"`
Enabled bool `json:"enabled"`
Metadata map[string]interface{} `json:"metadata"`
}
type ComplianceChecker struct {
Name string `json:"name"`
Description string `json:"description"`
Type string `json:"type"`
Command string `json:"command"`
Args []string `json:"args"`
Enabled bool `json:"enabled"`
Config map[string]interface{} `json:"config"`
}
type ComplianceReporter struct {
Name string `json:"name"`
Description string `json:"description"`
Format string `json:"format"`
Output string `json:"output"`
Enabled bool `json:"enabled"`
}
type ComplianceResult struct {
ID string `json:"id"`
Target string `json:"target"`
TargetType string `json:"target_type"`
CheckTime time.Time `json:"check_time"`
Duration time.Duration `json:"duration"`
Standards []StandardResult `json:"standards"`
Summary ComplianceSummary `json:"summary"`
Metadata map[string]interface{} `json:"metadata"`
}
type StandardResult struct {
StandardID string `json:"standard_id"`
StandardName string `json:"standard_name"`
Rules []RuleResult `json:"rules"`
Compliant bool `json:"compliant"`
Score float64 `json:"score"`
Metadata map[string]interface{} `json:"metadata"`
}
type RuleResult struct {
RuleID string `json:"rule_id"`
RuleName string `json:"rule_name"`
Status string `json:"status"`
Severity string `json:"severity"`
Message string `json:"message"`
Details map[string]interface{} `json:"details"`
FixApplied bool `json:"fix_applied"`
Metadata map[string]interface{} `json:"metadata"`
}
type ComplianceSummary struct {
TotalStandards int `json:"total_standards"`
CompliantStandards int `json:"compliant_standards"`
TotalRules int `json:"total_rules"`
PassedRules int `json:"passed_rules"`
FailedRules int `json:"failed_rules"`
WarningRules int `json:"warning_rules"`
OverallScore float64 `json:"overall_score"`
Recommendations []string `json:"recommendations"`
Metadata map[string]interface{} `json:"metadata"`
}
func NewComplianceChecker(config *ComplianceConfig, logger *logrus.Logger) *ComplianceChecker {
checker := &ComplianceChecker{
logger: logger,
config: config,
standards: make(map[string]ComplianceStandard),
checkers: make(map[string]ComplianceChecker),
reporters: make(map[string]ComplianceReporter),
}
// Initialize compliance standards
checker.initializeStandards()
// Initialize compliance checkers
checker.initializeCheckers()
// Initialize reporters
checker.initializeReporters()
return checker
}
func (cc *ComplianceChecker) initializeStandards() {
// Debian Security Team Compliance
cc.standards["debian_security"] = ComplianceStandard{
ID: "debian_security",
Name: "Debian Security Team Compliance",
Description: "Compliance with Debian Security Team requirements",
Version: "1.0.0",
Category: "security",
Enabled: true,
Rules: []ComplianceRule{
{
ID: "DS001",
Name: "No Critical CVEs",
Description: "No critical severity CVEs allowed in packages",
Category: "vulnerability",
Severity: "critical",
Check: "cve_scan",
Fix: "update_package",
Enabled: true,
},
{
ID: "DS002",
Name: "Package Signing",
Description: "All packages must be properly signed",
Category: "signature",
Severity: "high",
Check: "package_signature",
Fix: "sign_package",
Enabled: true,
},
{
ID: "DS003",
Name: "Security Updates",
Description: "Security updates must be applied within 24 hours",
Category: "updates",
Severity: "high",
Check: "security_updates",
Fix: "apply_updates",
Enabled: true,
},
},
}
// Debian Policy Compliance
cc.standards["debian_policy"] = ComplianceStandard{
ID: "debian_policy",
Name: "Debian Policy Compliance",
Description: "Compliance with Debian Policy Manual",
Version: "1.0.0",
Category: "policy",
Enabled: true,
Rules: []ComplianceRule{
{
ID: "DP001",
Name: "Filesystem Hierarchy",
Description: "Files must be in correct locations according to FHS",
Category: "filesystem",
Severity: "medium",
Check: "filesystem_hierarchy",
Fix: "move_files",
Enabled: true,
},
{
ID: "DP002",
Name: "Package Dependencies",
Description: "Package dependencies must be correct and minimal",
Category: "dependency",
Severity: "high",
Check: "package_dependencies",
Fix: "fix_dependencies",
Enabled: true,
},
{
ID: "DP003",
Name: "Package Metadata",
Description: "Package metadata must be complete and accurate",
Category: "metadata",
Severity: "medium",
Check: "package_metadata",
Fix: "update_metadata",
Enabled: true,
},
},
}
// Security Hardening Guidelines
cc.standards["security_hardening"] = ComplianceStandard{
ID: "security_hardening",
Name: "Security Hardening Guidelines",
Description: "Implementation of security hardening best practices",
Version: "1.0.0",
Category: "hardening",
Enabled: true,
Rules: []ComplianceRule{
{
ID: "SH001",
Name: "No Root Login",
Description: "Root login should be disabled",
Category: "authentication",
Severity: "medium",
Check: "root_login_check",
Fix: "disable_root_login",
Enabled: true,
},
{
ID: "SH002",
Name: "SSH Hardening",
Description: "SSH should be properly configured and hardened",
Category: "ssh",
Severity: "medium",
Check: "ssh_configuration",
Fix: "harden_ssh",
Enabled: true,
},
{
ID: "SH003",
Name: "Firewall Configuration",
Description: "Firewall should be properly configured",
Category: "firewall",
Severity: "medium",
Check: "firewall_configuration",
Fix: "configure_firewall",
Enabled: true,
},
{
ID: "SH004",
Name: "Unnecessary Services",
Description: "Unnecessary services should be disabled",
Category: "services",
Severity: "low",
Check: "unnecessary_services",
Fix: "disable_services",
Enabled: true,
},
},
}
}
func (cc *ComplianceChecker) initializeCheckers() {
// Package checker
cc.checkers["package"] = ComplianceChecker{
Name: "package",
Description: "Package compliance checker",
Type: "package",
Command: "dpkg",
Args: []string{"--audit"},
Enabled: true,
Config: map[string]interface{}{
"timeout": 60,
},
}
// Filesystem checker
cc.checkers["filesystem"] = ComplianceChecker{
Name: "filesystem",
Description: "Filesystem compliance checker",
Type: "filesystem",
Command: "find",
Args: []string{"/", "-type", "f"},
Enabled: true,
Config: map[string]interface{}{
"timeout": 120,
},
}
// Security checker
cc.checkers["security"] = ComplianceChecker{
Name: "security",
Description: "Security configuration checker",
Type: "security",
Command: "sshd",
Args: []string{"-T"},
Enabled: true,
Config: map[string]interface{}{
"timeout": 30,
},
}
// Policy checker
cc.checkers["policy"] = ComplianceChecker{
Name: "policy",
Description: "Debian policy compliance checker",
Type: "policy",
Command: "lintian",
Args: []string{"--format", "json"},
Enabled: true,
Config: map[string]interface{}{
"timeout": 180,
},
}
}
func (cc *ComplianceChecker) initializeReporters() {
// JSON reporter
cc.reporters["json"] = ComplianceReporter{
Name: "json",
Description: "JSON format compliance report",
Format: "json",
Output: "compliance-report.json",
Enabled: true,
}
// HTML reporter
cc.reporters["html"] = ComplianceReporter{
Name: "html",
Description: "HTML format compliance report",
Format: "html",
Output: "compliance-report.html",
Enabled: true,
}
// SARIF reporter for CI/CD integration
cc.reporters["sarif"] = ComplianceReporter{
Name: "sarif",
Description: "SARIF format for CI/CD tools",
Format: "sarif",
Output: "compliance-report.sarif",
Enabled: true,
}
}
func (cc *ComplianceChecker) CheckCompliance(target string, targetType string) (*ComplianceResult, error) {
cc.logger.Infof("Starting compliance check for target: %s (type: %s)", target, targetType)
startTime := time.Now()
// Create compliance result
result := &ComplianceResult{
ID: generateComplianceID(),
Target: target,
TargetType: targetType,
CheckTime: startTime,
Standards: []StandardResult{},
Metadata: make(map[string]interface{}),
}
// Run compliance checks for each standard
for _, standard := range cc.standards {
if !standard.Enabled {
continue
}
cc.logger.Infof("Checking compliance standard: %s", standard.Name)
standardResult := cc.checkStandard(standard, target, targetType)
result.Standards = append(result.Standards, standardResult)
}
// Calculate duration
result.Duration = time.Since(startTime)
// Generate summary
cc.generateSummary(result)
// Generate reports
if err := cc.generateReports(result); err != nil {
cc.logger.Warnf("Report generation failed: %v", err)
}
cc.logger.Infof("Compliance check completed in %v", result.Duration)
return result, nil
}
func (cc *ComplianceChecker) checkStandard(standard ComplianceStandard, target string, targetType string) StandardResult {
standardResult := StandardResult{
StandardID: standard.ID,
StandardName: standard.Name,
Rules: []RuleResult{},
Compliant: true,
Score: 100.0,
Metadata: make(map[string]interface{}),
}
// Check each rule in the standard
for _, rule := range standard.Rules {
if !rule.Enabled {
continue
}
cc.logger.Infof("Checking rule: %s", rule.Name)
ruleResult := cc.checkRule(rule, target, targetType)
standardResult.Rules = append(standardResult.Rules, ruleResult)
// Update compliance status
if ruleResult.Status == "failed" {
standardResult.Compliant = false
}
}
// Calculate standard score
standardResult.Score = cc.calculateStandardScore(standardResult.Rules)
return standardResult
}
func (cc *ComplianceChecker) checkRule(rule ComplianceRule, target string, targetType string) RuleResult {
ruleResult := RuleResult{
RuleID: rule.ID,
RuleName: rule.Name,
Status: "passed",
Severity: rule.Severity,
Message: "Rule passed",
Details: make(map[string]interface{}),
Metadata: make(map[string]interface{}),
}
// Execute rule check based on type
switch rule.Check {
case "cve_scan":
ruleResult = cc.checkCVEScan(rule, target, targetType)
case "package_signature":
ruleResult = cc.checkPackageSignature(rule, target, targetType)
case "filesystem_hierarchy":
ruleResult = cc.checkFilesystemHierarchy(rule, target, targetType)
case "package_dependencies":
ruleResult = cc.checkPackageDependencies(rule, target, targetType)
case "root_login_check":
ruleResult = cc.checkRootLogin(rule, target, targetType)
case "ssh_configuration":
ruleResult = cc.checkSSHConfiguration(rule, target, targetType)
case "firewall_configuration":
ruleResult = cc.checkFirewallConfiguration(rule, target, targetType)
default:
ruleResult.Status = "skipped"
ruleResult.Message = "Check type not implemented"
}
// Apply fix if enabled and rule failed
if cc.config.AutoFix && ruleResult.Status == "failed" && rule.Fix != "" {
if err := cc.applyFix(rule, target, targetType); err != nil {
cc.logger.Warnf("Failed to apply fix for rule %s: %v", rule.ID, err)
} else {
ruleResult.FixApplied = true
ruleResult.Status = "fixed"
ruleResult.Message = "Rule fixed automatically"
}
}
return ruleResult
}
func (cc *ComplianceChecker) checkCVEScan(rule ComplianceRule, target string, targetType string) RuleResult {
ruleResult := RuleResult{
RuleID: rule.ID,
RuleName: rule.Name,
Status: "passed",
Severity: rule.Severity,
Message: "No critical CVEs found",
Details: make(map[string]interface{}),
}
// This would integrate with the security scanner
// For now, return a placeholder result
ruleResult.Details["cve_count"] = 0
ruleResult.Details["critical_count"] = 0
return ruleResult
}
func (cc *ComplianceChecker) checkPackageSignature(rule ComplianceRule, target string, targetType string) RuleResult {
ruleResult := RuleResult{
RuleID: rule.ID,
RuleName: rule.Name,
Status: "passed",
Severity: rule.Severity,
Message: "Package signature verified",
Details: make(map[string]interface{}),
}
// Check package signature using dpkg-sig
cmd := exec.Command("dpkg-sig", "--verify", target)
output, err := cmd.Output()
if err != nil {
ruleResult.Status = "failed"
ruleResult.Message = "Package signature verification failed"
ruleResult.Details["error"] = err.Error()
return ruleResult
}
// Parse output for signature status
outputStr := string(output)
if strings.Contains(outputStr, "GOODSIG") {
ruleResult.Details["signature_status"] = "good"
} else if strings.Contains(outputStr, "BADSIG") {
ruleResult.Status = "failed"
ruleResult.Message = "Bad package signature detected"
ruleResult.Details["signature_status"] = "bad"
} else {
ruleResult.Status = "failed"
ruleResult.Message = "No valid signature found"
ruleResult.Details["signature_status"] = "none"
}
return ruleResult
}
func (cc *ComplianceChecker) checkFilesystemHierarchy(rule ComplianceRule, target string, targetType string) RuleResult {
ruleResult := RuleResult{
RuleID: rule.ID,
RuleName: rule.Name,
Status: "passed",
Severity: rule.Severity,
Message: "Filesystem hierarchy compliant",
Details: make(map[string]interface{}),
}
// Check if target is a package directory
if targetType == "package" {
// Check for common FHS violations
violations := []string{}
// Check for files in wrong locations
wrongLocations := []string{
"/usr/local/bin/",
"/usr/local/sbin/",
"/usr/local/lib/",
}
for _, location := range wrongLocations {
if _, err := os.Stat(filepath.Join(target, location)); err == nil {
violations = append(violations, fmt.Sprintf("Files found in %s", location))
}
}
if len(violations) > 0 {
ruleResult.Status = "failed"
ruleResult.Message = "Filesystem hierarchy violations found"
ruleResult.Details["violations"] = violations
}
}
return ruleResult
}
func (cc *ComplianceChecker) checkPackageDependencies(rule ComplianceRule, target string, targetType string) RuleResult {
ruleResult := RuleResult{
RuleID: rule.ID,
RuleName: rule.Name,
Status: "passed",
Severity: rule.Severity,
Message: "Package dependencies are correct",
Details: make(map[string]interface{}),
}
// Check package dependencies using apt-cache
if targetType == "package" {
cmd := exec.Command("apt-cache", "depends", target)
output, err := cmd.Output()
if err != nil {
ruleResult.Status = "failed"
ruleResult.Message = "Failed to check package dependencies"
ruleResult.Details["error"] = err.Error()
return ruleResult
}
// Parse dependencies
dependencies := []string{}
lines := strings.Split(string(output), "\n")
for _, line := range lines {
if strings.Contains(line, "Depends:") {
parts := strings.Split(line, ":")
if len(parts) >= 2 {
deps := strings.TrimSpace(parts[1])
dependencies = append(dependencies, deps)
}
}
}
ruleResult.Details["dependencies"] = dependencies
ruleResult.Details["dependency_count"] = len(dependencies)
}
return ruleResult
}
func (cc *ComplianceChecker) checkRootLogin(rule ComplianceRule, target string, targetType string) RuleResult {
ruleResult := RuleResult{
RuleID: rule.ID,
RuleName: rule.Name,
Status: "passed",
Severity: rule.Severity,
Message: "Root login is properly configured",
Details: make(map[string]interface{}),
}
// Check /etc/passwd for root login configuration
passwdFile := "/etc/passwd"
if targetType == "system" {
passwdFile = filepath.Join(target, "etc/passwd")
}
if _, err := os.Stat(passwdFile); err == nil {
data, err := os.ReadFile(passwdFile)
if err != nil {
ruleResult.Status = "failed"
ruleResult.Message = "Failed to read passwd file"
ruleResult.Details["error"] = err.Error()
return ruleResult
}
// Check if root login is disabled
lines := strings.Split(string(data), "\n")
for _, line := range lines {
if strings.HasPrefix(line, "root:") {
parts := strings.Split(line, ":")
if len(parts) >= 7 {
shell := parts[6]
if shell != "/usr/sbin/nologin" && shell != "/bin/false" {
ruleResult.Status = "failed"
ruleResult.Message = "Root login is enabled"
ruleResult.Details["shell"] = shell
}
}
break
}
}
}
return ruleResult
}
func (cc *ComplianceChecker) checkSSHConfiguration(rule ComplianceRule, target string, targetType string) RuleResult {
ruleResult := RuleResult{
RuleID: rule.ID,
RuleName: rule.Name,
Status: "passed",
Severity: rule.Severity,
Message: "SSH configuration is secure",
Details: make(map[string]interface{}),
}
// Check SSH configuration
sshConfigFile := "/etc/ssh/sshd_config"
if targetType == "system" {
sshConfigFile = filepath.Join(target, "etc/ssh/sshd_config")
}
if _, err := os.Stat(sshConfigFile); err == nil {
data, err := os.ReadFile(sshConfigFile)
if err != nil {
ruleResult.Status = "failed"
ruleResult.Message = "Failed to read SSH config"
ruleResult.Details["error"] = err.Error()
return ruleResult
}
// Check for security settings
config := string(data)
violations := []string{}
// Check for root login
if strings.Contains(config, "PermitRootLogin yes") {
violations = append(violations, "Root login enabled")
}
// Check for password authentication
if strings.Contains(config, "PasswordAuthentication yes") {
violations = append(violations, "Password authentication enabled")
}
// Check for empty passwords
if strings.Contains(config, "PermitEmptyPasswords yes") {
violations = append(violations, "Empty passwords allowed")
}
if len(violations) > 0 {
ruleResult.Status = "failed"
ruleResult.Message = "SSH security violations found"
ruleResult.Details["violations"] = violations
}
}
return ruleResult
}
func (cc *ComplianceChecker) checkFirewallConfiguration(rule ComplianceRule, target string, targetType string) RuleResult {
ruleResult := RuleResult{
RuleID: rule.ID,
RuleName: rule.Name,
Status: "passed",
Severity: rule.Severity,
Message: "Firewall is properly configured",
Details: make(map[string]interface{}),
}
// Check firewall status using ufw
cmd := exec.Command("ufw", "status")
output, err := cmd.Output()
if err != nil {
ruleResult.Status = "failed"
ruleResult.Message = "Failed to check firewall status"
ruleResult.Details["error"] = err.Error()
return ruleResult
}
outputStr := string(output)
if strings.Contains(outputStr, "Status: active") {
ruleResult.Details["status"] = "active"
} else {
ruleResult.Status = "failed"
ruleResult.Message = "Firewall is not active"
ruleResult.Details["status"] = "inactive"
}
return ruleResult
}
func (cc *ComplianceChecker) applyFix(rule ComplianceRule, target string, targetType string) error {
cc.logger.Infof("Applying fix for rule: %s", rule.ID)
switch rule.Fix {
case "update_package":
return cc.fixUpdatePackage(target)
case "sign_package":
return cc.fixSignPackage(target)
case "move_files":
return cc.fixMoveFiles(target)
case "fix_dependencies":
return cc.fixDependencies(target)
case "disable_root_login":
return cc.fixDisableRootLogin(target)
case "harden_ssh":
return cc.fixHardenSSH(target)
case "configure_firewall":
return cc.fixConfigureFirewall(target)
default:
return fmt.Errorf("fix type not implemented: %s", rule.Fix)
}
}
func (cc *ComplianceChecker) fixUpdatePackage(target string) error {
// Update package to fix vulnerabilities
cmd := exec.Command("apt-get", "update")
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to update package lists: %w", err)
}
cmd = exec.Command("apt-get", "upgrade", "-y")
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to upgrade packages: %w", err)
}
return nil
}
func (cc *ComplianceChecker) fixSignPackage(target string) error {
// Sign package using dpkg-sig
cmd := exec.Command("dpkg-sig", "--sign", "origin", target)
return cmd.Run()
}
func (cc *ComplianceChecker) fixMoveFiles(target string) error {
// Move files to correct FHS locations
// This is a placeholder implementation
return nil
}
func (cc *ComplianceChecker) fixDependencies(target string) error {
// Fix package dependencies
cmd := exec.Command("apt-get", "install", "-f", "-y")
return cmd.Run()
}
func (cc *ComplianceChecker) fixDisableRootLogin(target string) error {
// Disable root login by changing shell
passwdFile := "/etc/passwd"
if target != "" {
passwdFile = filepath.Join(target, "etc/passwd")
}
// Read current passwd file
data, err := os.ReadFile(passwdFile)
if err != nil {
return fmt.Errorf("failed to read passwd file: %w", err)
}
// Replace root shell with nologin
lines := strings.Split(string(data), "\n")
for i, line := range lines {
if strings.HasPrefix(line, "root:") {
parts := strings.Split(line, ":")
if len(parts) >= 7 {
parts[6] = "/usr/sbin/nologin"
lines[i] = strings.Join(parts, ":")
}
break
}
}
// Write updated passwd file
newData := strings.Join(lines, "\n")
return os.WriteFile(passwdFile, []byte(newData), 0644)
}
func (cc *ComplianceChecker) fixHardenSSH(target string) error {
// Harden SSH configuration
sshConfigFile := "/etc/ssh/sshd_config"
if target != "" {
sshConfigFile = filepath.Join(target, "etc/ssh/sshd_config")
}
// Read current config
data, err := os.ReadFile(sshConfigFile)
if err != nil {
return fmt.Errorf("failed to read SSH config: %w", err)
}
// Apply security hardening
config := string(data)
// Replace insecure settings
replacements := map[string]string{
"PermitRootLogin yes": "PermitRootLogin no",
"PasswordAuthentication yes": "PasswordAuthentication no",
"PermitEmptyPasswords yes": "PermitEmptyPasswords no",
}
for old, new := range replacements {
config = strings.ReplaceAll(config, old, new)
}
// Write updated config
return os.WriteFile(sshConfigFile, []byte(config), 0644)
}
func (cc *ComplianceChecker) fixConfigureFirewall(target string) error {
// Configure firewall using ufw
cmd := exec.Command("ufw", "--force", "enable")
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to enable firewall: %w", err)
}
// Allow SSH
cmd = exec.Command("ufw", "allow", "ssh")
return cmd.Run()
}
func (cc *ComplianceChecker) calculateStandardScore(rules []RuleResult) float64 {
if len(rules) == 0 {
return 100.0
}
passed := 0
for _, rule := range rules {
if rule.Status == "passed" || rule.Status == "fixed" {
passed++
}
}
return float64(passed) / float64(len(rules)) * 100.0
}
func (cc *ComplianceChecker) generateSummary(result *ComplianceResult) {
summary := &ComplianceSummary{
Metadata: make(map[string]interface{}),
}
// Count standards and rules
summary.TotalStandards = len(result.Standards)
for _, standard := range result.Standards {
if standard.Compliant {
summary.CompliantStandards++
}
summary.TotalRules += len(standard.Rules)
for _, rule := range standard.Rules {
switch rule.Status {
case "passed", "fixed":
summary.PassedRules++
case "failed":
summary.FailedRules++
case "warning":
summary.WarningRules++
}
}
}
// Calculate overall score
if summary.TotalStandards > 0 {
totalScore := 0.0
for _, standard := range result.Standards {
totalScore += standard.Score
}
summary.OverallScore = totalScore / float64(summary.TotalStandards)
}
// Generate recommendations
summary.Recommendations = cc.generateRecommendations(summary)
result.Summary = *summary
}
func (cc *ComplianceChecker) generateRecommendations(summary *ComplianceSummary) []string {
var recommendations []string
if summary.FailedRules > 0 {
recommendations = append(recommendations, "Address failed compliance rules")
}
if summary.WarningRules > 0 {
recommendations = append(recommendations, "Review warning compliance rules")
}
if summary.OverallScore < 80.0 {
recommendations = append(recommendations, "Overall compliance score is low - review standards")
}
if summary.CompliantStandards < summary.TotalStandards {
recommendations = append(recommendations, "Some standards are not compliant")
}
return recommendations
}
func (cc *ComplianceChecker) generateReports(result *ComplianceResult) error {
for name, reporter := range cc.reporters {
if !reporter.Enabled {
continue
}
cc.logger.Infof("Generating %s report", name)
switch reporter.Format {
case "json":
if err := cc.generateJSONReport(result, reporter); err != nil {
cc.logger.Warnf("JSON report generation failed: %v", err)
}
case "html":
if err := cc.generateHTMLReport(result, reporter); err != nil {
cc.logger.Warnf("HTML report generation failed: %v", err)
}
case "sarif":
if err := cc.generateSARIFReport(result, reporter); err != nil {
cc.logger.Warnf("SARIF report generation failed: %v", err)
}
}
}
return nil
}
func (cc *ComplianceChecker) generateJSONReport(result *ComplianceResult, reporter ComplianceReporter) error {
outputPath := filepath.Join(cc.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 (cc *ComplianceChecker) generateHTMLReport(result *ComplianceResult, reporter ComplianceReporter) error {
// Generate HTML report template
htmlContent := cc.generateHTMLTemplate(result)
outputPath := filepath.Join(cc.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 (cc *ComplianceChecker) generateHTMLTemplate(result *ComplianceResult) string {
// Simple HTML template for compliance report
return fmt.Sprintf(`<!DOCTYPE html>
<html>
<head>
<title>Compliance 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; }
.standard { background: #fff3cd; padding: 15px; margin: 20px 0; border-radius: 5px; }
.rule { background: #f8f9fa; padding: 10px; margin: 10px 0; border-radius: 3px; }
.passed { border-left: 5px solid #28a745; }
.failed { border-left: 5px solid #dc3545; }
.warning { border-left: 5px solid #ffc107; }
</style>
</head>
<body>
<div class="header">
<h1>Compliance Report</h1>
<p><strong>Target:</strong> %s</p>
<p><strong>Check Time:</strong> %s</p>
<p><strong>Duration:</strong> %v</p>
</div>
<div class="summary">
<h2>Summary</h2>
<p><strong>Total Standards:</strong> %d</p>
<p><strong>Compliant Standards:</strong> %d</p>
<p><strong>Total Rules:</strong> %d</p>
<p><strong>Passed Rules:</strong> %d</p>
<p><strong>Failed Rules:</strong> %d</p>
<p><strong>Overall Score:</strong> %.1f%%</p>
</div>
<h2>Standards</h2>
%s
</body>
</html>`,
result.Target, result.Target, result.CheckTime.Format("2006-01-02 15:04:05"),
result.Duration, result.Summary.TotalStandards, result.Summary.CompliantStandards,
result.Summary.TotalRules, result.Summary.PassedRules, result.Summary.FailedRules,
result.Summary.OverallScore, cc.generateStandardsHTML(result))
}
func (cc *ComplianceChecker) generateStandardsHTML(result *ComplianceResult) string {
var html strings.Builder
for _, standard := range result.Standards {
statusClass := "passed"
if !standard.Compliant {
statusClass = "failed"
}
html.WriteString(fmt.Sprintf(`<div class="standard %s">
<h3>%s</h3>
<p><strong>Score:</strong> %.1f%%</p>
<p><strong>Compliant:</strong> %t</p>
<h4>Rules:</h4>
%s
</div>`, statusClass, standard.StandardName, standard.Score, standard.Compliant,
cc.generateRulesHTML(standard.Rules)))
}
return html.String()
}
func (cc *ComplianceChecker) generateRulesHTML(rules []RuleResult) string {
var html strings.Builder
for _, rule := range rules {
statusClass := strings.ToLower(rule.Status)
html.WriteString(fmt.Sprintf(`<div class="rule %s">
<h5>%s</h5>
<p><strong>Status:</strong> %s</p>
<p><strong>Message:</strong> %s</p>
</div>`, statusClass, rule.RuleName, rule.Status, rule.Message))
}
return html.String()
}
func (cc *ComplianceChecker) generateSARIFReport(result *ComplianceResult, reporter ComplianceReporter) error {
// Generate SARIF format report for CI/CD integration
sarifReport := cc.generateSARIFTemplate(result)
outputPath := filepath.Join(cc.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 (cc *ComplianceChecker) generateSARIFTemplate(result *ComplianceResult) 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 Compliance Checker",
"version": "1.0.0"
}
},
"results": [
%s
]
}
]
}`, cc.generateSARIFResults(result))
}
func (cc *ComplianceChecker) generateSARIFResults(result *ComplianceResult) string {
var results []string
for _, standard := range result.Standards {
for _, rule := range standard.Rules {
if rule.Status == "failed" {
level := "error"
if rule.Severity == "low" {
level = "warning"
}
result := fmt.Sprintf(`{
"ruleId": "%s",
"level": "%s",
"message": {
"text": "%s"
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "%s"
}
}
}
]
}`, rule.RuleID, level, rule.Message, result.Target)
results = append(results, result)
}
}
}
return strings.Join(results, ",")
}
// Helper functions
func generateComplianceID() string {
return fmt.Sprintf("comp-%d", time.Now().UnixNano())
}