Test a Least-Privilege AWS Policy

Build and test an AWS IAM policy that grants only the intended S3 access.

Introduction

30 Second Summary

Giving someone access to one file can accidentally open the door to much more. Safe permissions cover the job without exposing anything else.

In this project, you will build an access policy based on least privilege for AWS Identity and Access Management (IAM). You will test it in IAM Policy Simulator Custom mode to prove that a fictional Amazon S3 reader can list one bucket and read its objects while uploads and deletions stay blocked.

What You'll Build

You run one final simulation and see a clear result matrix: bucket listing is allowed, object reading is allowed, uploads are implicitly denied, and deletions are explicitly denied.

By the end of this project, you'll have:

  • A working read policy that grants bucket listing plus object retrieval for the sample bucket. You can paste it into Custom mode for immediate testing.
  • Clear authorization evidence for four decisions. You can show allowed listing, allowed reading, an implicit deny for uploads, plus an explicit deny for deletions.
  • A reusable Amazon Resource Name (ARN) scoping method for matching each AWS action to its correct resource type. You can use the same method when reviewing future policies.
  • An optional Secret Mission: Permit uploads only inside prefix/. Prove that uploads elsewhere remain denied. Confirm that deletions stay explicitly denied.

Are there any prerequisites?

You need an AWS account, a web browser, and an existing non-root identity that can use IAM Policy Simulator Custom mode. Your identity needs iam:GetContextKeysForCustomPolicy plus iam:SimulateCustomPolicy. No command-line tools or local installations are required.

Before We Start

Before any hands-on work, lock in the access goal you are about to test. Your fictional reader needs permission to list amzn-s3-demo-bucket and read its objects. The s3:DeleteObject action must remain blocked.

Open the IAM Policy Simulator

Your fictional reader policy needs a safe place to inspect AWS authorization decisions before deployment. Custom mode in the IAM Policy Simulator keeps the draft separate from every AWS identity.

The simulator evaluates the policy without sending a real AWS service request. You will use a non-root identity throughout this step.

In this step, get ready to:
  • Sign in to the AWS Management Console with your existing non-root identity.
  • Enter Custom mode from the IAM Policy Simulator.
  • Check whether Custom mode is available to your identity.
Protect the account boundary

AWS recommends keeping the account root user out of everyday work. Your existing non-root identity keeps this permission check within normal account controls.

Signing in can feel sensitive. Your credentials stay inside the AWS sign-in flow.

  • Navigate to the AWS Management Console sign-in page in your web browser.
  • Sign in with your existing non-root identity.
  • Confirm the signed-in identity matches the non-root identity intended for this simulator check.

Strong start. Root credentials stay out of the project.

Enter Custom mode

AWS Identity and Access Management (IAM) controls who can sign in. It also controls which actions an identity can perform.

  • Use the search box in the top navigation bar to find AWS Identity and Access Management (IAM).
  • Select AWS Identity and Access Management (IAM) from the service results.
  • Choose Policy simulator in the IAM navigation pane.
  • Select Custom mode.

Why use Custom mode?

Principal mode starts from an existing identity. Custom mode evaluates the policy text that you supply.

The draft is not attached to an identity. The simulator makes no real AWS service request.

Custom mode does not save the policy to your account.

Confirm custom editor access

The custom policy editor doubles as a permission check. Custom mode requires two IAM actions from your signed-in identity.

Before you check, predict whether your non-root identity can load the editor.

  • Check whether the custom policy editor loads without an authorization error.

✔️ The custom policy editor loads

Your safe evaluation environment is ready. The blank editor confirms that your non-root identity can access Custom mode.

  • Leave the custom policy editor empty for now.

ⓧ I see an authorization error

The authorization error shows that your identity is missing access to Custom mode. The narrow access request uses two documented permissions.

Requesting IAM permissions can feel risky. These actions cover the required custom simulation access.

  • Record iam:GetContextKeysForCustomPolicy for your AWS account administrator.
  • Record iam:SimulateCustomPolicy for your AWS account administrator.
  • Send both recorded permissions to your AWS account administrator.
  • Continue with the same non-root identity after access is granted.

Still blocked after access is updated?

  • Confirm the administrator granted both actions to the same non-root identity.
  • Refresh the Custom mode page after the permission change.

