1use std::sync::Arc;
8
9use async_trait::async_trait;
10use http::StatusCode;
11use serde_json::{json, Map, Value};
12use tokio::sync::Mutex as AsyncMutex;
13use uuid::Uuid;
14
15use fakecloud_core::service::{AwsRequest, AwsResponse, AwsService, AwsServiceError};
16use fakecloud_persistence::SnapshotStore;
17
18use crate::persistence::save_snapshot;
19use crate::state::{CloudTrailData, SharedCloudTrailState};
20use crate::validate::validate_input;
21
22#[cfg(test)]
23mod tests;
24
25pub const CLOUDTRAIL_ACTIONS: &[&str] = &[
27 "AddTags",
28 "CancelQuery",
29 "CreateChannel",
30 "CreateDashboard",
31 "CreateEventDataStore",
32 "CreateTrail",
33 "DeleteChannel",
34 "DeleteDashboard",
35 "DeleteEventDataStore",
36 "DeleteResourcePolicy",
37 "DeleteTrail",
38 "DeregisterOrganizationDelegatedAdmin",
39 "DescribeQuery",
40 "DescribeTrails",
41 "DisableFederation",
42 "EnableFederation",
43 "GenerateQuery",
44 "GetChannel",
45 "GetDashboard",
46 "GetEventConfiguration",
47 "GetEventDataStore",
48 "GetEventSelectors",
49 "GetImport",
50 "GetInsightSelectors",
51 "GetQueryResults",
52 "GetResourcePolicy",
53 "GetTrail",
54 "GetTrailStatus",
55 "ListChannels",
56 "ListDashboards",
57 "ListEventDataStores",
58 "ListImportFailures",
59 "ListImports",
60 "ListInsightsData",
61 "ListInsightsMetricData",
62 "ListPublicKeys",
63 "ListQueries",
64 "ListTags",
65 "ListTrails",
66 "LookupEvents",
67 "PutEventConfiguration",
68 "PutEventSelectors",
69 "PutInsightSelectors",
70 "PutResourcePolicy",
71 "RegisterOrganizationDelegatedAdmin",
72 "RemoveTags",
73 "RestoreEventDataStore",
74 "SearchSampleQueries",
75 "StartDashboardRefresh",
76 "StartEventDataStoreIngestion",
77 "StartImport",
78 "StartLogging",
79 "StartQuery",
80 "StopEventDataStoreIngestion",
81 "StopImport",
82 "StopLogging",
83 "UpdateChannel",
84 "UpdateDashboard",
85 "UpdateEventDataStore",
86 "UpdateTrail",
87];
88
89pub struct CloudTrailService {
90 state: SharedCloudTrailState,
91 snapshot_store: Option<Arc<dyn SnapshotStore>>,
92 snapshot_lock: Arc<AsyncMutex<()>>,
93}
94
95impl CloudTrailService {
96 pub fn new(state: SharedCloudTrailState) -> Self {
97 Self {
98 state,
99 snapshot_store: None,
100 snapshot_lock: Arc::new(AsyncMutex::new(())),
101 }
102 }
103
104 pub fn with_snapshot_store(mut self, store: Arc<dyn SnapshotStore>) -> Self {
105 self.snapshot_store = Some(store);
106 self
107 }
108
109 async fn save(&self) {
110 save_snapshot(
111 &self.state,
112 self.snapshot_store.clone(),
113 &self.snapshot_lock,
114 )
115 .await;
116 }
117}
118
119#[async_trait]
120impl AwsService for CloudTrailService {
121 fn service_name(&self) -> &str {
122 "cloudtrail"
123 }
124
125 async fn handle(&self, request: AwsRequest) -> Result<AwsResponse, AwsServiceError> {
126 let mutates = is_mutating(request.action.as_str());
127 let result = dispatch(self, &request);
128 if mutates && matches!(result.as_ref(), Ok(resp) if resp.status.is_success()) {
129 self.save().await;
130 }
131 result
132 }
133
134 fn supported_actions(&self) -> &[&str] {
135 CLOUDTRAIL_ACTIONS
136 }
137}
138
139fn is_mutating(action: &str) -> bool {
141 !(action.starts_with("Describe")
142 || action.starts_with("List")
143 || action.starts_with("Get")
144 || action == "LookupEvents"
145 || action == "GenerateQuery")
146}
147
148fn dispatch(s: &CloudTrailService, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
149 let b = parse(req)?;
150 validate_input(req.action.as_str(), &b)?;
151 let ctx = Ctx {
152 account: req.account_id.clone(),
153 region: req.region.clone(),
154 };
155 match req.action.as_str() {
156 "CreateTrail" => s.create_trail(&ctx, &b),
158 "GetTrail" => s.get_trail(&ctx, &b),
159 "UpdateTrail" => s.update_trail(&ctx, &b),
160 "DeleteTrail" => s.delete_trail(&ctx, &b),
161 "DescribeTrails" => s.describe_trails(&ctx, &b),
162 "ListTrails" => s.list_trails(&ctx, &b),
163 "GetTrailStatus" => s.get_trail_status(&ctx, &b),
164 "StartLogging" => s.set_logging(&ctx, &b, true),
165 "StopLogging" => s.set_logging(&ctx, &b, false),
166 "GetEventSelectors" => s.get_event_selectors(&ctx, &b),
168 "PutEventSelectors" => s.put_event_selectors(&ctx, &b),
169 "GetInsightSelectors" => s.get_insight_selectors(&ctx, &b),
170 "PutInsightSelectors" => s.put_insight_selectors(&ctx, &b),
171 "CreateEventDataStore" => s.create_event_data_store(&ctx, &b),
173 "GetEventDataStore" => s.get_event_data_store(&ctx, &b),
174 "UpdateEventDataStore" => s.update_event_data_store(&ctx, &b),
175 "DeleteEventDataStore" => s.delete_event_data_store(&ctx, &b),
176 "ListEventDataStores" => s.list_event_data_stores(&ctx, &b),
177 "RestoreEventDataStore" => s.restore_event_data_store(&ctx, &b),
178 "StartEventDataStoreIngestion" => s.set_ingestion(&ctx, &b, true),
179 "StopEventDataStoreIngestion" => s.set_ingestion(&ctx, &b, false),
180 "EnableFederation" => s.enable_federation(&ctx, &b),
181 "DisableFederation" => s.disable_federation(&ctx, &b),
182 "CreateChannel" => s.create_channel(&ctx, &b),
184 "GetChannel" => s.get_channel(&ctx, &b),
185 "UpdateChannel" => s.update_channel(&ctx, &b),
186 "DeleteChannel" => s.delete_channel(&ctx, &b),
187 "ListChannels" => s.list_channels(&ctx, &b),
188 "StartImport" => s.start_import(&ctx, &b),
190 "StopImport" => s.stop_import(&ctx, &b),
191 "GetImport" => s.get_import(&ctx, &b),
192 "ListImports" => s.list_imports(&ctx, &b),
193 "ListImportFailures" => s.list_import_failures(&ctx, &b),
194 "StartQuery" => s.start_query(&ctx, &b),
196 "DescribeQuery" => s.describe_query(&ctx, &b),
197 "GetQueryResults" => s.get_query_results(&ctx, &b),
198 "CancelQuery" => s.cancel_query(&ctx, &b),
199 "ListQueries" => s.list_queries(&ctx, &b),
200 "GenerateQuery" => s.generate_query(&ctx, &b),
201 "CreateDashboard" => s.create_dashboard(&ctx, &b),
203 "GetDashboard" => s.get_dashboard(&ctx, &b),
204 "UpdateDashboard" => s.update_dashboard(&ctx, &b),
205 "DeleteDashboard" => s.delete_dashboard(&ctx, &b),
206 "ListDashboards" => s.list_dashboards(&ctx, &b),
207 "StartDashboardRefresh" => s.start_dashboard_refresh(&ctx, &b),
208 "PutResourcePolicy" => s.put_resource_policy(&ctx, &b),
210 "GetResourcePolicy" => s.get_resource_policy(&ctx, &b),
211 "DeleteResourcePolicy" => s.delete_resource_policy(&ctx, &b),
212 "RegisterOrganizationDelegatedAdmin" => s.register_delegated_admin(&ctx, &b),
214 "DeregisterOrganizationDelegatedAdmin" => s.deregister_delegated_admin(&ctx, &b),
215 "GetEventConfiguration" => s.get_event_configuration(&ctx, &b),
217 "PutEventConfiguration" => s.put_event_configuration(&ctx, &b),
218 "AddTags" => s.add_tags(&ctx, &b),
220 "RemoveTags" => s.remove_tags(&ctx, &b),
221 "ListTags" => s.list_tags(&ctx, &b),
222 "LookupEvents" => lookup_events(&b),
224 "ListPublicKeys" => list_public_keys(),
225 "ListInsightsData" => list_insights_data(&b),
226 "ListInsightsMetricData" => list_insights_metric_data(&b),
227 "SearchSampleQueries" => search_sample_queries(&b),
228 _ => Err(AwsServiceError::action_not_implemented(
229 s.service_name(),
230 &req.action,
231 )),
232 }
233}
234
235struct Ctx {
239 account: String,
240 region: String,
241}
242
243fn ok(v: Value) -> Result<AwsResponse, AwsServiceError> {
244 Ok(AwsResponse::json_value(StatusCode::OK, v))
245}
246
247fn empty_ok() -> Result<AwsResponse, AwsServiceError> {
248 ok(json!({}))
249}
250
251fn parse(req: &AwsRequest) -> Result<Value, AwsServiceError> {
252 if req.body.is_empty() {
253 return Ok(json!({}));
254 }
255 serde_json::from_slice(&req.body)
256 .map_err(|e| invalid_param(&format!("Request body is malformed: {e}")))
257}
258
259fn err(code: &str, msg: &str) -> AwsServiceError {
260 AwsServiceError::aws_error(StatusCode::BAD_REQUEST, code, msg)
261}
262
263fn invalid_param(msg: &str) -> AwsServiceError {
264 err("InvalidParameterException", msg)
265}
266
267fn trail_not_found(name: &str) -> AwsServiceError {
268 err(
269 "TrailNotFoundException",
270 &format!("Unknown trail: {name} for the given account."),
271 )
272}
273
274fn eds_not_found(arn: &str) -> AwsServiceError {
275 err(
276 "EventDataStoreNotFoundException",
277 &format!("The specified event data store {arn} was not found."),
278 )
279}
280
281fn channel_not_found(arn: &str) -> AwsServiceError {
282 err(
283 "ChannelNotFoundException",
284 &format!("The specified channel {arn} was not found."),
285 )
286}
287
288fn import_not_found(id: &str) -> AwsServiceError {
289 err(
290 "ImportNotFoundException",
291 &format!("The specified import {id} was not found."),
292 )
293}
294
295fn query_not_found(id: &str) -> AwsServiceError {
296 err(
297 "QueryIdNotFoundException",
298 &format!("The specified query ID {id} was not found."),
299 )
300}
301
302fn dashboard_not_found(id: &str) -> AwsServiceError {
303 err(
304 "ResourceNotFoundException",
305 &format!("The specified dashboard {id} was not found."),
306 )
307}
308
309fn req_str<'a>(b: &'a Value, f: &str) -> Result<&'a str, AwsServiceError> {
311 b.get(f)
312 .and_then(Value::as_str)
313 .filter(|s| !s.is_empty())
314 .ok_or_else(|| invalid_param(&format!("{f} is required.")))
315}
316
317fn opt_str<'a>(b: &'a Value, f: &str) -> Option<&'a str> {
318 b.get(f).and_then(Value::as_str)
319}
320
321fn now_ts() -> f64 {
322 chrono::Utc::now().timestamp() as f64
323}
324
325fn now_iso() -> String {
328 chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true)
329}
330
331fn new_uuid() -> String {
332 Uuid::new_v4().to_string()
333}
334
335fn trail_key(name: &str) -> &str {
338 match name.rsplit_once(":trail/") {
339 Some((_, n)) => n,
340 None => name,
341 }
342}
343
344fn trail_arn(ctx: &Ctx, name: &str) -> String {
345 format!(
346 "arn:aws:cloudtrail:{}:{}:trail/{}",
347 ctx.region, ctx.account, name
348 )
349}
350
351fn paginate(rows: Vec<Value>, b: &Value, default_max: usize) -> (Vec<Value>, Option<String>) {
354 let start = b
355 .get("NextToken")
356 .and_then(Value::as_str)
357 .and_then(|t| t.parse::<usize>().ok())
358 .unwrap_or(0);
359 let max = b
360 .get("MaxResults")
361 .and_then(Value::as_u64)
362 .map(|m| m.clamp(1, 10_000) as usize)
363 .unwrap_or(default_max);
364 let end = start.saturating_add(max).min(rows.len());
365 let page = rows.get(start..end).unwrap_or(&[]).to_vec();
366 let next = if end < rows.len() {
371 Some(format!("{end:04}"))
372 } else {
373 None
374 };
375 (page, next)
376}
377
378fn list_response(key: &str, rows: Vec<Value>, b: &Value) -> Result<AwsResponse, AwsServiceError> {
379 let (page, next) = paginate(rows, b, 100);
380 let mut out = Map::new();
381 out.insert(key.to_string(), Value::Array(page));
382 if let Some(t) = next {
383 out.insert("NextToken".to_string(), json!(t));
384 }
385 ok(Value::Object(out))
386}
387
388fn copy_fields(src: &Value, dst: &mut Map<String, Value>, fields: &[&str]) {
390 for f in fields {
391 if let Some(v) = src.get(*f) {
392 dst.insert((*f).to_string(), v.clone());
393 }
394 }
395}
396
397fn store_tags(data: &mut CloudTrailData, key: &str, b: &Value) {
399 let tags = b
400 .get("TagsList")
401 .or_else(|| b.get("Tags"))
402 .and_then(Value::as_array);
403 if let Some(tags) = tags {
404 let entry = data.tags.entry(key.to_string()).or_default();
405 for t in tags {
406 if let Some(k) = t.get("Key").and_then(Value::as_str) {
407 let v = t.get("Value").and_then(Value::as_str).unwrap_or("");
408 entry.insert(k.to_string(), v.to_string());
409 }
410 }
411 }
412}
413
414const TRAIL_INPUT_FIELDS: &[&str] = &[
418 "S3BucketName",
419 "S3KeyPrefix",
420 "IncludeGlobalServiceEvents",
421 "IsMultiRegionTrail",
422 "CloudWatchLogsLogGroupArn",
423 "CloudWatchLogsRoleArn",
424 "KmsKeyId",
425 "IsOrganizationTrail",
426];
427
428impl CloudTrailService {
429 fn build_trail(&self, ctx: &Ctx, name: &str, b: &Value, existing: Option<&Value>) -> Value {
430 let mut t = Map::new();
431 if let Some(prev) = existing {
432 if let Some(obj) = prev.as_object() {
433 t = obj.clone();
434 }
435 }
436 t.insert("Name".into(), json!(name));
437 t.insert("TrailARN".into(), json!(trail_arn(ctx, name)));
438 t.insert("HomeRegion".into(), json!(ctx.region));
439 copy_fields(b, &mut t, TRAIL_INPUT_FIELDS);
440 t.entry("IncludeGlobalServiceEvents").or_insert(json!(true));
442 t.entry("IsMultiRegionTrail").or_insert(json!(false));
443 t.entry("IsOrganizationTrail").or_insert(json!(false));
444 if let Some(sns) = opt_str(b, "SnsTopicName") {
446 t.insert("SnsTopicName".into(), json!(sns));
447 t.insert(
448 "SnsTopicARN".into(),
449 json!(format!(
450 "arn:aws:sns:{}:{}:{}",
451 ctx.region, ctx.account, sns
452 )),
453 );
454 }
455 let validation = b
456 .get("EnableLogFileValidation")
457 .and_then(Value::as_bool)
458 .unwrap_or_else(|| {
459 existing
460 .and_then(|e| e.get("LogFileValidationEnabled"))
461 .and_then(Value::as_bool)
462 .unwrap_or(false)
463 });
464 t.insert("LogFileValidationEnabled".into(), json!(validation));
465 t.entry("HasCustomEventSelectors").or_insert(json!(false));
466 t.entry("HasInsightSelectors").or_insert(json!(false));
467 Value::Object(t)
468 }
469
470 fn trail_mutation_output(trail: &Value) -> Value {
472 let mut out = Map::new();
473 copy_fields(
474 trail,
475 &mut out,
476 &[
477 "Name",
478 "S3BucketName",
479 "S3KeyPrefix",
480 "SnsTopicName",
481 "SnsTopicARN",
482 "IncludeGlobalServiceEvents",
483 "IsMultiRegionTrail",
484 "TrailARN",
485 "LogFileValidationEnabled",
486 "CloudWatchLogsLogGroupArn",
487 "CloudWatchLogsRoleArn",
488 "KmsKeyId",
489 "IsOrganizationTrail",
490 ],
491 );
492 Value::Object(out)
493 }
494
495 fn create_trail(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
496 let name = req_str(b, "Name")?.to_string();
497 req_str(b, "S3BucketName")?;
498 let mut guard = self.state.write();
499 let data = guard.get_or_create(&ctx.account);
500 if data.trails.contains_key(&name) {
501 return Err(err(
502 "TrailAlreadyExistsException",
503 &format!("Trail {name} already exists."),
504 ));
505 }
506 let trail = self.build_trail(ctx, &name, b, None);
507 let arn = trail_arn(ctx, &name);
508 store_tags(data, &arn, b);
509 data.trails.insert(name.clone(), trail.clone());
510 data.trail_logging.insert(name, false);
511 ok(Self::trail_mutation_output(&trail))
512 }
513
514 fn get_trail(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
515 let name = trail_key(req_str(b, "Name")?).to_string();
516 let guard = self.state.read();
517 let data = guard.get(&ctx.account);
518 match data.and_then(|d| d.trails.get(&name)) {
519 Some(t) => ok(json!({ "Trail": t })),
520 None => Err(trail_not_found(&name)),
521 }
522 }
523
524 fn update_trail(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
525 let name = trail_key(req_str(b, "Name")?).to_string();
526 let mut guard = self.state.write();
527 let data = guard.get_or_create(&ctx.account);
528 let existing = data
529 .trails
530 .get(&name)
531 .cloned()
532 .ok_or_else(|| trail_not_found(&name))?;
533 let trail = self.build_trail(ctx, &name, b, Some(&existing));
534 data.trails.insert(name, trail.clone());
535 ok(Self::trail_mutation_output(&trail))
536 }
537
538 fn delete_trail(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
539 let name = trail_key(req_str(b, "Name")?).to_string();
540 let mut guard = self.state.write();
541 let data = guard.get_or_create(&ctx.account);
542 if data.trails.remove(&name).is_none() {
543 return Err(trail_not_found(&name));
544 }
545 data.trail_logging.remove(&name);
546 data.trail_status.remove(&name);
547 data.event_selectors.remove(&name);
548 data.insight_selectors.remove(&name);
549 let arn = trail_arn(ctx, &name);
553 data.tags.remove(&arn);
554 data.event_configurations.remove(&arn);
555 empty_ok()
556 }
557
558 fn describe_trails(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
559 let guard = self.state.read();
560 let data = guard.get(&ctx.account);
561 let wanted: Option<Vec<String>> =
562 b.get("trailNameList").and_then(Value::as_array).map(|a| {
563 a.iter()
564 .filter_map(|v| v.as_str())
565 .map(|s| trail_key(s).to_string())
566 .collect()
567 });
568 let mut list = Vec::new();
569 if let Some(data) = data {
570 match &wanted {
571 Some(names) => {
572 for n in names {
573 if let Some(t) = data.trails.get(n) {
574 list.push(t.clone());
575 }
576 }
577 }
578 None => list.extend(data.trails.values().cloned()),
579 }
580 }
581 ok(json!({ "trailList": list }))
582 }
583
584 fn list_trails(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
585 let guard = self.state.read();
586 let rows: Vec<Value> = guard
587 .get(&ctx.account)
588 .map(|d| {
589 d.trails
590 .values()
591 .map(|t| {
592 json!({
593 "TrailARN": t.get("TrailARN").cloned().unwrap_or(Value::Null),
594 "Name": t.get("Name").cloned().unwrap_or(Value::Null),
595 "HomeRegion": t.get("HomeRegion").cloned().unwrap_or(Value::Null),
596 })
597 })
598 .collect()
599 })
600 .unwrap_or_default();
601 list_response("Trails", rows, b)
602 }
603
604 fn get_trail_status(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
605 let name = trail_key(req_str(b, "Name")?).to_string();
606 let guard = self.state.read();
607 let data = guard.get(&ctx.account);
608 let Some(data) = data else {
609 return Err(trail_not_found(&name));
610 };
611 if !data.trails.contains_key(&name) {
612 return Err(trail_not_found(&name));
613 }
614 let is_logging = data.trail_logging.get(&name).copied().unwrap_or(false);
615 let mut out = Map::new();
616 out.insert("IsLogging".into(), json!(is_logging));
617 if let Some(status) = data.trail_status.get(&name) {
618 if let Some(obj) = status.as_object() {
619 for (k, v) in obj {
620 out.insert(k.clone(), v.clone());
621 }
622 }
623 }
624 ok(Value::Object(out))
625 }
626
627 fn set_logging(
628 &self,
629 ctx: &Ctx,
630 b: &Value,
631 logging: bool,
632 ) -> Result<AwsResponse, AwsServiceError> {
633 let name = trail_key(req_str(b, "Name")?).to_string();
634 let mut guard = self.state.write();
635 let data = guard.get_or_create(&ctx.account);
636 if !data.trails.contains_key(&name) {
637 return Err(trail_not_found(&name));
638 }
639 data.trail_logging.insert(name.clone(), logging);
640 let status = data.trail_status.entry(name).or_insert_with(|| json!({}));
641 if let Some(obj) = status.as_object_mut() {
642 if logging {
643 obj.insert("StartLoggingTime".into(), json!(now_ts()));
644 obj.insert("TimeLoggingStarted".into(), json!(now_iso()));
645 } else {
646 obj.insert("StopLoggingTime".into(), json!(now_ts()));
647 obj.insert("TimeLoggingStopped".into(), json!(now_iso()));
648 }
649 }
650 empty_ok()
651 }
652}
653
654impl CloudTrailService {
657 fn get_event_selectors(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
658 let name = trail_key(req_str(b, "TrailName")?).to_string();
659 let guard = self.state.read();
660 let data = guard.get(&ctx.account);
661 let Some(data) = data.filter(|d| d.trails.contains_key(&name)) else {
662 return Err(trail_not_found(&name));
663 };
664 let arn = trail_arn(ctx, &name);
665 match data.event_selectors.get(&name) {
666 Some(sel) => {
667 let mut out = Map::new();
668 out.insert("TrailARN".into(), json!(arn));
669 if let Some(obj) = sel.as_object() {
670 for (k, v) in obj {
671 out.insert(k.clone(), v.clone());
672 }
673 }
674 ok(Value::Object(out))
675 }
676 None => ok(json!({
677 "TrailARN": arn,
678 "EventSelectors": [{
679 "ReadWriteType": "All",
680 "IncludeManagementEvents": true,
681 "DataResources": [],
682 "ExcludeManagementEventSources": [],
683 }],
684 })),
685 }
686 }
687
688 fn put_event_selectors(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
689 let name = trail_key(req_str(b, "TrailName")?).to_string();
690 let mut guard = self.state.write();
691 let data = guard.get_or_create(&ctx.account);
692 if !data.trails.contains_key(&name) {
693 return Err(trail_not_found(&name));
694 }
695 let mut sel = Map::new();
696 let mut out = Map::new();
697 out.insert("TrailARN".into(), json!(trail_arn(ctx, &name)));
698 if let Some(es) = b.get("EventSelectors") {
699 sel.insert("EventSelectors".into(), es.clone());
700 out.insert("EventSelectors".into(), es.clone());
701 }
702 if let Some(aes) = b.get("AdvancedEventSelectors") {
703 sel.insert("AdvancedEventSelectors".into(), aes.clone());
704 out.insert("AdvancedEventSelectors".into(), aes.clone());
705 }
706 data.event_selectors
707 .insert(name.clone(), Value::Object(sel));
708 if let Some(t) = data.trails.get_mut(&name) {
709 if let Some(obj) = t.as_object_mut() {
710 obj.insert("HasCustomEventSelectors".into(), json!(true));
711 }
712 }
713 ok(Value::Object(out))
714 }
715
716 fn get_insight_selectors(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
717 if let Some(eds) = opt_str(b, "EventDataStore").filter(|s| !s.is_empty()) {
721 let guard = self.state.read();
722 let sel = guard
723 .get(&ctx.account)
724 .and_then(|d| d.insight_selectors.get(eds))
725 .cloned()
726 .ok_or_else(|| {
727 err(
728 "InsightNotEnabledException",
729 "Insights are not enabled on the given event data store.",
730 )
731 })?;
732 let mut out = sel.as_object().cloned().unwrap_or_default();
733 out.insert("EventDataStoreArn".into(), json!(eds));
734 return ok(Value::Object(out));
735 }
736 let name = trail_key(req_str(b, "TrailName")?).to_string();
737 let guard = self.state.read();
738 let data = guard.get(&ctx.account);
739 let Some(data) = data.filter(|d| d.trails.contains_key(&name)) else {
740 return Err(trail_not_found(&name));
741 };
742 let sel = data.insight_selectors.get(&name).cloned().ok_or_else(|| {
743 err(
744 "InsightNotEnabledException",
745 "Insights are not enabled on the given trail.",
746 )
747 })?;
748 let mut out = sel.as_object().cloned().unwrap_or_default();
749 out.insert("TrailARN".into(), json!(trail_arn(ctx, &name)));
750 ok(Value::Object(out))
751 }
752
753 fn put_insight_selectors(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
754 let insight = b
755 .get("InsightSelectors")
756 .cloned()
757 .ok_or_else(|| invalid_param("InsightSelectors is required."))?;
758 if let Some(eds) = opt_str(b, "EventDataStore").filter(|s| !s.is_empty()) {
762 let mut guard = self.state.write();
763 let data = guard.get_or_create(&ctx.account);
764 let stored = json!({ "InsightSelectors": insight });
765 data.insight_selectors.insert(eds.to_string(), stored);
766 let mut out = Map::new();
767 out.insert("EventDataStoreArn".into(), json!(eds));
768 out.insert("InsightSelectors".into(), b["InsightSelectors"].clone());
769 if let Some(dest) = opt_str(b, "InsightsDestination") {
770 out.insert("InsightsDestination".into(), json!(dest));
771 }
772 return ok(Value::Object(out));
773 }
774 let name = trail_key(
775 opt_str(b, "TrailName")
776 .filter(|s| !s.is_empty())
777 .ok_or_else(|| {
778 err(
779 "InvalidParameterCombinationException",
780 "Either TrailName or EventDataStore must be specified.",
781 )
782 })?,
783 )
784 .to_string();
785 let mut guard = self.state.write();
786 let data = guard.get_or_create(&ctx.account);
787 if !data.trails.contains_key(&name) {
788 return Err(trail_not_found(&name));
789 }
790 let stored = json!({ "InsightSelectors": insight });
791 data.insight_selectors.insert(name.clone(), stored);
792 if let Some(t) = data.trails.get_mut(&name) {
793 if let Some(obj) = t.as_object_mut() {
794 obj.insert("HasInsightSelectors".into(), json!(true));
795 }
796 }
797 ok(json!({
798 "TrailARN": trail_arn(ctx, &name),
799 "InsightSelectors": b["InsightSelectors"],
800 }))
801 }
802}
803
804const EDS_OUTPUT_FIELDS: &[&str] = &[
807 "EventDataStoreArn",
808 "Name",
809 "Status",
810 "AdvancedEventSelectors",
811 "MultiRegionEnabled",
812 "OrganizationEnabled",
813 "RetentionPeriod",
814 "TerminationProtectionEnabled",
815 "CreatedTimestamp",
816 "UpdatedTimestamp",
817 "KmsKeyId",
818 "BillingMode",
819 "FederationStatus",
820 "FederationRoleArn",
821];
822
823impl CloudTrailService {
824 fn eds_output(eds: &Value) -> Value {
825 let mut out = Map::new();
826 copy_fields(eds, &mut out, EDS_OUTPUT_FIELDS);
827 Value::Object(out)
828 }
829
830 fn eds_summary(eds: &Value) -> Value {
833 let mut out = Map::new();
834 copy_fields(
835 eds,
836 &mut out,
837 &[
838 "EventDataStoreArn",
839 "Name",
840 "TerminationProtectionEnabled",
841 "Status",
842 "AdvancedEventSelectors",
843 "MultiRegionEnabled",
844 "OrganizationEnabled",
845 "RetentionPeriod",
846 "CreatedTimestamp",
847 "UpdatedTimestamp",
848 ],
849 );
850 Value::Object(out)
851 }
852
853 fn create_event_data_store(
854 &self,
855 ctx: &Ctx,
856 b: &Value,
857 ) -> Result<AwsResponse, AwsServiceError> {
858 let name = req_str(b, "Name")?.to_string();
859 let mut guard = self.state.write();
860 let data = guard.get_or_create(&ctx.account);
861 if data
862 .event_data_stores
863 .values()
864 .any(|e| e.get("Name").and_then(Value::as_str) == Some(&name))
865 {
866 return Err(err(
867 "EventDataStoreAlreadyExistsException",
868 &format!("An event data store already exists with the name {name}."),
869 ));
870 }
871 let arn = format!(
872 "arn:aws:cloudtrail:{}:{}:eventdatastore/{}",
873 ctx.region,
874 ctx.account,
875 new_uuid()
876 );
877 let mut eds = Map::new();
878 eds.insert("EventDataStoreArn".into(), json!(arn));
879 eds.insert("Name".into(), json!(name));
880 eds.insert("Status".into(), json!("ENABLED"));
881 eds.insert(
885 "AdvancedEventSelectors".into(),
886 b.get("AdvancedEventSelectors").cloned().unwrap_or_else(|| {
887 json!([{
888 "Name": "Default management events",
889 "FieldSelectors": [{
890 "Field": "eventCategory",
891 "Equals": ["Management"]
892 }]
893 }])
894 }),
895 );
896 eds.insert(
897 "MultiRegionEnabled".into(),
898 b.get("MultiRegionEnabled").cloned().unwrap_or(json!(true)),
899 );
900 eds.insert(
901 "OrganizationEnabled".into(),
902 b.get("OrganizationEnabled")
903 .cloned()
904 .unwrap_or(json!(false)),
905 );
906 eds.insert(
907 "RetentionPeriod".into(),
908 b.get("RetentionPeriod").cloned().unwrap_or(json!(2555)),
909 );
910 eds.insert(
911 "TerminationProtectionEnabled".into(),
912 b.get("TerminationProtectionEnabled")
913 .cloned()
914 .unwrap_or(json!(true)),
915 );
916 eds.insert(
917 "BillingMode".into(),
918 b.get("BillingMode")
919 .cloned()
920 .unwrap_or(json!("EXTENDABLE_RETENTION_PRICING")),
921 );
922 eds.insert("CreatedTimestamp".into(), json!(now_ts()));
923 eds.insert("UpdatedTimestamp".into(), json!(now_ts()));
924 copy_fields(b, &mut eds, &["KmsKeyId"]);
925 if let Some(tags) = b.get("TagsList") {
926 eds.insert("TagsList".into(), tags.clone());
927 }
928 let eds = Value::Object(eds);
929 store_tags(data, &arn, b);
930 data.event_data_stores.insert(arn, eds.clone());
931 ok(eds)
932 }
933
934 fn get_event_data_store(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
935 let arn = req_str(b, "EventDataStore")?.to_string();
936 let guard = self.state.read();
937 match guard
938 .get(&ctx.account)
939 .and_then(|d| d.event_data_stores.get(&arn))
940 {
941 Some(eds) => ok(Self::eds_output(eds)),
942 None => Err(eds_not_found(&arn)),
943 }
944 }
945
946 fn update_event_data_store(
947 &self,
948 ctx: &Ctx,
949 b: &Value,
950 ) -> Result<AwsResponse, AwsServiceError> {
951 let arn = req_str(b, "EventDataStore")?.to_string();
952 let mut guard = self.state.write();
953 let data = guard.get_or_create(&ctx.account);
954 let eds = data
955 .event_data_stores
956 .get_mut(&arn)
957 .ok_or_else(|| eds_not_found(&arn))?;
958 if let Some(obj) = eds.as_object_mut() {
959 copy_fields(
960 b,
961 obj,
962 &[
963 "Name",
964 "AdvancedEventSelectors",
965 "MultiRegionEnabled",
966 "OrganizationEnabled",
967 "RetentionPeriod",
968 "TerminationProtectionEnabled",
969 "KmsKeyId",
970 "BillingMode",
971 ],
972 );
973 obj.insert("UpdatedTimestamp".into(), json!(now_ts()));
974 }
975 let eds = eds.clone();
976 ok(Self::eds_output(&eds))
977 }
978
979 fn delete_event_data_store(
980 &self,
981 ctx: &Ctx,
982 b: &Value,
983 ) -> Result<AwsResponse, AwsServiceError> {
984 let arn = req_str(b, "EventDataStore")?.to_string();
985 let mut guard = self.state.write();
986 let data = guard.get_or_create(&ctx.account);
987 let eds = data
988 .event_data_stores
989 .get_mut(&arn)
990 .ok_or_else(|| eds_not_found(&arn))?;
991 if eds
992 .get("TerminationProtectionEnabled")
993 .and_then(Value::as_bool)
994 == Some(true)
995 {
996 return Err(err(
997 "EventDataStoreTerminationProtectedException",
998 "The event data store cannot be deleted because termination protection is enabled.",
999 ));
1000 }
1001 if let Some(obj) = eds.as_object_mut() {
1003 obj.insert("Status".into(), json!("PENDING_DELETION"));
1004 obj.insert("UpdatedTimestamp".into(), json!(now_ts()));
1005 }
1006 empty_ok()
1007 }
1008
1009 fn list_event_data_stores(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1010 let guard = self.state.read();
1011 let rows: Vec<Value> = guard
1012 .get(&ctx.account)
1013 .map(|d| {
1014 d.event_data_stores
1015 .values()
1016 .map(Self::eds_summary)
1017 .collect()
1018 })
1019 .unwrap_or_default();
1020 list_response("EventDataStores", rows, b)
1021 }
1022
1023 fn restore_event_data_store(
1024 &self,
1025 ctx: &Ctx,
1026 b: &Value,
1027 ) -> Result<AwsResponse, AwsServiceError> {
1028 let arn = req_str(b, "EventDataStore")?.to_string();
1029 let mut guard = self.state.write();
1030 let data = guard.get_or_create(&ctx.account);
1031 let eds = data
1032 .event_data_stores
1033 .get_mut(&arn)
1034 .ok_or_else(|| eds_not_found(&arn))?;
1035 if let Some(obj) = eds.as_object_mut() {
1036 obj.insert("Status".into(), json!("ENABLED"));
1037 obj.insert("UpdatedTimestamp".into(), json!(now_ts()));
1038 }
1039 let eds = eds.clone();
1040 ok(Self::eds_output(&eds))
1041 }
1042
1043 fn set_ingestion(
1044 &self,
1045 ctx: &Ctx,
1046 b: &Value,
1047 start: bool,
1048 ) -> Result<AwsResponse, AwsServiceError> {
1049 let arn = req_str(b, "EventDataStore")?.to_string();
1050 let mut guard = self.state.write();
1051 let data = guard.get_or_create(&ctx.account);
1052 let eds = data
1053 .event_data_stores
1054 .get_mut(&arn)
1055 .ok_or_else(|| eds_not_found(&arn))?;
1056 if let Some(obj) = eds.as_object_mut() {
1057 obj.insert(
1058 "Status".into(),
1059 json!(if start {
1060 "ENABLED"
1061 } else {
1062 "STOPPED_INGESTION"
1063 }),
1064 );
1065 obj.insert("UpdatedTimestamp".into(), json!(now_ts()));
1066 }
1067 empty_ok()
1068 }
1069
1070 fn enable_federation(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1071 let arn = req_str(b, "EventDataStore")?.to_string();
1072 let role = req_str(b, "FederationRoleArn")?.to_string();
1073 let mut guard = self.state.write();
1074 let data = guard.get_or_create(&ctx.account);
1075 let eds = data
1076 .event_data_stores
1077 .get_mut(&arn)
1078 .ok_or_else(|| eds_not_found(&arn))?;
1079 if let Some(obj) = eds.as_object_mut() {
1080 obj.insert("FederationStatus".into(), json!("ENABLED"));
1081 obj.insert("FederationRoleArn".into(), json!(role));
1082 }
1083 ok(json!({
1084 "EventDataStoreArn": arn,
1085 "FederationStatus": "ENABLED",
1086 "FederationRoleArn": role,
1087 }))
1088 }
1089
1090 fn disable_federation(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1091 let arn = req_str(b, "EventDataStore")?.to_string();
1092 let mut guard = self.state.write();
1093 let data = guard.get_or_create(&ctx.account);
1094 let eds = data
1095 .event_data_stores
1096 .get_mut(&arn)
1097 .ok_or_else(|| eds_not_found(&arn))?;
1098 if let Some(obj) = eds.as_object_mut() {
1099 obj.insert("FederationStatus".into(), json!("DISABLED"));
1100 }
1101 ok(json!({
1102 "EventDataStoreArn": arn,
1103 "FederationStatus": "DISABLED",
1104 }))
1105 }
1106}
1107
1108impl CloudTrailService {
1111 fn create_channel(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1112 let name = req_str(b, "Name")?.to_string();
1113 let source = req_str(b, "Source")?.to_string();
1114 let destinations = b
1115 .get("Destinations")
1116 .cloned()
1117 .ok_or_else(|| invalid_param("Destinations is required."))?;
1118 let mut guard = self.state.write();
1119 let data = guard.get_or_create(&ctx.account);
1120 if data
1121 .channels
1122 .values()
1123 .any(|c| c.get("Name").and_then(Value::as_str) == Some(&name))
1124 {
1125 return Err(err(
1126 "ChannelAlreadyExistsException",
1127 &format!("A channel already exists with the name {name}."),
1128 ));
1129 }
1130 let arn = format!(
1131 "arn:aws:cloudtrail:{}:{}:channel/{}",
1132 ctx.region,
1133 ctx.account,
1134 new_uuid()
1135 );
1136 let source_config = b
1141 .get("SourceConfig")
1142 .cloned()
1143 .unwrap_or_else(|| json!({ "ApplyToAllRegions": false, "AdvancedEventSelectors": [] }));
1144 let mut chan = Map::new();
1145 chan.insert("ChannelArn".into(), json!(arn));
1146 chan.insert("Name".into(), json!(name));
1147 chan.insert("Source".into(), json!(source));
1148 chan.insert("Destinations".into(), destinations);
1149 chan.insert("SourceConfig".into(), source_config);
1150 chan.insert("IngestionStatus".into(), json!({}));
1151 if let Some(tags) = b.get("Tags") {
1152 chan.insert("Tags".into(), tags.clone());
1153 }
1154 store_tags(data, &arn, b);
1155 let chan = Value::Object(chan);
1156 data.channels.insert(arn, chan.clone());
1157 let mut out = chan.as_object().cloned().unwrap_or_default();
1159 out.remove("SourceConfig");
1160 out.remove("IngestionStatus");
1161 ok(Value::Object(out))
1162 }
1163
1164 fn get_channel(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1165 let arn = req_str(b, "Channel")?.to_string();
1166 let guard = self.state.read();
1167 let chan = guard
1168 .get(&ctx.account)
1169 .and_then(|d| d.channels.get(&arn))
1170 .cloned()
1171 .ok_or_else(|| channel_not_found(&arn))?;
1172 let mut out = chan.as_object().cloned().unwrap_or_default();
1173 out.remove("Tags");
1174 out.entry("SourceConfig")
1177 .or_insert_with(|| json!({ "ApplyToAllRegions": false, "AdvancedEventSelectors": [] }));
1178 out.entry("IngestionStatus").or_insert_with(|| json!({}));
1179 ok(Value::Object(out))
1180 }
1181
1182 fn update_channel(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1183 let arn = req_str(b, "Channel")?.to_string();
1184 let mut guard = self.state.write();
1185 let data = guard.get_or_create(&ctx.account);
1186 let chan = data
1187 .channels
1188 .get_mut(&arn)
1189 .ok_or_else(|| channel_not_found(&arn))?;
1190 if let Some(obj) = chan.as_object_mut() {
1191 if let Some(name) = b.get("Name") {
1192 obj.insert("Name".into(), name.clone());
1193 }
1194 if let Some(dests) = b.get("Destinations") {
1195 obj.insert("Destinations".into(), dests.clone());
1196 }
1197 }
1198 let chan = chan.clone();
1199 let mut out = chan.as_object().cloned().unwrap_or_default();
1200 out.remove("Tags");
1201 out.remove("SourceConfig");
1202 out.remove("IngestionStatus");
1203 ok(Value::Object(out))
1204 }
1205
1206 fn delete_channel(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1207 let arn = req_str(b, "Channel")?.to_string();
1208 let mut guard = self.state.write();
1209 let data = guard.get_or_create(&ctx.account);
1210 if data.channels.remove(&arn).is_none() {
1211 return Err(channel_not_found(&arn));
1212 }
1213 empty_ok()
1214 }
1215
1216 fn list_channels(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1217 let guard = self.state.read();
1218 let rows: Vec<Value> = guard
1219 .get(&ctx.account)
1220 .map(|d| {
1221 d.channels
1222 .values()
1223 .map(|c| {
1224 json!({
1225 "ChannelArn": c.get("ChannelArn").cloned().unwrap_or(Value::Null),
1226 "Name": c.get("Name").cloned().unwrap_or(Value::Null),
1227 })
1228 })
1229 .collect()
1230 })
1231 .unwrap_or_default();
1232 list_response("Channels", rows, b)
1233 }
1234}
1235
1236impl CloudTrailService {
1239 fn start_import(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1240 let mut guard = self.state.write();
1241 let data = guard.get_or_create(&ctx.account);
1242 if let Some(id) = opt_str(b, "ImportId").filter(|s| !s.is_empty()) {
1245 let imp = data
1246 .imports
1247 .get_mut(id)
1248 .ok_or_else(|| import_not_found(id))?;
1249 if let Some(obj) = imp.as_object_mut() {
1250 obj.insert("ImportStatus".into(), json!("IN_PROGRESS"));
1251 obj.insert("UpdatedTimestamp".into(), json!(now_ts()));
1252 }
1253 return ok(imp.clone());
1254 }
1255 let id = new_uuid();
1256 let mut imp = Map::new();
1257 imp.insert("ImportId".into(), json!(id));
1258 copy_fields(
1259 b,
1260 &mut imp,
1261 &[
1262 "Destinations",
1263 "ImportSource",
1264 "StartEventTime",
1265 "EndEventTime",
1266 ],
1267 );
1268 imp.insert("ImportStatus".into(), json!("INITIALIZING"));
1269 imp.insert("CreatedTimestamp".into(), json!(now_ts()));
1270 imp.insert("UpdatedTimestamp".into(), json!(now_ts()));
1271 let imp = Value::Object(imp);
1272 data.imports.insert(id, imp.clone());
1273 ok(imp)
1274 }
1275
1276 fn stop_import(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1277 let id = req_str(b, "ImportId")?.to_string();
1278 let mut guard = self.state.write();
1279 let data = guard.get_or_create(&ctx.account);
1280 let imp = data
1281 .imports
1282 .get_mut(&id)
1283 .ok_or_else(|| import_not_found(&id))?;
1284 if let Some(obj) = imp.as_object_mut() {
1285 obj.insert("ImportStatus".into(), json!("STOPPED"));
1286 obj.insert("UpdatedTimestamp".into(), json!(now_ts()));
1287 obj.entry("ImportStatistics").or_insert(json!({
1288 "FilesCompleted": 0,
1289 "EventsCompleted": 0,
1290 "FailedEntries": 0,
1291 }));
1292 }
1293 ok(imp.clone())
1294 }
1295
1296 fn get_import(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1297 let id = req_str(b, "ImportId")?.to_string();
1298 let guard = self.state.read();
1299 let mut imp = guard
1300 .get(&ctx.account)
1301 .and_then(|d| d.imports.get(&id))
1302 .cloned()
1303 .ok_or_else(|| import_not_found(&id))?;
1304 if let Some(obj) = imp.as_object_mut() {
1305 obj.entry("ImportStatistics").or_insert(json!({
1306 "FilesCompleted": 0,
1307 "EventsCompleted": 0,
1308 "FailedEntries": 0,
1309 }));
1310 }
1311 ok(imp)
1312 }
1313
1314 fn list_imports(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1315 let status_filter = opt_str(b, "ImportStatus").map(str::to_string);
1316 let guard = self.state.read();
1317 let rows: Vec<Value> = guard
1318 .get(&ctx.account)
1319 .map(|d| {
1320 d.imports
1321 .values()
1322 .filter(|imp| {
1323 status_filter.as_deref().is_none_or(|want| {
1324 imp.get("ImportStatus").and_then(Value::as_str) == Some(want)
1325 })
1326 })
1327 .map(|imp| {
1328 json!({
1329 "ImportId": imp.get("ImportId").cloned().unwrap_or(Value::Null),
1330 "ImportStatus": imp.get("ImportStatus").cloned().unwrap_or(Value::Null),
1331 "Destinations": imp.get("Destinations").cloned().unwrap_or(json!([])),
1332 "CreatedTimestamp": imp.get("CreatedTimestamp").cloned().unwrap_or(Value::Null),
1333 "UpdatedTimestamp": imp.get("UpdatedTimestamp").cloned().unwrap_or(Value::Null),
1334 })
1335 })
1336 .collect()
1337 })
1338 .unwrap_or_default();
1339 list_response("Imports", rows, b)
1340 }
1341
1342 fn list_import_failures(&self, _ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1343 req_str(b, "ImportId")?;
1346 list_response("Failures", Vec::new(), b)
1347 }
1348}
1349
1350impl CloudTrailService {
1353 fn start_query(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1354 if opt_str(b, "QueryStatement").is_none() && opt_str(b, "QueryAlias").is_none() {
1355 return Err(invalid_param(
1356 "Either QueryStatement or QueryAlias must be specified.",
1357 ));
1358 }
1359 let id = new_uuid();
1360 let mut guard = self.state.write();
1361 let data = guard.get_or_create(&ctx.account);
1362 let mut q = Map::new();
1363 q.insert("QueryId".into(), json!(id));
1364 q.insert(
1365 "QueryString".into(),
1366 b.get("QueryStatement").cloned().unwrap_or(json!("")),
1367 );
1368 q.insert("QueryStatus".into(), json!("FINISHED"));
1369 q.insert("CreationTime".into(), json!(now_ts()));
1370 copy_fields(b, &mut q, &["DeliveryS3Uri", "QueryAlias"]);
1371 data.queries.insert(id.clone(), Value::Object(q));
1372 let mut out = Map::new();
1373 out.insert("QueryId".into(), json!(id));
1374 if let Some(owner) = opt_str(b, "EventDataStoreOwnerAccountId") {
1375 out.insert("EventDataStoreOwnerAccountId".into(), json!(owner));
1376 }
1377 ok(Value::Object(out))
1378 }
1379
1380 fn find_query<'a>(&self, data: &'a CloudTrailData, b: &Value) -> Option<&'a Value> {
1381 if let Some(id) = opt_str(b, "QueryId") {
1382 return data.queries.get(id);
1383 }
1384 if let Some(alias) = opt_str(b, "QueryAlias") {
1385 return data
1386 .queries
1387 .values()
1388 .find(|q| q.get("QueryAlias").and_then(Value::as_str) == Some(alias));
1389 }
1390 None
1391 }
1392
1393 fn describe_query(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1394 if opt_str(b, "QueryId").is_none() && opt_str(b, "QueryAlias").is_none() {
1395 return Err(invalid_param("QueryId or QueryAlias must be specified."));
1396 }
1397 let guard = self.state.read();
1398 let data = guard.get(&ctx.account);
1399 let q = data
1400 .and_then(|d| self.find_query(d, b))
1401 .ok_or_else(|| query_not_found(opt_str(b, "QueryId").unwrap_or("")))?;
1402 let mut out = Map::new();
1403 out.insert(
1404 "QueryId".into(),
1405 q.get("QueryId").cloned().unwrap_or(Value::Null),
1406 );
1407 out.insert(
1408 "QueryString".into(),
1409 q.get("QueryString").cloned().unwrap_or(json!("")),
1410 );
1411 out.insert(
1412 "QueryStatus".into(),
1413 q.get("QueryStatus").cloned().unwrap_or(json!("FINISHED")),
1414 );
1415 out.insert(
1416 "QueryStatistics".into(),
1417 json!({
1418 "EventsMatched": 0,
1419 "EventsScanned": 0,
1420 "BytesScanned": 0,
1421 "ExecutionTimeInMillis": 0,
1422 "CreationTime": q.get("CreationTime").cloned().unwrap_or(Value::Null),
1423 }),
1424 );
1425 out.insert("DeliveryStatus".into(), json!("SUCCESS"));
1426 ok(Value::Object(out))
1427 }
1428
1429 fn get_query_results(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1430 let id = req_str(b, "QueryId")?.to_string();
1431 let guard = self.state.read();
1432 let status = guard
1433 .get(&ctx.account)
1434 .and_then(|d| d.queries.get(&id))
1435 .and_then(|q| q.get("QueryStatus").and_then(Value::as_str))
1436 .map(str::to_string)
1437 .ok_or_else(|| query_not_found(&id))?;
1438 ok(json!({
1439 "QueryStatus": status,
1440 "QueryStatistics": {
1441 "ResultsCount": 0,
1442 "TotalResultsCount": 0,
1443 "BytesScanned": 0,
1444 },
1445 "QueryResultRows": [],
1446 }))
1447 }
1448
1449 fn cancel_query(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1450 let id = req_str(b, "QueryId")?.to_string();
1451 let mut guard = self.state.write();
1452 let data = guard.get_or_create(&ctx.account);
1453 let q = data
1454 .queries
1455 .get_mut(&id)
1456 .ok_or_else(|| query_not_found(&id))?;
1457 if let Some(obj) = q.as_object_mut() {
1458 obj.insert("QueryStatus".into(), json!("CANCELLED"));
1459 }
1460 let mut out = Map::new();
1461 out.insert("QueryId".into(), json!(id));
1462 out.insert("QueryStatus".into(), json!("CANCELLED"));
1463 if let Some(owner) = opt_str(b, "EventDataStoreOwnerAccountId") {
1464 out.insert("EventDataStoreOwnerAccountId".into(), json!(owner));
1465 }
1466 ok(Value::Object(out))
1467 }
1468
1469 fn list_queries(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1470 let eds = req_str(b, "EventDataStore")?.to_string();
1471 let status_filter = opt_str(b, "QueryStatus").map(str::to_string);
1472 let guard = self.state.read();
1473 let data = guard.get(&ctx.account);
1474 if !data
1475 .map(|d| d.event_data_stores.contains_key(&eds))
1476 .unwrap_or(false)
1477 {
1478 return Err(eds_not_found(&eds));
1479 }
1480 let rows: Vec<Value> = data
1481 .map(|d| {
1482 d.queries
1483 .values()
1484 .filter(|q| {
1485 status_filter.as_deref().is_none_or(|want| {
1486 q.get("QueryStatus").and_then(Value::as_str) == Some(want)
1487 })
1488 })
1489 .map(|q| {
1490 json!({
1491 "QueryId": q.get("QueryId").cloned().unwrap_or(Value::Null),
1492 "QueryStatus": q.get("QueryStatus").cloned().unwrap_or(Value::Null),
1493 "CreationTime": q.get("CreationTime").cloned().unwrap_or(Value::Null),
1494 })
1495 })
1496 .collect()
1497 })
1498 .unwrap_or_default();
1499 list_response("Queries", rows, b)
1500 }
1501
1502 fn generate_query(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1503 let stores = b
1504 .get("EventDataStores")
1505 .and_then(Value::as_array)
1506 .filter(|a| !a.is_empty())
1507 .ok_or_else(|| invalid_param("EventDataStores is required."))?;
1508 req_str(b, "Prompt")?;
1509 let guard = self.state.read();
1510 let data = guard.get(&ctx.account);
1511 for s in stores {
1512 if let Some(arn) = s.as_str() {
1513 if !data
1514 .map(|d| d.event_data_stores.contains_key(arn))
1515 .unwrap_or(false)
1516 {
1517 return Err(eds_not_found(arn));
1518 }
1519 }
1520 }
1521 ok(json!({
1522 "QueryStatement": "SELECT eventName FROM eds LIMIT 10",
1523 "QueryAlias": "generated-query",
1524 }))
1525 }
1526}
1527
1528impl CloudTrailService {
1531 fn create_dashboard(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1532 let name = req_str(b, "Name")?.to_string();
1533 let mut guard = self.state.write();
1534 let data = guard.get_or_create(&ctx.account);
1535 let arn = format!(
1536 "arn:aws:cloudtrail:{}:{}:dashboard/{}",
1537 ctx.region, ctx.account, name
1538 );
1539 if data.dashboards.contains_key(&arn) {
1540 return Err(err(
1541 "ConflictException",
1542 &format!("A dashboard already exists with the name {name}."),
1543 ));
1544 }
1545 let mut d = Map::new();
1546 d.insert("DashboardArn".into(), json!(arn));
1547 d.insert("Name".into(), json!(name));
1548 d.insert("Type".into(), json!("CUSTOM"));
1549 d.insert("Status".into(), json!("CREATED"));
1550 d.insert(
1551 "Widgets".into(),
1552 b.get("Widgets").cloned().unwrap_or(json!([])),
1553 );
1554 d.insert(
1555 "TerminationProtectionEnabled".into(),
1556 b.get("TerminationProtectionEnabled")
1557 .cloned()
1558 .unwrap_or(json!(false)),
1559 );
1560 if let Some(rs) = b.get("RefreshSchedule") {
1561 d.insert("RefreshSchedule".into(), rs.clone());
1562 }
1563 if let Some(tags) = b.get("TagsList") {
1564 d.insert("TagsList".into(), tags.clone());
1565 }
1566 d.insert("CreatedTimestamp".into(), json!(now_ts()));
1567 d.insert("UpdatedTimestamp".into(), json!(now_ts()));
1568 store_tags(data, &arn, b);
1569 let d = Value::Object(d);
1570 data.dashboards.insert(arn, d.clone());
1571 let mut out = d.as_object().cloned().unwrap_or_default();
1573 out.remove("Status");
1574 out.remove("CreatedTimestamp");
1575 out.remove("UpdatedTimestamp");
1576 ok(Value::Object(out))
1577 }
1578
1579 fn get_dashboard(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1580 let id = req_str(b, "DashboardId")?.to_string();
1581 let guard = self.state.read();
1582 let d = guard
1583 .get(&ctx.account)
1584 .and_then(|s| s.dashboards.get(&id))
1585 .cloned()
1586 .ok_or_else(|| dashboard_not_found(&id))?;
1587 let mut out = d.as_object().cloned().unwrap_or_default();
1588 out.remove("Name");
1589 out.remove("TagsList");
1590 ok(Value::Object(out))
1591 }
1592
1593 fn update_dashboard(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1594 let id = req_str(b, "DashboardId")?.to_string();
1595 let mut guard = self.state.write();
1596 let data = guard.get_or_create(&ctx.account);
1597 let d = data
1598 .dashboards
1599 .get_mut(&id)
1600 .ok_or_else(|| dashboard_not_found(&id))?;
1601 if let Some(obj) = d.as_object_mut() {
1602 copy_fields(
1603 b,
1604 obj,
1605 &["Widgets", "RefreshSchedule", "TerminationProtectionEnabled"],
1606 );
1607 obj.insert("UpdatedTimestamp".into(), json!(now_ts()));
1608 }
1609 let d = d.clone();
1610 let mut out = d.as_object().cloned().unwrap_or_default();
1611 out.remove("Status");
1612 out.remove("TagsList");
1613 ok(Value::Object(out))
1614 }
1615
1616 fn delete_dashboard(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1617 let id = req_str(b, "DashboardId")?.to_string();
1618 let mut guard = self.state.write();
1619 let data = guard.get_or_create(&ctx.account);
1620 if data.dashboards.remove(&id).is_none() {
1621 return Err(dashboard_not_found(&id));
1622 }
1623 data.tags.remove(&id);
1626 empty_ok()
1627 }
1628
1629 fn list_dashboards(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1630 let type_filter = opt_str(b, "Type").map(str::to_string);
1631 let name_prefix = opt_str(b, "NamePrefix").map(str::to_string);
1632 let guard = self.state.read();
1633 let rows: Vec<Value> = guard
1634 .get(&ctx.account)
1635 .map(|d| {
1636 d.dashboards
1637 .values()
1638 .filter(|dash| {
1639 type_filter.as_deref().is_none_or(|want| {
1640 dash.get("Type").and_then(Value::as_str) == Some(want)
1641 }) && name_prefix.as_deref().is_none_or(|p| {
1642 dash.get("Name")
1643 .and_then(Value::as_str)
1644 .map(|n| n.starts_with(p))
1645 .unwrap_or(false)
1646 })
1647 })
1648 .map(|dash| {
1649 json!({
1650 "DashboardArn": dash.get("DashboardArn").cloned().unwrap_or(Value::Null),
1651 "Type": dash.get("Type").cloned().unwrap_or(Value::Null),
1652 })
1653 })
1654 .collect()
1655 })
1656 .unwrap_or_default();
1657 list_response("Dashboards", rows, b)
1658 }
1659
1660 fn start_dashboard_refresh(
1661 &self,
1662 ctx: &Ctx,
1663 b: &Value,
1664 ) -> Result<AwsResponse, AwsServiceError> {
1665 let id = req_str(b, "DashboardId")?.to_string();
1666 let guard = self.state.read();
1667 if !guard
1668 .get(&ctx.account)
1669 .map(|d| d.dashboards.contains_key(&id))
1670 .unwrap_or(false)
1671 {
1672 return Err(dashboard_not_found(&id));
1673 }
1674 ok(json!({ "RefreshId": new_uuid() }))
1675 }
1676}
1677
1678impl CloudTrailService {
1681 fn put_resource_policy(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1682 let arn = req_str(b, "ResourceArn")?.to_string();
1683 let policy = req_str(b, "ResourcePolicy")?.to_string();
1684 let mut guard = self.state.write();
1685 let data = guard.get_or_create(&ctx.account);
1686 data.resource_policies.insert(arn.clone(), policy.clone());
1687 ok(json!({ "ResourceArn": arn, "ResourcePolicy": policy }))
1688 }
1689
1690 fn get_resource_policy(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1691 let arn = req_str(b, "ResourceArn")?.to_string();
1692 let guard = self.state.read();
1693 let policy = guard
1694 .get(&ctx.account)
1695 .and_then(|d| d.resource_policies.get(&arn))
1696 .cloned()
1697 .ok_or_else(|| {
1698 err(
1699 "ResourcePolicyNotFoundException",
1700 &format!("No resource policy was found for {arn}."),
1701 )
1702 })?;
1703 ok(json!({ "ResourceArn": arn, "ResourcePolicy": policy }))
1704 }
1705
1706 fn delete_resource_policy(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1707 let arn = req_str(b, "ResourceArn")?.to_string();
1708 let mut guard = self.state.write();
1709 let data = guard.get_or_create(&ctx.account);
1710 if data.resource_policies.remove(&arn).is_none() {
1711 return Err(err(
1712 "ResourcePolicyNotFoundException",
1713 &format!("No resource policy was found for {arn}."),
1714 ));
1715 }
1716 empty_ok()
1717 }
1718}
1719
1720impl CloudTrailService {
1723 fn register_delegated_admin(
1724 &self,
1725 ctx: &Ctx,
1726 b: &Value,
1727 ) -> Result<AwsResponse, AwsServiceError> {
1728 let account = req_str(b, "MemberAccountId")?.to_string();
1729 let mut guard = self.state.write();
1730 let data = guard.get_or_create(&ctx.account);
1731 if data.delegated_admins.contains_key(&account) {
1732 return Err(err(
1733 "AccountRegisteredException",
1734 "The account is already registered as the delegated administrator.",
1735 ));
1736 }
1737 data.delegated_admins
1738 .insert(account, json!({ "RegisteredTimestamp": now_ts() }));
1739 empty_ok()
1740 }
1741
1742 fn deregister_delegated_admin(
1743 &self,
1744 ctx: &Ctx,
1745 b: &Value,
1746 ) -> Result<AwsResponse, AwsServiceError> {
1747 let account = req_str(b, "DelegatedAdminAccountId")?.to_string();
1748 let mut guard = self.state.write();
1749 let data = guard.get_or_create(&ctx.account);
1750 if data.delegated_admins.remove(&account).is_none() {
1751 return Err(err(
1752 "AccountNotRegisteredException",
1753 "The account is not registered as the delegated administrator.",
1754 ));
1755 }
1756 empty_ok()
1757 }
1758}
1759
1760impl CloudTrailService {
1763 fn get_event_configuration(
1764 &self,
1765 ctx: &Ctx,
1766 b: &Value,
1767 ) -> Result<AwsResponse, AwsServiceError> {
1768 let (key, is_trail) = self.event_config_target(ctx, b)?;
1769 let guard = self.state.read();
1770 let stored = guard
1771 .get(&ctx.account)
1772 .and_then(|d| d.event_configurations.get(&key))
1773 .cloned();
1774 Ok(AwsResponse::json_value(
1775 StatusCode::OK,
1776 self.event_config_response(&key, is_trail, stored.as_ref(), None),
1777 ))
1778 }
1779
1780 fn put_event_configuration(
1781 &self,
1782 ctx: &Ctx,
1783 b: &Value,
1784 ) -> Result<AwsResponse, AwsServiceError> {
1785 let (key, is_trail) = self.event_config_target(ctx, b)?;
1786 let mut config = Map::new();
1787 copy_fields(
1788 b,
1789 &mut config,
1790 &[
1791 "MaxEventSize",
1792 "ContextKeySelectors",
1793 "AggregationConfigurations",
1794 ],
1795 );
1796 let config = Value::Object(config);
1797 let mut guard = self.state.write();
1798 let data = guard.get_or_create(&ctx.account);
1799 data.event_configurations
1800 .insert(key.clone(), config.clone());
1801 Ok(AwsResponse::json_value(
1802 StatusCode::OK,
1803 self.event_config_response(&key, is_trail, Some(&config), Some(b)),
1804 ))
1805 }
1806
1807 fn event_config_target(&self, ctx: &Ctx, b: &Value) -> Result<(String, bool), AwsServiceError> {
1810 if let Some(trail) = opt_str(b, "TrailName").filter(|s| !s.is_empty()) {
1811 let name = trail_key(trail).to_string();
1812 let guard = self.state.read();
1813 if !guard
1814 .get(&ctx.account)
1815 .map(|d| d.trails.contains_key(&name))
1816 .unwrap_or(false)
1817 {
1818 return Err(trail_not_found(&name));
1819 }
1820 return Ok((trail_arn(ctx, &name), true));
1821 }
1822 if let Some(eds) = opt_str(b, "EventDataStore").filter(|s| !s.is_empty()) {
1823 let guard = self.state.read();
1824 if !guard
1825 .get(&ctx.account)
1826 .map(|d| d.event_data_stores.contains_key(eds))
1827 .unwrap_or(false)
1828 {
1829 return Err(eds_not_found(eds));
1830 }
1831 return Ok((eds.to_string(), false));
1832 }
1833 Err(err(
1834 "InvalidParameterCombinationException",
1835 "Either TrailName or EventDataStore must be specified.",
1836 ))
1837 }
1838
1839 fn event_config_response(
1840 &self,
1841 key: &str,
1842 is_trail: bool,
1843 stored: Option<&Value>,
1844 input: Option<&Value>,
1845 ) -> Value {
1846 let mut out = Map::new();
1847 if is_trail {
1848 out.insert("TrailARN".into(), json!(key));
1849 } else {
1850 out.insert("EventDataStoreArn".into(), json!(key));
1851 }
1852 let source = input.or(stored);
1853 out.insert(
1854 "MaxEventSize".into(),
1855 source
1856 .and_then(|s| s.get("MaxEventSize"))
1857 .cloned()
1858 .unwrap_or(json!("Standard")),
1859 );
1860 out.insert(
1861 "ContextKeySelectors".into(),
1862 source
1863 .and_then(|s| s.get("ContextKeySelectors"))
1864 .cloned()
1865 .unwrap_or(json!([])),
1866 );
1867 out.insert(
1868 "AggregationConfigurations".into(),
1869 source
1870 .and_then(|s| s.get("AggregationConfigurations"))
1871 .cloned()
1872 .unwrap_or(json!([])),
1873 );
1874 Value::Object(out)
1875 }
1876}
1877
1878impl CloudTrailService {
1881 fn add_tags(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1882 let id = req_str(b, "ResourceId")?.to_string();
1883 b.get("TagsList")
1884 .and_then(Value::as_array)
1885 .ok_or_else(|| invalid_param("TagsList is required."))?;
1886 let mut guard = self.state.write();
1887 let data = guard.get_or_create(&ctx.account);
1888 store_tags(data, &id, b);
1889 empty_ok()
1890 }
1891
1892 fn remove_tags(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1893 let id = req_str(b, "ResourceId")?.to_string();
1894 let tags = b
1895 .get("TagsList")
1896 .and_then(Value::as_array)
1897 .ok_or_else(|| invalid_param("TagsList is required."))?
1898 .clone();
1899 let mut guard = self.state.write();
1900 let data = guard.get_or_create(&ctx.account);
1901 if let Some(entry) = data.tags.get_mut(&id) {
1902 for t in &tags {
1903 if let Some(k) = t.get("Key").and_then(Value::as_str) {
1904 entry.remove(k);
1905 }
1906 }
1907 }
1908 empty_ok()
1909 }
1910
1911 fn list_tags(&self, ctx: &Ctx, b: &Value) -> Result<AwsResponse, AwsServiceError> {
1912 let ids = b
1913 .get("ResourceIdList")
1914 .and_then(Value::as_array)
1915 .ok_or_else(|| invalid_param("ResourceIdList is required."))?;
1916 let guard = self.state.read();
1917 let data = guard.get(&ctx.account);
1918 let mut list = Vec::new();
1919 for id in ids {
1920 let Some(id) = id.as_str() else { continue };
1921 let tags: Vec<Value> = data
1922 .and_then(|d| d.tags.get(id))
1923 .map(|m| {
1924 m.iter()
1925 .map(|(k, v)| json!({ "Key": k, "Value": v }))
1926 .collect()
1927 })
1928 .unwrap_or_default();
1929 list.push(json!({ "ResourceId": id, "TagsList": tags }));
1930 }
1931 ok(json!({ "ResourceTagList": list }))
1932 }
1933}
1934
1935fn lookup_events(b: &Value) -> Result<AwsResponse, AwsServiceError> {
1938 if let Some(cat) = opt_str(b, "EventCategory") {
1940 if cat != "insight" {
1941 return Err(err(
1942 "InvalidEventCategoryException",
1943 &format!("{cat} is not a valid event category."),
1944 ));
1945 }
1946 }
1947 ok(json!({ "Events": [] }))
1948}
1949
1950fn list_public_keys() -> Result<AwsResponse, AwsServiceError> {
1951 ok(json!({ "PublicKeyList": [] }))
1952}
1953
1954fn list_insights_data(b: &Value) -> Result<AwsResponse, AwsServiceError> {
1955 req_str(b, "InsightSource")?;
1956 req_str(b, "DataType")?;
1957 ok(json!({ "Events": [] }))
1958}
1959
1960fn list_insights_metric_data(b: &Value) -> Result<AwsResponse, AwsServiceError> {
1961 let event_source = req_str(b, "EventSource")?.to_string();
1962 let event_name = req_str(b, "EventName")?.to_string();
1963 let insight_type = req_str(b, "InsightType")?.to_string();
1964 let mut out = Map::new();
1965 out.insert("EventSource".into(), json!(event_source));
1966 out.insert("EventName".into(), json!(event_name));
1967 out.insert("InsightType".into(), json!(insight_type));
1968 if let Some(ec) = opt_str(b, "ErrorCode") {
1969 out.insert("ErrorCode".into(), json!(ec));
1970 }
1971 out.insert("Timestamps".into(), json!([]));
1972 out.insert("Values".into(), json!([]));
1973 ok(Value::Object(out))
1974}
1975
1976fn search_sample_queries(b: &Value) -> Result<AwsResponse, AwsServiceError> {
1977 req_str(b, "SearchPhrase")?;
1978 ok(json!({ "SearchResults": [] }))
1979}