Good practice is not just finding the correct option. The wrong answers often show the exact trap the exam wants you to fall into.
A
Distractor review
Allow only listing and reading with a single statement: Action = ["s3:*"], Resource = ["arn:aws:s3:::prod-reporting/incoming/*"].
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
Best answer
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
Distractor review
Allow all S3 reads at the account level: Action = ["s3:GetObject"], Resource = ["arn:aws:s3:::*"].
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
Distractor review
Allow bucket listing with a condition that forces the prefix: Action = ["s3:ListBucket"], Resource = ["arn:aws:s3:::prod-reporting"], Condition = {"StringLike": {"s3:prefix": "incoming/*"}}.
This only affects ListBucket results, not object retrieval. GetObject calls require s3:GetObject permission on the specific object ARNs. Adding a condition to ListBucket does not resolve AccessDenied errors during GetObject for incoming objects.