chore: clean .gitignore cache
This commit is contained in:
@@ -1,169 +0,0 @@
|
||||
package security
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TestAuditEvent_JSONSerialization tests that audit events serialize correctly to JSON.
|
||||
func TestAuditEvent_JSONSerialization(t *testing.T) {
|
||||
t.Parallel()
|
||||
event := AuditEvent{
|
||||
Timestamp: "2025-12-31T12:00:00Z",
|
||||
Action: "url_validation",
|
||||
Host: "example.com",
|
||||
RequestID: "test-123",
|
||||
Result: "blocked",
|
||||
ResolvedIPs: []string{"192.168.1.1", "10.0.0.1"},
|
||||
BlockedReason: "private_ip",
|
||||
UserID: "user123",
|
||||
SourceIP: "203.0.113.1",
|
||||
}
|
||||
|
||||
// Serialize to JSON
|
||||
jsonBytes, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal AuditEvent: %v", err)
|
||||
}
|
||||
|
||||
// Verify all fields are present
|
||||
jsonStr := string(jsonBytes)
|
||||
expectedFields := []string{
|
||||
"timestamp", "action", "host", "request_id", "result",
|
||||
"resolved_ips", "blocked_reason", "user_id", "source_ip",
|
||||
}
|
||||
|
||||
for _, field := range expectedFields {
|
||||
if !strings.Contains(jsonStr, field) {
|
||||
t.Errorf("JSON output missing field: %s", field)
|
||||
}
|
||||
}
|
||||
|
||||
// Deserialize and verify
|
||||
var decoded AuditEvent
|
||||
err = json.Unmarshal(jsonBytes, &decoded)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to unmarshal AuditEvent: %v", err)
|
||||
}
|
||||
|
||||
if decoded.Timestamp != event.Timestamp {
|
||||
t.Errorf("Timestamp mismatch: got %s, want %s", decoded.Timestamp, event.Timestamp)
|
||||
}
|
||||
if decoded.UserID != event.UserID {
|
||||
t.Errorf("UserID mismatch: got %s, want %s", decoded.UserID, event.UserID)
|
||||
}
|
||||
if len(decoded.ResolvedIPs) != len(event.ResolvedIPs) {
|
||||
t.Errorf("ResolvedIPs length mismatch: got %d, want %d", len(decoded.ResolvedIPs), len(event.ResolvedIPs))
|
||||
}
|
||||
}
|
||||
|
||||
// TestAuditLogger_LogURLValidation tests audit logging of URL validation events.
|
||||
func TestAuditLogger_LogURLValidation(t *testing.T) {
|
||||
t.Parallel()
|
||||
logger := NewAuditLogger()
|
||||
|
||||
event := AuditEvent{
|
||||
Action: "url_test",
|
||||
Host: "malicious.com",
|
||||
RequestID: "req-456",
|
||||
Result: "blocked",
|
||||
ResolvedIPs: []string{"169.254.169.254"},
|
||||
BlockedReason: "metadata_endpoint",
|
||||
UserID: "attacker",
|
||||
SourceIP: "198.51.100.1",
|
||||
}
|
||||
|
||||
// This will log to standard logger, which we can't easily capture in tests
|
||||
// But we can verify it doesn't panic
|
||||
logger.LogURLValidation(event)
|
||||
|
||||
// Verify timestamp was auto-added if missing
|
||||
event2 := AuditEvent{
|
||||
Action: "test",
|
||||
Host: "test.com",
|
||||
}
|
||||
logger.LogURLValidation(event2)
|
||||
}
|
||||
|
||||
// TestAuditLogger_LogURLTest tests the convenience method for URL tests.
|
||||
func TestAuditLogger_LogURLTest(t *testing.T) {
|
||||
t.Parallel()
|
||||
logger := NewAuditLogger()
|
||||
|
||||
// Should not panic
|
||||
logger.LogURLTest("example.com", "req-789", "user456", "192.0.2.1", "allowed")
|
||||
}
|
||||
|
||||
// TestAuditLogger_LogSSRFBlock tests the convenience method for SSRF blocks.
|
||||
func TestAuditLogger_LogSSRFBlock(t *testing.T) {
|
||||
t.Parallel()
|
||||
logger := NewAuditLogger()
|
||||
|
||||
resolvedIPs := []string{"10.0.0.1", "192.168.1.1"}
|
||||
|
||||
// Should not panic
|
||||
logger.LogSSRFBlock("internal.local", resolvedIPs, "private_ip", "user123", "203.0.113.5")
|
||||
}
|
||||
|
||||
// TestGlobalAuditLogger tests the global audit logger functions.
|
||||
func TestGlobalAuditLogger(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Test global functions don't panic
|
||||
LogURLTest("test.com", "req-global", "user-global", "192.0.2.10", "allowed")
|
||||
LogSSRFBlock("blocked.local", []string{"127.0.0.1"}, "loopback", "user-global", "198.51.100.10")
|
||||
}
|
||||
|
||||
// TestAuditEvent_RequiredFields tests that required fields are enforced.
|
||||
func TestAuditEvent_RequiredFields(t *testing.T) {
|
||||
t.Parallel()
|
||||
// CRITICAL: UserID field must be present for attribution
|
||||
event := AuditEvent{
|
||||
Timestamp: time.Now().UTC().Format(time.RFC3339),
|
||||
Action: "ssrf_block",
|
||||
Host: "malicious.com",
|
||||
RequestID: "req-security",
|
||||
Result: "blocked",
|
||||
ResolvedIPs: []string{"192.168.1.1"},
|
||||
BlockedReason: "private_ip",
|
||||
UserID: "attacker123", // REQUIRED per Supervisor review
|
||||
SourceIP: "203.0.113.100",
|
||||
}
|
||||
|
||||
jsonBytes, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal: %v", err)
|
||||
}
|
||||
|
||||
// Verify UserID is in JSON output
|
||||
if !strings.Contains(string(jsonBytes), "attacker123") {
|
||||
t.Errorf("UserID not found in audit log JSON")
|
||||
}
|
||||
}
|
||||
|
||||
// TestAuditLogger_TimestampFormat tests that timestamps use RFC3339 format.
|
||||
func TestAuditLogger_TimestampFormat(t *testing.T) {
|
||||
t.Parallel()
|
||||
logger := NewAuditLogger()
|
||||
|
||||
event := AuditEvent{
|
||||
Action: "test",
|
||||
Host: "test.com",
|
||||
// Timestamp intentionally omitted to test auto-generation
|
||||
}
|
||||
|
||||
// Capture the event by marshaling after logging
|
||||
// In real scenario, LogURLValidation sets the timestamp
|
||||
if event.Timestamp == "" {
|
||||
event.Timestamp = time.Now().UTC().Format(time.RFC3339)
|
||||
}
|
||||
|
||||
// Parse the timestamp to verify it's valid RFC3339
|
||||
_, err := time.Parse(time.RFC3339, event.Timestamp)
|
||||
if err != nil {
|
||||
t.Errorf("Invalid timestamp format: %s, error: %v", event.Timestamp, err)
|
||||
}
|
||||
|
||||
logger.LogURLValidation(event)
|
||||
}
|
||||
Reference in New Issue
Block a user