Skip to main content

rusticity_core/
cloudtrail.rs

1use 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    /// Fetch minimal data (1 event) just to get the next token
168    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}