skip to content

You inherit an IAM role whose attached policy grants wildcard actions, and you are asked to cut it back to least privilege without breaking the workload. Which AWS evidence sources tell you what the role actually used, and what are their blind spots?

level: seniorimportance: should knowfreq 50%

answer

  1. what it used, not what it needs
  2. Access Advisor is service-level first
  3. generate a policy from trail history
  4. rare paths look identical to dead ones
  5. deploy with an AccessDenied detector

basics

~20 s

Use three evidence sources: IAM service last-accessed data (Access Advisor) for which services the role touched and when, IAM Access Analyzer to generate a policy from CloudTrail history and to flag unused access, and the CloudTrail record itself for individual calls.

solid answer

~40 s

There are three, and they answer different questions. **Service last-accessed data** (`GenerateServiceLastAccessedDetails` / `GetServiceLastAccessedDetails`, the API behind Access Advisor) tells you which services the role's permissions were used against and when, over a trailing window of roughly 400 days; for a subset of services it also reports action-level detail. **IAM Access Analyzer** can generate a candidate policy from the role's CloudTrail activity over a chosen period, and its unused-access findings surface roles and permissions nobody has exercised. **CloudTrail** is the ground truth underneath both. The blind spot is shared and important: absence of use is not proof of non-need. Quarterly jobs, disaster-recovery paths and failure-branch permissions look identical to dead grants. So I narrow in stages — remove untouched *services* first, deploy behind a change window, watch for `AccessDenied`, then tighten actions and resources.

code

bash · 5 lines
bash
JOB_ID=$(aws iam generate-service-last-accessed-details \
  --arn arn:aws:iam::111122223333:role/legacy-batch-role \
  --granularity ACTION_LEVEL \
  --query JobId --output text)
aws iam get-service-last-accessed-details --job-id "$JOB_ID"

go deeper

for a junior

Know the names and what each answers: Access Advisor shows which services a role's permissions were used against, IAM Access Analyzer can draft a policy from past activity, and CloudTrail holds the individual calls.

for a middle

Explain the generate-then-poll shape of service last-accessed data, why action-level granularity is only available for some services, and why the tracking window's length decides whether a rare job looks dead.

for a senior

Show the staged reduction: services first, then actions, then resources and conditions, each deployed with AccessDenied alarming and a retained policy version — and state plainly that evidence of use is a hypothesis about need, not a proof.

for a principal

Turn the one-off into a control: continuous unused-access analysis across the organization, an agreed threshold and exception process for rare-path permissions, and ownership rules so a wildcard policy cannot be born unowned in the first place.

