Skip to main content

fakecloud_config/
state.rs

1//! In-memory state for AWS Config (`config`).
2//!
3//! State is partitioned per account. Every map uses `String` keys so serde
4//! round-trips cleanly (no tuple-key `KeyMustBeAString` trap): where a logical
5//! key is a pair (e.g. a configuration item is identified by resource type +
6//! resource id), the two halves are joined with a `\u{1}` separator via
7//! [`resource_key`].
8
9use std::collections::BTreeMap;
10use std::sync::Arc;
11
12use chrono::{DateTime, Utc};
13use parking_lot::RwLock;
14use serde::{Deserialize, Serialize};
15
16pub type SharedConfigState = Arc<RwLock<ConfigAccounts>>;
17
18/// Join a resource type + id into a single stable map key.
19pub fn resource_key(resource_type: &str, resource_id: &str) -> String {
20    format!("{resource_type}\u{1}{resource_id}")
21}
22
23#[derive(Debug, Default, Clone, Serialize, Deserialize)]
24pub struct ConfigAccounts {
25    pub accounts: BTreeMap<String, AccountState>,
26}
27
28impl ConfigAccounts {
29    pub fn new() -> Self {
30        Self::default()
31    }
32
33    pub fn account_mut(&mut self, account_id: &str) -> &mut AccountState {
34        self.accounts.entry(account_id.to_string()).or_default()
35    }
36
37    pub fn account(&self, account_id: &str) -> Option<&AccountState> {
38        self.accounts.get(account_id)
39    }
40}
41
42#[derive(Debug, Default, Clone, Serialize, Deserialize)]
43pub struct AccountState {
44    /// Configuration recorders keyed by recorder name.
45    pub recorders: BTreeMap<String, ConfigurationRecorder>,
46    /// Delivery channels keyed by channel name.
47    pub delivery_channels: BTreeMap<String, DeliveryChannel>,
48    /// Config rules keyed by rule name.
49    pub rules: BTreeMap<String, ConfigRule>,
50    /// Configuration-item history keyed by `resource_key(type, id)`; each value
51    /// is an ordered (oldest-first) list of recorded items.
52    pub config_items: BTreeMap<String, Vec<ConfigurationItem>>,
53    /// Evaluation results keyed by rule name; each value is keyed by
54    /// `resource_key(type, id)`.
55    pub evaluations: BTreeMap<String, BTreeMap<String, EvaluationResult>>,
56    /// Conformance packs keyed by pack name.
57    pub conformance_packs: BTreeMap<String, ConformancePack>,
58    /// Organization config rules keyed by rule name.
59    pub org_rules: BTreeMap<String, OrganizationConfigRule>,
60    /// Organization conformance packs keyed by pack name.
61    pub org_conformance_packs: BTreeMap<String, OrganizationConformancePack>,
62    /// Configuration aggregators keyed by aggregator name.
63    pub aggregators: BTreeMap<String, ConfigurationAggregator>,
64    /// Aggregation authorizations keyed by `account\u{1}region`.
65    pub aggregation_authorizations: BTreeMap<String, AggregationAuthorization>,
66    /// Retention configurations keyed by name (AWS allows exactly one, named
67    /// `default`).
68    pub retention_configurations: BTreeMap<String, RetentionConfiguration>,
69    /// Stored queries keyed by query name.
70    pub stored_queries: BTreeMap<String, StoredQuery>,
71    /// Remediation configurations keyed by config-rule name.
72    pub remediation_configs: BTreeMap<String, RemediationConfiguration>,
73    /// Remediation exceptions keyed by `rule\u{1}resourceType\u{1}resourceId`.
74    pub remediation_exceptions: BTreeMap<String, RemediationException>,
75    /// Remediation execution statuses keyed by
76    /// `rule\u{1}resourceType\u{1}resourceId`, recorded by
77    /// `StartRemediationExecution`.
78    #[serde(default)]
79    pub remediation_executions: BTreeMap<String, RemediationExecutionStatus>,
80    /// Resource evaluations keyed by evaluation id.
81    pub resource_evaluations: BTreeMap<String, ResourceEvaluation>,
82    /// Tags per resource ARN.
83    pub tags: BTreeMap<String, BTreeMap<String, String>>,
84}
85
86// ─── Configuration recorder ──────────────────────────────────────────────
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct ConfigurationRecorder {
90    pub name: String,
91    pub role_arn: String,
92    /// The `recordingGroup` echoed back verbatim.
93    #[serde(default)]
94    pub recording_group: Option<serde_json::Value>,
95    #[serde(default)]
96    pub recording_mode: Option<serde_json::Value>,
97    /// `arn:aws:config:...:configuration-recorder/<name>` — only populated for
98    /// service-linked recorders.
99    #[serde(default)]
100    pub arn: Option<String>,
101    #[serde(default)]
102    pub service_principal: Option<String>,
103    /// Whether the recorder is currently running.
104    pub recording: bool,
105    pub last_start_time: Option<DateTime<Utc>>,
106    pub last_stop_time: Option<DateTime<Utc>>,
107    pub last_status: String,
108    pub last_status_change_time: Option<DateTime<Utc>>,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct DeliveryChannel {
113    pub name: String,
114    pub s3_bucket_name: Option<String>,
115    pub s3_key_prefix: Option<String>,
116    pub s3_kms_key_arn: Option<String>,
117    pub sns_topic_arn: Option<String>,
118    #[serde(default)]
119    pub config_snapshot_delivery_properties: Option<serde_json::Value>,
120    pub last_status: String,
121}
122
123// ─── Configuration items ─────────────────────────────────────────────────
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct ConfigurationItem {
127    pub version: String,
128    pub account_id: String,
129    pub configuration_item_capture_time: DateTime<Utc>,
130    /// `OK`, `ResourceDiscovered`, `ResourceNotRecorded`, `ResourceDeleted`,
131    /// `ResourceDeletedNotRecorded`.
132    pub configuration_item_status: String,
133    pub configuration_state_id: String,
134    pub arn: String,
135    pub resource_type: String,
136    pub resource_id: String,
137    #[serde(default)]
138    pub resource_name: Option<String>,
139    pub aws_region: String,
140    pub availability_zone: String,
141    pub resource_creation_time: Option<DateTime<Utc>>,
142    #[serde(default)]
143    pub tags: BTreeMap<String, String>,
144    /// The resource configuration as a JSON string (Config stores this as an
145    /// escaped JSON document).
146    pub configuration: String,
147    #[serde(default)]
148    pub supplementary_configuration: BTreeMap<String, String>,
149    /// True when this item was recorded via the `PutResourceConfig` API rather
150    /// than discovered from a native fakecloud service. The cross-service
151    /// recorder never marks externally-recorded items as deleted (their
152    /// lifecycle is owned by the caller, not by native discovery).
153    #[serde(default)]
154    pub externally_recorded: bool,
155}
156
157// ─── Config rules + evaluations ──────────────────────────────────────────
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct ConfigRule {
161    pub name: String,
162    pub arn: String,
163    pub rule_id: String,
164    #[serde(default)]
165    pub description: Option<String>,
166    /// The `Scope` object echoed back verbatim.
167    #[serde(default)]
168    pub scope: Option<serde_json::Value>,
169    /// The `Source` object: Owner (AWS/CUSTOM_LAMBDA/CUSTOM_POLICY),
170    /// SourceIdentifier, SourceDetails, CustomPolicyDetails.
171    pub source: serde_json::Value,
172    #[serde(default)]
173    pub input_parameters: Option<String>,
174    #[serde(default)]
175    pub maximum_execution_frequency: Option<String>,
176    /// `ACTIVE`, `DELETING`, `EVALUATING`.
177    pub state: String,
178    #[serde(default)]
179    pub created_by: Option<String>,
180    #[serde(default)]
181    pub evaluation_modes: Option<serde_json::Value>,
182    pub last_updated: DateTime<Utc>,
183    // Evaluation status bookkeeping.
184    pub first_activated_time: Option<DateTime<Utc>>,
185    pub last_successful_evaluation_time: Option<DateTime<Utc>>,
186    pub last_successful_invocation_time: Option<DateTime<Utc>>,
187}
188
189#[derive(Debug, Clone, Serialize, Deserialize)]
190pub struct EvaluationResult {
191    pub resource_type: String,
192    pub resource_id: String,
193    pub rule_name: String,
194    /// `COMPLIANT`, `NON_COMPLIANT`, `NOT_APPLICABLE`, `INSUFFICIENT_DATA`.
195    pub compliance_type: String,
196    pub annotation: Option<String>,
197    pub result_recorded_time: DateTime<Utc>,
198    pub config_rule_invoked_time: DateTime<Utc>,
199    pub ordering_timestamp: DateTime<Utc>,
200}
201
202// ─── Conformance packs ───────────────────────────────────────────────────
203
204#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct ConformancePack {
206    pub name: String,
207    pub arn: String,
208    pub id: String,
209    pub delivery_s3_bucket: Option<String>,
210    pub delivery_s3_key_prefix: Option<String>,
211    #[serde(default)]
212    pub input_parameters: Vec<serde_json::Value>,
213    pub template_body: Option<String>,
214    pub template_s3_uri: Option<String>,
215    #[serde(default)]
216    pub template_ssm_document_details: Option<serde_json::Value>,
217    pub last_update_requested_time: DateTime<Utc>,
218    pub created_by: Option<String>,
219    /// Rule names created by this pack.
220    #[serde(default)]
221    pub rule_names: Vec<String>,
222}
223
224#[derive(Debug, Clone, Serialize, Deserialize)]
225pub struct OrganizationConfigRule {
226    pub name: String,
227    pub arn: String,
228    #[serde(default)]
229    pub managed_rule_metadata: Option<serde_json::Value>,
230    #[serde(default)]
231    pub custom_rule_metadata: Option<serde_json::Value>,
232    #[serde(default)]
233    pub custom_policy_rule_metadata: Option<serde_json::Value>,
234    #[serde(default)]
235    pub excluded_accounts: Vec<String>,
236    pub last_update_time: DateTime<Utc>,
237}
238
239#[derive(Debug, Clone, Serialize, Deserialize)]
240pub struct OrganizationConformancePack {
241    pub name: String,
242    pub arn: String,
243    pub delivery_s3_bucket: Option<String>,
244    pub delivery_s3_key_prefix: Option<String>,
245    #[serde(default)]
246    pub input_parameters: Vec<serde_json::Value>,
247    pub template_body: Option<String>,
248    pub template_s3_uri: Option<String>,
249    #[serde(default)]
250    pub excluded_accounts: Vec<String>,
251    pub last_update_time: DateTime<Utc>,
252}
253
254// ─── Aggregators ─────────────────────────────────────────────────────────
255
256#[derive(Debug, Clone, Serialize, Deserialize)]
257pub struct ConfigurationAggregator {
258    pub name: String,
259    pub arn: String,
260    #[serde(default)]
261    pub account_aggregation_sources: Vec<serde_json::Value>,
262    #[serde(default)]
263    pub organization_aggregation_source: Option<serde_json::Value>,
264    pub creation_time: DateTime<Utc>,
265    pub last_updated_time: DateTime<Utc>,
266    pub created_by: Option<String>,
267}
268
269#[derive(Debug, Clone, Serialize, Deserialize)]
270pub struct AggregationAuthorization {
271    pub arn: String,
272    pub authorized_account_id: String,
273    pub authorized_aws_region: String,
274    pub creation_time: DateTime<Utc>,
275}
276
277// ─── Retention / stored queries / remediation / resource-eval ────────────
278
279#[derive(Debug, Clone, Serialize, Deserialize)]
280pub struct RetentionConfiguration {
281    pub name: String,
282    pub retention_period_in_days: i64,
283}
284
285#[derive(Debug, Clone, Serialize, Deserialize)]
286pub struct StoredQuery {
287    pub id: String,
288    pub name: String,
289    pub arn: String,
290    pub description: Option<String>,
291    pub expression: Option<String>,
292}
293
294#[derive(Debug, Clone, Serialize, Deserialize)]
295pub struct RemediationConfiguration {
296    pub config_rule_name: String,
297    pub arn: String,
298    pub target_type: String,
299    pub target_id: String,
300    #[serde(default)]
301    pub target_version: Option<String>,
302    #[serde(default)]
303    pub parameters: Option<serde_json::Value>,
304    #[serde(default)]
305    pub resource_type: Option<String>,
306    #[serde(default)]
307    pub automatic: bool,
308    #[serde(default)]
309    pub execution_controls: Option<serde_json::Value>,
310    #[serde(default)]
311    pub maximum_automatic_attempts: Option<i64>,
312    #[serde(default)]
313    pub retry_attempt_seconds: Option<i64>,
314    pub created_by_service: Option<String>,
315}
316
317#[derive(Debug, Clone, Serialize, Deserialize)]
318pub struct RemediationException {
319    pub config_rule_name: String,
320    pub resource_type: String,
321    pub resource_id: String,
322    pub message: Option<String>,
323    pub expiration_time: Option<DateTime<Utc>>,
324}
325
326/// A recorded remediation execution. FakeCloud does not run the underlying SSM
327/// Automation document (SSM automation is out of the config data plane here), so
328/// executions are recorded as `QUEUED` — honestly reflecting that the request
329/// was accepted but the automation outcome is not simulated, rather than
330/// fabricating a `SUCCEEDED` that never ran.
331#[derive(Debug, Clone, Serialize, Deserialize)]
332pub struct RemediationExecutionStatus {
333    pub config_rule_name: String,
334    pub resource_type: String,
335    pub resource_id: String,
336    /// `QUEUED`, `PENDING`, `SUCCEEDED`, `FAILED`.
337    pub state: String,
338    pub invocation_time: DateTime<Utc>,
339    pub last_updated_time: DateTime<Utc>,
340}
341
342#[derive(Debug, Clone, Serialize, Deserialize)]
343pub struct ResourceEvaluation {
344    pub resource_evaluation_id: String,
345    pub evaluation_mode: String,
346    pub time_stamp: DateTime<Utc>,
347    /// `IN_PROGRESS`, `SUCCEEDED`, `FAILED`.
348    pub status: String,
349    #[serde(default)]
350    pub resource_details: Option<serde_json::Value>,
351    #[serde(default)]
352    pub evaluation_context: Option<serde_json::Value>,
353    #[serde(default)]
354    pub evaluation_results: Vec<EvaluationResult>,
355}
356
357/// On-disk snapshot envelope. Versioned so format changes fail loudly on
358/// upgrade rather than silently mis-parsing.
359#[derive(Clone, Serialize, Deserialize)]
360pub struct ConfigSnapshot {
361    pub schema_version: u32,
362    #[serde(default)]
363    pub accounts: Option<ConfigAccounts>,
364}
365
366pub const CONFIG_SNAPSHOT_SCHEMA_VERSION: u32 = 1;