Skip to main content

fakecloud_ssoadmin/
state.rs

1//! Account-partitioned, serializable state for AWS IAM Identity Center's SSO
2//! Admin (`sso-admin`) control plane.
3//!
4//! Everything the service owns for one account: IAM Identity Center instances
5//! (each with its regions), permission sets (with their inline / managed /
6//! customer-managed / boundary policies), account assignments and their async
7//! operation statuses, permission-set provisioning statuses, applications (with
8//! their assignments, access scopes, authentication methods, grants and
9//! session/assignment configuration), trusted token issuers, per-instance
10//! access-control attribute configuration, and resource tags. Nested config
11//! objects are stored as the raw request `Value` so they echo back verbatim.
12
13use std::collections::BTreeMap;
14use std::sync::Arc;
15
16use parking_lot::RwLock;
17use serde::{Deserialize, Serialize};
18use serde_json::Value;
19
20use fakecloud_core::multi_account::{AccountState, MultiAccountState};
21
22pub const SSOADMIN_SNAPSHOT_SCHEMA_VERSION: u32 = 1;
23
24/// A region enabled on an IAM Identity Center instance.
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct StoredRegion {
27    pub region_name: String,
28    pub status: String,
29    pub added_date: i64,
30    pub is_primary: bool,
31}
32
33/// An IAM Identity Center instance.
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct StoredInstance {
36    pub arn: String,
37    /// The `ssoins-xxxxxxxxxxxxxxxx` fragment embedded in child ARNs.
38    pub ssoins: String,
39    pub identity_store_id: String,
40    pub owner_account_id: String,
41    #[serde(default)]
42    pub name: Option<String>,
43    pub status: String,
44    pub created_date: i64,
45    #[serde(default)]
46    pub regions: Vec<StoredRegion>,
47    #[serde(default)]
48    pub primary_region: Option<String>,
49    /// Access-control attribute configuration for this instance, if created.
50    /// Stores the raw `InstanceAccessControlAttributeConfiguration` value.
51    #[serde(default)]
52    pub attr_config: Option<Value>,
53    #[serde(default)]
54    pub attr_config_status: Option<String>,
55}
56
57/// A managed policy attached to a permission set.
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct StoredManagedPolicy {
60    pub name: String,
61    pub arn: String,
62}
63
64/// A permission set within an instance.
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct StoredPermissionSet {
67    pub arn: String,
68    pub instance_arn: String,
69    pub name: String,
70    #[serde(default)]
71    pub description: Option<String>,
72    #[serde(default)]
73    pub session_duration: Option<String>,
74    #[serde(default)]
75    pub relay_state: Option<String>,
76    pub created_date: i64,
77    #[serde(default)]
78    pub inline_policy: Option<String>,
79    /// Raw `PermissionsBoundary` value.
80    #[serde(default)]
81    pub permissions_boundary: Option<Value>,
82    #[serde(default)]
83    pub managed_policies: Vec<StoredManagedPolicy>,
84    /// Raw `CustomerManagedPolicyReference` values.
85    #[serde(default)]
86    pub customer_managed: Vec<Value>,
87}
88
89/// An account assignment linking a principal to a permission set on an account.
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct StoredAssignment {
92    pub account_id: String,
93    pub permission_set_arn: String,
94    pub principal_type: String,
95    pub principal_id: String,
96}
97
98/// Async status for account-assignment create/delete operations.
99#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct StoredOpStatus {
101    pub request_id: String,
102    pub status: String,
103    pub target_id: String,
104    pub target_type: String,
105    pub permission_set_arn: String,
106    pub principal_type: String,
107    pub principal_id: String,
108    pub created_date: i64,
109}
110
111/// Async status for permission-set provisioning.
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct StoredProvisioningStatus {
114    pub request_id: String,
115    pub status: String,
116    #[serde(default)]
117    pub account_id: Option<String>,
118    pub permission_set_arn: String,
119    pub created_date: i64,
120}
121
122/// An application registered on an instance.
123#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct StoredApplication {
125    pub arn: String,
126    pub provider_arn: String,
127    #[serde(default)]
128    pub name: Option<String>,
129    pub account: String,
130    pub instance_arn: String,
131    pub identity_store_arn: String,
132    #[serde(default)]
133    pub status: Option<String>,
134    /// Raw `PortalOptions` value.
135    #[serde(default)]
136    pub portal_options: Option<Value>,
137    #[serde(default)]
138    pub description: Option<String>,
139    pub created_date: i64,
140    #[serde(default)]
141    pub created_from: Option<String>,
142    /// Assignments as `(principal_id, principal_type)`.
143    #[serde(default)]
144    pub assignments: Vec<(String, String)>,
145    #[serde(default)]
146    pub assignment_required: Option<bool>,
147    /// scope -> raw `AuthorizedTargets` value.
148    #[serde(default)]
149    pub access_scopes: BTreeMap<String, Value>,
150    /// authentication-method-type -> raw `AuthenticationMethod` value.
151    #[serde(default)]
152    pub auth_methods: BTreeMap<String, Value>,
153    /// grant-type -> raw `Grant` value.
154    #[serde(default)]
155    pub grants: BTreeMap<String, Value>,
156    #[serde(default)]
157    pub session_status: Option<String>,
158}
159
160/// A trusted token issuer.
161#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct StoredTrustedTokenIssuer {
163    pub arn: String,
164    pub instance_arn: String,
165    #[serde(default)]
166    pub name: Option<String>,
167    pub issuer_type: String,
168    /// Raw `TrustedTokenIssuerConfiguration` value.
169    #[serde(default)]
170    pub config: Option<Value>,
171}
172
173/// Per-account SSO Admin state.
174#[derive(Debug, Clone, Default, Serialize, Deserialize)]
175pub struct SsoAdminData {
176    /// Instances keyed by `InstanceArn`.
177    #[serde(default)]
178    pub instances: BTreeMap<String, StoredInstance>,
179    /// Permission sets keyed by `PermissionSetArn`.
180    #[serde(default)]
181    pub permission_sets: BTreeMap<String, StoredPermissionSet>,
182    #[serde(default)]
183    pub assignments: Vec<StoredAssignment>,
184    /// Account-assignment creation statuses keyed by request id.
185    #[serde(default)]
186    pub aa_create_status: BTreeMap<String, StoredOpStatus>,
187    /// Account-assignment deletion statuses keyed by request id.
188    #[serde(default)]
189    pub aa_delete_status: BTreeMap<String, StoredOpStatus>,
190    /// Permission-set provisioning statuses keyed by request id.
191    #[serde(default)]
192    pub ps_provisioning: BTreeMap<String, StoredProvisioningStatus>,
193    /// Applications keyed by `ApplicationArn`.
194    #[serde(default)]
195    pub applications: BTreeMap<String, StoredApplication>,
196    /// Trusted token issuers keyed by `TrustedTokenIssuerArn`.
197    #[serde(default)]
198    pub trusted_token_issuers: BTreeMap<String, StoredTrustedTokenIssuer>,
199    /// Resource tags keyed by `ResourceArn` -> (key -> value).
200    #[serde(default)]
201    pub tags: BTreeMap<String, BTreeMap<String, String>>,
202}
203
204impl AccountState for SsoAdminData {
205    fn new_for_account(_account_id: &str, _region: &str, _endpoint: &str) -> Self {
206        Self::default()
207    }
208}
209
210pub type SharedSsoAdminState = Arc<RwLock<MultiAccountState<SsoAdminData>>>;
211
212#[derive(Debug, Serialize, Deserialize)]
213pub struct SsoAdminSnapshot {
214    pub schema_version: u32,
215    pub accounts: MultiAccountState<SsoAdminData>,
216}
217
218/// Seed a default, always-`ACTIVE` IAM Identity Center instance for `account_id`
219/// when it has none. Real AWS requires enabling IAM Identity Center before
220/// instances exist, but seeding one makes the control plane (and the paired
221/// Identity Store directory) usable out of the box — and lets tools that first
222/// resolve `ListInstances` (e.g. Terraform's `aws_ssoadmin_instances`) proceed.
223/// The instance id is derived from the account id so it is stable across
224/// restarts. No-op if an instance already exists (e.g. restored from a
225/// snapshot).
226pub fn ensure_default_instance(state: &SharedSsoAdminState, account_id: &str, region: &str) {
227    let mut guard = state.write();
228    let acct = guard.get_or_create(account_id);
229    if !acct.instances.is_empty() {
230        return;
231    }
232    let pad = format!("{account_id:0<16}");
233    let ssoins = format!("ssoins-{}", &pad[..16]);
234    let arn = format!("arn:aws:sso:::instance/{ssoins}");
235    let identity_store_id = format!("d-{}", &pad[..10]);
236    let ts = chrono::Utc::now().timestamp();
237    acct.instances.insert(
238        arn.clone(),
239        StoredInstance {
240            arn,
241            ssoins,
242            identity_store_id,
243            owner_account_id: account_id.to_string(),
244            name: None,
245            status: "ACTIVE".into(),
246            created_date: ts,
247            regions: vec![StoredRegion {
248                region_name: region.to_string(),
249                status: "ACTIVE".into(),
250                added_date: ts,
251                is_primary: true,
252            }],
253            primary_region: Some(region.to_string()),
254            attr_config: None,
255            attr_config_status: None,
256        },
257    );
258}