โ† AWS IAM & Access Engineer

Setup guide

Everything you must provision before this skill can run โ€” the exact credentials, permissions, and where to click. Links go to the official consoles and docs, which stay current.

Some setupโ‰ˆ 15-30 minaws

An AWS account with credentials that can read IAM (IAMReadOnlyAccess or equivalent), plus AWS CLI v2 installed and an authenticated profile or SSO session.

You need to already have

  • An AWS account you are authorized to inspect, and its account id written down
  • AWS CLI v2 installed (aws --version reports 2.x); v1 lacks several accessanalyzer subcommands
  • Python 3.9+ with boto3 installed (pip install boto3) if the scripted path is used
  • jq installed for parsing CloudTrail and simulator JSON output
  • Permission to read IAM: the AWS managed policy arn:aws:iam::aws:policy/IAMReadOnlyAccess at minimum
  • For SCP/RCP inspection: credentials in the Organizations management account or a delegated administrator account
  • For write operations: a separately approved role with iam:CreateRole, iam:PutRolePolicy and iam:AttachRolePolicy - do not grant these until a change is approved

How the pieces connect

Credentials this skill needs

Set these as environment variables. Never paste secrets into a chat or commit them.

VariableWhat it isWhere to get it
AWS_PROFILE
optional
AWS named profile
format: Profile name matching a [profile <name>] block in ~/.aws/config, e.g. security-audit
Preferred over static keys. Create with 'aws configure sso' (IAM Identity Center) or 'aws configure --profile <name>', then authenticate with 'aws sso login --profile <name>'. Verify the profile resolves with 'aws configure list --profile <name>'.
AWS_REGION
AWS region
format: Region code, e.g. us-east-1, eu-west-1
IAM itself is global, but CloudTrail lookup, Access Analyzer and the failing service call are regional. Set with 'export AWS_REGION=us-east-1' or read the configured value with 'aws configure get region'.
AWS_ACCESS_KEY_ID
secretoptional
AWS access key id
format: 20-character string beginning AKIA (long-lived user key) or ASIA (temporary session key)
Only if SSO/profile auth is unavailable. IAM console > Users > <user> > Security credentials > Access keys > Create access key. AWS recommends temporary credentials over long-lived keys; prefer AWS_PROFILE with IAM Identity Center. Never commit this value.
AWS_SECRET_ACCESS_KEY
secretoptional
AWS secret access key
format: 40-character base64-style string
Shown exactly once, at the moment the access key is created in IAM console > Users > <user> > Security credentials > Create access key. It cannot be retrieved later - rotate the key if lost.
AWS_SESSION_TOKEN
secretoptional
AWS session token
format: Long opaque base64 string, several hundred characters
Required only with temporary credentials (ASIA... keys) from 'aws sts assume-role' or IAM Identity Center. Emitted alongside the key pair in the AssumeRole response; expires with the session (default 1 hour).

Permissions to grant

arn:aws:iam::aws:policy/IAMReadOnlyAccessIAM policy

Baseline read of roles, users, policies, versions and account authorization details for every audit and diagnosis path.

โ†ณ Replace with a customer-managed policy limited to iam:GetRole, iam:ListRoles, iam:ListAttachedRolePolicies, iam:ListRolePolicies, iam:GetRolePolicy, iam:GetPolicy and iam:GetPolicyVersion if full IAM read is too broad.

iam:SimulatePrincipalPolicyIAM policy

Predicts the authorization decision for a real principal without making the live call - the core diagnostic step.

โ†ณ Scope Resource to the specific principal ARNs under audit rather than "*".

iam:SimulateCustomPolicyIAM policy

Tests a draft identity or resource policy document before it is attached to anything.

โ†ณ Operates on supplied documents only and touches no account state; safe to grant on "*".

iam:GetContextKeysForPrincipalPolicyIAM policy

Lists the condition keys a principal's policies reference so the simulation can be run with complete context.

โ†ณ Scope Resource to the principal ARNs under audit.

iam:GetAccountAuthorizationDetailsIAM policy

Bulk-exports every identity and policy in one call for whole-account privilege audits and 'who can do X' questions.

โ†ณ Highly sensitive - it returns the full authorization surface of the account. Grant only for scheduled audits, not day-to-day debugging.

iam:GenerateServiceLastAccessedDetailsIAM policy

Starts the job that produces evidence of which services and actions a principal actually used, driving least-privilege trimming.

โ†ณ Pair with iam:GetServiceLastAccessedDetails; scope to audited principal ARNs.

iam:GetServiceLastAccessedDetailsIAM policy