## The problem with "just write least privilege" A wildcard policy on an inherited role is a permission set nobody can describe. You cannot ask the original author, and reading the application code only tells you what it calls on the happy path. The only reliable description of what the role needs is a description of what it *did*, and AWS exposes that at three different resolutions. ## Source 1 — service last-accessed data (Access Advisor) This is the cheapest first cut. IAM tracks, per principal, which services that principal's permissions were last used to access. It is asynchronous: you submit a job and then poll for the report. ```bash JOB_ID=$(aws iam generate-service-last-accessed-details \ --arn arn:aws:iam::111122223333:role/legacy-batch-role \ --query JobId --output text) aws iam get-service-last-accessed-details --job-id "$JOB_ID" ``` Each entry names a service, the last time it was accessed through this principal, and the count of authenticated entities. Passing `--granularity ACTION_LEVEL` gives per-action detail, but only for the services that support it — for the rest you get service granularity and must fall back to CloudTrail. The tracking window is a trailing period of roughly 400 days, and it is not infinite history: activity older than the window is simply not reported. The report is also principal-scoped, so it answers "what did this role touch", not "who could have used this permission". What it buys you is the coarse but very safe move: a role with a wildcard policy typically shows use of five services and no recorded use of two hundred. Cutting the untouched services is a large reduction in blast radius for a small risk, and it is the change you can justify to a nervous owner. ## Source 2 — IAM Access Analyzer Access Analyzer contributes two distinct things here. Its **policy generation** feature reads the role's CloudTrail activity over a window you choose and emits a candidate identity policy containing the actions actually observed — a much better starting draft than anything you would write by hand, and one you must still review rather than apply blind. Its **unused access** analyzer runs continuously against an account or organization and raises findings for roles that have not been assumed, users' unused credentials, and permissions that were granted but never exercised within a configured age threshold. That turns the one-off cleanup into a standing control, which is the difference between a tidy-up and a programme. Access Analyzer's other well-known job — external access findings, which identify resources reachable by principals outside your account or organization — is a different question (who can reach in) rather than this one (what did this role use), but the same product answers both, and interviewers often expect you to distinguish them. ## Source 3 — CloudTrail itself Both of the above are derived views over CloudTrail. When action-level data is unavailable, or when you need the resource ARNs to write a tight `Resource` block rather than `"*"`, you go to the trail records for that role's `userIdentity` and read the actual `eventName` and `requestParameters`. The relevant caveat for scoping is that resource-level detail exists only where the events exist: object-level S3 access is a data event, so if data events were never enabled, the trail will show the role calling the S3 control plane and reveal nothing about which keys it read. ## The shared blind spot All three measure *use*, and least privilege is about *need*. The gap between them contains everything that runs rarely: - quarterly or annual jobs — a 90-day analysis window will call them dead, - disaster-recovery and failover paths, exercised only during a real incident, - error-handling branches, such as the permission to write to a dead-letter queue that has never been hit, - seasonal traffic, and code paths behind a feature flag that is currently off. Removing those looks safe in every report and fails at the worst possible moment. This is why "no recorded use" is a *candidate*, never a verdict. ## How to actually do it Stage the reduction so each step is reversible: 1. Choose a window longer than the workload's longest cycle — for anything with quarterly batch work, that means a year, which is why the roughly 400-day tracking period matters. 2. Cut untouched services first. Big reduction, low risk, easy to explain. 3. Move from services to actions, using action-level data where it exists and CloudTrail where it does not. 4. Add `Resource` scoping and conditions last, since that is where a mistake breaks a narrow path rather than the whole role. 5. Deploy each stage with a way to detect the failure: alarm on `AccessDenied` in the role's CloudTrail events, and keep the previous policy version so a rollback is one call. Managed policies keep versions, which makes this genuinely quick. 6. Ask a human. The team that owns the workload knows about the annual regulatory export the data never showed you. The answer an interviewer is listening for is not the list of tool names — it is step 5. Anyone can run Access Advisor; the senior signal is treating an evidence-derived policy as a hypothesis you deploy with a detector attached.

  • Access Advisor shows a service with no recorded access in the tracked window. Can you delete that permission?
    It is a candidate, not a verdict. Absence of use covers quarterly jobs, disaster-recovery paths and error branches that have never fired — all of which look identical to a dead grant. Remove it in a staged change with AccessDenied alarming on the role and a retained previous policy version, and check with the owning team about rare scheduled work.
  • How does IAM Access Analyzer's unused-access finding differ from its external-access finding?
    They answer opposite questions. Unused access looks inward at your own principals and flags roles, credentials and permissions that were granted but not exercised. External access looks outward at resource policies and flags resources reachable by principals outside your account or organization. One drives least-privilege cleanup, the other drives exposure review.
  • You want to scope the policy's Resource block to specific ARNs rather than "*". Where does the evidence come from, and where can it be missing?
    From CloudTrail's requestParameters and resources on the role's events, since service last-accessed data is service- or action-shaped rather than resource-shaped. It is missing wherever the events were never generated — most commonly S3 object-level access, which is a data event and is off unless a selector enabled it, so bucket-level calls appear while the keys read do not.

saying these in an interview costs you the question

  • Treats no-recorded-use as proof the permission is unneeded
  • Analyses a 30-day window for a quarterly workload
  • Applies a generated policy without review or rollback
  • Thinks Access Advisor reports action-level data for all services
  • Confuses unused-access findings with external-access findings

context