Claude Code and AWS IAM: A Practical Least-Privilege Policy Guide
Design and validate least-privilege AWS IAM policies with Claude Code, Access Analyzer, and CDK examples.
AWS IAM decides who can do what to which AWS resources. A policy is only a JSON document, but a careless Action: "*" or Resource: "*" can give a Lambda function, CI job, or human user far more power than the task requires. Claude Code is useful for drafting and reviewing IAM, but it must not become an unchecked policy generator.
This guide shows the workflow I use: describe the use case, let Claude Code draft a policy, validate it with IAM Access Analyzer, review it as code, and test both allowed and denied actions. The beginner translation is simple: a policy is the permission rule, a role is the temporary identity a workload assumes, and a principal is the user or service using the permission.
I changed my own process after finding a temporary broad S3 policy still attached to a production Lambda role weeks after a hotfix. Nothing leaked, but the incident made the review rule obvious: Claude Code can reduce drafting time, not accountability.
Use AWS docs as the baseline
This article follows the current AWS guidance in these official references:
- Security best practices in IAM
- IAM JSON policy element reference
- Validate policies with IAM Access Analyzer
- Temporary security credentials in IAM
- Permissions boundaries for IAM entities
The practical message is consistent: prefer temporary credentials and roles, apply least privilege, validate policies with Access Analyzer, use conditions carefully, and remove unused permissions regularly.
flowchart LR
A["Write the use case"] --> B["Claude Code draft"]
B --> C["Human ARN review"]
C --> D["IAM Access Analyzer"]
D --> E["CDK implementation"]
E --> F["Allowed and denied tests"]
Start with concrete use cases
Do not prompt Claude Code with only “write an IAM policy.” Give it the actor, resources, allowed operations, and operations that must stay forbidden.
| Use case | Principal | Required permissions | Explicitly avoid |
|---|---|---|---|
| Thumbnail Lambda | Lambda execution role | Read one S3 prefix, write one S3 prefix, write one DynamoDB table, publish one SNS topic, write logs | S3 delete, all-bucket read, IAM operations |
| Admin upload helper | API Lambda role | PutObject to one S3 prefix, GetItem for one DynamoDB table | Whole-bucket List, KMS key management |
| GitHub Actions deploy | OIDC CI role | Update one CloudFormation stack and target Lambda | AdministratorAccess, all-region operations |
| Incident read-only role | Federated human user | Search CloudWatch Logs, read X-Ray traces, GetItem on relevant table | Updates, deletes, Secrets Manager read |
The “avoid” column matters. Generative tools are much better when the negative boundary is explicit.
Copy-paste Lambda policy
This policy is for a Lambda function that reads images from one S3 prefix, writes thumbnails to another bucket prefix, records metadata in DynamoDB, publishes an SNS alert, and writes logs. The log group is assumed to be created by infrastructure code, so the role does not need logs:CreateLogGroup.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadSourceImages",
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::user-uploads-prod/incoming/*"
},
{
"Sid": "WriteThumbnails",
"Effect": "Allow",
"Action": ["s3:PutObject"],
"Resource": "arn:aws:s3:::user-thumbnails-prod/thumbnails/*",
"Condition": {
"StringEquals": {
"s3:x-amz-server-side-encryption": "AES256"
}
}
},
{
"Sid": "WriteMetadata",
"Effect": "Allow",
"Action": ["dynamodb:PutItem", "dynamodb:UpdateItem"],
"Resource": "arn:aws:dynamodb:ap-northeast-1:123456789012:table/image-metadata"
},
{
"Sid": "PublishAlerts",
"Effect": "Allow",
"Action": ["sns:Publish"],
"Resource": "arn:aws:sns:ap-northeast-1:123456789012:alert-topic"
},
{
"Sid": "WriteLambdaLogs",
"Effect": "Allow",
"Action": ["logs:CreateLogStream", "logs:PutLogEvents"],
"Resource": "arn:aws:logs:ap-northeast-1:123456789012:log-group:/aws/lambda/image-worker-prod:*"
}
]
}
Save it as policy-lambda-image-worker.json and run:
aws accessanalyzer validate-policy \
--policy-document file://policy-lambda-image-worker.json \
--policy-type IDENTITY_POLICY \
--query "findings[?findingType!='SUGGESTION']"
Then ask Claude Code for a structured review:
claude -p "Review policy-lambda-image-worker.json as an AWS IAM least-privilege policy.
Context: Lambda reads S3 incoming/, writes thumbnails/, writes DynamoDB image-metadata, publishes SNS alert-topic, and writes CloudWatch Logs.
Check: wildcards, delete permissions, broad Resources, Condition correctness, and log scope.
Return a table with Sid, risk, reason, and safer fix."
Access Analyzer checks grammar, ARNs, actions, condition keys, and security findings. It does not know whether your product really needs the permission. That final decision stays with the engineer.
Implement the role with CDK
Use infrastructure as code so the reviewed policy is versioned. This lib/image-worker-iam-stack.ts example creates the log group and Lambda execution role.
import * as cdk from "aws-cdk-lib";
import { Stack, StackProps, aws_iam as iam, aws_logs as logs } from "aws-cdk-lib";
import { Construct } from "constructs";
export class ImageWorkerIamStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const account = Stack.of(this).account;
const region = Stack.of(this).region;
new logs.LogGroup(this, "ImageWorkerLogGroup", {
logGroupName: "/aws/lambda/image-worker-prod",
retention: logs.RetentionDays.ONE_MONTH,
removalPolicy: cdk.RemovalPolicy.RETAIN,
});
const role = new iam.Role(this, "ImageWorkerRole", {
assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"),
description: "Execution role for image-worker-prod Lambda",
});
role.addToPolicy(new iam.PolicyStatement({
sid: "ReadSourceImages",
actions: ["s3:GetObject"],
resources: ["arn:aws:s3:::user-uploads-prod/incoming/*"],
}));
role.addToPolicy(new iam.PolicyStatement({
sid: "WriteThumbnails",
actions: ["s3:PutObject"],
resources: ["arn:aws:s3:::user-thumbnails-prod/thumbnails/*"],
conditions: {
StringEquals: {
"s3:x-amz-server-side-encryption": "AES256",
},
},
}));
role.addToPolicy(new iam.PolicyStatement({
sid: "WriteMetadataAndAlerts",
actions: ["dynamodb:PutItem", "dynamodb:UpdateItem", "sns:Publish"],
resources: [
`arn:aws:dynamodb:${region}:${account}:table/image-metadata`,
`arn:aws:sns:${region}:${account}:alert-topic`,
],
}));
role.addToPolicy(new iam.PolicyStatement({
sid: "WriteLambdaLogs",
actions: ["logs:CreateLogStream", "logs:PutLogEvents"],
resources: [
`arn:aws:logs:${region}:${account}:log-group:/aws/lambda/image-worker-prod:*`,
],
}));
}
}
Use OIDC for CI, not long-term keys
For CI/CD, avoid storing long-term AWS access keys in repository secrets. AWS guidance favors temporary credentials, and GitHub Actions can assume an IAM role through OIDC.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:example-org/example-repo:ref:refs/heads/main"
}
}
}
]
}
This is a trust policy: it decides who can assume the role. The permissions policy decides what the role can do after it is assumed. Review those two documents separately.
Pitfalls to catch in review
The first pitfall is temporary admin access that never gets removed. If emergency access is unavoidable, create a ticket with an owner, expiration date, CloudTrail check, and follow-up Access Analyzer run.
The second is mixing S3 bucket and object ARNs. s3:ListBucket uses arn:aws:s3:::bucket-name; object actions such as s3:GetObject and s3:PutObject use arn:aws:s3:::bucket-name/prefix/*.
The third is adding IP conditions to workload roles without understanding service calls. aws:SourceIp can make sense for human or public API requests, but it can break AWS service-to-service flows.
The fourth is testing only success. Also test that s3:DeleteObject, unrelated buckets, unrelated DynamoDB tables, and IAM actions are denied.
Related reading and next steps
For adjacent implementation details, read the AWS Lambda guide, AWS S3 guide, AWS CloudWatch guide, and Claude Code security best practices.
If your team wants a repeatable Claude Code and AWS permission review process, the training and consultation page is the practical next step. Solo readers can start with the free cheat sheet and reusable materials on the products page.
I tested this workflow by drafting the policy with Claude Code, validating the JSON, and reviewing the generated CDK against the same use case table. The biggest improvement came from writing the forbidden operations first: broad S3 permissions and unnecessary log wildcards were easier to catch before deployment.
Free PDF: Claude Code Cheatsheet
Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.
We handle your data with care and never send spam.
Level up your Claude Code workflow
Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.
About the Author
Masa
Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.
Related Posts
Claude Code Obsidian to CLAUDE.md Workflow: Stop Re-explaining Context
Turn Obsidian working notes into concise CLAUDE.md operating notes that make Claude Code sessions easier to resume.
Claude Code Revenue CTA Routing: Send Articles to PDF, Gumroad, and Consultation
A Claude Code workflow for routing article readers to the free PDF, Gumroad products, or consultation by intent.
Claude Code Team Handoff Rules: Review Evidence, Permissions, Rollback, and Revenue Paths
A practical Claude Code handoff format for team review, proof, permission rules, rollback, free PDF, Gumroad, and consultation paths.
Related Products
The Complete Claude Code Setup & Configuration Guide
From install to team-ready workflow.
A practical guide to installation, CLAUDE.md, hooks, MCP servers, permissions, IDE setup, and CI/CD workflows.
50 Battle-Tested Claude Code Prompt Templates
Copy, paste, ship. 50 production-ready prompts.
Use proven prompts for code review, refactoring, testing, documentation, debugging, architecture, and incident response.