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
1167 lines
33 KiB
Go
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())
|
|
}
|