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    ) -> Result<(
18        Vec<(
19            String,
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        )>,
35        Option<String>,
36    )> {
37        let client = self.config.cloudtrail_client().await;
38
39        let mut request = client.lookup_events();
40        if let Some(max) = max_results {
41            request = request.max_results(max);
42        }
43        if let Some(token) = next_token {
44            request = request.next_token(token);
45        }
46
47        let resp = request.send().await?;
48
49        let mut events = Vec::new();
50        for event in resp.events() {
51            let event_name = event.event_name().unwrap_or("").to_string();
52            let event_time = event
53                .event_time()
54                .map(|t| {
55                    let dt = chrono::DateTime::parse_from_rfc3339(&t.to_string())
56                        .unwrap_or_else(|_| chrono::Utc::now().into());
57                    format!("{} (UTC)", dt.format("%Y-%m-%d %H:%M:%S"))
58                })
59                .unwrap_or_default();
60            let username = event.username().unwrap_or("").to_string();
61            let event_source = event
62                .cloud_trail_event()
63                .and_then(|json| {
64                    serde_json::from_str::<serde_json::Value>(json)
65                        .ok()
66                        .and_then(|v| v["eventSource"].as_str().map(|s| s.to_string()))
67                })
68                .unwrap_or_default();
69            let resource_type = event
70                .resources()
71                .first()
72                .and_then(|r| r.resource_type())
73                .unwrap_or("")
74                .to_string();
75            let resource_name = event
76                .resources()
77                .first()
78                .and_then(|r| r.resource_name())
79                .unwrap_or("")
80                .to_string();
81            let read_only = event.read_only().map(|b| b.to_string()).unwrap_or_default();
82            let aws_region = event
83                .cloud_trail_event()
84                .and_then(|json| {
85                    serde_json::from_str::<serde_json::Value>(json)
86                        .ok()
87                        .and_then(|v| v["awsRegion"].as_str().map(|s| s.to_string()))
88                })
89                .unwrap_or_default();
90            let event_id = event.event_id().unwrap_or("").to_string();
91            let access_key_id = event.access_key_id().unwrap_or("").to_string();
92            let source_ip = event
93                .cloud_trail_event()
94                .and_then(|json| {
95                    serde_json::from_str::<serde_json::Value>(json)
96                        .ok()
97                        .and_then(|v| v["sourceIPAddress"].as_str().map(|s| s.to_string()))
98                })
99                .unwrap_or_default();
100            let error_code = event
101                .cloud_trail_event()
102                .and_then(|json| {
103                    serde_json::from_str::<serde_json::Value>(json)
104                        .ok()
105                        .and_then(|v| v["errorCode"].as_str().map(|s| s.to_string()))
106                })
107                .unwrap_or_default();
108            let request_id = event
109                .cloud_trail_event()
110                .and_then(|json| {
111                    serde_json::from_str::<serde_json::Value>(json)
112                        .ok()
113                        .and_then(|v| v["requestID"].as_str().map(|s| s.to_string()))
114                })
115                .unwrap_or_default();
116            let event_type = event
117                .cloud_trail_event()
118                .and_then(|json| {
119                    serde_json::from_str::<serde_json::Value>(json)
120                        .ok()
121                        .and_then(|v| v["eventType"].as_str().map(|s| s.to_string()))
122                })
123                .unwrap_or_default();
124            let cloud_trail_event_json = event
125                .cloud_trail_event()
126                .and_then(|json| {
127                    serde_json::from_str::<serde_json::Value>(json)
128                        .ok()
129                        .and_then(|v| serde_json::to_string_pretty(&v).ok())
130                })
131                .unwrap_or_else(|| "{}".to_string());
132
133            events.push((
134                event_name,
135                event_time,
136                username,
137                event_source,
138                resource_type,
139                resource_name,
140                read_only,
141                aws_region,
142                event_id,
143                access_key_id,
144                source_ip,
145                error_code,
146                request_id,
147                event_type,
148                cloud_trail_event_json,
149            ));
150        }
151
152        Ok((events, resp.next_token().map(|s| s.to_string())))
153    }
154
155    /// Fetch minimal data (1 event) just to get the next token
156    pub async fn get_next_token(&self, current_token: String) -> Result<Option<String>> {
157        let client = self.config.cloudtrail_client().await;
158        let resp = client
159            .lookup_events()
160            .max_results(1)
161            .next_token(current_token)
162            .send()
163            .await?;
164        Ok(resp.next_token().map(|s| s.to_string()))
165    }
166}