Retrieves the completed last-accessed report used to identify unused permissions safely.

โ†ณ Job ids are caller-scoped, so "*" is acceptable here.

iam:GenerateCredentialReportIAM policy

Produces the account-wide credential report for hygiene audits: stale keys, missing MFA, unused users.

โ†ณ Account-level operation that cannot be resource-scoped; pair with iam:GetCredentialReport and never print key material from the result.

access-analyzer:ValidatePolicyIAM policy

Lints every policy document for errors and security warnings before attachment - the mandatory dry-run for IAM.

โ†ณ Stateless check on a supplied document; safe on "*".

access-analyzer:CheckNoNewAccessIAM policy

Proves a policy edit does not widen access relative to the previous version, gating every change.

โ†ณ Stateless comparison of two supplied documents; safe on "*".

access-analyzer:CheckAccessNotGrantedIAM policy

Asserts a policy cannot grant named high-risk actions such as iam:PassRole or iam:CreateAccessKey.

โ†ณ Stateless check on a supplied document; safe on "*".

cloudtrail:LookupEventsIAM policy

Retrieves the actual denial events, including errorCode and the full errorMessage that names the responsible policy type.

โ†ณ Cannot be resource-scoped. Restrict by attaching it only to the audit role, and prefer a read-only CloudTrail Lake query role for long-range history.

organizations:ListPoliciesForTargetIAM policyadmin consent required

Walks the OU hierarchy to enumerate the SCPs and RCPs that apply to the account, since no effective-SCP API exists.

โ†ณ Only works from the management or delegated administrator account. Pair with organizations:ListParents and organizations:DescribePolicy; all three are read-only.

iam:PutRolePermissionsBoundaryIAM policyadmin consent required

Applies a permission boundary to cap a role's maximum privilege after the boundary has been simulated.

โ†ณ Write action - grant only for an approved change window, scoped to the specific role ARNs, and condition on iam:PermissionsBoundary to prevent boundary removal.

iam:CreateRoleIAM policyadmin consent required

Creates new service, cross-account or workload-identity roles once the trust policy has been reviewed.

โ†ณ Write action - scope Resource to a naming prefix such as arn:aws:iam::<account>:role/app-* and require a permissions boundary via the iam:PermissionsBoundary condition key.

Step by step

  1. 1

    Install and verify AWS CLI v2

    Install from the official installer, then confirm the major version: 'aws --version' must report aws-cli/2.x. CLI v1 does not ship the accessanalyzer check-no-new-access and check-access-not-granted subcommands this skill relies on.

    Open in the console / docs โ†—
  2. 2

    Authenticate with IAM Identity Center (preferred)

    Run 'aws configure sso', supply the SSO start URL and region, pick the account and permission set, and name the profile. Then run 'aws sso login --profile <name>' and export it with 'export AWS_PROFILE=<name>'. This yields temporary credentials and avoids long-lived keys entirely.

    Open in the console / docs โ†—
  3. 3

    Fallback: configure static credentials

    Only if SSO is unavailable. In the IAM console go to Users > <user> > Security credentials > Access keys > Create access key, then run 'aws configure' and paste the key id, secret, and default region. The secret is displayed once and cannot be recovered.

    Open in the console / docs โ†—
  4. 4

    Grant the read-only audit permissions

    Attach the managed policy with 'aws iam attach-role-policy --role-name <audit-role> --policy-arn arn:aws:iam::aws:policy/IAMReadOnlyAccess', then add a customer-managed policy granting cloudtrail:LookupEvents, access-analyzer:ValidatePolicy, access-analyzer:CheckNoNewAccess, access-analyzer:CheckAccessNotGranted, iam:SimulatePrincipalPolicy, iam:SimulateCustomPolicy and iam:GetContextKeysForPrincipalPolicy.

    Open in the console / docs โ†—
  5. 5

    Confirm the policy simulator is reachable

    Run: aws iam simulate-principal-policy --policy-source-arn "$(aws sts get-caller-identity --query Arn --output text)" --action-names iam:GetRole --query 'EvaluationResults[].EvalDecision' --output text. Any of allowed / implicitDeny / explicitDeny proves the API works. An AccessDenied here means iam:SimulatePrincipalPolicy is missing.

    Open in the console / docs โ†—
  6. 6

    Enable a CloudTrail trail if none exists

    Check with 'aws cloudtrail describe-trails --query "trailList[].[Name,IsMultiRegionTrail]" --output table'. Without a trail, lookup-events returns only the 90-day event history and no data events. Add data event selectors for S3/Lambda if object-level denials must be diagnosed.

    Open in the console / docs โ†—
  7. 7

    Optional: enable IAM Access Analyzer

    Create an account or organization analyzer with 'aws accessanalyzer create-analyzer --analyzer-name default --type ACCOUNT' to surface external-access findings. The validate-policy and check-* APIs work without an analyzer; only findings require one.

    Open in the console / docs โ†—
  8. 8

    Optional: obtain Organizations read access for SCP inspection

    SCP and RCP enumeration requires credentials in the management account or a delegated administrator. Verify with 'aws organizations describe-organization'. If this returns AccessDeniedException or AWSOrganizationsNotInUseException, report SCPs as unverifiable rather than ruling them out.

    Open in the console / docs โ†—

