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

Published: (December 17, 2025 at 10:29 AM EST)
3 min read
Source: Dev.to

Source: Dev.to

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

Common patterns seen 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

These issues often arise from the pressure to move fast. The fix isn’t blame; it’s adopting the right patterns.

Bad Policy vs Good Policy Examples

Example 1: S3 access

❌ Bad policy (too broad)

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

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)

{
  "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/*"
    }
  ]
}

Why this is better

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

Example 2: Lambda Logging

❌ Bad policy

{
  "Effect": "Allow",
  "Action": "logs:*",
  "Resource": "*"
}

Why this is risky

  • Allows writing logs anywhere, which is unnecessary and can expose sensitive information.

✅ Good policy

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

A Simple System to Design IAM the Right Way

Pattern that works in real projects

Separate roles

  • One role for deployment
  • One role for runtime
  • One role for monitoring

Start narrow, expand only when needed

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

Use guardrails

  • Apply Service Control Policies (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

Goal

Create:

  • One S3 bucket
  • One Lambda execution role
  • One least‑privilege IAM policy attached 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

This hands‑on example can be implemented with Terraform (details to be added later).

Back to Blog

Related posts

Read more »