Add ChainVerifier for cryptographic audit log verification: - VerifyLogFile(): Validates entire audit chain integrity - Detects tampering at specific event index (FirstTampered) - Returns chain root hash for external verification - GetChainRootHash(): Standalone hash computation - VerifyAndAlert(): Boolean tampering detection with logging Add audit-verifier CLI tool: - Standalone binary for audit chain verification - Takes log path argument and reports tampering Update audit logger for chain integrity: - Each event includes sequence number and hash chain - SHA-256 linking: hash_n = SHA-256(prev_hash || event_n) - Tamper detection through hash chain validation Add comprehensive test coverage: - Empty log handling - Valid chain verification - Tampering detection with modification - Root hash consistency - Alert mechanism tests Part of: V.7 audit verification from security plan
95 lines
2.5 KiB
Go
95 lines
2.5 KiB
Go
// Package main implements the audit-verifier standalone verification tool
|
|
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"log/slog"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/jfraeys/fetch_ml/internal/audit"
|
|
"github.com/jfraeys/fetch_ml/internal/logging"
|
|
)
|
|
|
|
func main() {
|
|
var (
|
|
logPath string
|
|
interval time.Duration
|
|
continuous bool
|
|
verbose bool
|
|
)
|
|
|
|
flag.StringVar(&logPath, "log-path", "", "Path to audit log file to verify (required)")
|
|
flag.DurationVar(&interval, "interval", 15*time.Minute, "Verification interval for continuous mode")
|
|
flag.BoolVar(&continuous, "continuous", false, "Run continuous verification in a loop")
|
|
flag.BoolVar(&verbose, "verbose", false, "Enable verbose output")
|
|
flag.Parse()
|
|
|
|
if logPath == "" {
|
|
fmt.Fprintln(os.Stderr, "Error: -log-path is required")
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Setup logging
|
|
logLevel := slog.LevelInfo
|
|
if verbose {
|
|
logLevel = slog.LevelDebug
|
|
}
|
|
logger := logging.NewLogger(logLevel, false)
|
|
|
|
verifier := audit.NewChainVerifier(logger)
|
|
|
|
if continuous {
|
|
fmt.Printf("Starting continuous audit verification every %v...\n", interval)
|
|
fmt.Printf("Press Ctrl+C to stop\n\n")
|
|
|
|
// Run with alert function that prints to stdout
|
|
verifier.ContinuousVerification(logPath, interval, func(result *audit.VerificationResult) {
|
|
printResult(result)
|
|
if !result.Valid {
|
|
// In continuous mode, we don't exit on tampering - we keep monitoring
|
|
// The alert function should notify appropriate channels (email, slack, etc.)
|
|
fmt.Println("\n*** TAMPERING DETECTED - INVESTIGATE IMMEDIATELY ***")
|
|
}
|
|
})
|
|
} else {
|
|
// Single verification run
|
|
fmt.Printf("Verifying audit log: %s\n", logPath)
|
|
|
|
result, err := verifier.VerifyLogFile(logPath)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Verification failed: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
printResult(result)
|
|
|
|
if !result.Valid {
|
|
fmt.Println("\n*** VERIFICATION FAILED - AUDIT CHAIN TAMPERING DETECTED ***")
|
|
os.Exit(2)
|
|
}
|
|
|
|
fmt.Println("\n✓ Audit chain integrity verified")
|
|
}
|
|
}
|
|
|
|
func printResult(result *audit.VerificationResult) {
|
|
fmt.Printf("\nVerification Time: %s\n", result.Timestamp.Format(time.RFC3339))
|
|
fmt.Printf("Total Events: %d\n", result.TotalEvents)
|
|
fmt.Printf("Valid: %v\n", result.Valid)
|
|
|
|
if result.ChainRootHash != "" {
|
|
fmt.Printf("Chain Root Hash: %s...\n", result.ChainRootHash[:16])
|
|
}
|
|
|
|
if !result.Valid {
|
|
if result.FirstTampered != -1 {
|
|
fmt.Printf("First Tampered Event: %d\n", result.FirstTampered)
|
|
}
|
|
if result.Error != "" {
|
|
fmt.Printf("Error: %s\n", result.Error)
|
|
}
|
|
}
|
|
}
|