A backend service uses an IAM role to read files from an S3 bucket. It must only read objects under s3://prod-reporting/incoming/ but currently receives AccessDenied (403) on GetObject for that prefix.
The role already has this statement: - Action: s3:ListBucket - Resource: arn:aws:s3:::prod-reporting
Which policy statement would most directly follow least privilege to allow only the required reads under the incoming prefix?
Trap 1: Allow only listing and reading with a single statement: Action =…
This is overly broad because s3:* includes write and delete actions not required for reads. Least privilege requires restricting to s3:GetObject only. While the resource scope is close, wildcard actions expand permissions beyond the stated need.
Trap 2: Allow all S3 reads at the account level: Action = ["s3:GetObject"],…
Using arn:aws:s3:::* is not least privilege and allows access to every bucket in the account. Even though the action is GetObject, the scope is far wider than the required prefix. This would violate the stated requirement to restrict to s3://prod-reporting/incoming/.
Trap 3: Allow bucket listing with a condition that forces the prefix:…
ListBucket is a bucket-level permission that governs listing operations (e.g., ListObjectsV2), not object retrieval. Even with a prefix condition, it merely filters which keys appear in the listing; it does not authorize s3:GetObject on the underlying objects. To read an object, the IAM policy must grant s3:GetObject on the object ARN (e.g., arn:aws:s3:::prod-reporting/incoming/*). The s3:prefix condition is not evaluated for GetObject calls, so this statement leaves object reads completely unauthorized and AccessDenied persists.
- A
Allow only listing and reading with a single statement: Action = ["s3:*"], Resource = ["arn:aws:s3:::prod-reporting/incoming/*"].
Why it fails: This is overly broad because s3:* includes write and delete actions not required for reads. Least privilege requires restricting to s3:GetObject only. While the resource scope is close, wildcard actions expand permissions beyond the stated need.
- B
Allow reads with a prefix-scoped statement: Action = ["s3:GetObject"], Resource = ["arn:aws:s3:::prod-reporting/incoming/*"].
This grants only the specific action s3:GetObject and scopes it to the exact prefix that the service needs. It aligns with least privilege by avoiding extra permissions like PutObject or DeleteObject. Since the service already has ListBucket, this completes the required read path for objects in incoming.
- C
Allow all S3 reads at the account level: Action = ["s3:GetObject"], Resource = ["arn:aws:s3:::*"].
Why it fails: Using arn:aws:s3:::* is not least privilege and allows access to every bucket in the account. Even though the action is GetObject, the scope is far wider than the required prefix. This would violate the stated requirement to restrict to s3://prod-reporting/incoming/.
- D
Allow bucket listing with a condition that forces the prefix: Action = ["s3:ListBucket"], Resource = ["arn:aws:s3:::prod-reporting"], Condition = {"StringLike": {"s3:prefix": "incoming/*"}}.
Why it fails: ListBucket is a bucket-level permission that governs listing operations (e.g., ListObjectsV2), not object retrieval. Even with a prefix condition, it merely filters which keys appear in the listing; it does not authorize s3:GetObject on the underlying objects. To read an object, the IAM policy must grant s3:GetObject on the object ARN (e.g., arn:aws:s3:::prod-reporting/incoming/*). The s3:prefix condition is not evaluated for GetObject calls, so this statement leaves object reads completely unauthorized and AccessDenied persists.