Write IAM Policies with Conditions

Learn how to write IAM policies with conditions to reduce blast radius. Step-by-step guide with hands-on exercises, troubleshooting, and next steps.

Focus: write iam policies with conditions

Sponsored

We’ve all been there: you lock down a bucket with a strict IAM policy, only to discover later that a contractor’s leaked credentials could still read every object in it. Why? Because who you are matters, but when, where, and how you act matter just as much. In this lesson, you’ll learn to write IAM policies with conditions — the secret ingredient that turns a blunt “allowed” into a precise, context-aware authorization that shrinks your blast radius.

The problem this lesson solves

Standard IAM policies answer a single question: Can this principal perform this action on this resource? If yes, the API call proceeds — regardless of whether the caller is in your office, on a compromised laptop, or using a script that hasn’t been updated in years.

The result is a world of over-permissioned identities. An admin key meant for automated backups can also be used to delete databases at 3 AM from a coffee shop in another country. A read-only role can be invoked with --source-ip spoofed to anywhere in the world. This is exactly where real-world breaches happen — not because the attacker cracked your password, but because a policy allowed the action with no additional checks.

Imagine a bank that lets anyone with a valid ID walk into the vault — no time-of-day check, no CCTV verification, no tamper-evident seal. That’s a policy without conditions. Conditions add the extra layers of verification that make a single leaked credential far less dangerous.

Core concept / mental model

Think of an IAM policy as a set of permission gates. The first gate checks the who (principal), the what (action), and the where (resource). A condition closes a second gate: it requires the request to also match additional context like the caller’s IP, the time of day, or whether MFA was used.

The easiest mental model? A nightclub’s door policy. The door list tells you who can enter (the principal). But the bouncer also checks your ID (MFA), your ticket’s validity window (time), and whether you’re at the correct entrance (IP range). If any check fails, you don’t get in — even if your name is on the list.

