rusticity_core/
cloudtrail.rs1use crate::config::AwsConfig;
2use anyhow::Result;
3
4pub struct CloudTrailClient {
5 config: AwsConfig,
6}
7
8impl CloudTrailClient {
9 pub fn new(config: AwsConfig) -> Self {
10 Self { config }
11 }
12
13 pub async fn lookup_events(
14 &self,
15 max_results: Option<i32>,
16 next_token: Option<String>,
17 event_name_filter: Option<String>,
18 ) -> Result<(
19 Vec<(
20 String,
21 String,
22 String,
23 String,
24 String,
25 String,
26 String,
27 String,
28 String,
29 String,
30 String,
31 String,
32 String,
33 String,
34 String,
35 )>,
36 Option<String>,
37 )> {
38 let client = self.config.cloudtrail_client();
39
40 let mut request = client.lookup_events();
41 if let Some(max) = max_results {
42 request = request.max_results(max);
43 }
44 if let Some(token) = next_token {
45 request = request.next_token(token);
46 }
47 if let Some(filter) = event_name_filter {
48 if !filter.is_empty() {
49 request = request.lookup_attributes(
50 aws_sdk_cloudtrail::types::LookupAttribute::builder()
51 .attribute_key(aws_sdk_cloudtrail::types::LookupAttributeKey::EventName)
52 .attribute_value(filter)
53 .build()
54 .expect("LookupAttribute build"),
55 );
56 }
57 }
58
59 let resp = request.send().await?;
60
61 let mut events = Vec::new();
62 for event in resp.events() {
63 let event_name = event.event_name().unwrap_or("").to_string();
64 let event_time = event
65 .event_time()
66 .map(|t| {
67 let dt = chrono::DateTime::parse_from_rfc3339(&t.to_string())
68 .unwrap_or_else(|_| chrono::Utc::now().into());
69 format!("{} (UTC)", dt.format("%Y-%m-%d %H:%M:%S"))
70 })
71 .unwrap_or_default();
72 let username = event.username().unwrap_or("").to_string();
73 let event_source = event
74 .cloud_trail_event()
75 .and_then(|json| {
76 serde_json::from_str::<serde_json::Value>(json)
77 .ok()
78 .and_then(|v| v["eventSource"].as_str().map(|s| s.to_string()))
79 })
80 .unwrap_or_default();
81 let resource_type = event
82 .resources()
83 .first()
84 .and_then(|r| r.resource_type())
85 .unwrap_or("")
86 .to_string();
87 let resource_name = event
88 .resources()
89 .first()
90 .and_then(|r| r.resource_name())
91 .unwrap_or("")
92 .to_string();
93 let read_only = event.read_only().map(|b| b.to_string()).unwrap_or_default();
94 let aws_region = event
95 .cloud_trail_event()
96 .and_then(|json| {
97 serde_json::from_str::<serde_json::Value>(json)
98 .ok()
99 .and_then(|v| v["awsRegion"].as_str().map(|s| s.to_string()))
100 })
101 .unwrap_or_default();
102 let event_id = event.event_id().unwrap_or("").to_string();
103 let access_key_id = event.access_key_id().unwrap_or("").to_string();
104 let source_ip = event
105 .cloud_trail_event()
106 .and_then(|json| {
107 serde_json::from_str::<serde_json::Value>(json)
108 .ok()
109 .and_then(|v| v["sourceIPAddress"].as_str().map(|s| s.to_string()))
110 })
111 .unwrap_or_default();
112 let error_code = event
113 .cloud_trail_event()
114 .and_then(|json| {
115 serde_json::from_str::<serde_json::Value>(json)
116 .ok()
117 .and_then(|v| v["errorCode"].as_str().map(|s| s.to_string()))
118 })
119 .unwrap_or_default();
120 let request_id = event
121 .cloud_trail_event()
122 .and_then(|json| {
123 serde_json::from_str::<serde_json::Value>(json)
124 .ok()
125 .and_then(|v| v["requestID"].as_str().map(|s| s.to_string()))
126 })
127 .unwrap_or_default();
128 let event_type = event
129 .cloud_trail_event()
130 .and_then(|json| {
131 serde_json::from_str::<serde_json::Value>(json)
132 .ok()
133 .and_then(|v| v["eventType"].as_str().map(|s| s.to_string()))
134 })
135 .unwrap_or_default();
136 let cloud_trail_event_json = event
137 .cloud_trail_event()
138 .and_then(|json| {
139 serde_json::from_str::<serde_json::Value>(json)
140 .ok()
141 .and_then(|v| serde_json::to_string_pretty(&v).ok())
142 })
143 .unwrap_or_else(|| "{}".to_string());
144
145 events.push((
146 event_name,
147 event_time,
148 username,
149 event_source,
150 resource_type,
151 resource_name,
152 read_only,
153 aws_region,
154 event_id,
155 access_key_id,
156 source_ip,
157 error_code,
158 request_id,
159 event_type,
160 cloud_trail_event_json,
161 ));
162 }
163
164 Ok((events, resp.next_token().map(|s| s.to_string())))
165 }
166
167 pub async fn get_next_token(&self, current_token: String) -> Result<Option<String>> {
169 let client = self.config.cloudtrail_client();
170 let resp = client
171 .lookup_events()
172 .max_results(1)
173 .next_token(current_token)
174 .send()
175 .await?;
176 Ok(resp.next_token().map(|s| s.to_string()))
177 }
178}