Skip to main content

sh_layer4/audit_logger/
entry.rs

1//! 审计日志条目定义
2//!
3//! 符合 SOC2 合规标准的审计日志格式。
4
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7use uuid::Uuid;
8
9/// 审计日志条目
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct AuditEntry {
12    /// 唯一 ID
13    pub id: Uuid,
14    /// 时间戳
15    pub timestamp: DateTime<Utc>,
16    /// 用户 ID
17    pub user_id: String,
18    /// 会话 ID (可选)
19    #[serde(skip_serializing_if = "Option::is_none")]
20    pub session_id: Option<String>,
21    /// 操作类型
22    pub action: AuditAction,
23    /// 资源类型
24    pub resource_type: String,
25    /// 资源 ID
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub resource_id: Option<String>,
28    /// 操作结果
29    pub result: AuditResult,
30    /// 详细信息 (已脱敏)
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub details: Option<serde_json::Value>,
33    /// IP 地址 (可选)
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub ip_address: Option<String>,
36    /// 请求 ID (可选)
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub request_id: Option<String>,
39    /// 持续时间 (毫秒)
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub duration_ms: Option<u64>,
42}
43
44/// 审计操作类型
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub enum AuditAction {
47    /// 登录
48    Login,
49    /// 登出
50    Logout,
51    /// 创建资源
52    Create,
53    /// 读取资源
54    Read,
55    /// 更新资源
56    Update,
57    /// 删除资源
58    Delete,
59    /// 执行操作
60    Execute,
61    /// 访问资源
62    Access,
63    /// 配置更改
64    ConfigChange,
65    /// 权限更改
66    PermissionChange,
67    /// 工具调用
68    ToolCall,
69    /// LLM 请求
70    LlmRequest,
71    /// 会话操作
72    SessionOperation,
73    /// 其他
74    Other(String),
75}
76
77impl AuditAction {
78    pub fn as_str(&self) -> &str {
79        match self {
80            AuditAction::Login => "login",
81            AuditAction::Logout => "logout",
82            AuditAction::Create => "create",
83            AuditAction::Read => "read",
84            AuditAction::Update => "update",
85            AuditAction::Delete => "delete",
86            AuditAction::Execute => "execute",
87            AuditAction::Access => "access",
88            AuditAction::ConfigChange => "config_change",
89            AuditAction::PermissionChange => "permission_change",
90            AuditAction::ToolCall => "tool_call",
91            AuditAction::LlmRequest => "llm_request",
92            AuditAction::SessionOperation => "session_operation",
93            AuditAction::Other(s) => s.as_str(),
94        }
95    }
96}
97
98/// 审计结果
99#[derive(Debug, Clone, Serialize, Deserialize)]
100pub enum AuditResult {
101    /// 成功
102    Success,
103    /// 失败
104    Failure {
105        /// 错误码
106        error_code: String,
107        /// 错误消息
108        error_message: String,
109    },
110    /// 拒绝访问
111    Denied {
112        /// 拒绝原因
113        reason: String,
114    },
115}
116
117impl AuditResult {
118    pub fn is_success(&self) -> bool {
119        matches!(self, AuditResult::Success)
120    }
121
122    pub fn success() -> Self {
123        AuditResult::Success
124    }
125
126    pub fn failure(code: &str, message: &str) -> Self {
127        AuditResult::Failure {
128            error_code: code.to_string(),
129            error_message: message.to_string(),
130        }
131    }
132
133    pub fn denied(reason: &str) -> Self {
134        AuditResult::Denied {
135            reason: reason.to_string(),
136        }
137    }
138}
139
140/// 审计日志过滤器
141#[derive(Debug, Clone, Default, Deserialize)]
142pub struct AuditFilter {
143    /// 用户 ID (可选)
144    #[serde(skip_serializing_if = "Option::is_none")]
145    pub user_id: Option<String>,
146    /// 操作类型 (可选)
147    #[serde(skip_serializing_if = "Option::is_none")]
148    pub action: Option<AuditAction>,
149    /// 资源类型 (可选)
150    #[serde(skip_serializing_if = "Option::is_none")]
151    pub resource_type: Option<String>,
152    /// 结果 (可选)
153    #[serde(skip_serializing_if = "Option::is_none")]
154    pub result: Option<AuditResult>,
155    /// 开始时间 (可选)
156    #[serde(skip_serializing_if = "Option::is_none")]
157    pub start_time: Option<DateTime<Utc>>,
158    /// 结束时间 (可选)
159    #[serde(skip_serializing_if = "Option::is_none")]
160    pub end_time: Option<DateTime<Utc>>,
161    /// 限制数量
162    #[serde(skip_serializing_if = "Option::is_none")]
163    pub limit: Option<usize>,
164}
165
166impl AuditEntry {
167    /// 创建新的审计条目
168    pub fn new(user_id: &str, action: AuditAction, resource_type: &str) -> Self {
169        Self {
170            id: Uuid::new_v4(),
171            timestamp: Utc::now(),
172            user_id: user_id.to_string(),
173            session_id: None,
174            action,
175            resource_type: resource_type.to_string(),
176            resource_id: None,
177            result: AuditResult::Success,
178            details: None,
179            ip_address: None,
180            request_id: None,
181            duration_ms: None,
182        }
183    }
184
185    /// 设置会话 ID
186    pub fn with_session(mut self, session_id: &str) -> Self {
187        self.session_id = Some(session_id.to_string());
188        self
189    }
190
191    /// 设置资源 ID
192    pub fn with_resource_id(mut self, resource_id: &str) -> Self {
193        self.resource_id = Some(resource_id.to_string());
194        self
195    }
196
197    /// 设置结果
198    pub fn with_result(mut self, result: AuditResult) -> Self {
199        self.result = result;
200        self
201    }
202
203    /// 设置详细信息
204    pub fn with_details(mut self, details: serde_json::Value) -> Self {
205        self.details = Some(details);
206        self
207    }
208
209    /// 设置 IP 地址
210    pub fn with_ip(mut self, ip_address: &str) -> Self {
211        self.ip_address = Some(ip_address.to_string());
212        self
213    }
214
215    /// 设置请求 ID
216    pub fn with_request_id(mut self, request_id: &str) -> Self {
217        self.request_id = Some(request_id.to_string());
218        self
219    }
220
221    /// 设置持续时间
222    pub fn with_duration(mut self, duration_ms: u64) -> Self {
223        self.duration_ms = Some(duration_ms);
224        self
225    }
226
227    /// 匹配过滤器
228    pub fn matches_filter(&self, filter: &AuditFilter) -> bool {
229        if let Some(user_id) = &filter.user_id {
230            if self.user_id != *user_id {
231                return false;
232            }
233        }
234
235        if let Some(action) = &filter.action {
236            if self.action.as_str() != action.as_str() {
237                return false;
238            }
239        }
240
241        if let Some(resource_type) = &filter.resource_type {
242            if self.resource_type != *resource_type {
243                return false;
244            }
245        }
246
247        if let Some(start_time) = &filter.start_time {
248            if self.timestamp < *start_time {
249                return false;
250            }
251        }
252
253        if let Some(end_time) = &filter.end_time {
254            if self.timestamp > *end_time {
255                return false;
256            }
257        }
258
259        true
260    }
261}
262
263/// 导出格式
264#[derive(Debug, Clone)]
265pub enum ExportFormat {
266    /// JSON
267    Json,
268    /// CSV
269    Csv,
270    /// Syslog 格式
271    Syslog,
272}
273
274#[cfg(test)]
275mod tests {
276    use super::*;
277
278    #[test]
279    fn test_audit_entry_creation() {
280        let entry = AuditEntry::new("user123", AuditAction::Read, "document");
281        assert_eq!(entry.user_id, "user123");
282        assert!(entry.result.is_success());
283    }
284
285    #[test]
286    fn test_audit_entry_with_details() {
287        let entry = AuditEntry::new("user123", AuditAction::Execute, "tool")
288            .with_details(serde_json::json!({ "tool_name": "test" }));
289
290        assert!(entry.details.is_some());
291    }
292
293    #[test]
294    fn test_audit_result() {
295        let success = AuditResult::success();
296        assert!(success.is_success());
297
298        let failure = AuditResult::failure("E001", "Test error");
299        assert!(!failure.is_success());
300    }
301
302    #[test]
303    fn test_audit_filter_matching() {
304        let entry = AuditEntry::new("user123", AuditAction::Read, "document");
305
306        let filter = AuditFilter {
307            user_id: Some("user123".to_string()),
308            ..Default::default()
309        };
310        assert!(entry.matches_filter(&filter));
311
312        let filter2 = AuditFilter {
313            user_id: Some("other_user".to_string()),
314            ..Default::default()
315        };
316        assert!(!entry.matches_filter(&filter2));
317    }
318
319    #[test]
320    fn test_serialize_audit_entry() {
321        let entry = AuditEntry::new("user123", AuditAction::Login, "session");
322        let json = serde_json::to_string(&entry).unwrap();
323        assert!(json.contains("user123"));
324        assert!(json.contains("Login")); // Serde uses PascalCase for enum variants
325    }
326}