Check it worked

aws sts get-caller-identity --query '[Account,Arn]' --output text && aws iam simulate-principal-policy --policy-source-arn "$(aws sts get-caller-identity --query Arn --output text)" --action-names iam:GetRole --query 'EvaluationResults[].EvalDecision' --output text

If you hit an error

An error occurred (AccessDenied) when calling the GetObject operation: User: arn:aws:iam::111122223333:user/alice is not authorized to perform: s3:GetObject on resource: ... because no identity-based policy allows the s3:GetObject action

Cause: Implicit deny - no attached or inline policy on the principal grants the action. Nothing is explicitly blocking it; nothing is permitting it either.

Fix: Confirm with 'aws iam simulate-principal-policy' returning implicitDeny, then add a scoped Allow statement naming the exact action and resource ARN. Do not reach for Action "*".

An error occurred (AccessDenied) when calling the CreateBucket operation: User: ... is not authorized to perform: s3:CreateBucket on resource: ... with an explicit deny in a service control policy

Cause: An Organizations SCP attached to the account or a parent OU explicitly denies the action. Identity policies cannot override it.

Fix: Walk the OU tree with 'aws organizations list-parents' and 'aws organizations list-policies-for-target --filter SERVICE_CONTROL_POLICY', read each with describe-policy, and request an SCP exception from the Organizations administrator. Never try to out-grant an SCP.

An error occurred (AccessDenied) when calling the AssumeRole operation: User: arn:aws:iam::111122223333:user/alice is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::444455556666:role/target-role

Cause: Either the target role's trust policy does not name the caller, or - when the trust policy names the account root - the caller's own identity policy lacks sts:AssumeRole.

Fix: Read the trust policy with 'aws iam get-role --role-name target-role --query Role.AssumeRolePolicyDocument'. If Principal is the account root ARN, also add sts:AssumeRole to the caller's identity policy scoped to the target role ARN.

An error occurred (MalformedPolicyDocument) when calling the PutRolePolicy operation: Policy document should not specify a principal.

Cause: A Principal element was included in an identity-based policy. Principal is valid only in resource-based policies such as trust policies, bucket policies and KMS key policies.

Fix: Remove the Principal block from the identity policy. If the intent was a trust policy, use 'aws iam update-assume-role-policy' instead of put-role-policy.

An error occurred (DeleteConflict) when calling the DeleteRole operation: Cannot delete entity, must detach all policies first.

Cause: The role still has managed policies attached, inline policies, or an instance profile association.

Fix: Detach each managed policy with 'aws iam detach-role-policy', delete inline policies with 'aws iam delete-role-policy', and remove the role from its instance profile with 'aws iam remove-role-from-instance-profile'. Confirm the deletion with the user before proceeding - it is irreversible.

An error occurred (LimitExceeded) when calling the CreatePolicyVersion operation: A managed policy can have up to 5 versions. Before you create a new version, you must delete an existing version.

Cause: The customer-managed policy already holds the maximum of five versions.

Fix: List versions with 'aws iam list-policy-versions --policy-arn <arn>' and delete a non-default one with 'aws iam delete-policy-version --version-id v2'. Prune proactively so rollback is never blocked during an incident.

An error occurred (NoSuchEntity) when calling the GetRole operation: The role with name app-role cannot be found.

Cause: Wrong account, a typo in the role name, or the caller passed a full ARN where --role-name expects a bare name.

Fix: Verify the account with 'aws sts get-caller-identity', then pass only the name segment: --role-name app-role, not the arn:aws:iam::...:role/app-role form.

An error occurred (ExpiredToken) when calling the ListRoles operation: The security token included in the request is expired

Cause: Temporary credentials from AssumeRole or IAM Identity Center have passed their expiry, typically one hour by default.

Fix: Re-authenticate with 'aws sso login --profile <name>' or re-run 'aws sts assume-role'. This is an authentication failure, not an authorization problem - do not start editing policies.

