◐partialEvery identifier the account has issued, with the date it was assigned and the AWS-generated unique id behind it; the workforce identifiers issued through IAM Identity Center and the external issuer each one came from; and the CloudTrail record of identifiers being deleted, which is the only dated evidence of a name becoming free to reusecontinuouscli
identifier-assignment-and-reuse-preventionAWS IAM · AWS IAM Identity Center · AWS CloudTrail
IA-04 is four verbs — receive authorization for an identifier, select it, assign it to the intended party, and prevent its reuse for a defined period. AWS proves the third completely, gives real but bounded evidence for the fourth, and returns nothing at all for the first two.
Assignment is the easy half and it is genuinely complete: every identifier in the account is enumerable with the date it was assigned and the AWS-generated unique id behind it. Selection and authorization are records about a decision, not about a resource, and no call returns them.
Reuse is where this control is usually mis-evidenced, and AWS's own documentation is unusually direct about why. Within an account a friendly name for a user, group, role or policy must be unique — but only while it exists. AWS documents the exact failure IA-04's reuse clause exists to prevent: an employee named John leaves, the IAM user John is deleted, a new employee named John arrives, a new IAM user John is created, and a policy written against the friendly name grants the new John access to what the old John left behind. Nothing in AWS prevents that. There is no cooling-off period, no reserved-name list, no setting. What AWS guarantees instead is narrower and worth reading precisely: the unique id is never reused, so the old John's AIDA... and the new John's AIDA... differ, and a resource-based policy or an aws:userId condition written against the unique id cannot be inherited by a successor. The identifier that is protected from reuse is the one nobody uses in policy by default.
So the honest evidence for the reuse limb is a comparison, not a field: DeleteUser events dated against the CreateDate of a live identifier bearing the same name — UserDetailList[].CreateDate is collected here and is the right side of that comparison, which is why no CreateUser lookup is listed. Note what to read on the left side: Username on a lookup-events entry is the REQUESTER, the administrator who ran the deletion, so the deleted identifier has to come out of the CloudTrailEvent JSON at requestParameters.userName. Reading Username as the deleted name is the easy mistake and it produces a comparison against the wrong string entirely. Three limits on the comparison, all hard. lookup-events reads management events for the last 90 DAYS only, so any reuse period your SSP states beyond 90 days cannot be evidenced from this call at all — a CloudTrail Lake event data store or an S3 trail with a longer retention is what closes that, and it is a different collection. The call is also Region-bound in a way that has nothing to do with GovCloud: IAM is a global service, its events are recorded in one Region, and lookup-events shows them in the Region where they occurred — so the same command run anywhere else returns an empty Events[] while succeeding, which is indistinguishable from a window in which nobody was deleted. And the comparison itself is a join between two commands' outputs, which the assertion grammar cannot express: it compares a field to a constant, never one response to another. Both of those are why the reuse limb is written into the notes as a manual reconciliation rather than dressed up as a clause below.
The IAM users this recipe enumerates are also, on a well-run estate, the smallest part of the answer. Workforce identifiers should be issued in an external identity provider and reach AWS through IAM Identity Center, where the identifier's authority is the ExternalIds pair naming the issuer — which is what the third call reads. An account with an empty UserDetailList and a populated identity store is in better shape than the reverse, and an assertion that ranges over IAM users will say almost nothing about it. Read the two together or read neither.
The identity-store calls are collected as EVIDENCE and deliberately carry no assertion. An earlier draft asserted that an Identity Center instance exists and that every identity-store user carries ExternalIds; both were withdrawn, because they fail an account that federates through an IAM SAML provider instead — which is a different architecture, not an IA-04 defect — and because the second is vacuous anyway on an instance with zero users, so the first does not guard it. An assertion that encodes an architectural preference as a control failure is worse than a paragraph saying which architecture this evidence reads best.
Emptiness traps to state rather than assert. list-instances returns a list whose documented minimum length is zero and whose maximum is ten, so an account with no instance returns an empty Instances[] rather than an error, and reading only the first entry misses up to nine. A DeleteUser lookup over a window in which nobody was deleted returns an empty Events[], indistinguishable from a window in which the trail was not recording. And GroupDetail carries no Tags, so the authorization-tag clause below cannot be extended to group identifiers at all; device and service identifiers are outside every call here.
Roles and groups are collected alongside users because their names are identifiers the control covers, and because the same reuse behaviour applies: RoleId and GroupId are never reused, while RoleName and GroupName are free the moment the resource is deleted.