Definition
- Principle of Least Privilege – Every identity should have exactly the permissions it needs to do its job and nothing more, minimizing damage from a compromise.
- Resource Scoping – Setting the policy's
Resourceelement to a specific ARN rather than*, so the identity can only touch that one resource. - Action Scoping – Listing only the specific API actions the workload needs rather than using
Action: *. - Condition Keys – Additional constraints such as
aws:SecureTransport,s3:prefix, oraws:MultiFactorAuthPresentthat further narrow when a policy applies. - IAM Access Analyzer – An AWS tool that generates least-privilege policy recommendations from observed workload activity.
At National Bank of India, least privilege means Rajesh the teller gets a key that opens only his own cash drawer, not the vault, not the manager's office, and not every other teller's drawer. If someone steals his key, the damage is limited to that one drawer. Giving him a master key just in case would be convenient but reckless. In IAM, the one-drawer key is a policy whose Resource names exactly one bucket.
Understanding the Concepts
What is the principle of least privilege?
Least privilege means every identity – every User, Role, and service – gets exactly the permissions it needs and nothing more. The purpose is to reduce blast radius. If a service's credentials leak, an attacker can only do what that narrow policy allows. The opposite – a policy with "Action": "*" and "Resource": "*" – means a single leak compromises the entire account. In a bank that difference is the difference between a contained incident and a front-page breach.
public class BlastRadius {
// DANGEROUS: full admin access
// If leaked, attacker owns the entire AWS account
// { "Effect": "Allow", "Action": "*", "Resource": "*" }
// SAFE: scoped to one bucket, read-only
// If leaked, attacker can only read statement PDFs
// { "Effect": "Allow", "Action": "s3:GetObject",
// "Resource": "arn:aws:s3:::nbi-customer-statements/*" }
}
How do you scope a policy to one S3 bucket?
Scoping to a single S3 bucket requires two statements, because bucket-level operations and object-level operations use different ARN shapes. Listing the bucket needs the bucket ARN itself, while reading and writing objects needs the ARN with a /* suffix.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListOnlyThisBucket",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::nbi-customer-statements"
},
{
"Sid": "ReadWriteObjectsInThisBucket",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::nbi-customer-statements/*"
}
]
}
How do you scope a policy to one ECR repository?
Scoping to a single ECR repository follows the same idea. You name the repository ARN so the service can pull only its own container image, not every image in the account.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PullSettlementImageOnly",
"Effect": "Allow",
"Action": [
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:BatchCheckLayerAvailability"
],
"Resource": "arn:aws:ecr:ap-south-1:123456789012:repository/settlement-service"
},
{
"Sid": "ECRLogin",
"Effect": "Allow",
"Action": "ecr:GetAuthorizationToken",
"Resource": "*"
}
]
}
Note that ecr:GetAuthorizationToken requires Resource: * because it is an account-level action, not a repository-level one.
How do condition keys tighten access further?
You can add conditions to policies for even more precision. Common conditions restrict access to objects with a specific prefix, require that requests come over TLS, require MFA for destructive actions, or restrict the source IP range.
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "OnlyTLSAndPrefixRestricted",
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::nbi-customer-statements/*",
"Condition": {
"Bool": { "aws:SecureTransport": "true" },
"StringLike": { "s3:prefix": "2024/*" }
}
}]
}
What is the difference between least privilege and zero trust?
| Concept | Least Privilege | Zero Trust |
|---|---|---|
| Scope | Permission scoping | Network + identity architecture |
| Core idea | Grant only needed permissions | Never trust, always verify |
| Implementation | IAM policies, resource ARNs, conditions | mTLS, service mesh, continuous verification |
| Level | Per-identity permission | Entire system design |
Least privilege is a subset of zero trust. A zero-trust architecture applies least privilege to permissions and extends it to network access, device trust, and continuous re-verification. In banking, we use both: least-privilege IAM policies and zero-trust network design with private subnets and VPC endpoints.
public class LeastPrivilegeVsZeroTrust {
// Least privilege: scope IAM policy to one bucket
// Zero trust: also verify mTLS on every service call,
// use private subnets, VPC endpoints, no implicit trust
}
What are common mistakes?
The first mistake is using "Action": "*" and "Resource": "*" because it is easy. This gives the identity full admin access to the entire account. The fix is to name specific actions and resources.
The second mistake is using "Resource": "*" even with specific actions. For example, allowing s3:GetObject on * means the identity can read every bucket in the account, not just the one it needs.
The third mistake is never reviewing permissions after initial setup. Workloads change, and unused permissions accumulate. IAM Access Analyzer finds these.
public class ScopingMistakes {
// WRONG: specific action but wildcard resource
// { "Action": "s3:GetObject", "Resource": "*" }
// Can read EVERY bucket in the account
// CORRECT: specific action AND specific resource
// { "Action": "s3:GetObject",
// "Resource": "arn:aws:s3:::nbi-customer-statements/*" }
// Can read only statements bucket
}
How do you achieve least privilege iteratively?
Achieving least privilege is iterative, not a one-shot guess. You start deliberately restrictive, run the workload, read IAM Access Analyzer findings and CloudTrail denied-action logs, and add only the permissions the workload actually used. AWS Access Analyzer can also generate a policy from observed activity.
# Step 1: Deploy with narrow permissions
# Step 2: Run the workload for a few days
# Step 3: Check Access Analyzer for recommendations
aws accessanalyzer list-findings \
--analyzer-arn arn:aws:access-analyzer:ap-south-1:123456789012:analyzer/my-analyzer
# Step 4: Generate a policy from actual usage
aws accessanalyzer start-policy-generation \
--policy-generation-details '{"principalArn": "arn:aws:iam::123456789012:role/SettlementRole"}'
What about cross-service access scoping?
When a service calls multiple AWS services, create separate policy statements for each service, each scoped to the exact resources needed. Do not combine them into one broad statement.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadStatements",
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": "arn:aws:s3:::nbi-customer-statements/*"
},
{
"Sid": "ReadSecrets",
"Effect": "Allow",
"Action": ["secretsmanager:GetSecretValue"],
"Resource": "arn:aws:secretsmanager:ap-south-1:123456789012:secret:nbi/settlement/db-*"
},
{
"Sid": "PublishEvents",
"Effect": "Allow",
"Action": ["sns:Publish"],
"Resource": "arn:aws:sns:ap-south-1:123456789012:settlement-notifications"
}
]
}
What should a senior engineer know?
A senior engineer knows that least privilege is the single most impactful security control in AWS. They use IAM Access Analyzer to generate policies from observed activity, enforce permission boundaries on all Roles created by development teams, and audit policies regularly using CloudTrail and AWS Config rules.
They also know that resource-based policies (like S3 bucket policies) can further restrict access by requiring that requests come through a specific VPC endpoint, which is a powerful data-exfiltration control for banking.
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "DenyNonVPCEndpointAccess",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::nbi-customer-statements",
"arn:aws:s3:::nbi-customer-statements/*"
],
"Condition": {
"StringNotEquals": {
"aws:sourceVpce": "vpce-0s3gateway"
}
}
}]
}
Visual Overview
┌──────────── Least Privilege Scoping ──────────────┐ │ │ │ WRONG: Wildcard everything │ │ ┌──────────────────────────────────┐ │ │ │ Action: * Resource: * │ │ │ │ Blast radius: ENTIRE ACCOUNT │ │ │ └──────────────────────────────────┘ │ │ │ │ CORRECT: Scoped to exact needs │ │ ┌──────────────────────────────────────────┐ │ │ │ S3 Statement: │ │ │ │ Action: s3:GetObject, s3:PutObject │ │ │ │ Resource: nbi-customer-statements/* │ │ │ │ Blast radius: ONE BUCKET │ │ │ ├──────────────────────────────────────────┤ │ │ │ ECR Statement: │ │ │ │ Action: ecr:BatchGetImage │ │ │ │ Resource: settlement-service repo │ │ │ │ Blast radius: ONE REPOSITORY │ │ │ ├──────────────────────────────────────────┤ │ │ │ Condition Keys: │ │ │ │ aws:SecureTransport = true (TLS only) │ │ │ │ s3:prefix = 2024/* (one year only) │ │ │ │ Blast radius: EVEN NARROWER │ │ │ └──────────────────────────────────────────┘ │ │ │ │ Iterative Tightening: │ │ Deploy narrow → Run workload → Access Analyzer │ │ → Add only observed permissions → Repeat │ └────────────────────────────────────────────────────┘
Key Takeaway
The principle of least privilege means every identity gets exactly the permissions it needs to do its job and not one permission more. The whole point is to shrink the blast radius: if a service's credentials leak or a bug misbehaves, the damage is limited to the narrow set of actions and resources that identity was granted, instead of the entire account. The opposite – a policy with action star and resource star – turns a single leaked token into a full account compromise, which in a bank is catastrophic.
The way you achieve it in IAM is by scoping tightly on three dimensions: name the specific actions, name the specific resource ARNs, and where it helps add condition keys. Scoping to a single S3 bucket actually needs two statements because bucket-level and object-level operations use different ARN shapes – listing the bucket uses the bucket ARN itself, while reading and writing objects uses the bucket ARN with a slash-star suffix. Scoping to a single ECR repository is the same idea: you put the one repository ARN in the resource element so the service can pull only its own image and no other.
At National Bank of India our settlement service only ever touches one bucket and one container repository. So its Role carries a policy that names exactly that bucket for list, get, and put, and exactly that repository for image pulls, and nothing else. The result is that even if the service were fully compromised, an attacker could not read the payroll bucket or any other team's data, and could not delete a statement because the delete action was never granted. That containment is the entire value of least privilege.
One senior point is that least privilege is iterative, not a one-shot guess. I start deliberately restrictive, run the workload, then read IAM Access Analyzer findings and CloudTrail logs of denied actions, and add back only the permissions the workload genuinely used. AWS even has Access Analyzer policy generation that drafts a least-privilege policy from observed activity. That evidence-based tightening is how you end up with a policy that is both secure and actually works in production.
Remember one sentence: Scope every policy to the exact actions and exact resource ARNs the workload needs, so a compromise can only harm that one thing.
---