What a transferred individual can still reach, and what they have actually used: the reassignment events themselves from CloudTrail (group membership, attached policy, and Identity Center account-assignment changes, each with its time and the administrator who made it), the current Identity Center assignments per permission set, and IAM's service-last-accessed report for the identities involved — a per-principal view of which services the identity is permitted to reach and which of those it has never authenticated to. `iam-access-analyzer-unused-access` answers the same question estate-wide from findings; this answers it for the named principal a transfer is about.
The API supplies the facts, but a human judgement against a documented baseline turns them into evidence.
Fetch
$ aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventName,AttributeValue=AddUserToGroup --start-time <T0> --end-time <T1>$ aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventName,AttributeValue=RemoveUserFromGroup --start-time <T0> --end-time <T1>$ aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventName,AttributeValue=AttachUserPolicy --start-time <T0> --end-time <T1>$ aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventName,AttributeValue=DetachUserPolicy --start-time <T0> --end-time <T1>$ aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventName,AttributeValue=CreateAccountAssignment --start-time <T0> --end-time <T1>$ aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventName,AttributeValue=DeleteAccountAssignment --start-time <T0> --end-time <T1>$ aws sso-admin list-permission-sets --instance-arn <INSTANCE_ARN>$ aws sso-admin list-account-assignments --instance-arn <INSTANCE_ARN> --account-id <ACCOUNT_ID> --permission-set-arn <PERMISSION_SET_ARN>$ aws iam generate-service-last-accessed-details --arn <PRINCIPAL_ARN> --granularity ACTION_LEVEL$ aws iam get-service-last-accessed-details --job-id <JOB_ID>Expected output
From each lookup-events call, Events[] with EventName, EventTime, Username — the REQUESTER who made the change, never the identity changed — and CloudTrailEvent, whose requestParameters names the user, group, policy or account assignment acted on. Six calls rather than one because lookup-events accepts exactly ONE lookup attribute per request and a reassignment is six different APIs across two services; the window is bounded at 90 days by the API. From list-permission-sets, PermissionSets[] of ARNs. From list-account-assignments, AccountAssignments[] with AccountId, PermissionSetArn, PrincipalId and PrincipalType (USER or GROUP) — for ONE permission set in ONE account, which is why list-permission-sets precedes it: the assignment set of an account is the union over its permission sets and no single call returns it. From generate-service-last-accessed-details, a JobId (36 characters); from get-service-last-accessed-details, JobStatus (IN_PROGRESS | COMPLETED | FAILED) and ServicesLastAccessed[] with ServiceName, ServiceNamespace, LastAuthenticated, LastAuthenticatedEntity and TotalAuthenticatedEntities, plus TrackedActionsLastAccessed[] when the request asked for ACTION_LEVEL. A service the principal is permitted to call and has never called comes back with the entry PRESENT and LastAuthenticated ABSENT, which is the shape that makes unused-but-granted access visible at all.
Assertions — what makes it a pass
| Field | Must be | For | Proves |
|---|---|---|---|
| get-service-last-accessed-details.ServicesLastAccessed[?!not_null(LastAuthenticated)] | count_eq 0After the reassignment review, the transferred principal holds no permission it has never used. The offender form — the services with no LastAuthenticated at all — because a service the principal has never called is listed WITHOUT the most-recent-attempt details rather than with a null timestamp, so a projection over LastAuthenticated drops exactly the rows this clause exists to find. Read a non-zero count as the review's work list, not as a failure: the control is satisfied by acting on it within the period the SSP defines, and whether the individual still needs each one is the HR judgement no command makes. | every row | PS-05 |
| get-service-last-accessed-details.JobStatus | eq "COMPLETED"The last-accessed report actually finished. The generate call returns a JobId immediately and the get call answers IN_PROGRESS until the report exists — a collector that reads ServicesLastAccessed[] from an unfinished job reads an empty array, which would make the clause above vacuously true at the exact moment it has learned nothing. | every row | PS-05 |
Authored opinion, like the commands. Units live in the operator name — max-age-days is days, and nothing here is converted for you.
Map — what it proves
- recipe1
- recipe— an authored recipe collects evidence for this control
- KSI only— a Key Security Indicator reaches it, but no recipe is authored yet
- orphan— no Key Security Indicator reaches it — a person writes it up instead
What else these families can fetch:PS 2/11 →
GovCloud
IAM, CloudTrail and IAM Identity Center all operate in both AWS GovCloud (US) Regions; ARNs use partition arn:aws-us-gov, and the sso-admin ARN patterns accept it. Two partition traps, both real here. IAM is global and its events are recorded in AWS GovCloud (US-West), us-gov-west-1, while lookup-events shows events in the Region where they occurred — so the four IAM lookups issued against us-gov-east-1 return an empty Events[] and exit zero, which reads exactly like a period in which nobody transferred. The equivalent commercial Region is us-east-1. Second, IAM Identity Center has no multi-Region support in GovCloud, so the sso-admin calls must be issued against the Region its instance was enabled in. What AWS documents is the absence of multi-Region support; what a call issued against the other Region actually does is not documented on that page and this recipe does not guess. Establish which Region holds the instance before collecting, rather than inferring it from a response.
Notes & assertions
PS-05 is about a person moving inside the organization: the access that fitted the old role is reviewed, what is no longer needed is removed, and the transfer is completed within a period the provider defines. AWS holds two-thirds of that and cannot hold the rest. What it holds is the CHANGE and the STATE. CloudTrail records the six APIs a reassignment actually travels through — two for group membership, two for attached user policies, two for Identity Center account assignments — each with a timestamp and the administrator who made it. Identity Center holds the assignments as they stand today. What no call knows is that a person MOVED. There is no roster in AWS, no job title, no transfer date, so the join between 'this human changed teams on the 4th' and 'this principal lost the finance permission set on the 9th' is made by a human against an HR record, and the period the control defines is checked on that join. This recipe produces the AWS-side column and the rating says so. The third call set is the one that earns its place. A separation is visible as an absence; a transfer is visible as a RESIDUE — the old permission that nobody removed because nothing failed when it stayed. Service-last-accessed is the signal that names it per principal: for a principal it lists every service the identity could reach under its permissions policies, with the last time it authenticated, and an entry with no LastAuthenticated is a permission granted and never exercised. Run against the transferred principal it turns 'review the access that no longer fits' from an interview into a list. It is not the only route to that question — `iam-access-analyzer-unused-access` answers it estate-wide from findings — but it is the one that answers it about a NAMED identity, which is the unit a transfer is measured in. Read its four documented limits before quoting it. It reports for at least the last 400 days, and less in a Region that began supporting the feature within the last year — so a young Region reports a shorter history than the reviewer assumes. Recent activity usually appears within four hours, so a report pulled immediately after a transfer describes the state before it. It records ATTEMPTS and not successes: a denied call still marks a service as accessed, and AWS names CloudTrail as the authoritative source for whether a call succeeded. And it applies permissions-policy logic ONLY — resource-based policies, ACLs, Organizations policies, permissions boundaries and STS assume-role trust are all excluded, so an identity that reaches a bucket purely through a bucket policy is invisible to it. That last one bounds the whole claim: 'no unused permissions' from this report is a statement about identity policies, not about reach. list-account-assignments takes an account id AND a permission set ARN, and returns the assignees of that one pair. There is no call that returns an account's assignments whole, which is why list-permission-sets runs first and the collection is a loop. A single unlooped call would be a slice of one permission set presented as an account's access, and an assertion over it would be green on an estate where every other permission set is wrong. The authentication indicators are untouched here — nothing collected reads a factor. KSI-IAM-ELP is earned outright: a permission that fits a role the holder has left is exactly the failure of 'each user can access only what they need'. KSI-IAM-JIT is deliberately NOT claimed, on this overlay's own precedent: the PS-04 recipe dropped it because nothing evaluated over STANDING credentials examines a just-in-time or attribute-based model, and only the 'persistently reviewed' tail is touched — a tail KSI-IAM-ELP already carries. Everything collected here is standing too: standing credentials, standing group membership, standing account assignments. The just-in-time half is the Identity Center elevation recipe's to answer, and claiming it here would have had two sibling recipes taking opposite positions on one indicator over one class of telemetry.
References
- AWS CLI: cloudtrail lookup-events — one lookup attribute per request, events within the last 90 days, Username is the REQUESTER that called the API https://docs.aws.amazon.com/cli/latest/reference/cloudtrail/lookup-events.html
- IAM API: GenerateServiceLastAccessedDetails — Granularity SERVICE_LEVEL | ACTION_LEVEL, at least 400 days of history, activity appears within about four hours, records ATTEMPTS including denied ones, and applies permissions-policy logic only (resource-based policies, ACLs, Organizations policies, permissions boundaries and assume-role trust excluded) https://docs.aws.amazon.com/IAM/latest/APIReference/API_GenerateServiceLastAccessedDetails.html
- IAM API: GetServiceLastAccessedDetails — JobStatus is IN_PROGRESS | COMPLETED | FAILED, and a service with no access attempt is listed WITHOUT details about the most recent attempt (the shape both assertions read) https://docs.aws.amazon.com/IAM/latest/APIReference/API_GetServiceLastAccessedDetails.html
- IAM Identity Center API: ListAccountAssignments — requires both AccountId and PermissionSetArn, returns AccountAssignments[] with PrincipalType and PrincipalId for that one pair https://docs.aws.amazon.com/singlesignon/latest/APIReference/API_ListAccountAssignments.html
- AWS GovCloud (US) User Guide: AWS CloudTrail — IAM and AWS STS events are recorded in us-gov-west-1 and lookup-events shows them in the Region where they occurred https://docs.aws.amazon.com/govcloud-us/latest/UserGuide/govcloud-ct.html
- AWS GovCloud (US) User Guide: IAM Identity Center — multi-Region support is not available, so the instance answers only in the Region it was enabled in https://docs.aws.amazon.com/govcloud-us/latest/UserGuide/govcloud-sso.html
This AWS mapping is authored opinion (overlay v3.0.0), versioned separately from the dataset and written against ruleset 2026.07.14.01. The upstream FedRAMP dataset names none of these tools.
The Identity and Access Management run (16)
- automatable
- partial — needs judgement
- narrative — no API proves this
Every authored recipe filed under IAM, in the order the plan works them. The mark says how much of the evidence the command produces on its own.
- partialAccount-wide credential report proving MFA is active per principal and that passwords/access keys are rotated within policymonthlyiam-credential-reportIAM
- partialFull snapshot of every IAM user, group, role, and attached/inline policy with their relationships, used to review that granted permissions match least-privilege intentquarterlyiam-account-authorization-detailsIAM
- partialActive unused-access findings identifying IAM roles, access keys, console passwords, and service/action-level permissions that have not been used within the configured age, driving right-sizing and removalcontinuousiam-access-analyzer-unused-accessIAM Access Analyzer
- partialAWS Config compliance result for the managed rule proving every IAM user with a console password has MFA enabledcontinuousconfig-mfa-enabled-console-accessAWS Config · IAM
- partialAWS Config compliance result proving long-lived IAM access keys (including those used by service/non-user identities) are rotated within the maximum agecontinuousconfig-access-keys-rotatedAWS Config · IAM
- partialAWS Config compliance result proving no customer-managed IAM policy grants full administrative access (Allow Action:* on Resource:*)continuousconfig-iam-policy-no-admin-accessAWS Config · IAM
- narrativeDocumented just-in-time / break-glass privilege-elevation process backed by IAM Identity Center permission sets and account assignments, showing privileged access is role/attribute-based, time-bound, and approval-gated rather than standingquarterlyidentity-center-jit-elevation-workflowIAM Identity Center
- partialGuardDuty IAM/credential-abuse findings (detection) paired with CloudTrail records of the responsive action taken to disable or secure the affected privileged principalcontinuousguardduty-suspicious-iam-activity-responseGuardDuty · CloudTrail · IAM
- partialHow operators actually reach the environment from outside it: the managed access paths that exist, the logging and encryption configured on them, the session-by-session record of who used them, and the negative check that no instance is directly reachable insteadweeklyremote-access-authorization-and-monitoringAWS Systems Manager Session Manager · AWS Client VPN · AWS Config · Amazon EC2
- partialEvery way a workforce user can authenticate into the account, counted and named in one pass — how many IAM users and federated trusts exist, which SAML and OIDC providers are registered, whether an IAM Identity Center instance is the workforce entry path — together with the state of the two credentials that belong to no person: the root user's access key and the account's X.509 signing certificatemonthlyidentity-sources-and-root-credential-lockdownAWS IAM · AWS IAM Identity Center · AWS Config
- partialFor every Amazon Cognito directory that fronts non-organizational users: the user pool's multi-factor configuration, and every identity pool's guest-access flag together with the named external providers it will exchange a token forweeklycognito-external-user-authenticationAmazon Cognito · AWS IAM
- partialThe configured ceiling on how long any credential stays valid before its holder must present an authenticator again — MaxSessionDuration on every IAM role, SessionDuration on every IAM Identity Center permission set, and the aws:MultiFactorAuthAge conditions in policy that expire an MFA-backed session independently of the session itselfquarterlysession-lifetime-and-reauthenticationAWS IAM · AWS IAM Identity Center · AWS STS
- partialThe mechanism that ends a temporary or emergency account without anyone deciding to: the AWS Config rule that measures how long an IAM credential has gone unused, the period it is configured with, the remediation configuration proving the revocation fires automatically, and an empty non-compliant set showing nothing has outlived the periodcontinuoustemporary-account-automatic-revocationAWS Config · AWS IAM · AWS Systems Manager Automation
- 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 reusecontinuousidentifier-assignment-and-reuse-preventionAWS IAM · AWS IAM Identity Center · AWS CloudTrail
- partialThe state of every credential in the account as of a stated moment, the enabled-or-disabled status of every workforce identity in the identity store, and the CloudTrail record of the revocations themselves — the five API calls that actually revoke standing access, each with the time it happened and the administrator who did itcontinuouspersonnel-separation-access-revocationAWS IAM · AWS IAM Identity Center · AWS CloudTrail
- partialWhat a transferred individual can still reach, and what they have actually used: the reassignment events themselves from CloudTrail (group membership, attached policy, and Identity Center account-assignment changes, each with its time and the administrator who made it), the current Identity Center assignments per permission set, and IAM's service-last-accessed report for the identities involved — a per-principal view of which services the identity is permitted to reach and which of those it has never authenticated to. `iam-access-analyzer-unused-access` answers the same question estate-wide from findings; this answers it for the named principal a transfer is about.you are herecontinuouspersonnel-transfer-access-reassignmentAWS IAM · AWS IAM Identity Center · AWS CloudTrail