1use std::collections::HashMap;
4use std::sync::{Arc, RwLock};
5use std::time::{SystemTime, UNIX_EPOCH};
6
7use serde_json::json;
8
9use super::redaction::{sanitize_field_key, sanitize_field_value, sanitize_text};
10
11pub const DEFAULT_MAX_SECURITY_AUDIT_EVENTS: usize = 1024;
13pub const MAX_SECURITY_AUDIT_TEXT_LEN: usize = 256;
15
16fn truncate_audit_text(value: &str) -> String {
17 if value.len() <= MAX_SECURITY_AUDIT_TEXT_LEN {
18 return value.to_string();
19 }
20
21 let mut truncated = String::with_capacity(MAX_SECURITY_AUDIT_TEXT_LEN);
22 for ch in value.chars() {
23 if truncated.len() + ch.len_utf8() > MAX_SECURITY_AUDIT_TEXT_LEN {
24 break;
25 }
26 truncated.push(ch);
27 }
28 truncated
29}
30
31fn sanitize_audit_text(value: &str) -> String {
32 truncate_audit_text(&sanitize_text(value))
33}
34
35fn sanitize_audit_field_key(key: &str) -> String {
36 truncate_audit_text(&sanitize_field_key(key))
37}
38
39fn sanitize_audit_field_value(key: &str, value: &str) -> String {
40 truncate_audit_text(&sanitize_field_value(key, value))
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum SecurityAuditAction {
46 RequestRejected,
48 CapabilityDenied,
50 ValidationRejected,
52 ReplayRejected,
54 SignatureRejected,
56 TransportRejected,
58 ShutdownRejected,
60}
61
62impl SecurityAuditAction {
63 pub fn as_str(self) -> &'static str {
65 match self {
66 Self::RequestRejected => "request_rejected",
67 Self::CapabilityDenied => "capability_denied",
68 Self::ValidationRejected => "validation_rejected",
69 Self::ReplayRejected => "replay_rejected",
70 Self::SignatureRejected => "signature_rejected",
71 Self::TransportRejected => "transport_rejected",
72 Self::ShutdownRejected => "shutdown_rejected",
73 }
74 }
75}
76
77#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct SecurityAuditEvent {
80 pub timestamp: u64,
82 pub action: SecurityAuditAction,
84 pub reason: String,
86 pub method: Option<String>,
88 pub request_id: Option<String>,
90 pub fields: HashMap<String, String>,
92}
93
94impl SecurityAuditEvent {
95 pub fn new(action: SecurityAuditAction, reason: impl Into<String>) -> Self {
97 let timestamp = SystemTime::now()
98 .duration_since(UNIX_EPOCH)
99 .unwrap_or_default()
100 .as_millis() as u64;
101
102 Self {
103 timestamp,
104 action,
105 reason: sanitize_audit_text(&reason.into()),
106 method: None,
107 request_id: None,
108 fields: HashMap::new(),
109 }
110 }
111
112 pub fn with_method(mut self, method: impl Into<String>) -> Self {
114 self.method = Some(sanitize_audit_text(&method.into()));
115 self
116 }
117
118 pub fn with_request_id(mut self, request_id: impl Into<String>) -> Self {
120 self.request_id = Some(sanitize_audit_text(&request_id.into()));
121 self
122 }
123
124 pub fn with_field(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
126 let key = key.into();
127 let value = value.into();
128 let sanitized_key = sanitize_audit_field_key(&key);
129 self.fields
130 .insert(sanitized_key, sanitize_audit_field_value(&key, &value));
131 self
132 }
133
134 fn sanitized_copy(&self) -> Self {
135 let mut fields = HashMap::new();
136 for (key, value) in &self.fields {
137 fields.insert(
138 sanitize_audit_field_key(key),
139 sanitize_audit_field_value(key, value),
140 );
141 }
142
143 Self {
144 timestamp: self.timestamp,
145 action: self.action,
146 reason: sanitize_audit_text(&self.reason),
147 method: self
148 .method
149 .as_ref()
150 .map(|method| sanitize_audit_text(method)),
151 request_id: self
152 .request_id
153 .as_ref()
154 .map(|request_id| sanitize_audit_text(request_id)),
155 fields,
156 }
157 }
158
159 pub fn to_json(&self) -> String {
161 let event = self.sanitized_copy();
162 let mut value = json!({
163 "timestamp": event.timestamp,
164 "action": event.action.as_str(),
165 "reason": event.reason,
166 "fields": event.fields,
167 });
168
169 if let Some(method) = &event.method {
170 value["method"] = json!(method);
171 }
172 if let Some(request_id) = &event.request_id {
173 value["request_id"] = json!(request_id);
174 }
175
176 serde_json::to_string(&value).unwrap_or_else(|_| "{}".to_string())
177 }
178}
179
180#[derive(Debug, Default)]
181struct SecurityAuditState {
182 events: Vec<SecurityAuditEvent>,
183 dropped_count: u64,
184}
185
186#[derive(Debug, Clone)]
188pub struct SecurityAuditLog {
189 state: Arc<RwLock<SecurityAuditState>>,
190 max_events: usize,
191}
192
193impl SecurityAuditLog {
194 pub fn new() -> Self {
196 Self::with_capacity(DEFAULT_MAX_SECURITY_AUDIT_EVENTS)
197 }
198
199 pub fn with_capacity(max_events: usize) -> Self {
201 Self {
202 state: Arc::new(RwLock::new(SecurityAuditState::default())),
203 max_events,
204 }
205 }
206
207 pub fn record(&self, event: SecurityAuditEvent) {
209 if let Ok(mut state) = self.state.write() {
210 let event = event.sanitized_copy();
211 if self.max_events == 0 {
212 state.dropped_count = state.dropped_count.saturating_add(1);
213 return;
214 }
215
216 if state.events.len() >= self.max_events {
217 state.events.remove(0);
218 state.dropped_count = state.dropped_count.saturating_add(1);
219 }
220 state.events.push(event);
221 }
222 }
223
224 pub fn events(&self) -> Vec<SecurityAuditEvent> {
226 self.state
227 .read()
228 .map(|state| state.events.clone())
229 .unwrap_or_default()
230 }
231
232 pub fn dropped_count(&self) -> u64 {
234 self.state
235 .read()
236 .map(|state| state.dropped_count)
237 .unwrap_or_default()
238 }
239
240 pub fn clear(&self) {
242 if let Ok(mut state) = self.state.write() {
243 state.events.clear();
244 state.dropped_count = 0;
245 }
246 }
247}
248
249impl Default for SecurityAuditLog {
250 fn default() -> Self {
251 Self::new()
252 }
253}