<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Toluwanimi Emmanuel Banji-Idowu</title>
    <description>The latest articles on Forem by Toluwanimi Emmanuel Banji-Idowu (@toluid_).</description>
    <link>https://forem.com/toluid_</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2505989%2F15303b31-09ca-41c5-bf39-a14b882eea76.jpeg</url>
      <title>Forem: Toluwanimi Emmanuel Banji-Idowu</title>
      <link>https://forem.com/toluid_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/toluid_"/>
    <language>en</language>
    <item>
      <title>IAM Security Audit</title>
      <dc:creator>Toluwanimi Emmanuel Banji-Idowu</dc:creator>
      <pubDate>Sat, 07 Feb 2026 13:27:46 +0000</pubDate>
      <link>https://forem.com/aws-builders/iam-security-audit-3lp1</link>
      <guid>https://forem.com/aws-builders/iam-security-audit-3lp1</guid>
      <description>&lt;p&gt;This is Part 4 of a 4-part series I wrote while prepping to renew my AWS Security Specialty certification (SCS-C03). Parts 1 through 3 cover IAM foundations, STS and federation, and advanced patterns like ABAC, SCPs, and permission boundaries. You can find them on my blog at &lt;a href="https://tolubanji.com/posts/" rel="noopener noreferrer"&gt;tolubanji.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I ran IAM Access Analyzer on an account I'd been managing for quite a while now and found 14 resources with external access. Three S3 buckets, two Lambda functions, and nine IAM roles that could be assumed by accounts outside our organization. I knew about one of the buckets. The rest were news to me.&lt;/p&gt;

&lt;p&gt;Traced it back to a CloudFormation template with an overly permissive trust policy that had been copied across 18 months of microservice deployments. Nobody noticed because nothing broke.&lt;/p&gt;

&lt;p&gt;If Parts 1 through 3 covered how IAM works, this is about what goes wrong and how to find it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhc7tvy3kevn7gd6x0kmq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhc7tvy3kevn7gd6x0kmq.png" alt="IAM Security Audit Overview" width="800" height="549"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Common IAM Misconfigurations
&lt;/h2&gt;

&lt;p&gt;These are the ones I keep seeing across accounts. None of them are exotic. They're all mundane, easy to create, and easy to miss.&lt;/p&gt;

&lt;h3&gt;
  
  
  Overly Permissive Policies
&lt;/h3&gt;

&lt;p&gt;The classics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'd think nobody does this in production. They do. Sometimes it starts as a "temporary" policy during development. Sometimes it's an AWS managed policy that's broader than the name suggests. &lt;code&gt;ReadOnlyAccess&lt;/code&gt; sounds safe until you see it includes &lt;code&gt;secretsmanager:GetSecretValue&lt;/code&gt;. Access Analyzer's policy validation (covered below) catches these, and the credential report will show you which identities have them attached.&lt;/p&gt;

&lt;h3&gt;
  
  
  Overly Permissive Trust Policies
&lt;/h3&gt;

&lt;p&gt;Worse than a bad permission policy because this one controls who gets in the door:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"AWS"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sts:AssumeRole"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;*&lt;/code&gt; means any AWS account in the world can assume your role. I've found this in production more than once. Usually it's a role that was set up for cross-account access and someone used &lt;code&gt;*&lt;/code&gt; as a placeholder. The placeholder became permanent.&lt;/p&gt;

&lt;p&gt;The slightly less obvious version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"AWS"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::123456789012:root"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows any principal in that account. If that's a partner account, every user and role they have can assume your role. Scope it to specific roles instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unused Credentials
&lt;/h3&gt;

&lt;p&gt;Long-lived access keys that nobody's using are the credentials most likely to be compromised. They sit in old repos, forgotten config files, and former contractors' laptops.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Generate and download the credential report&lt;/span&gt;
aws iam generate-credential-report
aws iam get-credential-report &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Content'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; cred-report.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The credential report shows you everything: password last used, access key last used, MFA status, key age. I run this monthly and look for access keys older than 90 days with no recent usage. If they haven't been used, they probably shouldn't exist.&lt;/p&gt;

&lt;h3&gt;
  
  
  Missing MFA
&lt;/h3&gt;

&lt;p&gt;Console access without MFA is an open invitation. Check for it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Find users with console access but no MFA&lt;/span&gt;
aws iam get-credential-report &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Content'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt;&lt;span class="s1"&gt;','&lt;/span&gt; &lt;span class="s1"&gt;'$4 == "true" &amp;amp;&amp;amp; $8 == "false" {print $1}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one-liner pulls users who have a password (console access) but no MFA device. Every name on that list is a risk.&lt;/p&gt;

&lt;h3&gt;
  
  
  Inline Policies
&lt;/h3&gt;

