IAM Policy Generator for Python (boto3)

Paste a Python script or Lambda handler that uses boto3 and get a draft IAM policy covering the AWS actions it calls, ready to tighten and attach.

Your codePython (boto3)
Drop the file to load it
Tab indents · Esc, Tab exits
ResultIAM policy JSON
Your IAM policy JSON will appear hereClick Generate IAM policy or press Ctrl Enter
policy.json
Don't paste secrets

How to generate an IAM policy from Python code

To generate an IAM policy from Python, paste the script or Lambda handler that uses boto3 above and press Generate IAM policy. Client calls such as sns.publish(), resource calls such as table.get_item() and paginators are mapped to IAM actions and grouped by resource. Scope any wildcards, then validate and test the policy before attaching it.

  1. Paste your boto3 codeAdd the handler or script and any helper modules that create clients or resources, using Paste, Open file (.py or .txt, up to 500 KB) or drag and drop. Sample loads a Lambda handler that uses DynamoDB, S3 and SNS.
  2. Let the secret check runThe page scans your code in the browser for AWS access keys, secret keys, private keys and API tokens before sending it, and highlights anything it finds.
  3. Generate the policyClick Generate IAM policy or press Ctrl + Enter. The policy streams in as it's written, and you can stop it at any point.
  4. Scope the resourcesReplace wildcards with the real bucket, table and topic ARNs. Values read from os.environ can't be resolved from the source, so they're the first place to look.
  5. Validate, test and attachCheck the policy with IAM Access Analyzer policy validation, try specific calls in the IAM policy simulator, then download policy.json and attach it to the Lambda or instance role.
Five-step loop for boto3 code: generate a draft IAM policy, replace wildcard ARNs, validate with IAM Access Analyzer, test with the IAM policy simulator, attach to the role, and adjust if a test hits AccessDenied
Generating the draft is the first step. Scoping, validating and testing make it safe to attach.

Why generate an IAM policy from boto3 code

Python is where a lot of AWS automation lives: Lambda handlers, cron jobs on EC2, data pipelines and one-off admin scripts. Those scripts often run with far broader permissions than they need, because working out the exact actions for every boto3 call is tedious. Generating the policy from the code gives you the list of actions in seconds.

It's most useful when you're creating an execution role for a new Lambda function, moving a script from a developer's admin credentials onto its own role, or reviewing a change that adds new boto3 calls. The S3 diagram below is typical: a script that lists and reads a bucket needs two statements on two different ARNs, which is easy to get wrong by hand.

The generator reads the source you paste, so it can't see values from os.environ, modules you didn't include, or permissions Lambda needs outside your code. For example, reading from an SQS event source needs queue permissions that never appear in the handler. Treat the result as a draft to review.