Help me troubleshoot my Custom mode access.

You now know whether Custom mode is ready or which exact permissions unlock it. Next, you will run the first custom policy test against a fictional object request.

Test a Mis-Scoped Read Policy

You already opened the IAM Policy Simulator in Custom mode from AWS Identity and Access Management (IAM). That gives you a safe place to evaluate a draft without attaching it to an identity.

A policy can name the intended read action while its resource describes a different level of Amazon S3. You will test that relationship before changing the draft.

In this step, get ready to:
  • Add a deliberately mis-scoped read policy to the custom policy editor.
  • Configure an object-read request against the sample report.
  • Run the planned simulation to reveal how IAM evaluates the resource scope.
Paste the mis-scoped policy

An IAM identity-based policy uses JSON to describe which actions apply to which resources. This draft deliberately pairs an object-read action with a bucket-level Amazon Resource Name (ARN).

  • Paste this policy into the custom policy editor:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowReadProjectObjects",
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::amzn-s3-demo-bucket"
    }
  ]
}

What does this policy do?

  • The Version value selects the current IAM policy language version, 2012-10-17.
  • The Sid value gives the statement the name AllowReadProjectObjects.
  • The Effect value allows requests that match the statement.
  • The Action value names the object-read operation s3:GetObject.
  • The Resource value points only to arn:aws:s3:::amzn-s3-demo-bucket. This deliberate scope is the part under test.
  • Review the editor for one statement named AllowReadProjectObjects.
  • Confirm the policy resource ends with the bucket name amzn-s3-demo-bucket.

Policy editor rejecting the draft?

  • Remove any placeholder text that remained in the custom policy editor before you pasted the policy.
  • Check that every property name has matching double quotation marks.
  • Compare each brace with the complete draft below.

Ask for help with the policy syntax: Why is my IAM custom policy editor rejecting this JSON policy?

Your editor should now match the complete planned-failure policy for this step.

✔️ Awesome, I've got everything!

Good work. The custom policy editor now holds the complete draft for the planned simulation.

ⓧ I'd like to double check the full code

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowReadProjectObjects",
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::amzn-s3-demo-bucket"
    }
  ]
}

How to use this reference

This is the complete failing draft for this step. Its resource stops at the sample bucket name.

Set the simulated request

A simulation evaluates the policy against one specific action on one specific resource. Keeping the draft unchanged isolates how IAM matches the resource scope.

  • Add s3:GetObject as the action to simulate.
  • Enter arn:aws:s3:::amzn-s3-demo-bucket/report.csv as the simulated resource.
  • Confirm the policy resource still shows arn:aws:s3:::amzn-s3-demo-bucket.

The simulated request now targets the report.csv object. The policy still describes only the bucket.

Run the planned failure

Before you run the simulation, do you think the allow statement applies to the requested object?

  • Run the simulation.

You will see the s3:GetObject result marked as an implicit deny. This planned failure confirms that the allow statement did not match the object request.

  • Open the result explanation for s3:GetObject.
  • Inspect the result for a matching allow statement.

Why was the request implicitly denied?

An implicit deny occurs when no applicable allow statement matches the request. The policy resource identifies the bucket while the simulated request identifies an object inside that bucket.

The simulator evaluated only the supplied policy model. It did not send a request to Amazon S3 or change your AWS account.

Seeing a different result?

  • Confirm the simulated action is exactly s3:GetObject.
  • Confirm the simulated resource is exactly arn:aws:s3:::amzn-s3-demo-bucket/report.csv.
  • Confirm the policy resource remains arn:aws:s3:::amzn-s3-demo-bucket.

Ask for help comparing the policy with the simulated request: Why did my IAM Policy Simulator result differ from the planned implicit deny?

You made the draft fail in a controlled simulation. That result exposes the exact resource-scope gap you will correct next.

Match the Object ARN

Your planned implicit deny in IAM Policy Simulator exposed the resource mismatch. You proved that the bucket ARN does not cover the simulated object request.

Amazon S3 object actions need an object Amazon Resource Name that includes the path after the bucket name. In this step, you will add that path so the intended read permission applies.

In this step, get ready to:
  • Correct the policy resource with an object path wildcard.
  • Rerun the existing object-read simulation.
  • Inspect the matched allow statement behind the result.
