If you’ve inherited AWS accounts with IAM policies full of wildcards—"Action": "*" or "Resource": "*"—you’re not alone. I’ve audited dozens of accounts where quick prototyping made it to production, leaving massive security gaps. In this post, I’ll walk through exactly how to identify overly permissive policies and systematically right-size them using AWS IAM Access Analyzer and service last-accessed data.

The Problem

You find IAM policies like this in production:

{
  "Effect": "Allow",
  "Action": "*",
  "Resource": "*"
}

or slightly more restricted:

{
  "Effect": "Allow",
  "Action": "s3:*",
  "Resource": "*"
}

These policies create unnecessary blast radius. If credentials are compromised, an attacker has access to everything. You need to right-size them, but you don’t want to break anything in the process.

Risk Type Description
Credential Compromise If access keys leak, attacker has full account permissions
Insider Threat Malicious employee can access any resource without restrictions
Third-Party Risk Contractors/vendors with these keys have excessive access

The goal: replace wildcards with minimal necessary permissions.

Why Does This Happen?

  • Copy-pasted from Stack Overflow: Quick fix during prototyping that never got refactored
  • Lack of review process: Policies weren’t audited before pushing to production
  • Over-engineering for “least privilege”: Developers err on the side of too much access rather than debugging permission errors
  • Lack of access analyzer awareness: Teams don’t know how to use tooling to right-size policies
  • Business pressure: “Move fast” culture that doesn’t prioritize security hardening

The Fix

Step 1: Find All Overly Permissive Policies

Start by identifying which roles/users have wildcard permissions:

# List all inline policies on a role
aws iam list-role-policies \
  --role-name MyRole \
  --output text

# Get the policy document
aws iam get-role-policy \
  --role-name MyRole \
  --policy-name MyPolicy \
  --query 'RolePolicyDocument' \
  --output text | jq .

Look for these red flags:

  • "Action": "*"
  • "Action": "service:*" (e.g., "s3:*")
  • "Resource": "*" combined with broad actions
  • Missing ResourceArn conditions

Step 2: Use IAM Access Analyzer to Check for Public Access Issues

IAM Access Analyzer identifies public access problems automatically:

# Create or get an analyzer (if not already created)
aws accessanalyzer list-analyzers \
  --output text

# Create an analyzer if needed
aws accessanalyzer create-analyzer \
  --analyzer-name account-analyzer \
  --type ACCOUNT \
  --output text

# List findings
aws accessanalyzer list-findings \
  --analyzer-arn arn:aws:access-analyzer:us-east-1:123456789012:analyzer/account-analyzer \
  --query 'findings[?resourceType==`AWS::IAM::Role`]' \
  --output text

Access Analyzer shows policies that allow public access or access from other accounts — immediate red flags for overly permissive policies.

Step 3: Check Service Last Accessed Data

Find out which services and actions the role actually uses:

# Generate service last accessed report for a role
aws iam generate-service-last-accessed-details \
  --arn arn:aws:iam::123456789012:role/MyRole \
  --output text

# This returns a job-id. Poll for completion.
aws iam get-service-last-accessed-details \
  --job-id 12345678-1234-1234-1234-123456789012 \
  --output text

This shows which services the role has accessed in the last 90 days. Services not in this list are candidates for removal from the policy.

Example output format shows:

  • s3:GetObject — last accessed 5 days ago
  • ec2:DescribeInstances — last accessed 30 days ago
  • iam:AttachUserPolicy — never accessed

Any service/action never accessed can be removed from the policy.

Step 4: Validate Policies with Access Analyzer

Use Access Analyzer to validate policy syntax and find issues:

# Validate an **IAM** policy document
aws accessanalyzer validate-policy \
  --policy-document file://my-policy.json \
  --policy-type IDENTITY_POLICY \
  --output text

This shows warnings about:

  • Overly permissive actions (e.g., *)
  • Empty resource lists
  • Missing resource constraints
  • Unreachable policy statements

Step 5: Right-Size the Policy Incrementally

Create a minimal policy based on last-accessed data:

# Old overly permissive policy
{
  "Effect": "Allow",
  "Action": "s3:*",
  "Resource": "*"
}

# New right-sized policy (based on actual usage)
{
  "Effect": "Allow",
  "Action": [
    "s3:GetObject",
    "s3:PutObject",
    "s3:ListBucket"
  ],
  "Resource": [
    "arn:aws:s3:::my-bucket",
    "arn:aws:s3:::my-bucket/*"
  ]
}

Key improvements:

  • Specific actions instead of s3:*
  • Specific resources instead of *
  • Only permissions actually used

Step 6: Test the New Policy

Before replacing the old policy, test the new one using Policy Simulator:

# Simulate the old broad policy
aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::123456789012:role/MyRole \
  --action-names s3:GetObject s3:PutObject s3:DeleteObject \
  --resource-arns arn:aws:s3:::my-bucket arn:aws:s3:::my-bucket/* \
  --output text

Compare results between old and new policies. The new policy should allow exactly the same actions used in production.

Step 7: Deploy the New Policy

Once tested, update the role:

# Update the inline policy
aws iam put-role-policy \
  --role-name MyRole \
  --policy-name MyPolicy \
  --policy-document file://new-minimal-policy.json

Or create a managed policy for better lifecycle management:

# Create new managed policy
aws iam create-policy \
  --policy-name S3ReadWriteMinimal \
  --policy-document file://new-minimal-policy.json \
  --output text

# Attach to role
aws iam attach-role-policy \
  --role-name MyRole \
  --policy-arn arn:aws:iam::123456789012:policy/S3ReadWriteMinimal

# Detach old overly permissive policy
aws iam detach-role-policy \
  --role-name MyRole \
  --policy-arn arn:aws:iam::aws:policy/S3FullAccess

Audit Workflow Summary

  1. Identify overly permissive policies → Search for "Action": "*" and "Resource": "*"
  2. Check Access Analyzer findings → Look for public access warnings
  3. Get service last-accessed data → See which services actually used in 90 days
  4. Build minimal policy → Only include used services/actions and specific resources
  5. Validate new policy → Use Policy Simulator to verify functionality preserved
  6. Deploy incrementally → Test in non-production first
  7. Monitor CloudTrail → Watch for new access denied errors after deployment

Is This Safe?

Right-sizing policies is safe if you follow the workflow above. Using Policy Simulator to test is completely safe — it’s read-only. Deploy to non-production environments first before production rollout.

Key Takeaway

Service last-accessed data is your best friend when right-sizing IAM policies. Generate it for every role, remove services/actions never accessed in 90 days, and use specific resource ARNs instead of wildcards. In my experience, overly permissive policies can be cut down by 60-80% using this approach without breaking anything. The hardest part isn’t the technical work — it’s changing the culture from “give admin access and debug later” to “minimal access by default.”


Have questions or ran into a different IAM issue? Connect with me on LinkedIn or X.