An S3 bucket named acme-reports with its objects, and two IAM statements: s3:ListBucket on the bucket ARN arn:aws:s3:::acme-reports and s3:GetObject on the object ARN arn:aws:s3:::acme-reports/*
A script that lists and reads a bucket needs two statements with two different ARNs.

How boto3 calls become IAM actions

Each boto3 client method corresponds to an API operation, and the IAM action is the service prefix plus that operation name in PascalCase. get_item() needs dynamodb:GetItem, update_item() needs dynamodb:UpdateItem, and sns.publish() needs sns:Publish. The generator reads client calls (boto3.client), resource calls (boto3.resource) and paginators, and turns them into statements grouped by service.

The resource API hides the operation name, so it's worth knowing what's underneath. Table.batch_writer() sends BatchWriteItem requests, bucket.objects.all() lists the bucket (s3:ListBucket), and the list_objects_v2 paginator in the sample needs s3:ListBucket on the bucket ARN, not on the objects inside it. The Service Authorization Reference is the authoritative list of actions and resource types for each service.

Not every action supports resource-level permissions. ec2.describe_instances() and most other Describe* calls need "Resource": "*", so a wildcard there is correct rather than lazy.

boto3's upload_file and download_file switch to multipart transfers and ranged reads for large files. Those are still authorized by s3:PutObject and s3:GetObject, but SSE-KMS buckets also need kms:GenerateDataKey and kms:Decrypt.

How boto3 calls map to IAM actions: the client call sns.publish to sns:Publish, the resource call table.get_item to dynamodb:GetItem, the list_objects_v2 paginator to s3:ListBucket on the bucket ARN, and s3.upload_file to s3:PutObject
Clients, resources, paginators and transfer helpers all come down to API operations.

Common boto3 calls and their IAM actions

A quick reference for the calls Python scripts use most, including the ones whose permission isn't obvious from the method name.

boto3 calls, IAM actions and resources
boto3 callIAM actionResource
s3.get_object(), download_file()s3:GetObjectarn:aws:s3:::bucket/key
s3.put_object(), upload_file()s3:PutObjectarn:aws:s3:::bucket/key
get_paginator("list_objects_v2")s3:ListBucketarn:aws:s3:::bucket (the bucket itself)
s3.generate_presigned_url()None to generate; the URL uses the signer's s3:GetObject or s3:PutObjectThe object the URL points to
table.get_item()dynamodb:GetItemarn:aws:dynamodb:region:account:table/NAME
table.batch_writer()dynamodb:BatchWriteItemarn:aws:dynamodb:region:account:table/NAME
sqs.send_message()sqs:SendMessageThe queue ARN
ssm.get_parameter()ssm:GetParameterarn:aws:ssm:region:account:parameter/app/prod/db-url
ec2.describe_instances()ec2:DescribeInstances* (no resource-level permissions)

Key features

  • Clients, resources and paginatorsReads boto3.client calls, boto3.resource objects such as Table and Bucket, and paginators, and maps each to its API operation.
  • Paginators mapped to the right ARNThe list_objects_v2 paginator becomes s3:ListBucket on the bucket ARN, the permission hand-written S3 policies most often get wrong.
  • Constants become exact ARNsNames defined in the script, like REPORT_BUCKET = "acme-reports", go straight into the resource ARNs.
  • Clear wildcards for the unknownsValues from os.environ, the region and the account ID become *, so you can see at a glance what to fill in.
  • Nothing inventedPolicy JSON can't carry comments, so anything the model can't infer from your code is left out rather than guessed. Review the draft against your calls.
  • Download as policy.jsonCopy the result or save it as policy.json for the IAM console, the AWS CLI or your infrastructure code.
  • Secret check before sendingKeys and tokens are flagged in your browser before any code is sent.

Examples: boto3 code and the IAM policy it needs

Three everyday Python patterns with a correct least-privilege policy for each. Choose Try this example to load the code into the generator and compare its draft with the policy shown here.

Lambda handler that writes SQS messages to DynamoDB

batch_writer() sends BatchWriteItem. The SQS permissions come from the Lambda event source mapping, not from anything in the handler, so a code-based draft won't include them. They're shown here so the role works end to end. See Using Lambda with Amazon SQS.

Python (boto3)
import json

import boto3

table = boto3.resource("dynamodb").Table("events")


def handler(event, context):
    with table.batch_writer() as batch:
        for record in event["Records"]:
            body = json.loads(record["body"])
            batch.put_item(Item={"id": body["id"], "type": body["type"]})
    return {"written": len(event["Records"])}
IAM policy JSON
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "dynamodb:BatchWriteItem",
      "Resource": "arn:aws:dynamodb:*:*:table/events"
    },
    {
      "Effect": "Allow",
      "Action": [
        "sqs:ReceiveMessage",
        "sqs:DeleteMessage",
        "sqs:GetQueueAttributes"
      ],
      "Resource": "arn:aws:sqs:*:*:events-queue"
    }
  ]
}

Download, resize and upload with transfer helpers

download_file and upload_file are managed transfers, but they only need the plain object permissions, each on its own bucket.

Python (boto3)
import boto3
from PIL import Image

s3 = boto3.client("s3")


def make_thumbnail(key: str) -> str:
    s3.download_file("acme-raw-uploads", key, "/tmp/original")
    with Image.open("/tmp/original") as img:
        img.thumbnail((256, 256))
        img.save("/tmp/thumb.png")
    thumb_key = f"thumbs/{key}.png"
    s3.upload_file("/tmp/thumb.png", "acme-thumbnails", thumb_key)
    return thumb_key
IAM policy JSON
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::acme-raw-uploads/*"
    },
    {
      "Effect": "Allow",
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::acme-thumbnails/thumbs/*"
    }
  ]
}

Read a config value from Parameter Store

Parameter ARNs drop the leading slash of the name: /app/prod/db-url becomes parameter/app/prod/db-url. describe_instances doesn't support resource-level permissions, so its resource stays *.

Python (boto3)
import boto3

ssm = boto3.client("ssm")
ec2 = boto3.client("ec2")


def running_instances_for_app():
    app = ssm.get_parameter(Name="/app/prod/app-name")["Parameter"]["Value"]
    paginator = ec2.get_paginator("describe_instances")
    ids = []
    for page in paginator.paginate(
        Filters=[
            {"Name": "tag:app", "Values": [app]},
            {"Name": "instance-state-name", "Values": ["running"]},
        ]
    ):
        for reservation in page["Reservations"]:
            ids += [i["InstanceId"] for i in reservation["Instances"]]
    return ids
IAM policy JSON
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "ssm:GetParameter",
      "Resource": "arn:aws:ssm:*:*:parameter/app/prod/app-name"
    },
    {
      "Effect": "Allow",
      "Action": "ec2:DescribeInstances",
      "Resource": "*"
    }
  ]
}

Tips for better results

  • Include the modules that create clients. If your handler imports a db.py or aws_clients.py helper, paste it too, or the calls it makes won't be in the policy.
  • Show the real names when you can. A script that reads os.environ["BUCKET"] produces a wildcard. Paste a comment or constant with the real bucket name to get an exact ARN.
  • Know what the resource API hides. batch_writer() is BatchWriteItem and bucket.objects.filter() is ListBucket. If a call looks magical, check which operation it sends.
  • Remember the Lambda-side permissions. Event source mappings for SQS, Kinesis and DynamoDB Streams need their own read permissions on the execution role, and CloudWatch Logs access usually comes from AWSLambdaBasicExecutionRole.
  • Cross-check with CloudTrail. After a test run, IAM Access Analyzer policy generation builds a policy from the role's recorded activity. Differences between the two are worth a look.

Common mistakes to avoid

  • Missing the event source permissions. A handler triggered by SQS never calls receive_message, but its role still needs sqs:ReceiveMessage, sqs:DeleteMessage and sqs:GetQueueAttributes.
  • Scoping Describe* calls to an ARN. Most EC2 describe actions only work with "Resource": "*". Restricting them to an instance ARN makes the call fail.
  • Putting s3:ListBucket on bucket/*. Paginating list_objects_v2 is authorized on the bucket ARN, so this grants nothing useful.
  • Assuming presigned URLs need their own permission. generate_presigned_url makes no API call. Whoever uses the URL acts with the signer's permissions, so the signing role needs s3:GetObject or s3:PutObject.
  • Forgetting KMS on encrypted resources. Reading SSE-KMS objects or SecureString parameters with a customer managed key also needs kms:Decrypt on that key.
  • Keeping the script's old admin policy "just in case". Detach the broad policy once the generated one passes your tests, or the new policy protects nothing.

Frequently asked questions

How do I generate an IAM policy from Python code?

Paste the Python script or Lambda handler that uses boto3 into the editor at the top of this page and press Generate IAM policy. Each boto3 call is mapped to its IAM action and grouped by resource into policy JSON. Scope the wildcards, validate it and test it before attaching it to a role.

Does it understand boto3 resources and paginators?

Yes. It reads boto3.client calls, boto3.resource objects such as Table and Bucket, and paginators such as get_paginator("list_objects_v2"), and maps each to the underlying IAM action.

Is the generated policy least-privilege?

It's a least-privilege starting point: it includes only the actions your code calls. Resources the code can't name, such as values from os.environ, appear as wildcards. Replace them with exact ARNs and run IAM Access Analyzer policy validation before using it. Before you attach it, follow the checklist to review a generated IAM policy for least privilege.

Why are some resources still a wildcard after I fill in names?

Some actions don't support resource-level permissions. ec2:DescribeInstances and most other describe or list-all actions require "Resource": "*". The Service Authorization Reference shows which resource types each action supports.

Will it include the permissions Lambda needs?

Only the ones your code calls. Permissions Lambda uses on your behalf, such as reading from an SQS or Kinesis event source and writing CloudWatch Logs, don't appear in handler code, so add them separately. AWS managed policies like AWSLambdaBasicExecutionRole cover logging.

Can it miss permissions?

Yes. It can't see calls in modules you didn't paste, permissions triggered indirectly such as kms:Decrypt or iam:PassRole, or a call whose target is built at runtime. Test the policy with the IAM policy simulator and a real run before relying on it.

Does it work with scripts that use sessions or profiles?

Yes. Clients created from boto3.Session(profile_name=...) or with an explicit region_name make the same API calls, so they need the same IAM actions. The profile only decides which credentials are used, and the policy belongs on the role or user behind them.

Is my code stored?

No. The code is sent to an AI model hosted on NVIDIA's API only to produce the policy, and ChatWithCloud doesn't store or log it. Your latest draft stays in your browser. Replace real keys and passwords with placeholders first; the page warns you if it spots one. For the full picture, see whether it's safe to paste AWS code into an AI converter.

Is it free, and how much code can I paste?

Yes, it's free. Each run accepts up to 60,000 characters, and files up to 500 KB. Without an account you can generate 6 policies a minute and 60 a day. A free account raises that to 12 a minute and 200 a day and lists your recent runs on your dashboard. The guide to ChatWithCloud free converter limits shows how far they stretch for a real migration.

Related tools and next steps