An error occurred (InvalidClientTokenId) when calling the GetCallerIdentity operation: The security token included in the request is invalid.

Cause: The access key was deleted or deactivated, AWS_SESSION_TOKEN is missing for an ASIA... temporary key, or key and secret belong to different credentials.

Fix: Run 'aws configure list' to see which source supplies each value. For ASIA keys, export AWS_SESSION_TOKEN as well. Environment variables silently override the profile - unset stale ones.

An error occurred (UnauthorizedOperation) when calling the TerminateInstances operation: You are not authorized to perform this operation.

Cause: EC2's authorization failure code. Returned in place of AccessDenied, and also emitted for a --dry-run call the principal is not permitted to make.

Fix: Decode the encoded message with 'aws sts decode-authorization-message --encoded-message <blob>' to reveal the matched statements, then simulate the action. A successful dry-run instead returns DryRunOperation.

An error occurred (AccessDeniedException) when calling the DescribeOrganization operation

Cause: The credentials are not in the Organizations management account or a delegated administrator account.

Fix: Switch to management-account credentials for SCP work, or report explicitly that SCPs could not be inspected. Do not conclude that no SCP is involved.

An error occurred (AccessDenied) when calling the PutObject operation: ... because no permissions boundary allows the s3:PutObject action

Cause: A permissions boundary attached to the principal caps its maximum privilege and omits this action, so the identity policy Allow has no effect.

Fix: Read the boundary with 'aws iam get-role --role-name <role> --query Role.PermissionsBoundary', simulate with --permissions-boundary-policy-input-list, and amend the boundary rather than the identity policy.

Official documentation: https://docs.aws.amazon.com/IAM/latest/UserGuide/introduction.html โ†—

API operations this skill uses (20)

Every call the skill makes, linked to its official reference page.

