IAMAccess AnalyzerCloudTrail

Letting CloudTrail write the IAM policy

naresh has AdministratorAccess and no MFA. Before I pulled the admin policy off, I wanted to know what the account actually does all day, so I let IAM Access Analyzer read CloudTrail and write the policy for me. It came back with ten read-only calls.

Section 01The user I was trying to fix

naresh lives in one of my sandbox accounts. Ten permissions policies attached, AdministratorAccess at the top of the list, console access enabled without MFA, and two access keys that are eleven days old and have never been used.

Ten attached policies, AdministratorAccess through a group, console access without MFA, two unused keys.
Ten attached policies, AdministratorAccess through a group, console access without MFA, two unused keys.

Nobody needs tooling to tell them this is too much. The useful question is the harder one: what does this user actually do? Taking AdministratorAccess away is a two-click job. Knowing what to put back without breaking the person on the other end is what stalls every least-privilege project I have worked on.

IAM will answer that from CloudTrail. The feature has been sitting in the console for years without much attention.

Section 02Where the generator hides

It is at the bottom of the user's Permissions tab, under the permissions boundary panel, far enough down the page that I have scrolled past it plenty of times.

Generate policy based on CloudTrail events, below the permissions boundary panel.
Generate policy based on CloudTrail events, below the permissions boundary panel.

That "no requests in the past 7 days" line tripped me up for a second. It is the history of generation requests, not of the user's activity.

Section 03The wizard, and the trail I did not have

Three decisions on one screen: how far back to look, which trail to read, and which role IAM assumes to read it.

Time period, CloudTrail trail, and service role, all on one page.
Time period, CloudTrail trail, and service role, all on one page.

The window is either a rolling number of days or an explicit range capped at 90.

Rolling window, last N days.
Rolling window, last N days.
Or a fixed start and end, up to 90 days apart.
Or a fixed start and end, up to 90 days apart.

I picked 1 day because the activity I wanted was from that afternoon and I wanted a fast loop. For a real access review that number is wrong. Go to 90, and accept that even 90 days misses quarter-end and anything annual.

Then the trail dropdown came up empty.

No Trails in this regions.
No Trails in this regions.

Section 04Creating the trail

The CloudTrail console. The banner notes that CloudTrail Lake closes to new customers on May 31, 2026.
The CloudTrail console. The banner notes that CloudTrail Lake closes to new customers on May 31, 2026.

Quick trail create takes about fifteen seconds. Name it, accept the bucket it generates for you, done. Anything created in the console is multi-region.

Quick trail create: a name, an auto-generated bucket, and a straight warning about S3 charges.
Quick trail create: a name, an auto-generated bucket, and a straight warning about S3 charges.

Logging management events costs nothing. The S3 bucket underneath it does, and a multi-region trail in a busy account writes more than people expect.

What quick create skips is everything that makes the trail worth trusting.

Quick create defaults: SSE-KMS off, log file validation off, SNS off.
Quick create defaults: SSE-KMS off, log file validation off, SNS off.

Going through the full Create trail workflow instead lets you turn on SSE-KMS with a customer managed key and switch on log file validation.

Customer managed KMS key and log file validation, both enabled.
Customer managed KMS key and log file validation, both enabled.

The KMS key and the bucket have to live in the same region. The console does say so, but it says so quietly, right after you have already picked a key.

One trail: management-events, home region Mumbai, multi-region.
One trail: management-events, home region Mumbai, multi-region.
Trail details. Logging, multi-region, and because I used quick create, validation disabled and no KMS.
Trail details. Logging, multi-region, and because I used quick create, validation disabled and no KMS.

Validation disabled and no encryption is fine for a lab and not fine for anything an auditor will open. If this trail were staying, that is the first thing I would change.

Section 05Running the generator

Trail selected, with a warning about cross-account bucket access.
Trail selected, with a warning about cross-account bucket access.

Two things here cost me time.

The trail's home region is Mumbai, but it shows up under N. Virginia in this dropdown too. That is the multi-region trail's shadow copy. Same trail, so pick whichever region surfaces it.

The blue callout matters if your logs land in a central logging account. The service role IAM creates needs read access to that bucket in the other account, and this wizard does nothing to set that up for you. Sort out the bucket policy first or the run fails.

Regions to analyse: N. Virginia, Hyderabad as an opt-in region, and Mumbai.
Regions to analyse: N. Virginia, Hyderabad as an opt-in region, and Mumbai.

Only the regions you tick get reviewed. Miss one and you will generate a confidently incomplete policy. Opt-in regions like Hyderabad have to be added by hand.

In progress. The Generate policy button greys out while it runs.
In progress. The Generate policy button greys out while it runs.
Success, a few minutes later.
Success, a few minutes later.

A one-day window in a quiet account took a few minutes. The request survives you navigating away, so there is no need to sit on the page.

Section 06What it found

Nine EC2 Describe actions and one Route 53 Resolver list call.
Nine EC2 Describe actions and one Route 53 Resolver list call.

Nine ec2:Describe* calls and route53resolver:ListFirewallRuleGroupAssociations. That is the whole footprint. A user holding AdministratorAccess spent the window clicking around the VPC console, and every action it touched is read-only.

This is why I run the generator on long-lived human identities before I go anywhere near their policies. Whatever number I had in my head, it was not ten.

Section 07The policy it wrote

The generated template, with a resource placeholder flagged as an error on line 23.
The generated template, with a resource placeholder flagged as an error on line 23.

Two statements, and the split tells you something.

The first holds the nine actions that do not support resource-level permissions, so Resource is "*" and there is nothing to tighten. The second is ec2:DescribeVpcAttribute, which does support resource-level permissions, so instead of an answer you get a template:

"arn:aws:ec2:${Region}:${Account}:vpc/${VpcId}"

Red underline, error marker on line 23. You cannot create the policy until those placeholders become real values. I like that it blocks you. It forces one deliberate scoping decision instead of letting a wildcard slip through because you were clicking fast.

Section 08What it will not tell you

The generated policy is a first draft. Five things it leaves out:

Section 09How I would actually run this

  1. Turn on a trail with validation and SSE-KMS, then leave it alone for 90 days before reviewing anything.
  2. Generate across every region the identity touches, opt-in regions included.
  3. Diff the result against what is attached today. That delta is a conversation with whoever owns the identity, not a ticket you close on your own.
  4. Replace the resource placeholders on purpose. Turning ${VpcId} into * is a decision, so write down why you made it.
  5. Attach the policy, remove AdministratorAccess, then watch the Last Accessed tab and Access Analyzer's unused access findings for a month before calling it done.

For this user, none of that is step one. Step one is MFA on the console login and deleting two access keys that are eleven days old and have never been used. A least-privilege policy on an identity with no MFA and two dangling long-lived keys is decoration.