Implement comprehensive audit and security infrastructure: - Immutable audit logs with platform-specific backends (Linux/Other) - Sealed log entries with tamper-evident checksums - Audit alert system for real-time security notifications - Log rotation with retention policies - Checkpoint-based audit verification Add multi-tenant security features: - Tenant manager with quota enforcement - Middleware for tenant authentication/authorization - Per-tenant cryptographic key isolation - Supply chain security for container verification - Cross-platform secure file utilities (Unix/Windows) Add test coverage: - Unit tests for audit alerts and sealed logs - Platform-specific audit backend tests
171 lines
4 KiB
Go
171 lines
4 KiB
Go
package audit_test
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/jfraeys/fetch_ml/internal/audit"
|
|
)
|
|
|
|
// mockLogger implements the logger interface for testing
|
|
type mockLogger struct {
|
|
errors []string
|
|
warns []string
|
|
}
|
|
|
|
func (m *mockLogger) Error(msg string, keysAndValues ...any) {
|
|
m.errors = append(m.errors, msg)
|
|
}
|
|
|
|
func (m *mockLogger) Warn(msg string, keysAndValues ...any) {
|
|
m.warns = append(m.warns, msg)
|
|
}
|
|
|
|
func TestLoggingAlerter_CriticalAlert(t *testing.T) {
|
|
mock := &mockLogger{}
|
|
alerter := audit.NewLoggingAlerter(mock)
|
|
|
|
alert := audit.TamperAlert{
|
|
DetectedAt: time.Now(),
|
|
Severity: "critical",
|
|
Description: "TAMPERING DETECTED in audit log",
|
|
FilePath: "/var/log/audit/audit.log",
|
|
ExpectedHash: "abc123",
|
|
ActualHash: "def456",
|
|
}
|
|
|
|
err := alerter.Alert(context.Background(), alert)
|
|
if err != nil {
|
|
t.Fatalf("Alert failed: %v", err)
|
|
}
|
|
|
|
if len(mock.errors) != 1 {
|
|
t.Errorf("Expected 1 error log, got %d", len(mock.errors))
|
|
}
|
|
if !strings.Contains(mock.errors[0], "TAMPERING DETECTED") {
|
|
t.Errorf("Expected error to contain 'TAMPERING DETECTED', got %s", mock.errors[0])
|
|
}
|
|
if len(mock.warns) != 0 {
|
|
t.Errorf("Expected 0 warn logs, got %d", len(mock.warns))
|
|
}
|
|
}
|
|
|
|
func TestLoggingAlerter_WarningAlert(t *testing.T) {
|
|
mock := &mockLogger{}
|
|
alerter := audit.NewLoggingAlerter(mock)
|
|
|
|
alert := audit.TamperAlert{
|
|
DetectedAt: time.Now(),
|
|
Severity: "warning",
|
|
Description: "Potential tampering detected",
|
|
FilePath: "/var/log/audit/audit.log",
|
|
}
|
|
|
|
err := alerter.Alert(context.Background(), alert)
|
|
if err != nil {
|
|
t.Fatalf("Alert failed: %v", err)
|
|
}
|
|
|
|
if len(mock.warns) != 1 {
|
|
t.Errorf("Expected 1 warn log, got %d", len(mock.warns))
|
|
}
|
|
if !strings.Contains(mock.warns[0], "Potential tampering") {
|
|
t.Errorf("Expected warn to contain 'Potential tampering', got %s", mock.warns[0])
|
|
}
|
|
if len(mock.errors) != 0 {
|
|
t.Errorf("Expected 0 error logs, got %d", len(mock.errors))
|
|
}
|
|
}
|
|
|
|
func TestLoggingAlerter_NilLogger(t *testing.T) {
|
|
alerter := audit.NewLoggingAlerter(nil)
|
|
|
|
alert := audit.TamperAlert{
|
|
DetectedAt: time.Now(),
|
|
Severity: "critical",
|
|
Description: "Test",
|
|
}
|
|
|
|
// Should not panic or error
|
|
err := alerter.Alert(context.Background(), alert)
|
|
if err != nil {
|
|
t.Fatalf("Alert with nil logger failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestMultiAlerter(t *testing.T) {
|
|
mock1 := &mockLogger{}
|
|
mock2 := &mockLogger{}
|
|
alerter1 := audit.NewLoggingAlerter(mock1)
|
|
alerter2 := audit.NewLoggingAlerter(mock2)
|
|
|
|
multi := audit.NewMultiAlerter(alerter1, alerter2)
|
|
|
|
alert := audit.TamperAlert{
|
|
DetectedAt: time.Now(),
|
|
Severity: "critical",
|
|
Description: "Test multi",
|
|
}
|
|
|
|
err := multi.Alert(context.Background(), alert)
|
|
if err != nil {
|
|
t.Fatalf("Multi alert failed: %v", err)
|
|
}
|
|
|
|
// Both alerters should have logged
|
|
if len(mock1.errors) != 1 {
|
|
t.Errorf("Expected alerter1 to have 1 error, got %d", len(mock1.errors))
|
|
}
|
|
if len(mock2.errors) != 1 {
|
|
t.Errorf("Expected alerter2 to have 1 error, got %d", len(mock2.errors))
|
|
}
|
|
}
|
|
|
|
func TestMultiAlerter_Empty(t *testing.T) {
|
|
multi := audit.NewMultiAlerter()
|
|
|
|
alert := audit.TamperAlert{
|
|
DetectedAt: time.Now(),
|
|
Severity: "critical",
|
|
Description: "Test empty",
|
|
}
|
|
|
|
// Should not error with no alerters
|
|
err := multi.Alert(context.Background(), alert)
|
|
if err != nil {
|
|
t.Fatalf("Multi alert with no alerters failed: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestTamperAlert_Struct(t *testing.T) {
|
|
now := time.Now()
|
|
alert := audit.TamperAlert{
|
|
DetectedAt: now,
|
|
Severity: "critical",
|
|
Description: "Test description",
|
|
ExpectedHash: "expected",
|
|
ActualHash: "actual",
|
|
FilePath: "/path/to/file",
|
|
}
|
|
|
|
if alert.DetectedAt != now {
|
|
t.Error("DetectedAt mismatch")
|
|
}
|
|
if alert.Severity != "critical" {
|
|
t.Error("Severity mismatch")
|
|
}
|
|
if alert.Description != "Test description" {
|
|
t.Error("Description mismatch")
|
|
}
|
|
if alert.ExpectedHash != "expected" {
|
|
t.Error("ExpectedHash mismatch")
|
|
}
|
|
if alert.ActualHash != "actual" {
|
|
t.Error("ActualHash mismatch")
|
|
}
|
|
if alert.FilePath != "/path/to/file" {
|
|
t.Error("FilePath mismatch")
|
|
}
|
|
}
|