Guide

Least privilege means giving a workload only the permissions it needs to do its job. It is a loop, not a setting you pick once. Start from what the workload really calls, then tighten until only those calls remain.

Concept

A policy lists allowed actions, such as s3:GetObject, and the resources they apply to. A role is the identity that a workload uses to hold those policies. Narrowing a role means removing actions or shrinking the resource list.

The big idea

Most teams start with a broad managed policy because it works on day one. Managed policies are maintained by the cloud provider, but they almost always grant more than one service needs.

Trade-off

Broad managed policy vs custom least privilege. Managed policies ship faster and AWS maintains them. Custom policies take review time but contain the blast radius. Use managed policies for sandboxes and humans browsing. Use custom policies for production workloads and CI.

The practical loop

  1. Start narrow. Use managed policies only to bootstrap. Replace them with customer-managed policies scoped to resource ARNs.
  2. Watch real access. IAM Access Analyzer and CloudTrail show what a role actually used.
  3. Ban wildcards on sensitive actions. Especially iam:*, kms:*, s3:* on *, and *:* near production.
  4. Add conditions. Restrict by VPC endpoint, org ID, MFA, or tags.
  5. Separate duties. The role that deploys Terraform should not read customer data.

Walking through a payment API

A payment API needs to read one secret and write to one queue. The first draft attaches two broad managed policies: SecretsManagerReadWrite and AmazonSQSFullAccess.

After a week of CloudTrail history, Access Analyzer suggests a much smaller set:

JSON
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "secretsmanager:GetSecretValue",
      "Resource": "arn:aws:secretsmanager:us-east-1:111122223333:secret:prod/payments/*"
    },
    {
      "Effect": "Allow",
      "Action": "sqs:SendMessage",
      "Resource": "arn:aws:sqs:us-east-1:111122223333:payments-events"
    }
  ]
}

This is a policy document, not a program, so there is no output to run. Check it with the policy simulator before you ship it.

VersionWhat a stolen task role can do
Broad managed policiesUse the account's queues and secrets
Scoped policySend messages to one queue, and read one secret family

The second version is almost as useful to the service and far less useful to an attacker.

Conditions and tags at scale

Resource ARNs stop scaling when you have hundreds of buckets. Attribute-based access control (ABAC) grants access when tags match. A role tagged team=payments can touch resources tagged team=payments.

Useful condition keys include the VPC endpoint the call came through, the org ID, whether MFA was used, and resource tags. ABAC fails loudly when tags are missing, so pair it with an org policy that denies creating resources without the required tags.

Common mistakes

  • Pasting * to clear the ticket. Then the wildcard stays for years.
  • Scoping the resource but not the action. A narrow ARN with s3:* is still wide open on that bucket.
  • Never pruning. Add the single action that failed, then schedule a cleanup after a week of usage data.

Check yourself

  1. Why is a week of CloudTrail history better than guessing? It shows what the role actually called, so you can remove everything else.
  2. A deploy fails with AccessDenied on one action. What is the minimal fix? Add that one action on the one resource ARN that failed, then schedule a prune.

Key takeaways

  • Least privilege is an iterative loop: start narrow, watch usage, tighten.
  • Wildcards on sensitive actions are the fastest way to a breach.
  • Use conditions and tags to scale without listing every ARN.
  • Keep each duty in its own role, so one leaked role does less damage.

Further learning

Next, see how workloads get credentials without any stored key in workload identity.