Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

IAM Least Privilege Walkthrough

A real before/after case study: scoping down an overly permissive IAM role to the exact permissions it actually needs — then proving it still works.

This uses the IAM role from my serverless daily report pipeline project as the real-world example.

The Problem

My Lambda function's execution role started with the AWS-managed policy AmazonS3FullAccess attached — the fastest way to get the pipeline working. But "fastest" and "correct" aren't the same thing.

Before: overly broad permissions

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

See the full file: policies/before-overly-permissive.json

"Resource": "*" means this Lambda function could act on every S3 bucket in the entire AWS account — not just the one it was built for. It could read, write, delete, or even change permissions on buckets belonging to completely unrelated projects.

Before: broad policy JSON

The Analysis

Looking at what the function's code actually does with S3, there's exactly one interaction:

s3.put_object(
    Bucket=bucket_name,
    Key=file_name,
    Body=report
)

The function only ever:

  • Writes — never reads, lists, or deletes
  • Touches one bucket — not the whole account
  • Writes to one folder path within that bucket — weather-reports/

Full breakdown: notes/permission-breakdown.md

The Fix: Least-Privilege Policy

After: scoped to exactly what's needed

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::daily-weather-demo/weather-reports/*"
    }
  ]
}

See the full file: policies/after-least-privilege.json

This restricts the role to a single action (s3:PutObject) on a single resource path — nothing else.

Policy created confirmation

Verification

Rather than assuming the scoped-down policy would work, I tested it in two stages:

Stage 1 — with both policies attached, confirming the new policy doesn't break anything:

Lambda test succeeded with scoped policy attached S3 report saved successfully

Stage 2 — after removing AmazonS3FullAccess entirely, to prove the broad access was never actually necessary:

Policy removed confirmation Final test succeeded after removing broad policy

The function ran successfully both times. The second result is the real proof: the broad permissions were never required in the first place.

Why This Matters

If this function were ever compromised — through a vulnerable dependency, a leaked credential, or a misconfiguration — the difference in blast radius is significant:

Before After
Buckets affected Every bucket in the account One bucket
Actions possible Read, write, delete, change permissions Write only
Scope within bucket Entire bucket One folder path

This is the core idea behind least privilege: a role should only ever be able to do the exact thing it needs to do. It doesn't eliminate every risk, but it shrinks the potential damage dramatically.

What This Demonstrates

  • Reading and interpreting IAM policy JSON
  • Identifying the gap between granted permissions and actual usage
  • Writing scoped-down, resource-specific IAM policies
  • Verifying changes empirically rather than assuming correctness
  • Understanding the real-world security impact of "quick fix" permissions like AmazonS3FullAccess

Related Project

This case study builds directly on my Serverless Daily Report Pipeline project, which uses this exact IAM role.

About

Real-world before/after case study scoping an IAM role down to least-privilege permissions

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors