Principle of Least Privilege in AWS

The principle of least privilege means every IAM identity in your AWS account should have only the permissions it needs to do its job. Nothing more. This is the single most effective practice for limiting the damage when credentials are compromised, misconfigured, or misused.

Most AWS accounts fail to follow least privilege consistently. Developers attach broad managed policies during development, those policies ship to production, and nobody revisits them. Over time, roles accumulate permissions far beyond what they actually use. This page shows you how to avoid that.

You will learn how AWS permissions work at a high level, how to build least-privilege policies step by step, and how to use AWS tools like IAM Access Analyzer to tighten permissions over time. The examples cover DynamoDB, S3, EC2 tag restrictions, and CI/CD pipeline scoping.

Simple explanation

Least privilege is a straightforward idea: give people and services access to only what they need.

Why least privilege matters in AWS

Permissions that are not needed are not harmless. They are a liability. When a credential is compromised through a leaked access key, a misconfigured service, or a supply chain attack, the attacker inherits every permission attached to that credential.

If a Lambda function that processes order confirmations has AdministratorAccess, a single exploit on that function gives the attacker full control of your AWS account. If the same function has only dynamodb:PutItem on the orders table, the worst outcome is a bad database row.

Real-world impact

Least privilege does not eliminate breaches, but it controls how far a breach can spread. Security practitioners call this reducing the blast radius. A role with AdministratorAccess has a massive blast radius. A role scoped to one S3 bucket has a tiny one.

Beyond security, least privilege also reduces operational risk. A developer who accidentally runs a destructive command against production cannot cause damage if their role does not have delete permissions. Tighter permissions create guardrails that protect teams from their own mistakes.

How AWS permissions work

Before applying least privilege, you need a basic understanding of how AWS decides whether to allow or deny a request. For the full details, see IAM policy structure in AWS.

Identity-based policies

These are JSON documents attached to IAM users, groups, or roles. They define what actions that identity can perform on which resources. Most of the policies you write for least privilege are identity-based.

Resource-based policies

Some AWS resources (S3 buckets, KMS keys, SQS queues) have their own policies that define who can access them. These work alongside identity-based policies. For cross-account access, resource-based policies are often required.

Actions, resources, and conditions

Every IAM policy statement has three core parts:

  • Action: the specific AWS API calls being allowed or denied (e.g., s3:GetObject, dynamodb:PutItem)
  • Resource: the specific AWS resources the actions apply to, defined by ARN (e.g., a single S3 bucket or DynamoDB table)
  • Condition: optional constraints like IP range, MFA requirement, time of day, or resource tags. See IAM policy conditions explained for details.

Explicit deny vs allow

AWS defaults to deny. Everything is denied unless explicitly allowed. If any policy explicitly denies an action, that deny wins, even if another policy allows it. This means you do not need to write deny rules for most scenarios. You simply omit the allow.

Why wildcards are dangerous

Warning

Using "Action": "*" or "Resource": "*" in a policy means “everything.” A policy that allows all actions on all resources is functionally AdministratorAccess. Wildcards are the most common source of over-permissioning. Always specify the exact actions and resource ARNs when possible.

Where SCPs and permissions boundaries fit

Service control policies (SCPs) set maximum permission limits across an entire AWS Organization or account. Permissions boundaries set limits on individual roles. Neither grants access on its own. They only restrict what identity-based policies can allow. These are useful for enforcing least privilege at the organizational level.

How to apply least privilege step by step

Applying least privilege is an iterative process, not a one-time configuration. Here is a practical workflow:

1. Identify the exact API actions needed

Before writing any policy, ask: what AWS API calls will this service actually make? A web application that reads product data from DynamoDB needs dynamodb:GetItem and dynamodb:Query on that table. It does not need dynamodb:DeleteTable.

Check the AWS documentation for the service to find the specific action names. Write them down before you start writing the policy.

2. Scope access to specific resources

