BlogCloud

AWS IAM least privilege with Access Analyzer

Generate tight IAM policies from real CloudTrail activity and find resources shared outside your account.

Jan 8, 2026·4 min readIntermediate·By the SecOpsLog team · command-tested

IAM least privilege is easy to preach and hard to practice because nobody knows which permissions a role actually uses. Teams default to s3: on “until we know what we need,” and that temporary policy lives for three years. IAM Access Analyzer closes the gap with two concrete workflows: policy generation from CloudTrail activity (what did this role actually call?) and external access analysis (what resource is reachable from outside the account?).

This walkthrough creates an analyzer, starts a policy generation job for an overprivileged CI role, reviews the generated policy against what CloudTrail recorded, and triages external-access findings on an S3 bucket and an assumable role. For SCPs, permission boundaries, and org-wide guardrails, continue in AWS security for DevOps; for the exam framing on IAM and resource policies, see AWS Solutions Architect Associate.

From overprivileged role to evidence-based policy

Policy generation needs CloudTrail logging the role’s activity. Run it across a representative window — a quiet week under-generates; Black Friday over-fits.

1CloudTraillogs role sessions2Access Analyzerenabled per region/account3start-policy-generationanchor to role ARN4Job completesJSON policy draft5Human reviewdrop unused, add conditions6Deploy policyreplace * wildcards7list-findingsexternal access loop

Enable Access Analyzer and create an analyzer

Access Analyzer is regional for resource analysis — enable it in every active region or use an organization-level analyzer in the security account. Findings appear when a resource policy or ACL allows access from outside your zone of trust (another account, the internet, unknown principals).

bash — create an account analyzerlive
aws accessanalyzer create-analyzer \
--analyzer-name account-analyzer \
--type ACCOUNT
arn:aws:access-analyzer:eu-west-1:111122223333:analyzer/account-analyzer
aws accessanalyzer list-analyzers --query "analyzers[].status"
ACTIVE

Generate a policy from real CloudTrail usage

Start a policy generation job anchored to the role you want to tighten. Access Analyzer reads CloudTrail data for that principal and emits a policy containing only actions and resources it actually invoked. Treat the output as a draft — it reflects history, not intent, and it will not invent least-privilege conditions you still need to add.

bash — policy generation joblive
aws accessanalyzer start-policy-generation \
--policy-generation-details '{"principalArn":"arn:aws:iam::111122223333:role/ci-deploy"}'
jobId: 2b57a4e8-...
aws accessanalyzer get-generated-policy --job-id 2b57a4e8-...
generated policy: 14 actions across s3, ecr, sts
was: s3:*, ecr:* — now: the 14 it actually used

Find resources shared outside your account

External access findings are how you catch the backup bucket shared with a vendor account you forgot about, or a role trust policy with Principal: * hiding in a sandbox. Resolve findings by tightening the resource policy or removing the cross-account grant — do not just archive and forget.

bash — triage external-access findingslive
aws accessanalyzer list-findings \
--analyzer-arn arn:aws:access-analyzer:eu-west-1:111122223333:analyzer/account-analyzer
S3Bucket acme-backups — principal AWS:999988887777
IAMRole deploy — condition missing, assumable broadly
aws accessanalyzer get-finding --analyzer-arn ... --id f-abc123
read finding.action and finding.resource for remediation ticket
Resource wildcards hide in plain sight
Teams obsess over Action scoping but leave Resource as `"*"`. Scope to specific ARNs, split read vs write policies, and add conditions (`aws:SecureTransport`, source VPC endpoints, `aws:MultiFactorAuthPresent`) where the action supports them. Generated policies rarely include conditions — you add those.

Tighten the draft with conditions and boundaries

A generated allow list is a starting point. Add explicit denies for destructive actions, attach a permissions boundary on human roles, and use service control policies at the org level to cap what even an admin can grant. For CI roles, prefer OIDC federation (AssumeRoleWithWebIdentity) over long-lived access keys — then generate policy from the federated role, not from a static key user.

tightened-statement.json
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::acme-app-artifacts/*",
"Condition": {
"Bool": { "aws:SecureTransport": "true" },
"StringEquals": {
"s3:x-amz-server-side-encryption": "AES256"
}
}
}
Policy generation vs manual IAM review
Generated from CloudTrail
Evidence-based actions
Misses unused future needs
No conditions by default
Great for CI/CD roles
Manual + Access Analyzer findings
Catches external grants
Trust policy review
SCP + boundary layering
Required for human admins

Where this goes next

Least privilege is a loop, not a ticket. Re-run policy generation after major refactors, wire IAM Access Analyzer findings into Slack or Jira, and pair IAM tightening with S3 bucket controls so readable buckets and readable roles fail together. AWS security for DevOps walks OIDC for GitHub Actions, permission boundaries, and org SCP patterns that keep generated policies from being undone by a single admin mistake.

Go deeper in a courseAWS security for DevOps engineersIAM that scales, OIDC federation, Access Analyzer, and org guardrails.View course

Related posts