Everyone agrees in principle
Least privilege — granting only the permissions required to perform a specific task, and nothing more — is one of the most widely agreed-upon principles in cloud security. Ask any AWS architect whether they follow it and they'll say yes. Look at the IAM policies in most AWS accounts and you'll find `"Action": "*"` more often than you'd expect.
The gap between principle and practice isn't due to negligence. It's due to several structural difficulties that make IAM harder to get right than the documentation implies.
The iteration problem
The most common path to overpermissioned IAM looks like this: an engineer creates a Lambda function or an ECS task that needs to access a few AWS services. They run it with a restrictive policy, it fails. They add a permission, run it again, it fails on a different call. They add another permission. Eventually they add `"Action": "*"` for the relevant service or, worse, across all services, because they need it working and don't want to keep iterating.
This is a tooling and workflow problem as much as a discipline problem. IAM policy simulator helps, but it requires knowing in advance what API calls your code will make — which isn't always obvious for third-party libraries or SDK internals.
A better workflow: run your Lambda or task with a permissive policy in a non-production environment, but with CloudTrail and AWS Access Analyzer enabled. Access Analyzer can automatically generate IAM policies based on actual CloudTrail activity. After running your workload through its normal operations (including error paths), use the generated policy as a starting point — then review it before applying to production.
This doesn't eliminate judgement, but it grounds the policy in observed behaviour rather than guesswork.
Wildcard resources
Even when teams get the Action right, they often grant it on `"Resource": "*"`. A policy that says "allow s3:GetObject on *" gives access to every S3 bucket in the account (and in some cases, across accounts).
Resource-level restrictions are available for most AWS services and should be the default, not the exception. For S3, restrict to the specific bucket ARN and path prefix your workload needs. For DynamoDB, restrict to the specific table. For Secrets Manager, restrict to the specific secret ARN or ARN prefix.
The objection is usually "we don't know the resource ARN at policy creation time." For infrastructure provisioned by Terraform, this is solvable: the resource ARN is a Terraform output, and you can reference it directly in the IAM policy resource.
resource "aws_iam_policy" "app_policy" {
policy = jsonencode({
Statement = [{
Effect = "Allow"
Action = ["s3:GetObject", "s3:PutObject"]
Resource = "${aws_s3_bucket.app_data.arn}/*"
}]
})
}When the specific ARN genuinely isn't known (cross-account access, dynamic resource names), use ARN conditions to restrict by prefix or tag rather than granting `*`.
Permission drift over time
IAM policies aren't just set once — they're modified over time as applications evolve. A permission that was necessary eighteen months ago may no longer be used. A role that was created for a temporary migration task may still exist with full migration-level permissions.
Without active management, IAM policies drift toward permissiveness over time. The engineering effort to remove permissions is always greater than the effort to add them (adding permissions is required to unblock work; removing them requires audit, testing, and the confidence that nothing depends on them).
AWS IAM Access Analyzer's "unused access" findings help here. It tracks which permissions in a role or policy have not been used in the last 90 days, based on CloudTrail data. This isn't a perfect signal — some permissions are used rarely but legitimately — but it identifies candidates for review.
Build permission review into your operations cadence. Once per quarter, review the "unused access" findings for your most sensitive roles (anything with write access to production data, anything with admin-level scope). This doesn't need to be exhaustive; focus on the highest-risk roles first.
Cross-account trust
Multi-account AWS architectures (which we recommend) introduce cross-account IAM trust: roles in one account that can be assumed by principals in another. This is the correct mechanism for cross-account access — it's auditable and revocable — but it can create transitive permission paths that are hard to reason about.
A principal in account A assumes a role in account B. That role can assume a role in account C. The net effect is that a principal in account A has whatever permissions the role in account C has, even if that was never intended.
AWS IAM Access Analyzer's cross-account findings surface external access grants — roles and resources that are accessible from outside the account. Review these regularly. Anything that grants cross-account access should have a documented reason and an owner.
Also enforce `aws:PrincipalOrgID` conditions on your trust policies where appropriate. This restricts role assumption to principals within your AWS Organization, preventing external accounts from assuming your roles even if they somehow obtained valid credentials.
The practical programme
Least privilege isn't a one-time configuration — it's an ongoing programme. The sustainable version looks like:
New workloads: use Access Analyzer policy generation based on actual CloudTrail activity. Start restrictive, grant specifically.
Existing workloads: run Access Analyzer's unused access findings monthly. Review and clean up the highest-risk roles quarterly.
Cross-account trust: review external access findings from Access Analyzer after any account or permission changes.
Break-glass roles: document your emergency access roles explicitly. They should have the most permissive access and the most stringent logging and alerting. Assuming a break-glass role should trigger an alert to your security team.
The goal isn't a perfect least-privilege posture in a single pass — it's a practice that continuously tightens permissions over time. Organisations that treat IAM as a living part of their security programme consistently end up with better postures than those who treat it as a one-time configuration task.