Identity and access management · Intermediate
Least-privilege policies you can test
Why it matters
Identity and access management appeared in a fifth of the DevOps job descriptions we read. Broad permissions such as s3:* on * are convenient on day one and dangerous forever after: a leaked key or a bug can then touch everything. Least privilege means granting only the actions, on only the resources, that a job needs.
The concept
An AWS IAM policy is a list of statements. Each statement has:
Effect:
AlloworDeny.Action: API operations such as
s3:GetObject.Resource: ARNs the actions apply to, such as
arn:aws:s3:::acme-reports/monthly/*.Optionally Condition: extra requirements, such as a source IP range, MFA or a tag.
Evaluation rules to remember: everything is denied by default; an Allow grants access; an explicit Deny always wins over any Allow.
Note that some actions apply to the bucket (s3:ListBucket on arn:aws:s3:::bucket) and some to objects (s3:GetObject on arn:aws:s3:::bucket/key). Getting this wrong is the most common reason a "correct" policy does not work.
Worked example: a report reader
import json
import boto3
from botocore.exceptions import ClientError
from moto import mock_aws
from moto.core import set_initial_no_auth_action_count
REGION = "ap-south-1"
READ_REPORTS_POLICY = {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListReportsBucket",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::acme-reports",
},
{
"Sid": "ReadMonthlyReports",
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::acme-reports/monthly/*",
},
],
}
@mock_aws
@set_initial_no_auth_action_count(6) # the 6 admin setup calls run unchecked; every later call is enforced
def main():
iam = boto3.client("iam", region_name=REGION)
s3 = boto3.client("s3", region_name=REGION)
for bucket in ("acme-reports", "acme-payroll"):
s3.create_bucket(Bucket=bucket, CreateBucketConfiguration={"LocationConstraint": REGION})
s3.put_object(Bucket="acme-reports", Key="monthly/2026-08.csv", Body=b"region,revenue\nwest,1200\n")
iam.create_user(UserName="report-reader")
iam.put_user_policy(UserName="report-reader", PolicyName="read-reports", PolicyDocument=json.dumps(READ_REPORTS_POLICY))
key = iam.create_access_key(UserName="report-reader")["AccessKey"]
reader = boto3.client("s3", region_name=REGION, aws_access_key_id=key["AccessKeyId"], aws_secret_access_key=key["SecretAccessKey"])
attempts = [
("list reports", lambda: reader.list_objects_v2(Bucket="acme-reports")),
("read monthly report", lambda: reader.get_object(Bucket="acme-reports", Key="monthly/2026-08.csv")),
("overwrite monthly report", lambda: reader.put_object(Bucket="acme-reports", Key="monthly/2026-08.csv", Body=b"x")),
("read payroll", lambda: reader.list_objects_v2(Bucket="acme-payroll")),
]
for label, call in attempts:
try:
call()
print(f"{label:26} allowed")
except ClientError as error:
print(f"{label:26} denied ({error.response['Error']['Code']})")
main()$ pip install "moto[all]" boto3
$ python3 least_privilege.py
list reports allowed
read monthly report allowed
overwrite monthly report denied (AccessDenied)
read payroll denied (AccessDenied)The reader can list and read monthly reports and nothing else: it cannot overwrite a report, and it cannot see the payroll bucket at all. As a check that the policy is really being evaluated, changing s3:GetObject to s3:PutObject in the policy flips the result: reading is then denied and overwriting is allowed.
Common mistakes
Wildcards by default.
"Action": "*"or"Resource": "*"to get something working, never tightened afterwards.Long-lived access keys for people. Use single sign-on and roles instead; the access key here is only for the demo.
Policies attached to individual users instead of groups or roles, which makes reviews impossible.
Testing by trying things in production. Test policies in code, as here, or with the provider's policy simulator.
Interview angle
"What is least privilege and how do you apply it?", "why can a user with s3:GetObject not list the bucket?" (listing is a bucket-level action), and "how do you find out which permissions an application really uses?" (access logs such as CloudTrail, or tools that generate policies from observed activity, then tighten).
Practice task
Add a condition so the reader can only read objects when the request comes from your office IP range, and test both cases.
Write a policy for a job that writes to
acme-reports/incoming/but can never read frommonthly/, and prove it.