OperationCallPermissionRef
Verify caller identity, account and principal ARN
Mandatory first step and pre-mutation guard - proves which account and principal the agent is acting as. Requires no IAM permission, so failure means an authentication problem.
CLI aws sts get-caller-identity --query '[Account,Arn,UserId]' --output textsts:GetCallerIdentitydocs โ†—
Simulate an action for an existing principal
Core diagnosis: returns allowed / implicitDeny / explicitDeny plus MatchedStatements, OrganizationsDecisionDetail and PermissionsBoundaryDecisionDetail without making the live call.
CLI aws iam simulate-principal-policy --policy-source-arn arn:aws:iam::111122223333:role/app-role --action-names s3:GetObject --resource-arns arn:aws:s3:::example-bucket/prod/report.csviam:SimulatePrincipalPolicydocs โ†—
Simulate a draft policy document before attaching it
Dry-run for a policy that does not exist yet, including resource policies passed via --resource-policy. Changes no account state.
CLI aws iam simulate-custom-policy --policy-input-list file://draft-policy.json --action-names s3:PutObject --resource-arns arn:aws:s3:::example-bucket/*iam:SimulateCustomPolicydocs โ†—
Discover condition keys a principal's policies require
Prevents false denials in simulation by revealing which keys must be supplied through --context-entries before the decision can be trusted.
CLI aws iam get-context-keys-for-principal-policy --policy-source-arn arn:aws:iam::111122223333:role/app-roleiam:GetContextKeysForPrincipalPolicydocs โ†—
Read a role definition and its trust policy
Retrieves the trust relationship and any attached permissions boundary - the two things that most often explain an AssumeRole or capped-privilege failure.
CLI aws iam get-role --role-name app-role --query 'Role.[Arn,AssumeRolePolicyDocument,PermissionsBoundary]'iam:GetRoledocs โ†—
Read a specific managed policy document version
Fetches the actual JSON behind a policy ARN, and captures the current default version to a file so an edit can be rolled back.
CLI aws iam get-policy-version --policy-arn arn:aws:iam::111122223333:policy/app-policy --version-id v3 --query 'PolicyVersion.Document'iam:GetPolicyVersiondocs โ†—
Find every identity a policy is attached to
Establishes blast radius before editing a shared managed policy - the count of affected principals must be reported in the confirmation prompt.
CLI aws iam list-entities-for-policy --policy-arn arn:aws:iam::111122223333:policy/app-policy --query '[PolicyRoles[].RoleName,PolicyUsers[].UserName]'iam:ListEntitiesForPolicydocs โ†—
Bulk-export the account authorization surface
Single call returning every identity and policy document, enabling offline 'who can do X?' analysis without thousands of per-entity requests.
CLI aws iam get-account-authorization-details --filter Role User Group LocalManagedPolicy --output jsoniam:GetAccountAuthorizationDetailsdocs โ†—
Gather action-level usage evidence for least-privilege trimming
Two-call async job producing evidence of which services and actions the principal actually used, so permission removal is data-driven rather than guessed. Services with zero authenticated entities are removal candidates.
CLI JOB=$(aws iam generate-service-last-accessed-details --arn arn:aws:iam::111122223333:role/app-role --granularity ACTION_LEVEL --query JobId --output text) && aws iam get-service-last-accessed-details --job-id "$JOB" --query 'ServicesLastAccessed[?TotalAuthenticatedEntities>`0`].[ServiceNamespace,LastAuthenticated]'iam:GenerateServiceLastAccessedDetails, iam:GetServiceLastAccessedDetailsdocs โ†—
Lint a policy document for errors and security warnings
Mandatory pre-attach check. Surfaces ERROR and SECURITY_WARNING findings such as overly permissive wildcards or invalid actions before anything is applied.
CLI aws accessanalyzer validate-policy --policy-document file://policy.json --policy-type IDENTITY_POLICYaccess-analyzer:ValidatePolicydocs โ†—
Prove a policy edit grants no new access
Gates every policy change: a FAIL result means the edit widens access and must be reviewed before it is applied.
CLI aws accessanalyzer check-no-new-access --new-policy-document file://policy-new.json --existing-policy-document file://policy-old.json --policy-type IDENTITY_POLICYaccess-analyzer:CheckNoNewAccessdocs โ†—
Assert a policy cannot grant high-risk actions
Enforces guardrails in CI or review - confirms a policy never permits privilege-escalation actions such as iam:PassRole or iam:CreateAccessKey.
CLI aws accessanalyzer check-access-not-granted --policy-document file://policy.json --access actions=iam:PassRole,iam:CreateAccessKey --policy-type IDENTITY_POLICYaccess-analyzer:CheckAccessNotGranteddocs โ†—
Retrieve denial events with full error messages
Recovers the verbatim errorCode and errorMessage whose tail names the responsible policy type. Cannot filter on errorCode server-side, so filter client-side; covers 90 days of management events only.
CLI aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventName,AttributeValue=AssumeRole --max-results 50 --query 'Events[].CloudTrailEvent'cloudtrail:LookupEventsdocs โ†—
Enumerate SCPs applying to an account or OU
There is no effective-SCP API, so this plus organizations:ListParents walks the OU tree upward to find every SCP that could deny the action.
CLI aws organizations list-policies-for-target --target-id 111122223333 --filter SERVICE_CONTROL_POLICYorganizations:ListPoliciesForTargetdocs โ†—
Read an SCP or RCP document
Returns the JSON of a control policy so the specific Deny statement blocking the action can be quoted precisely in the diagnosis.
CLI aws organizations describe-policy --policy-id p-examplepolicyid --query 'Policy.Content'organizations:DescribePolicydocs โ†—
Inspect a bucket's resource-based policy
Checks the resource side of a cross-account decision, where both the identity policy and the resource policy must allow the action.
CLI aws s3api get-bucket-policy --bucket example-bucket --query Policy --output texts3:GetBucketPolicydocs โ†—
Inspect a KMS key policy for decrypt grants
Resolves the most common false-negative: an S3 or Secrets Manager AccessDenied caused by a missing kms:Decrypt grant on the customer-managed key.
CLI aws kms get-key-policy --key-id arn:aws:kms:us-east-1:111122223333:key/<key-id> --policy-name default --query Policy --output textkms:GetKeyPolicydocs โ†—
Decode an encoded EC2 authorization failure message
Turns the opaque blob returned with UnauthorizedOperation into the matched statements and failure reason, which EC2 does not otherwise reveal.
CLI aws sts decode-authorization-message --encoded-message <encoded-blob> --query DecodedMessage --output textsts:DecodeAuthorizationMessagedocs โ†—
Update a role trust policy after review
Applies a corrected trust relationship. Destructive - it replaces the document wholesale, so capture the current one first and require explicit confirmation.
CLI aws iam update-assume-role-policy --role-name app-role --policy-document file://trust.jsoniam:UpdateAssumeRolePolicydocs โ†—
Attach a permissions boundary to cap a role
Enforces a maximum-privilege ceiling on a role after the boundary has been simulated. Can instantly break a working role, so simulate then confirm.
CLI aws iam put-role-permissions-boundary --role-name app-role --permissions-boundary arn:aws:iam::111122223333:policy/dev-boundaryiam:PutRolePermissionsBoundarydocs โ†—