Use specific resource ARNs in your policies. A policy that allows s3:GetObject on arn:aws:s3:::my-app-uploads/* is far safer than one that allows it on * (all buckets).

3. Use conditions where possible

Conditions add another layer of restriction. You can limit access based on resource tags, source IP, MFA status, or AWS region. Tag-based conditions are especially useful when you cannot predict resource ARNs in advance.

4. Prefer customer managed policies for production workloads

AWS managed policies are convenient for development but often grant more permissions than a specific workload needs. For production, write customer managed policies scoped to your workload’s actual requirements.

5. Start broad only temporarily, then reduce

If you are uncertain which exact permissions a workload needs, it is acceptable to start with a broader policy in development. But schedule a follow-up to tighten it. Never ship a broad policy to production without review.

Tip

A useful pattern: attach a broader policy in dev, let the workload run for a few weeks, then use IAM Access Analyzer to generate a policy based on what the workload actually used. Replace the broad policy with the generated one before promoting to production.

6. Use IAM Access Analyzer policy generation

IAM Access Analyzer analyzes CloudTrail logs and generates a policy recommendation based on actual usage. If a role has 20 permissions but only uses 4 of them over 90 days, Access Analyzer flags the unused 16.

# Generate a policy based on CloudTrail activity for a role
aws accessanalyzer start-policy-generation \
  --policy-generation-details '{"principalArn": "arn:aws:iam::123456789012:role/MyAppRole"}' \
  --cloud-trail-details '{
    "accessRole": "arn:aws:iam::123456789012:role/AccessAnalyzerRole",
    "trailArn": "arn:aws:cloudtrail:us-east-1:123456789012:trail/MyTrail",
    "startTime": "2026-01-01T00:00:00Z"
  }'

The generated policy includes only the actions actually observed in the logs. Use this as a starting point for writing your production policy.

7. Review last accessed information

In the IAM console, every role shows “last accessed” data for each service. If a role has not used a permission in months, it is a strong candidate for removal.

8. Validate policies before rollout

Use the IAM Policy Simulator to test policies against specific API calls before attaching them to production roles. Use Access Analyzer policy validation to check for syntax issues, overly broad access, and security warnings.

9. Separate dev and prod access

A developer role in a dev account should not have access to production resources. Use separate roles per environment, with separate policies scoped to that environment’s resources. Keeping environments in separate AWS accounts (using AWS Organizations) ensures a compromised dev credential cannot touch production data.

10. Prefer roles and temporary credentials over long-lived keys

IAM roles issue temporary credentials through STS that expire automatically. Long-lived access keys are a persistent risk. If they leak, they work until someone manually rotates them. For why this matters, see why long-lived access keys are dangerous.

Note

Least privilege is not a destination. It is a practice. Start with tighter permissions than you think you need, then expand based on actual errors. It is much easier to add permissions than to track down and remove them later.

Practical examples

DynamoDB table access

This policy allows a Lambda function to read from and write to a single DynamoDB table.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DynamoDBProductsTableReadWrite",
      "Effect": "Allow",
      "Action": [
        "dynamodb:GetItem",
        "dynamodb:Query",
        "dynamodb:BatchGetItem",
        "dynamodb:PutItem",
        "dynamodb:UpdateItem"
      ],
      "Resource": "arn:aws:dynamodb:us-east-1:123456789012:table/products"
    }
  ]
}

What it allows: Reading and writing individual items in the products table.

What it does NOT allow: Deleting items, deleting the table, scanning the entire table, or accessing any other DynamoDB table. It also does not allow access to the table’s indexes. If the function needs to query a GSI, you would add the index ARN separately.

Why this is least privilege: The function can only perform the specific read/write operations it needs, on the one table it uses. Compare this to AmazonDynamoDBFullAccess, which allows all DynamoDB actions on all tables in the account.

S3 bucket access

This policy allows an application to read and write objects in a specific S3 bucket.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "S3AppUploadsReadWrite",
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:PutObject"],
      "Resource": "arn:aws:s3:::my-app-uploads/*"
    },
    {
      "Sid": "S3AppUploadsList",
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::my-app-uploads"
    }
  ]
}

What it allows: Reading and writing objects in the my-app-uploads bucket, and listing the bucket contents.

What it does NOT allow: Deleting objects, deleting the bucket, modifying bucket policies, enabling/disabling versioning, or accessing any other bucket. Note that s3:ListBucket targets the bucket ARN itself, while object actions target the /* path.

Why this is least privilege: The application can upload and retrieve files but cannot delete data or modify bucket configuration. An attacker who compromises this role cannot exfiltrate data from other buckets or destroy existing uploads.

Tag-based EC2 restrictions

This policy allows starting and stopping EC2 instances, but only those tagged with a specific environment.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "EC2DevInstancesOnly",
      "Effect": "Allow",
      "Action": ["ec2:StartInstances", "ec2:StopInstances"],
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "aws:ResourceTag/Environment": "dev"
        }
      }
    }
  ]
}

What it allows: Starting and stopping EC2 instances that have the tag Environment=dev.

What it does NOT allow: Starting or stopping production instances, terminating any instances, or performing any other EC2 actions. The Resource: "*" looks broad, but the condition restricts it to only dev-tagged instances.

Why this is least privilege: Tag-based conditions let you scope access dynamically without hardcoding instance IDs. Developers can manage their own dev instances without any risk to production.

CI/CD deployment role

This policy scopes a CI/CD pipeline role to deploy only specific Lambda functions and update their code.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "LambdaDeploy",
      "Effect": "Allow",
      "Action": [
        "lambda:UpdateFunctionCode",
        "lambda:UpdateFunctionConfiguration",
        "lambda:PublishVersion"
      ],
      "Resource": "arn:aws:lambda:us-east-1:123456789012:function:order-*"
    },
    {
      "Sid": "S3ArtifactRead",
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::deploy-artifacts/order-service/*"
    }
  ]
}

What it allows: Updating code and configuration for Lambda functions whose names start with order-, and reading deployment artifacts from a specific S3 path.

What it does NOT allow: Creating or deleting Lambda functions, modifying IAM roles, accessing other S3 buckets, or deploying to any function outside the order- namespace. The pipeline cannot escalate its own privileges or deploy to unrelated services.

Why this is least privilege: A compromised CI/CD pipeline is a serious risk. Scoping its role to specific deployment actions on specific resources limits what an attacker can do if they gain control of the pipeline.

Danger

The AdministratorAccess policy and the wildcard "Action": "*" should exist only on break-glass emergency roles with strict audit logging and MFA requirements. Attaching it to application roles or CI/CD pipelines is a high-severity security finding.

When to use least privilege

Least privilege applies everywhere permissions exist, but some use cases benefit the most:

  • Application roles: every application running on EC2, ECS, or Lambda should have a role scoped to only the AWS services and resources it actually uses.
  • Lambda and compute workloads: Lambda functions often need access to one or two services. Giving them broad access is unnecessary and dangerous given how frequently functions are deployed.
  • Developer access: developers need enough access to build and debug, but rarely need production write access. Use separate roles for dev and prod, and prefer read-only prod access for most developers.
  • CI/CD pipelines: pipelines should deploy specific resources, not manage your entire AWS account. A deployment role should only have permissions for the services the pipeline deploys.
  • Cross-account access: when granting access across AWS accounts (via role assumption), scope the assumed role tightly. The trust relationship defines who can assume the role. The role’s policy defines what they can do.
  • Production access: standing production access should be minimal. Most production work should happen through deployment pipelines, not manual console access.
  • Break-glass emergency roles: even emergency roles should be scoped where possible. If they must have broad access, require MFA, log all activity to CloudTrail, and alert on usage.

Least privilege vs broad access

The difference between least privilege and over-permissioned access is significant in both security posture and operational risk:

FactorLeast privilegeBroad access (e.g., AdministratorAccess)
Blast radius if compromisedLimited to specific resources and actionsFull account access: data deletion, resource creation, privilege escalation
Accidental damage riskLow. Destructive actions are not permitted.High. Any mistake can affect any resource.
Audit clarityClear. Each role’s purpose is visible in its policy.Unclear. You cannot tell what a role is supposed to do vs what it can do.
Compliance readinessMeets most compliance frameworks (SOC 2, ISO 27001, PCI DSS)Fails most compliance audits
Initial setup effortHigher. Requires identifying specific permissions.Lower. Attach one broad policy and move on.
Long-term maintenanceLower. Permissions are intentional and documented.Higher. Unknown permissions create debugging and security overhead.

AWS managed policies vs customer managed policies

AWS managed policies are pre-built by AWS for common use cases. Customer managed policies are written by you. For least privilege in production, the distinction matters:

AspectAWS managed policiesCustomer managed policies
ScopeBroad. Designed for general use cases.Narrow. Scoped to your specific workload.
Resource specificityUsually * (all resources)Specific ARNs for your resources
MaintenanceAWS updates them, which may add permissions you do not wantYou control every change through version control
Best forDevelopment, prototyping, learningProduction workloads, compliance environments
Least privilege fitPoor. Too broad for most production roles.Strong. Built specifically for the permissions needed.
Tip

AWS managed policies are a fine starting point. Use them to learn which permissions a workload needs, then write a customer managed policy that includes only those permissions on specific resources. Manage your policies in version control using tools like Terraform so changes go through code review.

The over-permissioning trap

Over-permissioning happens in predictable stages. A developer builds a new Lambda function and attaches AmazonS3FullAccess to unblock themselves quickly. The function ships. The permission is never revisited. Six months later, nobody remembers why it has full S3 access, and removing it feels risky because it might break something.

This pattern compounds over time. An account that started with tight permissions gradually accumulates broad, unexplained policies. Each time someone hits an access denied error, the fastest fix is to add a broader policy, which makes the problem worse.

The countermeasure is building least privilege into your deployment pipeline. Use IAM Access Analyzer in CI/CD to flag new policies that are broader than necessary. Require policy reviews before deployment. Use Terraform for IAM so permissions are tracked in version control and changes go through pull request review.

Common mistakes

  1. Using wildcard actions or wildcard resources. "Action": "s3:*" or "Resource": "*" are short-term fixes that become long-term liabilities. Always scope to the specific actions and resource ARNs needed.
  2. Leaving broad access in place after testing. Developers often attach a broad policy to unblock themselves during development. The policy ships to production, and nobody revisits it. Schedule a permission review before any production deployment.
  3. Giving CI/CD pipelines too much power. A deployment pipeline needs to deploy specific resources, not manage your entire AWS account. A compromised pipeline with AdministratorAccess is a full account compromise.
  4. Not separating dev and prod. Using the same role or the same permissions across environments means a mistake in dev can affect production. Use separate accounts through AWS Organizations and separate roles per environment.
  5. Not distinguishing read from write. Many applications need to read from a resource but should never write to it. Always split read and write permissions into separate statements so you can grant them independently.
  6. Relying only on AWS managed policies in production. Managed policies are a reasonable starting point, but they are too broad for production workloads. Transition to customer managed policies that name specific actions and resources.
  7. Ignoring review and validation after launch. Your application changes over time. Run IAM Access Analyzer quarterly to identify unused permissions. Review last accessed data. Remove permissions that are no longer needed.

Frequently asked questions

What does least privilege mean in AWS?

Least privilege means granting an IAM identity only the specific permissions it needs to perform its job. Nothing more. A Lambda function that reads from one DynamoDB table should have read access to that table only, not to all DynamoDB tables and not write access.

How is least privilege different from just using AWS managed policies?

AWS managed policies are pre-built by AWS and designed for broad use cases. They often include more permissions than any single workload needs. Least privilege requires writing customer managed policies scoped to the exact actions and resources your workload uses.

What AWS tools help me build least-privilege policies?

IAM Access Analyzer generates least-privilege policies from CloudTrail activity logs. IAM last accessed information shows which permissions a role actually uses. The IAM Policy Simulator lets you test policies before applying them. Together these tools help you write and validate tightly scoped policies.

Should developers have production access?

Most developers should not have standing production access. Use read-only roles for debugging, break-glass roles with MFA and audit logging for emergencies, and separate accounts for dev and prod through AWS Organizations.

What is the difference between roles, policies, and permissions in this context?

A role is an IAM identity that a service, user, or application can assume. A policy is a JSON document that defines what actions are allowed or denied on which resources. Permissions are the individual allow or deny rules inside a policy. You attach policies to roles to grant permissions.

Last verified: 30 March 2026 Cloud services change frequently. Verify details against official documentation before making infrastructure decisions.