&lt;p&gt;I mentioned inline policies in Part 1. They're embedded directly in a user, group, or role. Hard to audit, don't show up in policy listings, and nobody remembers they exist.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Find inline policies on all users&lt;/span&gt;
aws iam list-users &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Users[].UserName'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text | &lt;span class="se"&gt;\&lt;/span&gt;
  xargs &lt;span class="nt"&gt;-I&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt; sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s1"&gt;'policies=$(aws iam list-user-policies --user-name {} --query "PolicyNames[]" --output text); \
     [ -n "$policies" ] &amp;amp;&amp;amp; echo "User {}: $policies"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this returns anything, move those policies to customer managed policies where they can be versioned and audited.&lt;/p&gt;

&lt;h2&gt;
  
  
  Privilege Escalation Paths
&lt;/h2&gt;

&lt;p&gt;An attacker who gets limited IAM access will immediately look for ways to escalate. These are the paths they'll try.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Dangerous Permissions
&lt;/h3&gt;

&lt;p&gt;Not all IAM permissions are equal. Some let you escalate to admin without anyone noticing:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Permission&lt;/th&gt;
&lt;th&gt;What an Attacker Does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iam:CreatePolicyVersion&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Updates an existing policy to grant admin, sets it as default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;iam:AttachUserPolicy&lt;/code&gt; / &lt;code&gt;iam:AttachRolePolicy&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Attaches &lt;code&gt;AdministratorAccess&lt;/code&gt; to themselves or a role they can assume&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;iam:PutUserPolicy&lt;/code&gt; / &lt;code&gt;iam:PutRolePolicy&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Creates an inline admin policy on their identity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;iam:PassRole&lt;/code&gt; + &lt;code&gt;lambda:CreateFunction&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Creates a Lambda function with an admin role, invokes it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;iam:PassRole&lt;/code&gt; + &lt;code&gt;ec2:RunInstances&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Launches an EC2 instance with an admin role, SSMs into it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iam:UpdateAssumeRolePolicy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Changes a role's trust policy to allow themselves to assume it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;iam:PassRole&lt;/code&gt; combinations are the ones people miss. By itself, &lt;code&gt;PassRole&lt;/code&gt; doesn't look dangerous. Combined with a service that executes code (Lambda, EC2, ECS, Glue), it's a full escalation path.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lambda + PassRole Escalation
&lt;/h3&gt;

&lt;p&gt;I see this one more than any other:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Attacker has: lambda:CreateFunction, lambda:InvokeFunction, iam:PassRole&lt;/span&gt;

&lt;span class="c"&gt;# Step 1: Create function with a powerful role&lt;/span&gt;
aws lambda create-function &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--function-name&lt;/span&gt; escalate &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--role&lt;/span&gt; arn:aws:iam::123456789012:role/AdminRole &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--handler&lt;/span&gt; index.handler &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--runtime&lt;/span&gt; python3.12 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--zip-file&lt;/span&gt; fileb://payload.zip

&lt;span class="c"&gt;# Step 2: Invoke it. The code runs as AdminRole.&lt;/span&gt;
aws lambda invoke &lt;span class="nt"&gt;--function-name&lt;/span&gt; escalate output.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Lambda function's code can do anything AdminRole can do. Create new access keys, exfiltrate data, modify other roles. The attacker never directly had admin access, but they got it through the service.&lt;/p&gt;

&lt;h3&gt;
  
  
  CreatePolicyVersion Escalation
&lt;/h3&gt;

&lt;p&gt;This one is subtle. If you can create a new version of a policy that's attached to your own identity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws iam create-policy-version &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--policy-arn&lt;/span&gt; arn:aws:iam::123456789012:policy/MyPolicy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--policy-document&lt;/span&gt; &lt;span class="s1"&gt;'{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"*","Resource":"*"}]}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set-as-default&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One command. The policy now grants admin. And because it's the same policy ARN, it might not trigger alerts that look for new policy attachments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Preventing Escalation
&lt;/h3&gt;

&lt;p&gt;Permission boundaries from Part 3. They set a ceiling that can't be exceeded regardless of what policies get attached, and they're the best tool you have here.&lt;/p&gt;

&lt;p&gt;Beyond that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deny &lt;code&gt;iam:CreatePolicyVersion&lt;/code&gt; and &lt;code&gt;iam:SetDefaultPolicyVersion&lt;/code&gt; for non-admin roles&lt;/li&gt;
&lt;li&gt;Scope &lt;code&gt;iam:PassRole&lt;/code&gt; to specific role ARNs and specific services (&lt;code&gt;iam:PassedToService&lt;/code&gt; condition)&lt;/li&gt;
&lt;li&gt;Monitor IAM changes through CloudTrail (covered below)&lt;/li&gt;
&lt;li&gt;Run regular access reviews. Quarterly at minimum.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  IAM Access Analyzer
&lt;/h2&gt;

