1pub mod authz;
2pub mod error;
3mod ids;
4mod operations;
5pub mod state;
6
7pub use operations::users::verify_password;
12
13use std::sync::Arc;
14
15use async_trait::async_trait;
16use awsim_core::{AccountRegionStore, AwsError, Protocol, RequestContext, ServiceHandler};
17use serde_json::Value;
18use tracing::debug;
19
20use state::{DeletionTask, IamState, IamStateSnapshot};
21
22pub const IAM_REGION: &str = "global";
25
26pub const ROOT_USERNAME: &str = "root";
32
33pub fn deny_if_targets_root(user_name: &str, ctx: &RequestContext) -> Result<(), AwsError> {
42 if ctx.internal_bypass {
43 return Ok(());
44 }
45 if user_name == ROOT_USERNAME {
46 return Err(AwsError::access_denied(
47 "The root account owner cannot be modified via the IAM API. \
48 Use the account-owner workflow instead.",
49 ));
50 }
51 Ok(())
52}
53
54const USER_MUTATIONS_REQUIRING_ROOT_PROTECTION: &[&str] = &[
62 "CreateUser",
63 "DeleteUser",
64 "UpdateUser",
65 "TagUser",
66 "UntagUser",
67 "PutUserPermissionsBoundary",
68 "DeleteUserPermissionsBoundary",
69 "CreateLoginProfile",
70 "UpdateLoginProfile",
71 "DeleteLoginProfile",
72 "ChangePassword",
73 "CreateAccessKey",
74 "DeleteAccessKey",
75 "UpdateAccessKey",
76 "AttachUserPolicy",
77 "DetachUserPolicy",
78 "PutUserPolicy",
79 "DeleteUserPolicy",
80 "AddUserToGroup",
81 "RemoveUserFromGroup",
82 "EnableMFADevice",
83 "DeactivateMFADevice",
84 "ResyncMFADevice",
85 "UploadSSHPublicKey",
86 "UpdateSSHPublicKey",
87 "DeleteSSHPublicKey",
88 "UploadSigningCertificate",
89 "UpdateSigningCertificate",
90 "DeleteSigningCertificate",
91 "CreateServiceSpecificCredential",
92 "DeleteServiceSpecificCredential",
93 "UpdateServiceSpecificCredential",
94 "ResetServiceSpecificCredential",
95];
96
97pub struct IamService {
99 store: AccountRegionStore<IamState>,
100 authz: std::sync::OnceLock<Arc<awsim_core::AuthzEngine>>,
105}
106
107impl IamService {
108 pub fn new() -> Self {
109 Self {
110 store: AccountRegionStore::new(),
111 authz: std::sync::OnceLock::new(),
112 }
113 }
114
115 fn get_state(&self, ctx: &RequestContext) -> Arc<IamState> {
116 self.store.get(&ctx.account_id, IAM_REGION)
117 }
118
119 pub fn store(&self) -> AccountRegionStore<IamState> {
122 self.store.clone()
123 }
124
125 pub fn set_authz(&self, authz: Arc<awsim_core::AuthzEngine>) {
131 let _ = self.authz.set(authz);
132 }
133
134 pub(crate) fn authz(&self) -> Option<&Arc<awsim_core::AuthzEngine>> {
135 self.authz.get()
136 }
137
138 pub fn lookup_role_trust_policy(&self, account_id: &str, role_arn: &str) -> Option<String> {
146 let role_name = role_arn.rsplit_once('/').map(|(_, last)| last)?;
147 let state = self.store.get(account_id, IAM_REGION);
148 state
149 .roles
150 .get(role_name)
151 .map(|r| r.assume_role_policy_document.clone())
152 }
153}
154
155impl Default for IamService {
156 fn default() -> Self {
157 Self::new()
158 }
159}
160
161#[async_trait]
162impl ServiceHandler for IamService {
163 fn service_name(&self) -> &str {
164 "iam"
165 }
166
167 fn signing_name(&self) -> &str {
168 "iam"
169 }
170
171 fn protocol(&self) -> Protocol {
172 Protocol::AwsQuery
173 }
174
175 async fn handle(
176 &self,
177 operation: &str,
178 input: Value,
179 ctx: &RequestContext,
180 ) -> Result<Value, AwsError> {
181 debug!(operation, "IAM request");
182 let state = self.get_state(ctx);
183
184 if USER_MUTATIONS_REQUIRING_ROOT_PROTECTION.contains(&operation)
193 && let Some(target) = input.get("UserName").and_then(Value::as_str)
194 {
195 deny_if_targets_root(target, ctx)?;
196 }
197
198 match operation {
199 "CreateUser" => operations::users::create_user(&state, &input, ctx),
201 "GetUser" => operations::users::get_user(&state, &input, ctx),
202 "DeleteUser" => operations::users::delete_user(&state, &input),
203 "ListUsers" => operations::users::list_users(&state, &input),
204 "UpdateUser" => operations::users::update_user(&state, &input),
205
206 "CreateAccessKey" => operations::users::create_access_key(&state, &input, ctx),
208 "DeleteAccessKey" => operations::users::delete_access_key(&state, &input),
209 "ListAccessKeys" => operations::users::list_access_keys(&state, &input, ctx),
210
211 "CreateGroup" => operations::groups::create_group(&state, &input, ctx),
213 "GetGroup" => operations::groups::get_group(&state, &input),
214 "DeleteGroup" => operations::groups::delete_group(&state, &input),
215 "ListGroups" => operations::groups::list_groups(&state, &input),
216 "AddUserToGroup" => operations::groups::add_user_to_group(&state, &input),
217 "RemoveUserFromGroup" => operations::groups::remove_user_from_group(&state, &input),
218
219 "CreateRole" => operations::roles::create_role(&state, &input, ctx),
221 "GetRole" => operations::roles::get_role(&state, &input),
222 "DeleteRole" => operations::roles::delete_role(&state, &input),
223 "ListRoles" => operations::roles::list_roles(&state, &input),
224 "UpdateAssumeRolePolicy" => {
225 operations::roles::update_assume_role_policy(&state, &input)
226 }
227 "UpdateRole" => operations::roles::update_role(&state, &input),
228 "UpdateRoleDescription" => operations::roles::update_role_description(&state, &input),
229
230 "CreatePolicy" => operations::policies::create_policy(&state, &input, ctx),
232 "GetPolicy" => operations::policies::get_policy(&state, &input),
233 "DeletePolicy" => operations::policies::delete_policy(&state, &input),
234 "ListPolicies" => operations::policies::list_policies(&state, &input),
235
236 "CreatePolicyVersion" => operations::policies::create_policy_version(&state, &input),
238 "DeletePolicyVersion" => operations::policies::delete_policy_version(&state, &input),
239 "GetPolicyVersion" => operations::policies::get_policy_version(&state, &input),
240 "ListPolicyVersions" => operations::policies::list_policy_versions(&state, &input),
241 "SetDefaultPolicyVersion" => {
242 operations::policies::set_default_policy_version(&state, &input)
243 }
244
245 "AttachUserPolicy" => operations::policies::attach_user_policy(&state, &input),
247 "DetachUserPolicy" => operations::policies::detach_user_policy(&state, &input),
248 "AttachRolePolicy" => operations::policies::attach_role_policy(&state, &input),
249 "DetachRolePolicy" => operations::policies::detach_role_policy(&state, &input),
250 "AttachGroupPolicy" => operations::policies::attach_group_policy(&state, &input),
251 "DetachGroupPolicy" => operations::policies::detach_group_policy(&state, &input),
252
253 "ListAttachedUserPolicies" => {
255 operations::policies::list_attached_user_policies(&state, &input)
256 }
257 "ListAttachedRolePolicies" => {
258 operations::policies::list_attached_role_policies(&state, &input)
259 }
260 "ListAttachedGroupPolicies" => {
261 operations::policies::list_attached_group_policies(&state, &input)
262 }
263
264 "PutUserPolicy" => operations::policies::put_user_policy(&state, &input),
266 "PutRolePolicy" => operations::policies::put_role_policy(&state, &input),
267 "PutGroupPolicy" => operations::policies::put_group_policy(&state, &input),
268
269 "GetUserPolicy" => operations::users::get_user_policy(&state, &input),
271 "DeleteUserPolicy" => operations::users::delete_user_policy(&state, &input),
272 "ListUserPolicies" => operations::users::list_user_policies(&state, &input),
273
274 "GetRolePolicy" => operations::roles::get_role_policy(&state, &input),
276 "DeleteRolePolicy" => operations::roles::delete_role_policy(&state, &input),
277 "ListRolePolicies" => operations::roles::list_role_policies(&state, &input),
278
279 "GetGroupPolicy" => operations::groups::get_group_policy(&state, &input),
281 "DeleteGroupPolicy" => operations::groups::delete_group_policy(&state, &input),
282 "ListGroupPolicies" => operations::groups::list_group_policies(&state, &input),
283
284 "ListGroupsForUser" => operations::users::list_groups_for_user(&state, &input),
286 "ListEntitiesForPolicy" => {
287 operations::policies::list_entities_for_policy(&state, &input)
288 }
289
290 "TagPolicy" => operations::policies::tag_policy(&state, &input),
292 "UntagPolicy" => operations::policies::untag_policy(&state, &input),
293 "ListPolicyTags" => operations::policies::list_policy_tags(&state, &input),
294
295 "CreateInstanceProfile" => {
297 operations::instance_profiles::create_instance_profile(&state, &input, ctx)
298 }
299 "DeleteInstanceProfile" => {
300 operations::instance_profiles::delete_instance_profile(&state, &input)
301 }
302 "GetInstanceProfile" => {
303 operations::instance_profiles::get_instance_profile(&state, &input)
304 }
305 "ListInstanceProfiles" => {
306 operations::instance_profiles::list_instance_profiles(&state, &input)
307 }
308 "ListInstanceProfilesForRole" => {
309 operations::instance_profiles::list_instance_profiles_for_role(&state, &input)
310 }
311 "AddRoleToInstanceProfile" => {
312 operations::instance_profiles::add_role_to_instance_profile(&state, &input)
313 }
314 "RemoveRoleFromInstanceProfile" => {
315 operations::instance_profiles::remove_role_from_instance_profile(&state, &input)
316 }
317
318 "TagUser" => operations::tags::tag_user(&state, &input),
320 "UntagUser" => operations::tags::untag_user(&state, &input),
321 "ListUserTags" => operations::tags::list_user_tags(&state, &input),
322
323 "TagRole" => operations::tags::tag_role(&state, &input),
325 "UntagRole" => operations::tags::untag_role(&state, &input),
326 "ListRoleTags" => operations::tags::list_role_tags(&state, &input),
327
328 "TagInstanceProfile" => operations::tags::tag_instance_profile(&state, &input),
330 "UntagInstanceProfile" => operations::tags::untag_instance_profile(&state, &input),
331 "ListInstanceProfileTags" => {
332 operations::tags::list_instance_profile_tags(&state, &input)
333 }
334
335 "CreateAccountAlias" => operations::account::create_account_alias(&state, &input),
337 "DeleteAccountAlias" => operations::account::delete_account_alias(&state, &input),
338 "ListAccountAliases" => operations::account::list_account_aliases(&state, &input),
339
340 "GetAccountPasswordPolicy" => {
342 operations::account::get_account_password_policy(&state, &input)
343 }
344 "UpdateAccountPasswordPolicy" => {
345 operations::account::update_account_password_policy(&state, &input)
346 }
347 "DeleteAccountPasswordPolicy" => {
348 operations::account::delete_account_password_policy(&state, &input)
349 }
350
351 "GetAccountSummary" => operations::account::get_account_summary(&state, &input),
353 "GetAccountAuthorizationDetails" => {
354 operations::account::get_account_authorization_details(&state, &input)
355 }
356
357 "CreateOpenIDConnectProvider" => {
359 operations::oidc::create_open_id_connect_provider(&state, &input, ctx)
360 }
361 "GetOpenIDConnectProvider" => {
362 operations::oidc::get_open_id_connect_provider(&state, &input)
363 }
364 "ListOpenIDConnectProviders" => {
365 operations::oidc::list_open_id_connect_providers(&state, &input)
366 }
367 "DeleteOpenIDConnectProvider" => {
368 operations::oidc::delete_open_id_connect_provider(&state, &input)
369 }
370 "AddClientIDToOpenIDConnectProvider" => {
371 operations::oidc::add_client_id_to_open_id_connect_provider(&state, &input)
372 }
373 "RemoveClientIDFromOpenIDConnectProvider" => {
374 operations::oidc::remove_client_id_from_open_id_connect_provider(&state, &input)
375 }
376 "UpdateOpenIDConnectProviderThumbprint" => {
377 operations::oidc::update_open_id_connect_provider_thumbprint(&state, &input)
378 }
379
380 "CreateSAMLProvider" => operations::saml::create_saml_provider(&state, &input, ctx),
382 "GetSAMLProvider" => operations::saml::get_saml_provider(&state, &input),
383 "ListSAMLProviders" => operations::saml::list_saml_providers(&state, &input),
384 "DeleteSAMLProvider" => operations::saml::delete_saml_provider(&state, &input),
385 "UpdateSAMLProvider" => operations::saml::update_saml_provider(&state, &input),
386
387 "UploadServerCertificate" => {
389 operations::certificates::upload_server_certificate(&state, &input, ctx)
390 }
391 "GetServerCertificate" => {
392 operations::certificates::get_server_certificate(&state, &input)
393 }
394 "ListServerCertificates" => {
395 operations::certificates::list_server_certificates(&state, &input)
396 }
397 "DeleteServerCertificate" => {
398 operations::certificates::delete_server_certificate(&state, &input)
399 }
400 "TagServerCertificate" => {
401 operations::certificates::tag_server_certificate(&state, &input)
402 }
403 "UntagServerCertificate" => {
404 operations::certificates::untag_server_certificate(&state, &input)
405 }
406 "ListServerCertificateTags" => {
407 operations::certificates::list_server_certificate_tags(&state, &input)
408 }
409
410 "CreateVirtualMFADevice" => {
412 operations::mfa::create_virtual_mfa_device(&state, &input, ctx)
413 }
414 "ListVirtualMFADevices" => operations::mfa::list_virtual_mfa_devices(&state, &input),
415 "DeleteVirtualMFADevice" => operations::mfa::delete_virtual_mfa_device(&state, &input),
416 "EnableMFADevice" => operations::mfa::enable_mfa_device(&state, &input),
417 "DeactivateMFADevice" => operations::mfa::deactivate_mfa_device(&state, &input),
418 "ListMFADevices" => operations::mfa::list_mfa_devices(&state, &input),
419
420 "UploadSSHPublicKey" => operations::ssh_keys::upload_ssh_public_key(&state, &input),
422 "GetSSHPublicKey" => operations::ssh_keys::get_ssh_public_key(&state, &input),
423 "ListSSHPublicKeys" => operations::ssh_keys::list_ssh_public_keys(&state, &input),
424 "DeleteSSHPublicKey" => operations::ssh_keys::delete_ssh_public_key(&state, &input),
425 "UpdateSSHPublicKey" => operations::ssh_keys::update_ssh_public_key(&state, &input),
426
427 "CreateLoginProfile" => operations::users::create_login_profile(&state, &input),
429 "GetLoginProfile" => operations::users::get_login_profile(&state, &input),
430 "UpdateLoginProfile" => operations::users::update_login_profile(&state, &input),
431 "DeleteLoginProfile" => operations::users::delete_login_profile(&state, &input),
432
433 "ListServiceSpecificCredentials" => {
435 operations::misc::list_service_specific_credentials(&state, &input)
436 }
437 "ListSigningCertificates" => {
438 operations::misc::list_signing_certificates(&state, &input)
439 }
440 "SimulateCustomPolicy" => {
441 operations::misc::simulate_custom_policy(&state, self.authz(), &input)
442 }
443 "SimulatePrincipalPolicy" => {
444 operations::misc::simulate_principal_policy(&state, self.authz(), &input)
445 }
446 "GetContextKeysForCustomPolicy" => {
447 operations::misc::get_context_keys_for_custom_policy(&state, &input)
448 }
449 "GetContextKeysForPrincipalPolicy" => {
450 operations::misc::get_context_keys_for_principal_policy(&state, &input)
451 }
452
453 "CreateServiceLinkedRole" => {
455 operations::service_linked_roles::create_service_linked_role(&state, &input, ctx)
456 }
457 "DeleteServiceLinkedRole" => {
458 operations::service_linked_roles::delete_service_linked_role(&state, &input)
459 }
460 "GetServiceLinkedRoleDeletionStatus" => {
461 operations::service_linked_roles::get_service_linked_role_deletion_status(
462 &state, &input,
463 )
464 }
465
466 "GenerateCredentialReport" => {
468 operations::credential_report::generate_credential_report(&state, &input)
469 }
470 "GetCredentialReport" => {
471 operations::credential_report::get_credential_report(&state, &input)
472 }
473
474 "GenerateServiceLastAccessedDetails" => {
476 operations::credential_report::generate_service_last_accessed_details(
477 &state, &input,
478 )
479 }
480 "GetServiceLastAccessedDetails" => {
481 operations::credential_report::get_service_last_accessed_details(&state, &input)
482 }
483 "GetServiceLastAccessedDetailsWithEntities" => {
484 operations::misc::get_service_last_accessed_details_with_entities(&state, &input)
485 }
486
487 "PutUserPermissionsBoundary" => {
489 operations::users::put_user_permissions_boundary(&state, &input)
490 }
491 "DeleteUserPermissionsBoundary" => {
492 operations::users::delete_user_permissions_boundary(&state, &input)
493 }
494 "PutRolePermissionsBoundary" => {
495 operations::roles::put_role_permissions_boundary(&state, &input)
496 }
497 "DeleteRolePermissionsBoundary" => {
498 operations::roles::delete_role_permissions_boundary(&state, &input)
499 }
500
501 "GetAccessKeyLastUsed" => operations::users::get_access_key_last_used(&state, &input),
503 "UpdateAccessKey" => operations::users::update_access_key(&state, &input),
504 "ChangePassword" => operations::users::change_password(&state, &input),
505
506 "UpdateGroup" => operations::groups::update_group(&state, &input),
508 "UpdateServerCertificate" => {
509 operations::certificates::update_server_certificate(&state, &input)
510 }
511
512 "GetMFADevice" => operations::mfa::get_mfa_device(&state, &input),
514 "ResyncMFADevice" => operations::mfa::resync_mfa_device(&state, &input),
515 "TagMFADevice" => operations::mfa::tag_mfa_device(&state, &input),
516 "UntagMFADevice" => operations::mfa::untag_mfa_device(&state, &input),
517 "ListMFADeviceTags" => operations::mfa::list_mfa_device_tags(&state, &input),
518
519 "UploadSigningCertificate" => {
521 operations::misc::upload_signing_certificate(&state, &input)
522 }
523 "UpdateSigningCertificate" => {
524 operations::misc::update_signing_certificate(&state, &input)
525 }
526 "DeleteSigningCertificate" => {
527 operations::misc::delete_signing_certificate(&state, &input)
528 }
529
530 "CreateServiceSpecificCredential" => {
532 operations::misc::create_service_specific_credential(&state, &input)
533 }
534 "DeleteServiceSpecificCredential" => {
535 operations::misc::delete_service_specific_credential(&state, &input)
536 }
537 "ResetServiceSpecificCredential" => {
538 operations::misc::reset_service_specific_credential(&state, &input)
539 }
540 "UpdateServiceSpecificCredential" => {
541 operations::misc::update_service_specific_credential(&state, &input)
542 }
543
544 "ListPoliciesGrantingServiceAccess" => {
546 operations::misc::list_policies_granting_service_access(&state, &input)
547 }
548 "SetSecurityTokenServicePreferences" => {
549 operations::misc::set_security_token_service_preferences(&state, &input)
550 }
551 "GenerateOrganizationsAccessReport" => {
552 operations::misc::generate_organizations_access_report(&state, &input)
553 }
554 "GetOrganizationsAccessReport" => {
555 operations::misc::get_organizations_access_report(&state, &input)
556 }
557
558 "TagOpenIDConnectProvider" => {
560 operations::tags::tag_open_id_connect_provider(&state, &input)
561 }
562 "UntagOpenIDConnectProvider" => {
563 operations::tags::untag_open_id_connect_provider(&state, &input)
564 }
565 "ListOpenIDConnectProviderTags" => {
566 operations::tags::list_open_id_connect_provider_tags(&state, &input)
567 }
568 "TagSAMLProvider" => operations::tags::tag_saml_provider(&state, &input),
569 "UntagSAMLProvider" => operations::tags::untag_saml_provider(&state, &input),
570 "ListSAMLProviderTags" => operations::tags::list_saml_provider_tags(&state, &input),
571
572 _ => Err(AwsError::unknown_operation(operation)),
573 }
574 }
575
576 fn iam_action(&self, operation: &str) -> Option<String> {
577 Some(format!("iam:{operation}"))
578 }
579
580 fn iam_resource(&self, operation: &str, input: &Value, ctx: &RequestContext) -> Option<String> {
581 let prefix = format!("arn:aws:iam::{}", ctx.account_id);
582 match operation {
583 "ListUsers"
584 | "ListGroups"
585 | "ListRoles"
586 | "ListPolicies"
587 | "ListInstanceProfiles"
588 | "ListAccountAliases"
589 | "ListOpenIDConnectProviders"
590 | "ListSAMLProviders"
591 | "ListServerCertificates"
592 | "ListVirtualMFADevices"
593 | "GetAccountSummary"
594 | "GetAccountPasswordPolicy"
595 | "UpdateAccountPasswordPolicy"
596 | "DeleteAccountPasswordPolicy"
597 | "GetAccountAuthorizationDetails"
598 | "GenerateCredentialReport"
599 | "GetCredentialReport"
600 | "GenerateServiceLastAccessedDetails"
601 | "GetServiceLastAccessedDetails"
602 | "GetServiceLastAccessedDetailsWithEntities"
603 | "SimulateCustomPolicy"
604 | "SimulatePrincipalPolicy"
605 | "GetContextKeysForCustomPolicy"
606 | "GetContextKeysForPrincipalPolicy"
607 | "ListServiceSpecificCredentials"
608 | "ListSigningCertificates"
609 | "CreateAccountAlias"
610 | "DeleteAccountAlias" => Some("*".to_string()),
611 op if op.contains("User")
612 && !op.contains("LoginProfile")
613 && !op.contains("AccessKey")
614 && !op.contains("SSHPublicKey")
615 && !op.contains("MFADevice")
616 && !op.contains("ServiceSpecificCredential") =>
617 {
618 input
619 .get("UserName")
620 .and_then(|v| v.as_str())
621 .map(|n| format!("{prefix}:user/{n}"))
622 }
623 "CreateLoginProfile"
624 | "GetLoginProfile"
625 | "UpdateLoginProfile"
626 | "DeleteLoginProfile"
627 | "CreateAccessKey"
628 | "DeleteAccessKey"
629 | "ListAccessKeys"
630 | "UpdateAccessKey"
631 | "GetAccessKeyLastUsed"
632 | "ChangePassword"
633 | "UploadSSHPublicKey"
634 | "GetSSHPublicKey"
635 | "ListSSHPublicKeys"
636 | "DeleteSSHPublicKey"
637 | "UpdateSSHPublicKey"
638 | "EnableMFADevice"
639 | "DeactivateMFADevice"
640 | "ListMFADevices"
641 | "PutUserPermissionsBoundary"
642 | "DeleteUserPermissionsBoundary"
643 | "ListGroupsForUser" => input
644 .get("UserName")
645 .and_then(|v| v.as_str())
646 .map(|n| format!("{prefix}:user/{n}")),
647 op if op.contains("Role")
648 && !op.contains("InstanceProfile")
649 && !op.contains("ServiceLinkedRole") =>
650 {
651 input
652 .get("RoleName")
653 .and_then(|v| v.as_str())
654 .map(|n| format!("{prefix}:role/{n}"))
655 }
656 "PutRolePermissionsBoundary" | "DeleteRolePermissionsBoundary" => input
657 .get("RoleName")
658 .and_then(|v| v.as_str())
659 .map(|n| format!("{prefix}:role/{n}")),
660 "CreateServiceLinkedRole"
661 | "DeleteServiceLinkedRole"
662 | "GetServiceLinkedRoleDeletionStatus" => input
663 .get("RoleName")
664 .and_then(|v| v.as_str())
665 .map(|n| format!("{prefix}:role/aws-service-role/{n}"))
666 .or(Some("*".to_string())),
667 op if op.contains("Group") && !op.contains("ListGroupsForUser") => input
668 .get("GroupName")
669 .and_then(|v| v.as_str())
670 .map(|n| format!("{prefix}:group/{n}")),
671 op if op.contains("InstanceProfile") => input
672 .get("InstanceProfileName")
673 .and_then(|v| v.as_str())
674 .map(|n| format!("{prefix}:instance-profile/{n}")),
675 "CreatePolicy" => input
676 .get("PolicyName")
677 .and_then(|v| v.as_str())
678 .map(|n| format!("{prefix}:policy/{n}")),
679 op if op.contains("Policy") => {
680 if let Some(arn) = input.get("PolicyArn").and_then(|v| v.as_str()) {
681 Some(arn.to_string())
682 } else if let Some(name) = input.get("PolicyName").and_then(|v| v.as_str()) {
683 Some(format!("{prefix}:policy/{name}"))
684 } else {
685 Some("*".to_string())
686 }
687 }
688 op if op.contains("OpenIDConnectProvider") => input
689 .get("OpenIDConnectProviderArn")
690 .and_then(|v| v.as_str())
691 .map(|s| s.to_string())
692 .or_else(|| {
693 input
694 .get("Url")
695 .and_then(|v| v.as_str())
696 .map(|u| format!("{prefix}:oidc-provider/{u}"))
697 }),
698 op if op.contains("SAMLProvider") => input
699 .get("SAMLProviderArn")
700 .and_then(|v| v.as_str())
701 .map(|s| s.to_string())
702 .or_else(|| {
703 input
704 .get("Name")
705 .and_then(|v| v.as_str())
706 .map(|n| format!("{prefix}:saml-provider/{n}"))
707 }),
708 op if op.contains("ServerCertificate") => input
709 .get("ServerCertificateName")
710 .and_then(|v| v.as_str())
711 .map(|n| format!("{prefix}:server-certificate/{n}")),
712 op if op.contains("VirtualMFADevice") || op.contains("MFADevice") => input
713 .get("SerialNumber")
714 .and_then(|v| v.as_str())
715 .map(|s| s.to_string())
716 .or_else(|| {
717 input
718 .get("VirtualMFADeviceName")
719 .and_then(|v| v.as_str())
720 .map(|n| format!("{prefix}:mfa/{n}"))
721 }),
722 _ => Some("*".to_string()),
723 }
724 }
725
726 fn snapshot(&self) -> Option<Vec<u8>> {
727 let mut snapshot = IamStateSnapshot {
728 users: vec![],
729 groups: vec![],
730 roles: vec![],
731 policies: vec![],
732 instance_profiles: vec![],
733 account_aliases: vec![],
734 account_password_policy: None,
735 oidc_providers: vec![],
736 saml_providers: vec![],
737 server_certificates: vec![],
738 virtual_mfa_devices: vec![],
739 login_profiles: vec![],
740 signing_certificates: vec![],
741 service_specific_credentials: vec![],
742 user_permissions_boundaries: vec![],
743 role_permissions_boundaries: vec![],
744 access_key_last_used: vec![],
745 deletion_tasks: vec![],
746 };
747
748 for (_, state) in self.store.iter_all() {
749 snapshot
750 .users
751 .extend(state.users.iter().map(|e| e.value().clone()));
752 snapshot
753 .groups
754 .extend(state.groups.iter().map(|e| e.value().clone()));
755 snapshot
756 .roles
757 .extend(state.roles.iter().map(|e| e.value().clone()));
758 snapshot
759 .policies
760 .extend(state.policies.iter().map(|e| e.value().clone()));
761 snapshot
762 .instance_profiles
763 .extend(state.instance_profiles.iter().map(|e| e.value().clone()));
764 if let Ok(aliases) = state.account_aliases.lock() {
765 snapshot.account_aliases.extend(aliases.clone());
766 }
767 if let Ok(policy) = state.account_password_policy.lock()
768 && snapshot.account_password_policy.is_none()
769 {
770 snapshot.account_password_policy = policy.clone();
771 }
772 snapshot
773 .oidc_providers
774 .extend(state.oidc_providers.iter().map(|e| e.value().clone()));
775 snapshot
776 .saml_providers
777 .extend(state.saml_providers.iter().map(|e| e.value().clone()));
778 snapshot
779 .server_certificates
780 .extend(state.server_certificates.iter().map(|e| e.value().clone()));
781 snapshot
782 .virtual_mfa_devices
783 .extend(state.virtual_mfa_devices.iter().map(|e| e.value().clone()));
784 snapshot
785 .login_profiles
786 .extend(state.login_profiles.iter().map(|e| e.value().clone()));
787 snapshot
788 .signing_certificates
789 .extend(state.signing_certificates.iter().map(|e| e.value().clone()));
790 snapshot.service_specific_credentials.extend(
791 state
792 .service_specific_credentials
793 .iter()
794 .map(|e| e.value().clone()),
795 );
796 snapshot.user_permissions_boundaries.extend(
797 state
798 .user_permissions_boundaries
799 .iter()
800 .map(|e| (e.key().clone(), e.value().clone())),
801 );
802 snapshot.role_permissions_boundaries.extend(
803 state
804 .role_permissions_boundaries
805 .iter()
806 .map(|e| (e.key().clone(), e.value().clone())),
807 );
808 snapshot.access_key_last_used.extend(
809 state
810 .access_key_last_used
811 .iter()
812 .map(|e| (e.key().clone(), e.value().clone())),
813 );
814 snapshot
815 .deletion_tasks
816 .extend(state.deletion_tasks.iter().map(|e| DeletionTask {
817 task_id: e.key().clone(),
818 role_name: e.value().clone(),
819 status: "SUCCEEDED".to_string(),
820 }));
821 }
822
823 serde_json::to_vec(&snapshot).ok()
824 }
825
826 fn restore(&self, data: &[u8]) -> Result<(), String> {
827 let snapshot: IamStateSnapshot = serde_json::from_slice(data).map_err(|e| e.to_string())?;
828
829 let account_id = snapshot
832 .users
833 .first()
834 .map(|u| {
835 let parts: Vec<&str> = u.arn.splitn(6, ':').collect();
837 if parts.len() >= 5 {
838 parts[4].to_string()
839 } else {
840 "000000000000".to_string()
841 }
842 })
843 .or_else(|| {
844 snapshot.roles.first().map(|r| {
845 let parts: Vec<&str> = r.arn.splitn(6, ':').collect();
846 if parts.len() >= 5 {
847 parts[4].to_string()
848 } else {
849 "000000000000".to_string()
850 }
851 })
852 })
853 .unwrap_or_else(|| "000000000000".to_string());
854
855 let state = self.store.get(&account_id, IAM_REGION);
856
857 for user in snapshot.users {
858 state.users.insert(user.user_name.clone(), user);
859 }
860 for group in snapshot.groups {
861 state.groups.insert(group.group_name.clone(), group);
862 }
863 for role in snapshot.roles {
864 state.roles.insert(role.role_name.clone(), role);
865 }
866 for policy in snapshot.policies {
867 state.policies.insert(policy.arn.clone(), policy);
868 }
869 for ip in snapshot.instance_profiles {
870 state
871 .instance_profiles
872 .insert(ip.instance_profile_name.clone(), ip);
873 }
874 if !snapshot.account_aliases.is_empty()
875 && let Ok(mut aliases) = state.account_aliases.lock()
876 {
877 *aliases = snapshot.account_aliases;
878 }
879 if let Ok(mut policy) = state.account_password_policy.lock() {
880 *policy = snapshot.account_password_policy;
881 }
882 for provider in snapshot.oidc_providers {
883 state.oidc_providers.insert(provider.arn.clone(), provider);
884 }
885 for provider in snapshot.saml_providers {
886 state.saml_providers.insert(provider.arn.clone(), provider);
887 }
888 for cert in snapshot.server_certificates {
889 state
890 .server_certificates
891 .insert(cert.server_certificate_name.clone(), cert);
892 }
893 for device in snapshot.virtual_mfa_devices {
894 state
895 .virtual_mfa_devices
896 .insert(device.serial_number.clone(), device);
897 }
898 for profile in snapshot.login_profiles {
899 state
900 .login_profiles
901 .insert(profile.user_name.clone(), profile);
902 }
903 for cert in snapshot.signing_certificates {
904 state
905 .signing_certificates
906 .insert(cert.certificate_id.clone(), cert);
907 }
908 for cred in snapshot.service_specific_credentials {
909 state
910 .service_specific_credentials
911 .insert(cred.service_specific_credential_id.clone(), cred);
912 }
913 for (user_name, boundary_arn) in snapshot.user_permissions_boundaries {
914 state
915 .user_permissions_boundaries
916 .insert(user_name, boundary_arn);
917 }
918 for (role_name, boundary_arn) in snapshot.role_permissions_boundaries {
919 state
920 .role_permissions_boundaries
921 .insert(role_name, boundary_arn);
922 }
923 for (key_id, last_used) in snapshot.access_key_last_used {
924 state.access_key_last_used.insert(key_id, last_used);
925 }
926 for task in snapshot.deletion_tasks {
927 state.deletion_tasks.insert(task.task_id, task.role_name);
928 }
929
930 Ok(())
931 }
932}
933
934#[cfg(test)]
935mod root_protection_tests {
936 use super::*;
937 use serde_json::json;
938
939 fn external_ctx() -> RequestContext {
940 RequestContext::new_with_account("iam", "us-east-1", "000000000000")
941 }
942
943 fn internal_ctx() -> RequestContext {
944 RequestContext::internal("iam", "us-east-1", "000000000000")
945 }
946
947 #[tokio::test]
948 async fn external_delete_user_root_is_denied() {
949 let svc = IamService::new();
950 let err = svc
951 .handle("DeleteUser", json!({ "UserName": "root" }), &external_ctx())
952 .await
953 .expect_err("expected AccessDenied");
954 assert_eq!(err.code, "AccessDeniedException");
955 }
956
957 #[tokio::test]
958 async fn external_attach_user_policy_root_is_denied() {
959 let svc = IamService::new();
960 let err = svc
961 .handle(
962 "AttachUserPolicy",
963 json!({
964 "UserName": "root",
965 "PolicyArn": "arn:aws:iam::aws:policy/AdministratorAccess",
966 }),
967 &external_ctx(),
968 )
969 .await
970 .expect_err("expected AccessDenied");
971 assert_eq!(err.code, "AccessDeniedException");
972 }
973
974 #[tokio::test]
975 async fn external_create_login_profile_root_is_denied() {
976 let svc = IamService::new();
977 let err = svc
978 .handle(
979 "CreateLoginProfile",
980 json!({ "UserName": "root", "Password": "anything!ABC" }),
981 &external_ctx(),
982 )
983 .await
984 .expect_err("expected AccessDenied");
985 assert_eq!(err.code, "AccessDeniedException");
986 }
987
988 #[tokio::test]
989 async fn internal_create_user_root_is_allowed() {
990 let svc = IamService::new();
991 svc.handle("CreateUser", json!({ "UserName": "root" }), &internal_ctx())
992 .await
993 .expect("internal bootstrap should succeed");
994 }
995
996 #[tokio::test]
997 async fn external_list_users_succeeds_with_root_present() {
998 let svc = IamService::new();
999 svc.handle("CreateUser", json!({ "UserName": "root" }), &internal_ctx())
1000 .await
1001 .unwrap();
1002 let resp = svc.handle("ListUsers", json!({}), &external_ctx()).await;
1005 assert!(resp.is_ok());
1006 }
1007
1008 #[tokio::test]
1009 async fn external_get_user_root_is_allowed() {
1010 let svc = IamService::new();
1011 svc.handle("CreateUser", json!({ "UserName": "root" }), &internal_ctx())
1012 .await
1013 .unwrap();
1014 let resp = svc
1015 .handle("GetUser", json!({ "UserName": "root" }), &external_ctx())
1016 .await;
1017 assert!(resp.is_ok(), "GetUser on root must remain readable");
1018 }
1019
1020 #[tokio::test]
1021 async fn external_mutation_on_non_root_user_is_allowed() {
1022 let svc = IamService::new();
1023 svc.handle(
1027 "CreateUser",
1028 json!({ "UserName": "alice" }),
1029 &external_ctx(),
1030 )
1031 .await
1032 .unwrap();
1033 let resp = svc
1034 .handle(
1035 "DeleteUser",
1036 json!({ "UserName": "alice" }),
1037 &external_ctx(),
1038 )
1039 .await;
1040 assert!(resp.is_ok());
1041 }
1042}