DEV Community

Cover image for IAM Least Privilege: What Everyone Gets Wrong (and How to Fix It with Terraform)

IAM Least Privilege: What Everyone Gets Wrong (and How to Fix It with Terraform)

When we’re under pressure, the fastest solution often wins. Someone needs access, something is failing, and a deadline is approaching. So we do the classic move: grant wide permissions “temporarily.”

The problem is that temporary permissions have a habit of becoming permanent.

Least privilege isn’t about being paranoid. It’s about being intentional. We grant only what’s needed, so mistakes stay small, and security remains predictable.

What does least privilege actually mean?

Least privilege means:

  • Only the actions needed (not everything)
  • Only the resources needed (not “any resource”)
  • Only when needed (not forever)
  • Only for the right identity (role, user, or service)

With these points in mind, I always ask myself the following questions:

  • What does this workload need to do?
  • Where does it need to do it?
  • What should never be allowed?

This mindset matters because IAM is not just about security; it’s also about reliability.
A role with too much power can break more things, faster.

Why Least Privilege Matters at Scale

In small environments, wide permissions might not cause immediate problems.
At scale, they usually do.

Here’s what least privilege protects you from:

  • A larger blast radius: one compromised key can impact everything
  • Accidental deletions: someone runs the wrong command in production
  • Shadow access: old roles retain permissions nobody remembers granting
  • Audit headaches: difficult questions like “Why does this role have this access?”

When permissions are tight, debugging becomes easier as well.
If something fails, we know the access boundary is real and meaningful.

Where Teams Usually Go Wrong

These are the most common patterns we see in real AWS environments:

  • Wildcards like:*
  • Policies copied from the internet and never cleaned up
  • A single role reused across multiple systems
  • “Temporary” permissions that quietly become permanent
  • No separation between deployment permissions and runtime permissions

This happens to good teams as well. It usually comes from moving fast.
The fix isn’t blame; the fix is adopting the right patterns.

Bad Policy vs Good Policy Examples

Example 1: S3 access
❌ Bad policy (too broad)

This allows any S3 action on any bucket.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:*",
      "Resource": "*"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Why This Is Risky

  • Can delete any bucket
  • Can read data that should be private
  • No limits on specific paths or environments

✅ Good Policy (Tight and Practical)

This policy grants read-only access to a single bucket, limited to a specific prefix.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ListBucketInPrefix",
      "Effect": "Allow",
      "Action": ["s3:ListBucket"],
      "Resource": "arn:aws:s3:::my-app-data",
      "Condition": {
        "StringLike": {
          "s3:prefix": ["public/*"]
        }
      }
    },
    {
      "Sid": "ReadObjectsInPrefix",
      "Effect": "Allow",
      "Action": ["s3:GetObject"],
      "Resource": "arn:aws:s3:::my-app-data/public/*"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Why This Is Better

  • Limited to a single bucket
  • Limited to a specific path
  • No delete permissions

Example 2: Lambda Logging
❌ Bad Policy

This policy allows writing logs anywhere, which is unnecessary and risky.

{
  "Effect": "Allow",
  "Action": "logs:*",
  "Resource": "*"
}
Enter fullscreen mode Exit fullscreen mode

✅ Good Policy

This policy allows only log stream creation and log writing for one specific log group.

{
  "Effect": "Allow",
  "Action": [
    "logs:CreateLogStream",
    "logs:PutLogEvents"
  ],
  "Resource": "arn:aws:logs:us-east-1:123456789012:log-group:/aws/lambda/my-function:*"
}
Enter fullscreen mode Exit fullscreen mode

A Simple System to Design IAM the Right Way

Here’s a pattern that works in real projects:

Separate roles

  • Use different roles for different responsibilities:
  1. One for deployment
  2. One for runtime
  3. One for monitoring

Start narrow, expand only when needed

  • When an action fails, add only the minimum permission required to fix it.

Use guardrails

  • Apply SCPs and permission boundaries to enforce “this must never happen” rules.

Review regularly

  • If a permission hasn’t been used in months, remove it.
  • Least privilege is not a one-time setup.
  • It’s a maintenance habit.

🧪 Mini Project: Least-Privilege IAM Role for Lambda + S3

(Terraform Coming-Soon)

Goal

We will create:

  • One S3 bucket
  • One Lambda execution role
  • One least-privilege IAM policy
  • Attach the policy to the role
  • The Lambda will be able to:
  • Read only from s3://bucket/public/*
  • Write only to s3://bucket/results/*
  • Write logs to CloudWatch

If this article helped you, here’s what you can do next:

Follow me on X and YouTube for more AWS, DevOps, and Terraform content that’s beginner-friendly.

Leave a comment with your thoughts, your own AWS journey, or questions you’d like me to cover next.

Coming soon: a GitHub repository where I’ll share resources and examples for the Mini Project: Least-Privilege IAM Role for Lambda + S3 (Terraform), so you can practice hands-on.

Top comments (0)