How to write a least-privilege IAM policy
Least privilege means a policy allows only the actions a workload actually uses, only on the resources it actually touches. It's the most effective control you have against mistakes and stolen credentials. This generator helps at every step: start from what you are building, narrow it, check it, and export it in the format you deploy with.
1. Start from what the workload does
- A common task — such as "Lambda reads one DynamoDB table and writes its logs" or "invoke one Bedrock model" — gives you a policy already scoped to single resources. Set your region and account under Defaults, and replace the UPPER_CASE placeholders.
- From your code: paste boto3, AWS CLI or JavaScript SDK v3 calls. Each call is mapped to the IAM actions it needs. Some calls need a different action than their name suggests: for example,
list_objects_v2needss3:ListBucket,converseneedsbedrock:InvokeModel, andupload_fileneedss3:PutObject. - From CloudTrail: load events the workload produced, and get a draft of exactly the actions it called, with resource ARNs where CloudTrail recorded them. S3 object reads and writes only appear if data events are logged.
2. Narrow the resources
When you tick an action, the generator suggests the ARN format for the resources that action supports, for example arn:aws:s3:::${BucketName}/${ObjectName}. Actions marked · * don't support resource-level permissions, so their Resource must be "*". Put those in a separate statement so they don't widen the others. The access-level badges (Read, List, Write, Permissions, Tagging) come from AWS's service data. Treat Permissions actions with extra care, because they can change who has access.
3. Check it
The Checks tab flags:
"Action": "*"(full administrator) andservice:*wildcards.iam:PassRoleon"*", a well-known privilege-escalation path. Scope it to role ARNs and addiam:PassedToService.- Actions that don't exist (typos), unknown condition operators, and
"Resource": "*"where a specific ARN is possible. ForAllValueswithout aNullcheck. This is true when the key is missing, which can make an Allow too broad.- Size limits, counted without whitespace:
- 6,144 characters for a managed policy;
- inline policies: 2,048 per user, 5,120 per group and 10,240 per role (combined);
- 10,240 for an SCP and 5,120 for an RCP.
Then run AWS's own check: the Export tab gives you the aws accessanalyzer validate-policy command.
4. Guardrails for the whole organisation
Service control policies (SCPs) limit what principals in member accounts can do, and resource control policies (RCPs) limit what can be done to resources. Neither grants anything on its own. The guardrail library includes:
- SCPs: region restriction (with exemptions for global services, based on the AWS example), stop accounts leaving the organisation, block the root user, protect CloudTrail, and require IMDSv2.
- RCPs: an organisation data perimeter, and HTTPS-only access to S3.
- Permissions boundary: a starting point for developer roles.
5. Export and test
Export to CloudFormation, Terraform (policy variables are escaped as $${aws:username}), CDK (TypeScript or Python) or AWS CLI commands. Use Compare to see exactly which permissions a change adds or removes. Use Test to open the policy in the IAM Policy Simulator, or a bucket policy in the S3 Bucket Policy Validator.