&lt;p&gt;Access Analyzer is the tool I wish existed when I first started managing AWS accounts. It does four things: finds resources shared externally, validates policies, generates least-privilege policies from activity, and checks for unused access.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting It Up
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create an account-level analyzer&lt;/span&gt;
aws accessanalyzer create-analyzer &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--analyzer-name&lt;/span&gt; my-analyzer &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--type&lt;/span&gt; ACCOUNT

&lt;span class="c"&gt;# Or organization-level (sees all accounts)&lt;/span&gt;
aws accessanalyzer create-analyzer &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--analyzer-name&lt;/span&gt; org-analyzer &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--type&lt;/span&gt; ORGANIZATION
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;ORGANIZATION&lt;/code&gt; type if you have AWS Organizations set up. It detects sharing between your accounts and external accounts, not just within one account.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reviewing Findings
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws accessanalyzer list-findings &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--analyzer-arn&lt;/span&gt; arn:aws:access-analyzer:us-east-1:123456789012:analyzer/my-analyzer &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'findings[].{Resource:resource,Type:resourceType,Access:principal}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each finding tells you what resource is shared, with whom, and through which policy. You can archive findings that are intentional (like a bucket shared with a partner) so they don't clutter future reviews.&lt;/p&gt;

&lt;h3&gt;
  
  
  Policy Validation
&lt;/h3&gt;

&lt;p&gt;I run this on every policy before it touches an environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws accessanalyzer validate-policy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--policy-document&lt;/span&gt; file://policy.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--policy-type&lt;/span&gt; IDENTITY_POLICY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It catches things like unused actions, overly permissive resources, and missing conditions. Not perfect, but it catches the obvious mistakes before they reach production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Policy Generation from CloudTrail
&lt;/h3&gt;

&lt;p&gt;Access Analyzer can also analyze CloudTrail logs and generate a least-privilege policy based on what a role has been doing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start policy generation (analyzes last 90 days of CloudTrail)&lt;/span&gt;
aws accessanalyzer start-policy-generation &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--policy-generation-details&lt;/span&gt; &lt;span class="s1"&gt;'{
    "principalArn": "arn:aws:iam::123456789012:role/MyRole"
  }'&lt;/span&gt;

&lt;span class="c"&gt;# Check the result&lt;/span&gt;
aws accessanalyzer get-generated-policy &lt;span class="nt"&gt;--job-id&lt;/span&gt; JOB_ID
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I use this when inheriting roles from other teams. Instead of guessing what permissions they need, I let Access Analyzer tell me what they've been using. Then I create a policy based on that. Not the permissions they were granted, but the ones they used.&lt;/p&gt;

&lt;h3&gt;
  
  
  Access Analyzer vs Access Advisor
&lt;/h3&gt;

&lt;p&gt;The exam tests the difference. Access Analyzer finds external sharing and validates policies. Access Advisor (the "Last Accessed" data in the IAM console) shows which services a role has used and when. Different tools, different purposes. Analyzer is about security posture. Advisor is about right-sizing permissions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Auditing IAM with CloudTrail
&lt;/h2&gt;

&lt;p&gt;CloudTrail records every API call in your account. Most of those events are noise. Here's what's worth watching.&lt;/p&gt;

&lt;h3&gt;
  
  
  Events That Should Trigger Alerts
&lt;/h3&gt;

&lt;p&gt;The events I care about most: &lt;code&gt;CreateUser&lt;/code&gt;, &lt;code&gt;CreateAccessKey&lt;/code&gt;, &lt;code&gt;AttachUserPolicy&lt;/code&gt;, &lt;code&gt;AttachRolePolicy&lt;/code&gt;, &lt;code&gt;CreatePolicyVersion&lt;/code&gt;, &lt;code&gt;UpdateAssumeRolePolicy&lt;/code&gt;, &lt;code&gt;PutUserPolicy&lt;/code&gt;, &lt;code&gt;PutRolePolicy&lt;/code&gt;. Any of these happening outside a change management window is worth investigating.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ConsoleLogin&lt;/code&gt; failures are also worth watching. A burst of failed logins followed by a success is a classic brute-force pattern. &lt;code&gt;AssumeRole&lt;/code&gt; from unexpected source accounts is another red flag.&lt;/p&gt;

&lt;h3&gt;
  
  
  Querying CloudTrail
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# All IAM changes in the last 7 days&lt;/span&gt;
aws cloudtrail lookup-events &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--lookup-attributes&lt;/span&gt; &lt;span class="nv"&gt;AttributeKey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;EventSource,AttributeValue&lt;span class="o"&gt;=&lt;/span&gt;iam.amazonaws.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--start-time&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-v-7d&lt;/span&gt; &lt;span class="s1"&gt;'+%Y-%m-%d'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Events[].{Time:EventTime,Event:EventName,User:Username}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  EventBridge Rule for IAM Alerts
&lt;/h3&gt;

