DEV Community

Cover image for Prove an Explicit Deny with the IAM Policy Simulator
miruky
miruky

Posted on

Prove an Explicit Deny with the IAM Policy Simulator

Introduction

Hi, I'm miruky.

An IAM policy can contain an Allow that appears broad enough for a request and still deny that request. When an applicable Deny statement also matches, the explicit deny wins.

This Console run uses the current IAM Policy Simulator in Custom mode. One policy allows s3:GetObject across a generated bucket ARN, then explicitly denies the private/ prefix. The private object ARN produces Denied with an explicit-deny detail; changing only that prefix to public/ produces the Allowed control result.

The simulator does not perform either S3 request, and this workflow never creates the referenced bucket. The results are policy-evaluation evidence, not proof that a real object exists or that every control in a live authorization path has been modeled.

1. Open a clean Custom-mode session

This run uses the English AWS Console in United States (N. Virginia) (us-east-1) before opening IAM. IAM and its Policy Simulator are global; the Region selection does not change the simulated S3 ARN.

The English AWS Console is visible before opening the global IAM Policy Simulator.

The main Console shows English labels and United States (N. Virginia) without exposing the account menu. The Region is only an entry check because IAM policy simulation is global.

Open the IAM Policy Simulator in a blank custom session. AWS no longer maintains the standalone Policy Simulator, so this walkthrough uses the simulator built into the IAM Console.

The IAM Policy Simulator is in Custom mode with no identity-based policy added.

The simulator shows Custom, Identity-based policies (0), and Add policy. No account principal or attached policy participates in this session.

Using a blank custom session also keeps real principal names out of the evidence. Avoid the Principal mode because it can expose identity names that are irrelevant to this test.

2. Apply an allow plus an explicit deny

Choose Add policy, enter miruky-nwwjdgktlwqvgkhh as the policy name, and choose Create.

The custom-policy dialog uses the generated simulator-only policy name.

The dialog shows only the generated policy label. In this Custom-mode workflow, the policy stays inside the simulator session and is not created as an IAM customer managed policy. The label identifies this browser session without referring to a real principal.

Replace the starter document in the JSON editor with this policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowObjectReads",
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::miruky-kfgwuryutmsnanij/*"
    },
    {
      "Sid": "DenyPrivatePrefix",
      "Effect": "Deny",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::miruky-kfgwuryutmsnanij/private/*"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

The IAM editor shows the generated policy label and separate AllowObjectReads and DenyPrivatePrefix statements for GetObject.

The editor shows the generated policy label, AllowObjectReads, DenyPrivatePrefix, and s3:GetObject in both statements. The code block immediately above preserves both full resource strings, while the Console crop proves that the document loaded into this simulator policy with Save available.

Choose Save, then Back to simulator. The policy remains inside the simulator session; it is not created as an IAM customer managed policy and is not attached to a principal.

3. Prove the explicit deny

Use Add actions to add exactly one S3 action: GetObject.

The initial row uses Not required for its resource type and * for its resource. Edit that resource, choose Specific and object, enter this ARN, then save it:

arn:aws:s3:::miruky-kfgwuryutmsnanij/private/guide.txt
Enter fullscreen mode Exit fullscreen mode

The resource editor targets S3:GetObject with object type and the full private-prefix object ARN.

The resource editor shows S3:GetObject, type object, and the full arn:aws:s3:::miruky-kfgwuryutmsnanij/private/guide.txt value. No bucket lookup or object request occurs.

Choose Simulate. The result should be Denied, even though the broader allow statement also matches the same action and bucket.

The private object GetObject simulation result is Denied with an explicit-deny detail.

The result is Denied for the private object ARN, and the detail reads Explicit deny found in 1 or more statements. The allow covering every object key still matches, but it cannot override an applicable deny.

Open the result details to inspect which policy source supplied the decision.

The result details identify an explicit deny in the generated custom policy.

The current popover says that one or more evaluated statements contain the explicit deny and links to a statement range in the generated custom policy. It does not print the statement Sid; the fixed document still makes the controlling statement unambiguous because only DenyPrivatePrefix has Effect set to Deny, and its resource matches the private ARN. Account identities and unrelated policy controls remain outside the crop.

An implicit deny would mean that no applicable allow produced permission. This case is different: an applicable Deny is present, so another matching Allow cannot override it.

4. Recover by changing only the object prefix

Keep the service and action unchanged. Double-click the resource cell and replace only the ARN with this public-prefix object:

arn:aws:s3:::miruky-kfgwuryutmsnanij/public/guide.txt
Enter fullscreen mode Exit fullscreen mode

The resource editor keeps S3:GetObject and object type while using the full public-prefix ARN.

The resource editor shows S3:GetObject, type object, and the full arn:aws:s3:::miruky-kfgwuryutmsnanij/public/guide.txt value. Compared with the earlier case, only the object-key prefix has changed, which isolates the policy decision to the resource path.

Run the simulation again. The result should be Allowed because AllowObjectReads matches and DenyPrivatePrefix does not match the public prefix.

The public object GetObject simulation result is Allowed.

The result changes to Allowed, and the detail reads Explicit allow in 1 statement(s). This is the recovery control: the broader allow works when the narrower deny no longer applies.

Both results come from the custom identity-policy document only. They do not check object existence, bucket configuration, a live session's credentials, or an unmodeled resource policy.

Wrap-up

The two simulations differed only in the object prefix. private/guide.txt was explicitly denied by the narrower statement despite the bucket-wide allow, while public/guide.txt was allowed as the control.

The Policy Simulator is a focused way to inspect policy logic before attachment. Its result still needs to be interpreted within the simulator's documented limits and should not be presented as a successful live S3 request.

Thanks for reading this far.

See you in the next one.

Disclosure: This article was written with AI assistance and independently verified against the linked primary sources and observed results.

References

Top comments (0)