defmodule Raxol.Audit.Analyzer do @moduledoc """ Analyzes audit events in real-time to detect security threats, anomalies, and compliance violations. This module implements various detection algorithms including: - Brute force attack detection - Privilege escalation attempts - Data exfiltration patterns - Unusual access patterns - Compliance violations """ use Raxol.Core.Behaviours.BaseManager alias Raxol.Core.Runtime.Log alias Raxol.Core.Utils.TimerManager defstruct [ :config, :detection_rules, :alert_thresholds, :user_profiles, :anomaly_detector, :threat_indicators, :compliance_rules, :recent_events, :metrics ] @type detection_rule :: %{ name: String.t(), type: :threshold | :pattern | :anomaly | :correlation, condition: function(), severity: :low | :medium | :high | :critical, action: :alert | :block | :investigate } ## Client API # BaseManager provides start_link/1 # Usage: Raxol.Audit.Analyzer.start_link(name: __MODULE__, config: config) @doc """ Analyzes an audit event for threats and anomalies. """ def analyze_event(analyzer \\ __MODULE__, event) do GenServer.call(analyzer, {:analyze_event, event}) end @doc """ Analyzes a batch of events for correlations. """ def analyze_batch(analyzer \\ __MODULE__, events) do GenServer.call(analyzer, {:analyze_batch, events}) end @doc """ Gets current threat level assessment. """ def get_threat_assessment(analyzer \\ __MODULE__) do GenServer.call(analyzer, :get_threat_assessment) end @doc """ Gets compliance status. """ def get_compliance_status(analyzer \\ __MODULE__) do GenServer.call(analyzer, :get_compliance_status) end ## BaseManager Implementation @impl Raxol.Core.Behaviours.BaseManager def init_manager(opts) do config = Keyword.get(opts, :config, %{}) state = %__MODULE__{ config: config, detection_rules: init_detection_rules(), alert_thresholds: init_alert_thresholds(), user_profiles: %{}, anomaly_detector: init_anomaly_detector(), threat_indicators: init_threat_indicators(), compliance_rules: init_compliance_rules(config), recent_events: :queue.new(), metrics: init_metrics() } # Schedule periodic analysis tasks using TimerManager intervals = TimerManager.intervals() {:ok, _} = TimerManager.start_interval(:analyze_patterns, intervals.minute) {:ok, _} = TimerManager.start_interval(:update_profiles, intervals.five_minutes) Log.info("Audit analyzer initialized") {:ok, state} end @impl Raxol.Core.Behaviours.BaseManager def handle_manager_call({:analyze_event, event}, _from, state) do {analysis_result, new_state} = perform_analysis(event, state) {:reply, analysis_result, new_state} end def handle_manager_call({:analyze_batch, events}, _from, state) do {analysis_results, new_state} = perform_batch_analysis(events, state) {:reply, analysis_results, new_state} end def handle_manager_call(:get_threat_assessment, _from, state) do assessment = calculate_threat_assessment(state) {:reply, {:ok, assessment}, state} end def handle_manager_call(:get_compliance_status, _from, state) do status = check_compliance_status(state) {:reply, {:ok, status}, state} end @impl Raxol.Core.Behaviours.BaseManager def handle_manager_info(:analyze_patterns, state) do new_state = analyze_recent_patterns(state) {:noreply, new_state} end def handle_manager_info(:update_profiles, state) do new_state = update_user_profiles(state) {:noreply, new_state} end ## Private Functions defp perform_analysis(event, state) do # Add event to recent events queue new_recent = :queue.in(event, state.recent_events) new_recent = trim_queue_if_needed(new_recent, 1000) # Run detection rules detections = run_detection_rules(event, state.detection_rules, state) # Check for anomalies anomalies = detect_anomalies(event, state) # Check compliance compliance_violations = check_compliance(event, state.compliance_rules) # Update user profile new_profiles = update_user_profile(event, state.user_profiles) # Update metrics new_metrics = update_metrics(event, detections, state.metrics) # Build analysis result analysis_result = %{ event_id: event.event_id, timestamp: event.timestamp, detections: detections, anomalies: anomalies, compliance_violations: compliance_violations, risk_score: calculate_risk_score(detections, anomalies), recommended_actions: determine_actions(detections, anomalies) } new_state = %{ state | recent_events: new_recent, user_profiles: new_profiles, metrics: new_metrics } # Trigger alerts if needed _ = maybe_send_alert(analysis_result, state) {analysis_result, new_state} end defp perform_batch_analysis(events, state) do # Detect correlated patterns correlations = detect_correlations(events, state) # Check for attack patterns attack_patterns = detect_attack_patterns(events, state) # Analyze data flow data_flow_anomalies = analyze_data_flow(events, state) analysis_results = %{ correlations: correlations, attack_patterns: attack_patterns, data_flow_anomalies: data_flow_anomalies, batch_risk_score: calculate_batch_risk_score(correlations, attack_patterns) } {analysis_results, state} end defp run_detection_rules(event, rules, state) do rules |> Enum.filter(fn rule -> rule.condition.(event, state) end) |> Enum.map(fn rule -> %{ rule_name: rule.name, type: rule.type, severity: rule.severity, action: rule.action, details: extract_rule_details(rule, event) } end) end defp detect_anomalies(event, state) do anomalies = [] # Check for unusual time of activity anomalies = add_time_anomaly_if_needed(anomalies, event, state) # Check for unusual location anomalies = add_location_anomaly_if_needed(anomalies, event, state) # Check for unusual behavior pattern anomalies = add_behavior_anomaly_if_needed(anomalies, event, state) anomalies end defp detect_correlations(events, _state) do correlations = [] # Check for rapid succession of failed authentications failed_auth_count = Enum.count(events, fn e -> Map.get(e, :event_type) == :authentication and Map.get(e, :outcome) == :failure end) correlations_with_auth = add_brute_force_correlation(correlations, failed_auth_count) # Check for data exfiltration pattern data_access_events = Enum.filter(events, fn e -> Map.get(e, :operation) in [:read, :export] end) check_and_add_exfiltration_pattern( correlations_with_auth, data_access_events ) end defp detect_attack_patterns(events, _state) do patterns = [] # Check for privilege escalation attempts priv_events = Enum.filter(events, fn e -> Map.get(e, :action) == :privilege_escalation or String.contains?(Map.get(e, :command, ""), ["sudo", "su", "chmod"]) end) patterns_with_priv = add_privilege_escalation_pattern(patterns, priv_events) # Check for reconnaissance patterns recon_commands = [ "whoami", "id", "uname", "ps", "netstat", "ifconfig", "ls -la" ] recon_events = Enum.filter(events, fn e -> command = Map.get(e, :command, "") Enum.any?(recon_commands, &String.contains?(command, &1)) end) add_reconnaissance_pattern(patterns_with_priv, recon_events) end defp analyze_data_flow(events, _state) do # Group events by user by_user = Enum.group_by(events, &Map.get(&1, :user_id)) Enum.flat_map(by_user, fn {user_id, user_events} -> # Check for unusual data access patterns resources_accessed = user_events |> Enum.map(&Map.get(&1, :resource_id)) |> Enum.filter(&(&1 != nil)) |> Enum.uniq() |> length() check_excessive_resource_access(user_id, resources_accessed) end) end defp check_compliance(event, compliance_rules) do compliance_rules |> Enum.filter(fn rule -> rule.check.(event) end) |> Enum.map(fn rule -> %{ framework: rule.framework, requirement: rule.requirement, violation: rule.violation_type, severity: rule.severity, remediation: rule.remediation } end) end defp unusual_time?(event, state) do hour = event.timestamp |> DateTime.from_unix!(:millisecond) |> Map.get(:hour) user_id = Map.get(event, :user_id) user_profile = Map.get(state.user_profiles, user_id, %{}) usual_hours = Map.get(user_profile, :usual_hours, 8..18) hour not in usual_hours end defp unusual_location?(%{ip_address: nil}, _state), do: false defp unusual_location?(event, state) do user_id = Map.get(event, :user_id) user_profile = Map.get(state.user_profiles, user_id, %{}) known_ips = Map.get(user_profile, :known_ips, MapSet.new()) ip = Map.get(event, :ip_address) not MapSet.member?(known_ips, ip) end defp unusual_behavior?(event, state) do # Simplified behavior analysis user_id = Map.get(event, :user_id) user_profile = Map.get(state.user_profiles, user_id, %{}) baseline = Map.get(user_profile, :behavior_baseline, %{}) # Check if event type is unusual for this user event_type = Map.get(event, :event_type) usual_events = Map.get(baseline, :common_events, []) event_type not in usual_events and length(usual_events) > 10 end defp calculate_risk_score(detections, anomalies) do detection_score = Enum.reduce(detections, 0, fn detection, acc -> acc + severity_score(detection.severity) end) anomaly_score = Enum.reduce(anomalies, 0, fn anomaly, acc -> acc + severity_score(anomaly.severity) end) min(100, detection_score + anomaly_score) end defp calculate_batch_risk_score(correlations, patterns) do correlation_score = Enum.reduce(correlations, 0, fn corr, acc -> acc + severity_score(corr.severity) end) pattern_score = Enum.reduce(patterns, 0, fn pattern, acc -> acc + severity_score(pattern.severity) end) min(100, correlation_score * 2 + pattern_score * 3) end defp severity_score(:low), do: 10 defp severity_score(:medium), do: 25 defp severity_score(:high), do: 50 defp severity_score(:critical), do: 100 defp determine_actions(detections, anomalies) do actions = [] critical_detection = Enum.any?(detections, &(&1.severity == :critical)) high_detection = Enum.any?(detections, &(&1.severity == :high)) actions = add_critical_actions(actions, critical_detection) actions = add_high_priority_actions(actions, high_detection) add_anomaly_review_if_needed(actions, anomalies) end defp should_alert?(analysis_result) do analysis_result.risk_score > 50 or Enum.any?( analysis_result.detections, &(&1.severity in [:high, :critical]) ) or length(analysis_result.compliance_violations) > 0 end defp send_security_alert(analysis_result, _state) do Task.start(fn -> Log.warning("Security Alert: #{inspect(analysis_result)}") # Here you would integrate with alerting systems (PagerDuty, Slack, etc.) end) end defp update_user_profile(%{user_id: nil}, profiles), do: profiles defp update_user_profile(event, profiles) do user_id = Map.get(event, :user_id) profile = Map.get(profiles, user_id, %{ usual_hours: MapSet.new(), known_ips: MapSet.new(), common_events: [], behavior_baseline: %{} }) # Update profile with new data hour = event.timestamp |> DateTime.from_unix!(:millisecond) |> Map.get(:hour) updated_profile = profile |> Map.update!(:usual_hours, &MapSet.put(&1, hour)) |> Map.update!(:known_ips, &update_known_ips(&1, event)) |> Map.update!(:common_events, &update_common_events(&1, event)) Map.put(profiles, user_id, updated_profile) end defp analyze_recent_patterns(state) do recent_list = :queue.to_list(state.recent_events) # Analyze patterns in recent events patterns = detect_attack_patterns(recent_list, state) log_patterns_if_found(patterns) state end defp update_user_profiles(state) do # Periodically refine user profiles based on accumulated data state end defp calculate_threat_assessment(state) do recent_list = :queue.to_list(state.recent_events) critical_events = Enum.count(recent_list, fn e -> Map.get(e, :severity) == :critical end) high_events = Enum.count(recent_list, fn e -> Map.get(e, :severity) == :high end) threat_level = determine_threat_level(critical_events, high_events) %{ threat_level: threat_level, critical_events: critical_events, high_events: high_events, total_events: length(recent_list), assessment_time: System.system_time(:millisecond) } end defp check_compliance_status(_state) do # Check current compliance status %{ frameworks: [:soc2, :hipaa, :gdpr, :pci_dss], status: :compliant, last_check: System.system_time(:millisecond), findings: [] } end defp init_detection_rules do [ %{ name: "brute_force_detection", type: :threshold, condition: fn event, _state -> Map.get(event, :event_type) == :authentication and Map.get(event, :outcome) == :failure end, severity: :high, action: :alert }, %{ name: "suspicious_command_execution", type: :pattern, condition: fn event, _state -> command = case event do %{command: cmd} when is_binary(cmd) -> cmd _ -> Map.get(event, :command, "") end suspicious = ["curl", "wget", "rm -rf", ":()", "| sh", "| bash"] Enum.any?(suspicious, &String.contains?(String.downcase(command), &1)) end, severity: :critical, action: :block }, %{ name: "unauthorized_access_attempt", type: :pattern, condition: fn event, _state -> Map.get(event, :event_type) == :authorization and Map.get(event, :outcome) == :denied end, severity: :medium, action: :alert }, %{ name: "security_event_detected", type: :pattern, condition: fn event, _state -> Map.get(event, :event_type) == :security or Map.get(event, :action) == :intrusion_detected end, severity: :high, action: :investigate } ] end defp init_alert_thresholds do %{ failed_auth_threshold: 5, data_access_threshold: 1000, configuration_changes_threshold: 10, time_window_minutes: 5 } end defp init_anomaly_detector do %{ baseline_window_days: 30, # Standard deviations deviation_threshold: 3.0, min_data_points: 100 } end defp init_threat_indicators do %{ known_bad_ips: MapSet.new(), suspicious_user_agents: [], malicious_patterns: [] } end defp init_compliance_rules(_config) do [ %{ framework: :gdpr, requirement: "Data access logging", check: fn event -> Map.get(event, :event_type) == :data_access and Map.get(event, :data_classification) == :personal and Map.get(event, :legal_basis) == nil end, violation_type: :missing_legal_basis, severity: :high, remediation: "Ensure legal basis is documented for personal data access" }, %{ framework: :hipaa, requirement: "PHI access control", check: fn event -> Map.get(event, :data_classification) == :phi and Map.get(event, :mfa_used) == false end, violation_type: :missing_mfa, severity: :critical, remediation: "MFA required for PHI access" } ] end defp init_metrics do %{ events_analyzed: 0, detections_triggered: 0, anomalies_detected: 0, compliance_violations: 0, alerts_sent: 0, start_time: System.system_time(:millisecond) } end defp update_metrics(_event, detections, metrics) do metrics |> Map.update!(:events_analyzed, &(&1 + 1)) |> Map.update!(:detections_triggered, &(&1 + length(detections))) end defp extract_rule_details(_rule, event) do %{ triggered_at: event.timestamp, event_id: event.event_id, user_id: Map.get(event, :user_id), resource: Map.get(event, :resource_id) } end # Helper functions for pattern matching refactoring defp determine_threat_level(critical_events, _high_events) when critical_events > 5, do: :critical defp determine_threat_level(critical_events, _high_events) when critical_events > 0, do: :high defp determine_threat_level(_critical_events, high_events) when high_events > 10, do: :high defp determine_threat_level(_critical_events, high_events) when high_events > 5, do: :medium defp determine_threat_level(_critical_events, _high_events), do: :low defp trim_queue_if_needed(queue, max_length) do case :queue.len(queue) > max_length do true -> {_, trimmed} = :queue.out(queue) trimmed false -> queue end end defp maybe_send_alert(analysis_result, state) do case should_alert?(analysis_result) do true -> send_security_alert(analysis_result, state) false -> :ok end end defp add_time_anomaly_if_needed(anomalies, event, state) do case unusual_time?(event, state) do true -> [ %{ type: :unusual_time, severity: :low, details: "Activity at unusual hour" } | anomalies ] false -> anomalies end end defp add_location_anomaly_if_needed(anomalies, event, state) do case unusual_location?(event, state) do true -> [ %{ type: :unusual_location, severity: :medium, details: "Access from new location" } | anomalies ] false -> anomalies end end defp add_behavior_anomaly_if_needed(anomalies, event, state) do case unusual_behavior?(event, state) do true -> [ %{ type: :unusual_behavior, severity: :high, details: "Behavior deviates from baseline" } | anomalies ] false -> anomalies end end defp add_brute_force_correlation(correlations, failed_auth_count) when failed_auth_count > 5 do [ %{ type: :brute_force_attempt, severity: :high, event_count: failed_auth_count, details: "Multiple failed authentication attempts detected" } | correlations ] end defp add_brute_force_correlation(correlations, _failed_auth_count), do: correlations defp check_and_add_exfiltration_pattern(correlations, data_access_events) when length(data_access_events) > 10 do total_records = Enum.sum(Enum.map(data_access_events, &Map.get(&1, :records_count, 0))) add_exfiltration_if_high_volume(correlations, total_records) end defp check_and_add_exfiltration_pattern(correlations, _data_access_events), do: correlations defp add_exfiltration_if_high_volume(correlations, total_records) when total_records > 1000 do [ %{ type: :potential_data_exfiltration, severity: :critical, records_accessed: total_records, details: "Large volume of data accessed in short time" } | correlations ] end defp add_exfiltration_if_high_volume(correlations, _total_records), do: correlations defp add_critical_actions(actions, true) do [ :immediate_investigation, :notify_security_team, :consider_blocking | actions ] end defp add_critical_actions(actions, false), do: actions defp add_high_priority_actions(actions, true) do [:investigate, :increase_monitoring | actions] end defp add_high_priority_actions(actions, false), do: actions defp add_anomaly_review_if_needed(actions, anomalies) when length(anomalies) > 3 do [:review_user_activity | actions] end defp add_anomaly_review_if_needed(actions, _anomalies), do: actions defp update_known_ips(ips, %{ip_address: nil}), do: ips defp update_known_ips(ips, %{ip_address: ip}), do: MapSet.put(ips, ip) defp update_known_ips(ips, _event), do: ips defp update_common_events(events, event) do event_type = Map.get(event, :event_type) case event_type in events do true -> events # Keep last 20 event types false -> [event_type | Enum.take(events, 19)] end end defp log_patterns_if_found([]), do: :ok defp log_patterns_if_found(patterns) do Log.warning("Patterns detected in recent events: #{inspect(patterns)}") end defp add_privilege_escalation_pattern(patterns, priv_events) when length(priv_events) > 3 do [ %{ type: :privilege_escalation_attempt, severity: :critical, event_count: length(priv_events), details: "Multiple privilege escalation attempts detected" } | patterns ] end defp add_privilege_escalation_pattern(patterns, _priv_events), do: patterns defp add_reconnaissance_pattern(patterns, recon_events) when length(recon_events) > 5 do [ %{ type: :reconnaissance_activity, severity: :medium, event_count: length(recon_events), details: "System reconnaissance activity detected" } | patterns ] end defp add_reconnaissance_pattern(patterns, _recon_events), do: patterns defp check_excessive_resource_access(user_id, resources_accessed) when resources_accessed > 50 do [ %{ type: :excessive_resource_access, user_id: user_id, severity: :medium, resources_count: resources_accessed, details: "User accessed unusually high number of resources" } ] end defp check_excessive_resource_access(_user_id, _resources_accessed), do: [] end