&lt;p&gt;An EventBridge rule watching for IAM privilege changes is one of those things you configure once and it pays for itself every week:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"aws.iam"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"detail-type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"AWS API Call via CloudTrail"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"detail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"eventName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"AttachUserPolicy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"AttachRolePolicy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"CreatePolicyVersion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"PutUserPolicy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"PutRolePolicy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"CreateAccessKey"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"UpdateAssumeRolePolicy"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Route this to SNS and you get an email every time someone modifies IAM permissions. Noisy at first. Essential once you tune it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Credential Reports and Last Accessed
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Credential Report
&lt;/h3&gt;

&lt;p&gt;I mentioned this above for finding missing MFA. The full report covers every user in the account: password status, access key age, last login, MFA status. It's the single best snapshot of your IAM hygiene. Same commands from above to generate it.&lt;/p&gt;

&lt;p&gt;What I look for every month: users with no MFA, access keys older than 90 days, access keys that have never been used, users who haven't logged in for 90+ days. Each of these is either a cleanup task or a conversation with someone about why the credential still exists.&lt;/p&gt;

&lt;h3&gt;
  
  
  Service Last Accessed
&lt;/h3&gt;

&lt;p&gt;Shows you which AWS services a role has touched and when:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;JOB_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws iam generate-service-last-accessed-details &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--arn&lt;/span&gt; arn:aws:iam::123456789012:role/MyRole &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'JobId'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text&lt;span class="si"&gt;)&lt;/span&gt;

aws iam get-service-last-accessed-details &lt;span class="nt"&gt;--job-id&lt;/span&gt; &lt;span class="nv"&gt;$JOB_ID&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'ServicesLastAccessed[?LastAuthenticated!=`null`].{Service:ServiceName,LastUsed:LastAuthenticated}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a role has &lt;code&gt;s3:*&lt;/code&gt;, &lt;code&gt;dynamodb:*&lt;/code&gt;, &lt;code&gt;sqs:*&lt;/code&gt;, and &lt;code&gt;sns:*&lt;/code&gt; but has only used S3 and DynamoDB in the last 6 months, remove SQS and SNS. This is how you get to least privilege without guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Responding to Compromised Credentials
&lt;/h2&gt;

&lt;p&gt;When you find compromised credentials, speed matters. The attacker might be active while you're figuring out what happened. Here's the sequence I follow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhcuo90zznw3pjeg8lptj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhcuo90zznw3pjeg8lptj.png" alt="Compromised Credentials Response Flow" width="800" height="276"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, attach an inline deny-all policy to the compromised user. An explicit deny overrides everything (remember the evaluation logic from Part 1). This is faster than trying to detach all their policies one by one because you might miss one. The deny-all takes effect immediately and locks them out regardless of what other policies they have.&lt;/p&gt;

&lt;p&gt;Second, disable all access keys on that identity. Disable, not delete. You'll need the key IDs later for the investigation. Disabling stops the keys from working while preserving the evidence.&lt;/p&gt;

&lt;p&gt;Third, remove console access by deleting their login profile. If the attacker has the password, this kills their ability to use the AWS console.&lt;/p&gt;

&lt;p&gt;Fourth, investigate through CloudTrail. Pull all events for that username going back at least 30 days. You're looking for three categories of activity: data access (S3 GetObject, DynamoDB Scan, Secrets Manager GetSecretValue), persistence attempts (CreateUser, CreateAccessKey, UpdateAssumeRolePolicy), and lateral movement (AssumeRole into other accounts or more privileged roles). The persistence attempts are the most important to find because they mean the attacker may still have access through a different path even after you've locked down the original credentials.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exam Notes
&lt;/h2&gt;

&lt;p&gt;Know the privilege escalation paths, especially &lt;code&gt;iam:PassRole&lt;/code&gt; combined with service actions. The exam gives scenarios where a user has seemingly limited permissions and asks what they can do.&lt;/p&gt;

&lt;p&gt;Access Analyzer vs Access Advisor: Analyzer finds external sharing and validates policies. Advisor shows service last accessed data. Different tools.&lt;/p&gt;

&lt;p&gt;Access Analyzer can generate policies from CloudTrail activity. Know this for questions about achieving least privilege.&lt;/p&gt;

&lt;p&gt;Credential reports cover every IAM user in the account. They show password age, key age, MFA status, and last usage.&lt;/p&gt;

&lt;p&gt;CloudTrail records all IAM API calls. Know the event names for privilege changes.&lt;/p&gt;

&lt;p&gt;For compromised credentials: deny first, then disable keys, then investigate. Order matters because the attacker might be active while you're responding.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>security</category>
      <category>cloud</category>
    </item>
  </channel>
</rss>