Correct the resource boundary

A bucket ARN ends at the bucket name. An object ARN continues with a forward slash followed by an object key or wildcard.

  • In the custom policy editor from earlier, find the Resource line containing arn:aws:s3:::amzn-s3-demo-bucket.
  • Replace only that value with the object scope shown below:
"Resource": "arn:aws:s3:::amzn-s3-demo-bucket/*"

What Changes in the ARN?

  • The forward slash moves the resource scope below the bucket name.
  • The * wildcard covers every object key beneath that boundary.
  • The s3:GetObject action stays unchanged so the resource scope is the only policy change.
  • Confirm the edited Resource value ends with /*.
  • Keep Action set to s3:GetObject.

Does the Resource Still Look Wrong?

  • Check that the forward slash appears between the bucket name and the wildcard.
  • Keep the quotation marks around the full ARN.
  • Keep the comma after the Action line.

Help me check my corrected object ARN.

✔️ Awesome, I've got everything!

Great. Your policy now targets objects beneath the sample bucket.

ⓧ I'd like to double check the full code

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowReadProjectObjects",
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::amzn-s3-demo-bucket/*"
    }
  ]
}

How the Corrected Policy Fits Together

  • The 2012-10-17 value keeps the current policy language version.
  • The AllowReadProjectObjects statement grants the object-read action.
  • The corrected resource includes every object beneath the sample bucket.
Rerun the object simulation

Keeping the test inputs fixed isolates the policy resource as the cause of the result change.

  • Keep the simulated action set to s3:GetObject.
  • Keep the simulated resource set to arn:aws:s3:::amzn-s3-demo-bucket/report.csv.

Before you rerun the simulation, do you think the corrected wildcard now includes report.csv?

  • Rerun the simulation using the same control beneath the simulation inputs.

You should see the result change to allowed. The corrected object ARN now matches the simulated object request.

  • Open the result row for s3:GetObject.
  • Inspect the policy explanation for the matched statement.

You should see AllowReadProjectObjects identified as the applicable allow statement. That connection proves exactly why the request succeeds.

Still Seeing an Implicit Deny?

  • Confirm the policy resource is exactly arn:aws:s3:::amzn-s3-demo-bucket/*.
  • Confirm the simulated resource is exactly arn:aws:s3:::amzn-s3-demo-bucket/report.csv.
  • Rerun the simulation after editing the policy.

Help me diagnose the remaining implicit deny.

You have proved how an action and its resource type must line up. Next, you will add bucket listing while keeping object uploads outside the permission set.

Build the Read-Only Policy

Your corrected Amazon S3 object ARN now lets the fictional reader retrieve report.csv. The reader still cannot browse the bucket to discover which objects exist.

Least privilege keeps the new permission limited to bucket listing. The final IAM Policy Simulator run tests one bucket request plus two object requests.

In this step, get ready to:
  • Add a bucket-level permission for s3:ListBucket.
  • Configure the final three-action simulation.
  • Compare the reader's access across all three requests.
Add the bucket-list statement

Object retrieval uses an object ARN. Bucket listing uses the bucket ARN itself.

The JSON policy therefore needs one statement for each resource type.

  • In the custom policy editor from earlier, locate the existing object that starts with "Sid": "AllowReadProjectObjects".
  • Insert the bucket-list statement before that object by pasting this block:
    {
      "Sid": "AllowListProjectBucket",
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::amzn-s3-demo-bucket"
    },

What Does This Statement Do?

  • AllowListProjectBucket gives the statement a descriptive identifier.
  • "Effect": "Allow" can grant access when the action matches the resource.
  • s3:ListBucket grants the bucket-list operation used by this test.
  • arn:aws:s3:::amzn-s3-demo-bucket targets the bucket itself.
  • Confirm the Statement array now begins with AllowListProjectBucket.

Editor Showing a Policy Error?

  • Check that the new object ends with a comma after its closing brace.
  • Check that AllowListProjectBucket appears before AllowReadProjectObjects.
  • Compare every brace against the full policy in the double-check tab below.

Help me fix the structure of my read-only policy.

✔️ Awesome, I've got everything!

Your policy now has separate statements for bucket listing and object reading.

ⓧ I'd like to double check the full code

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowListProjectBucket",
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::amzn-s3-demo-bucket"
    },
    {
      "Sid": "AllowReadProjectObjects",
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::amzn-s3-demo-bucket/*"
    }
  ]
}
Configure the three requests

A least-privilege test needs requests that should work plus a request outside the granted permissions. These three cases test the complete read-only boundary.

  • In the simulation controls from earlier, add s3:ListBucket.
  • Scope the list request to arn:aws:s3:::amzn-s3-demo-bucket.

You now have a bucket-level request aimed at the bucket itself.

  • Leave s3:GetObject selected from the earlier simulation.
  • Confirm its object resource remains arn:aws:s3:::amzn-s3-demo-bucket/report.csv.

Your existing read test still targets one object inside the sample bucket.

  • Add s3:PutObject to the simulation.
  • Scope the upload request to arn:aws:s3:::amzn-s3-demo-bucket/report.csv.

The simulation now contains one bucket action plus two object actions.

Run and compare the simulation

The configured requests now test whether the policy grants only the intended read operations.

Before you run the simulation, think about which requests the policy will allow.

  • Run the configured simulation.

The s3:ListBucket row shows allowed. The s3:GetObject row also shows allowed.

The s3:PutObject row shows implicitly denied. This is an implicit deny because no applicable allow statement grants uploads.

That is a strong least-privilege result. Read access works while write access stays blocked.

  • Inspect the s3:ListBucket explanation for the matched AllowListProjectBucket statement.
  • Inspect the s3:GetObject explanation for the matched AllowReadProjectObjects statement.
  • Inspect the s3:PutObject explanation for the absence of an applicable allow statement.

Results Do Not Match

  • Check that the s3:ListBucket request uses arn:aws:s3:::amzn-s3-demo-bucket.
  • Check that the s3:GetObject request uses arn:aws:s3:::amzn-s3-demo-bucket/report.csv.
  • Check that the policy does not contain an allow statement for s3:PutObject.

Help me diagnose unexpected IAM Policy Simulator results.

That is the read-only boundary proven. Next, you will add a deletion guardrail that stays effective if another permission changes later.

Add a Deletion Guardrail

Your Amazon S3 read policy now supports bucket browsing. Ungranted writes remain blocked through an implicit deny.

That boundary can change if another policy later allows deletion. An explicit deny creates a permission guardrail because an applicable deny overrides an allow.

In this step, get ready to:
  • Add an explicit guardrail against object deletion.
  • Prove that the delete request matches the new deny statement.
  • Recheck the four decisions in the final policy matrix.
Add the explicit deletion guardrail

The new statement targets object deletion across the sample bucket. Its object-scoped Amazon Resource Name (ARN) covers every object below the bucket name.

  • In the custom policy editor from earlier, find the closing brace of the AllowReadProjectObjects statement.
  • Add a comma immediately after that closing brace.
  • Paste the new statement on the next line:
    {
      "Sid": "DenyDeleteProjectObjects",
      "Effect": "Deny",
      "Action": "s3:DeleteObject",
      "Resource": "arn:aws:s3:::amzn-s3-demo-bucket/*"
    }

What Does This Statement Do?

  • The DenyDeleteProjectObjects identifier gives the guardrail a name you can recognize in simulation results.
  • The Effect value uses Deny to override any applicable allow.
  • The s3:DeleteObject action targets object deletion.
  • The arn:aws:s3:::amzn-s3-demo-bucket/* resource applies the guardrail to every object in the sample bucket.
  • Review the Statement array in the custom policy editor.

You should see three statement objects. The new DenyDeleteProjectObjects object should appear after AllowReadProjectObjects.

Policy Editor Rejecting the JSON?

  • Check that the AllowReadProjectObjects object ends with a comma.
  • Confirm that the new statement sits before the closing square bracket of the Statement array.
  • Compare every brace with the full policy below.

Help me find the JSON structure error in my three-statement policy.

✔️ Awesome, I've got everything!

Great. Your custom policy now contains the deletion guardrail alongside the two read permissions.

ⓧ I'd like to double check the full code

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowListProjectBucket",
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::amzn-s3-demo-bucket"
    },
    {
      "Sid": "AllowReadProjectObjects",
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::amzn-s3-demo-bucket/*"
    },
    {
      "Sid": "DenyDeleteProjectObjects",
      "Effect": "Deny",
      "Action": "s3:DeleteObject",
      "Resource": "arn:aws:s3:::amzn-s3-demo-bucket/*"
    }
  ]
}

How the Final Policy Fits Together

  • The first statement allows bucket listing against the bucket ARN.
  • The second statement allows object reads against the object ARN wildcard.
  • The third statement blocks object deletion across the same object scope.
Prove the guardrail blocks deletion

The IAM Policy Simulator can now evaluate a destructive request against the new guardrail. The explanation connects the result directly to the statement that caused it.

Before you test it, consider whether the simulator will return an implicit denial or an explicit denial.

  • Use the existing action controls to add s3:DeleteObject to the simulation.
  • Enter arn:aws:s3:::amzn-s3-demo-bucket/report.csv as the simulated resource.
  • Run the simulation.

The result row should show explicitly denied. This proves the request matched an applicable deny.

  • Open the result explanation for the delete request.

You should see DenyDeleteProjectObjects identified as the statement responsible for the decision.

Delete Request Still Implicitly Denied?

  • Confirm that the custom policy editor still contains DenyDeleteProjectObjects.
  • Check that the simulated action is exactly s3:DeleteObject.
  • Verify that the simulated object starts with arn:aws:s3:::amzn-s3-demo-bucket/.

Help me understand why my delete request is not matching the explicit deny.

Recheck the final decision matrix

A single delete check proves the guardrail works. The final matrix confirms that the new statement has preserved every earlier decision.

Before you recheck the policy, predict which two requests will pass and how the other two will be denied.

  • Simulate s3:ListBucket against arn:aws:s3:::amzn-s3-demo-bucket.
  • Simulate s3:GetObject against arn:aws:s3:::amzn-s3-demo-bucket/report.csv.
  • Simulate s3:PutObject against arn:aws:s3:::amzn-s3-demo-bucket/report.csv.
  • Simulate s3:DeleteObject against arn:aws:s3:::amzn-s3-demo-bucket/report.csv.

What Should I See?

  • The s3:ListBucket request on arn:aws:s3:::amzn-s3-demo-bucket is allowed.
  • The s3:GetObject request on arn:aws:s3:::amzn-s3-demo-bucket/report.csv is allowed.
  • The s3:PutObject request on arn:aws:s3:::amzn-s3-demo-bucket/report.csv is implicitly denied.
  • The s3:DeleteObject request on arn:aws:s3:::amzn-s3-demo-bucket/report.csv is explicitly denied by DenyDeleteProjectObjects.

That's the safety boundary proven. The policy now blocks object deletion with an explicit guardrail.

Secret mission

Scope Uploads to One Prefix

Your read-only policy protects the sample bucket from unwanted writes. This challenge adds one controlled upload path while preserving every existing access decision.

Clean Up Your Resources

Clean Up Your Resources

Your IAM Policy Simulator session created no live resources, so there is nothing to pause or delete. This project has no ongoing costs.

Why Is There Nothing to Clean Up?

Custom mode does not save the policy to your account. The simulator sent no real Amazon S3 request.

AWS Identity and Access Management (IAM) is offered at no additional charge. No bucket or attached identity policy needs removal.

Nice Work!

Nice Work!

Excellent work! You built an access policy based on least privilege for AWS Identity and Access Management (IAM).

You've learned how to:

  • Create a JSON identity policy with a dedicated bucket-level resource scope. Give object operations their own resource scope.
  • Use IAM Policy Simulator Custom mode to evaluate a draft. Keep the policy detached from an identity. Avoid live Amazon S3 requests.
  • Diagnose the planned implicit deny as an Amazon Resource Name (ARN) scope mismatch. Prove that listing is allowed. Prove that reading is allowed. Distinguish the upload's implicit deny from the deletion's explicit deny.
  • Secret Mission: Grant s3:PutObject access only under arn:aws:s3:::amzn-s3-demo-bucket/prefix/*. Prove that an upload outside prefix/ remains implicitly denied. Confirm that s3:DeleteObject remains explicitly denied.

Ready to quiz yourself?