Write and test least-privilege IAM policies
Design an IAM policy for a Lambda function that reads from S3 and writes to DynamoDB. You will start with broad permissions, enumerate the exact API calls the function makes via CloudTrail, write a least-privilege policy with explicit denies, and verify the deny rules hold using the AWS IAM Policy Simulator.
Why this matters
Overly broad IAM policies are the most common cloud security vulnerability in real AWS accounts; and the hardest to notice because everything still works. Writing a least-privilege policy from scratch teaches you the IAM policy language, the difference between identity-based and resource-based policies, and how to use the simulator to verify your reasoning before an incident does it for you.
Before you start
- AWS account with IAM and Lambda access
- AWS CLI configured with admin credentials for the setup steps
- A simple Lambda function that reads one S3 bucket and writes to one DynamoDB table
- Basic understanding of what IAM roles and policies are conceptually
Step-by-step guide
- 1
Enumerate actual API calls via CloudTrail
Temporarily attach AdministratorAccess to your Lambda role. Enable CloudTrail in the same region. Invoke the Lambda 5-10 times. Query CloudTrail for the IAM calls made by your function's role ARN. This gives you the ground truth: the exact API calls your function actually makes.
- 2
Write the minimal policy
Based on the CloudTrail output, write an IAM policy with only the specific actions (e.g., s3:GetObject, dynamodb:PutItem) on the specific resource ARNs. Add explicit Deny statements for destructive actions like s3:DeleteObject and dynamodb:DeleteTable.
- 3
Attach and test with the CLI
Swap AdministratorAccess for your custom policy. Invoke the Lambda and verify it still works. Then use aws iam simulate-principal-policy to test each action: your allows should show Match and your explicit denies should show ExplicitDeny.
- 4
Add a resource condition
Update the S3 policy to only allow access when the object key starts with a specific prefix using the s3:prefix condition key. Test that a request to a key outside the prefix is denied. Conditions are where IAM gets powerful; they scope by time, IP, tag, and dozens of other attributes.
- 5
Block privilege escalation
Add an explicit Deny for iam:AttachRolePolicy and iam:CreateUser to prevent the Lambda role from granting itself more permissions. This is the self-escalation prevention pattern; a compromised Lambda should not be able to create new IAM credentials.
- 6
Document each statement
Write a short comment above each statement in the policy JSON explaining what it does and why. Good IAM policies are self-documenting because permissions that look unnecessary get removed by the next engineer unless there is a comment explaining why they exist.