In IAM, conditions are expressed as a Condition block in the policy. They use operators like IpAddress, Bool, NumericLessThanEquals, or StringEquals. Here’s a simple skeleton:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-private-bucket/*",
      "Condition": {
        "IpAddress": {
          "aws:SourceIp": "203.0.113.0/24"
        }
      }
    }
  ]
}

This policy says: only allow s3:GetObject on that bucket if the request comes from the specified IP range. If the caller is outside that range, the action is denied — even if the principal is an admin.

How it works step by step

Building a condition-based policy is a methodical process. Let’s break it down into five steps you can apply to any scenario:

Step 1: Identify the context you care about

Ask yourself: What extra signal would make this permission safe? Common ones are:

  • Caller IP (aws:SourceIp) for office-only access
  • Time (aws:CurrentTime) for scheduled jobs
  • MFA presence (aws:MultiFactorAuthPresent) for sensitive destructive actions
  • Tag on resource (aws:ResourceTag) to enforce naming or environment separation
  • Requested region (aws:RequestedRegion) to keep actions inside your compliance boundary

Step 2: Choose the right operator

Different checks need different operators. For IP conditions use IpAddress or NotIpAddress. For true/false like MFA, use Bool. For strings, StringEquals or StringLike. For numbers, NumericGreaterThan etc. Each operator has a strict and relaxed variant (StringEquals vs StringEqualsIgnoreCase, IpAddress vs NotIpAddress).

Step 3: Write the Condition block

Place the condition inside the statement. You can have multiple keys, and they are combined with AND logic by default. Use Condition with a JSON object that maps operators to their key-value pairs.

Step 4: Test with the IAM Policy Simulator

Always simulate your policy with the AWS Console’s IAM Policy Simulator before applying it. This tool checks whether a given principal, action, resource, and context would be allowed or denied — and shows you which conditions failed.

Step 5: Apply and monitor

Attach the policy to a group or role. Then watch CloudTrail logs to see if any legitimate requests are being denied unexpectedly. If so, adjust the condition or the test context — don’t simply remove the condition.

Hands-on walkthrough

Let’s create a real policy that only allows read access to a specific bucket from a trusted IP, and requires MFA for delete operations. We’ll use the AWS CLI to create and test it.

Setup

Make sure you have the AWS CLI configured with appropriate permissions:

aws sts get-caller-identity

Write the policy JSON

Create a file named condition-policy.json:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowReadFromOffice",
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:ListBucket"],
      "Resource": [
        "arn:aws:s3:::my-private-bucket",
        "arn:aws:s3:::my-private-bucket/*"
      ],
      "Condition": {
        "IpAddress": {
          "aws:SourceIp": "203.0.113.0/24"
        }
      }
    },
    {
      "Sid": "DenyDeleteWithoutMFA",
      "Effect": "Deny",
      "Action": "s3:DeleteObject",
      "Resource": "arn:aws:s3:::my-private-bucket/*",
      "Condition": {
        "BoolIfExists": {
          "aws:MultiFactorAuthPresent": "false"
        }
      }
    }
  ]
}

Notice the second statement is a Deny with a BoolIfExists condition. BoolIfExists treats a missing key (like when MFA isn’t used) as false, so it effectively denies deletes when MFA is absent.

Attach the policy to a role

aws iam create-policy --policy-name ConditionDemo --policy-document file://condition-policy.json
aws iam attach-role-policy --role-name my-app-role --policy-arn arn:aws:iam::123456789012:policy/ConditionDemo

Test the policy

Use the IAM Policy Simulator in the console (or the aws iam simulate-principal-policy CLI command) to check:

aws iam simulate-principal-policy --policy-source-arn arn:aws:iam::123456789012:role/my-app-role --action-names s3:GetObject --resource-arns arn:aws:s3:::my-private-bucket/secret.txt

If you pass --context-entries with an IP outside the allowed range, the simulator returns explicitDeny. Expect output like:

{
  "EvaluationResults": [
    {
      "EvalActionName": "s3:GetObject",
      "EvalDecision": "explicitDeny",
      "MatchedStatements": []
    }
  ]
}

Compare options / when to choose what

Conditions aren’t one-size-fits-all. Here’s a comparison of the most common condition keys and when to use them:

Condition key Use case Example operator When to choose it
aws:SourceIp Restrict to IP ranges IpAddress When your team works from static IPs (office, VPN)
aws:CurrentTime Allow only during business hours or a maintenance window DateGreaterThan, DateLessThan For scheduled jobs or to reduce attack surface at night
aws:MultiFactorAuthPresent Force MFA for critical actions Bool, BoolIfExists For any destructive or sensitive API call
aws:RequestedRegion Keep actions inside specific regions StringEquals For compliance (e.g., data residency)
aws:ResourceTag Require certain tags on the resource StringEquals To enforce environment separation (prod vs dev)

General rule: start with aws:SourceIp for user-facing roles, add aws:MultiFactorAuthPresent for high-risk actions, and use aws:RequestedRegion when you need geographic compliance.

Troubleshooting & edge cases

“It’s not working – my IP condition is ignored”

  • Verify the IP format: Use CIDR notation (e.g., 203.0.113.0/24), not a bare IP like 203.0.113.5. If you want a single IP, use /32.
  • Check whether the condition key exists: Some services don’t propagate aws:SourceIp for instance roles via EC2. For EC2, use aws:VpcSourceIp if the request comes from within a VPC.

“The MFA condition denies everything”

  • Bool vs BoolIfExists: Bool fails when the key is missing; BoolIfExists treats a missing key as false. If your CLI isn’t MFA-authenticated, use BoolIfExists for a deny that triggers appropriately.
  • Test with the simulator: Temporarily add a Condition with BoolIfExists: true to see if the rest of the policy works.

“I used StringEquals but I need a wildcard”

Use StringLike instead. For example, to match any s3 prefix:

"Condition": {
  "StringLike": {
    "s3:prefix": "logs/*"
  }
}

“My policy allows, but the API call still fails”

  • Check for an explicit Deny elsewhere. An explicit deny always overrides an allow.
  • Check service-level conditions: S3 has s3:prefix, s3:max-keys; EC2 has ec2:Region — always look up the service’s condition keys in the documentation.

What you learned & what's next

You now know how to write IAM policies with conditions — restricting permissions by IP, time, MFA, region, and more. You can build policies that reduce blast radius and keep your cloud environment secure even if a credential leaks.

Next up in the Cloud Security Essentials path is KMS usage stories — we’ll apply the same conditional thinking to encryption keys, and learn how to protect data at rest. You’re one step closer to full-stack cloud security mastery.

Practice recap

Create a policy that requires MFA for any s3:DeleteBucket action but allows reads from any IP between 9 AM and 6 PM UTC. Attach it to a test role and use the IAM Policy Simulator to verify both allow and deny scenarios.

Common mistakes

  • Using Bool instead of BoolIfExists for MFA checks — this denies requests when the key is absent, which might be your intent, but if you want to allow non-MFA requests for some actions, use BoolIfExists or separate statements.
  • Forgetting to use CIDR notation for aws:SourceIp — a single IP like 203.0.113.5 is invalid; use 203.0.113.5/32.
  • Assuming aws:SourceIp works for EC2 instance roles — for VPC traffic, use aws:VpcSourceIp or check if the service supports it.
  • Placing conditions on the wrong statement or mixing AND/OR logic without parentheses — conditions combine with AND by default, so use multiple statements for OR logic.
  • Not testing with the IAM Policy Simulator before production — you might miss unexpected denies due to missing context keys.

Variations

  1. Use StringLike instead of StringEquals for pattern matching on resource tags or prefixes.
  2. Leverage IAM Roles Anywhere to get temporary credentials with conditions outside AWS, reducing the need for long-lived access keys.
  3. Combine conditions with aws:PrincipalTag to enforce that only principals with a specific tag can perform actions, ideal for cross-account access.

Real-world use cases

  • Restrict S3 bucket access to office IP ranges so that a leaked developer key is useless outside the corporate network.
  • Require MFA for any data deletion (e.g., DynamoDB table delete) to prevent accidental or malicious irreversible actions.
  • Keep EC2 instances in a single region by enforcing aws:RequestedRegion so misconfigured scripts can’t spin up resources in non-compliant regions.

Key takeaways

  • IAM conditions gate actions on context, not just identity — they dramatically reduce blast radius.
  • Choose the right condition key and operator for your use case: IP, time, MFA, region, or tags.
  • Use BoolIfExists instead of Bool when you want a deny to trigger when the key is missing.
  • Always test policies with the IAM Policy Simulator before applying to production.
  • Remember: an explicit deny with a condition overrides an allow, so design with deny-first for risky actions.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.