NotWhat?!? Lock Out Regions with a Double Negative SCP

Today we review regions and learn how to use NotAction and Conditions in an SCP.

Prerequisites

  • Have an Organization with Service Control Policies enabled. You’ll need our OU structure from this lab for the screenshots to match, but the technique will work anywhere.

The Lesson

This short but important lab starts some more advanced IAM concepts. First some quick reminders on AWS regions and Service Control Policies (SCPs):

  • AWS offers different regions in different geographies so we can meet regulatory requirements (e.g. by keeping European data in Europe) and build more resilient deployments (against natural disasters, etc.).

  • Each region has its own management plane, which means its own web console and API endpoints. Services only run in a single region, and regions can only talk to each other if we have deliberately created connections.

  • AWS also has some global services which span regions. IAM is the main one we’ve worked with. Most of them just run out of us-east-1.

  • SCPs are a type of IAM policy called an organization policy. They are default deny, and can only remove permission. If you see an allow permission that doesn’t mean something is being granted to someone— it just means the SCP won’t block it.

  • We have an allow all policy in place since, without it, enabling SCPs would instantly block everything everywhere all at once (with default deny, simply activating denies everything).

  • We have a policy which denies using root, and denies the API call to leave the organization.

The “Problem” (and Solution) with Regions

Most AWS services need to be configured and managed on a per-region basis, including security services. Real breaches have happened because security wasn’t activated for a region. For you historians, it was even worse when new regions were added as a default region, but the security service wasn’t yet available in that region.

Threat actors love to work in less-defended regions. Developers sometimes use alternative regions with fewer security guardrails.

This is why we are taking about regions and SCPs today. There is absolutely no reason to allow any actions in regions you don’t need. We can enforce this with SCPs, but they need to not break global services. It’s easy, but I run into people all the time who forget about those global services and break things.

I’ve mentioned this before, but about 10 years ago I accidentally posted some credentials to GitHub (long before the cool kids were doing it). Attackers found them and started cryptomining in 2 regions where I didn’t have anything else running. So I didn’t see it until I got a warning email.

Locking out regions is incredibly common and highly recommended.

Lesson Key Points

  • Regions are isolated, and run independently.

  • A relatively small number of services are global and span regions. Most of them run out of us-east-1.

  • We don’t want to let people use unneeded regions.

  • We can block regions with Service Control Policies.

WARNING

If you are ever going to do this in an organization or account that’s already up and running, you need to make sure you won’t break anything in a region someone forgot to tell you they were using. Down the road we’ll learn how to quickly identify regions with something running in them. The TL;DR is you can look in the billing console and/or use Access Advisor (ask me about it later if you cannot wait).

The Lab

We will copy and paste an SCP to block using any region except us-west-2 or us-east-1, exempting some global services. Since that will only take 2 minutes, we’ll also run through the policy.

I’ll paste the full policy below, but here are the sections and how to interpret them. This is a great opportunity to start learning more about how they work.

First, the top of the policy:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "DenyAllOutsideRegions",
            "Effect": "Deny",
            "NotAction": [

This is JSON. The “{“ means it’s the start of an object with name and value pairs. When you see a [], that means it’s an array (list). Just think of these as blocks which you can nest. In this case we are telling AWS which version of policies to use, and that we are then making a statement with the IS DenyAllOutsideRegions. That’s followed by the obviously-named Deny, but then you’ll see the weird NotAction.

NotAction means “everything except this”, so the sentence means, “Deny everything except this list of things”. Okay, why the double negative? Think about how you would have to write this otherwise: you would have to Deny thousands of actions! Remember, this sits below an Allow All policy. Using NotAction means we can just list the things we want to allow, everything else will be denied, and we don’t need to remove our Allow All.

Okay, now you’re asking yourself why we don’t just deny everything and allow only what we want? There are a few reasons, but the main one is…

Deny statements override allow statements, but allow statements in different policies all add up together.

If we just use allow statements without denying all the things we don’t want, it’s easy to make a mistake where someone adds a permission we didn’t want them to have later. It’s much easier and more reliable to say, “block everything except these:”.

This mental contortion lies at the heart of IAM. Today I just want you to understand it at the surface; we will have more labs later to reinforce the concepts.

Why allow these at all? Because they are global services, and if we block the region they are running in they will break… everywhere. To my knowledge they are all currently in us-east-1 which we will leave region active, but I don’t know that for sure, and I don’t know whether that will change. Let’s look at the actions we want to allow:

      "NotAction": [
        "a4b:*",
        "acm:*",
        "aws-marketplace-management:*",
        "aws-marketplace:*",
        "aws-portal:*",
        "budgets:*",
        "ce:*",
        "chime:*",
        "cloudfront:*",
        "config:*",
        "cur:*",
        "directconnect:*",
        "ec2:DescribeRegions",
        "ec2:DescribeTransitGateways",
        "ec2:DescribeVpnGateways",
        "fms:*",
        "globalaccelerator:*",
        "health:*",
        "iam:*",
        "importexport:*",
        "kms:*",
        "mobileanalytics:*",
        "networkmanager:*",
        "organizations:*",
        "pricing:*",
        "route53:*",
        "route53domains:*",
        "route53-recovery-cluster:*",
        "route53-recovery-control-config:*",
        "route53-recovery-readiness:*",
        "s3:GetAccountPublic*",
        "s3:ListAllMyBuckets",
        "s3:ListMultiRegionAccessPoints",
        "s3:PutAccountPublic*",
        "shield:*",
        "sts:*",
        "support:*",
        "trustedadvisor:*",
        "waf-regional:*",
        "waf:*",
        "wafv2:*",
        "wellarchitected:*"
      ],
      "Resource": "*",

I left NotAction in there as a reminder. No, we will not use all these, but this is the recommended list from the AWS documentation. Maybe we will block them later, maybe not, but today we are focused on fundamentals. Also notice that we have to specify the resource, and use the wildcard “*”.

Now the conditional:

 "Condition": {
        "StringNotEquals": {
          "aws:RequestedRegion": [
            "us-east-1",
            "us-west-2"
          ]
        }

This is both magical and ANOTHER FRIGGIN’ DOUBLE NEGATIVE! In this case if the string for the region the API call is being made to is not us-east-1 or us-west-2, the deny statement above kicks in and the API call is blocked. We use another double negative so we don’t need to list all the regions. Because, like, what if AWS adds a new region? Written as you see here, any new region is blocked. If we listed the regions to block we would have to add every new region to our list, and it would be allowed until we did.

We use double negatives in AWS policies because they enable us to say “block everything except this”. This keeps us secure when new services, permissions (e.g., IAM user policies), actions, or regions are added!

So this entire policy translates to a (barely) readable sentence as, “Block all actions except this list, and block them in all regions except these two.”

With this our global services will still work, but everything else is blocked if it isn’t in Oregon or Virginia.

Video Walkthrough

Step-by-Step

Log into your IAM Identity Center portal, choose your management account, and select AdministratorAccess.

Go to Organizations > Policies > Service Control Policies.

You probably could have figured this one out, but it’s time to Create policy.

Name it “RegionLockout” and enter a detailed description like “Block all regions except us-west-2 and us-east-1, and allow global services”, so that the hacker who takes over your account later knows what he/she is looking at. (Kidding! This is more of a good commenting habit like code, because someday you’ll forget what you did.)

Now copy out the policy from this code block:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyAllOutsideRegions",
      "Effect": "Deny",
      "NotAction": [
        "a4b:*",
        "acm:*",
        "aws-marketplace-management:*",
        "aws-marketplace:*",
        "aws-portal:*",
        "budgets:*",
        "ce:*",
        "chime:*",
        "cloudfront:*",
        "config:*",
        "cur:*",
        "directconnect:*",
        "ec2:DescribeRegions",
        "ec2:DescribeTransitGateways",
        "ec2:DescribeVpnGateways",
        "fms:*",
        "globalaccelerator:*",
        "health:*",
        "iam:*",
        "importexport:*",
        "kms:*",
        "mobileanalytics:*",
        "networkmanager:*",
        "organizations:*",
        "pricing:*",
        "route53:*",
        "route53domains:*",
        "route53-recovery-cluster:*",
        "route53-recovery-control-config:*",
        "route53-recovery-readiness:*",
        "s3:GetAccountPublic*",
        "s3:ListAllMyBuckets",
        "s3:ListMultiRegionAccessPoints",
        "s3:PutAccountPublic*",
        "shield:*",
        "sts:*",
        "support:*",
        "trustedadvisor:*",
        "waf-regional:*",
        "waf:*",
        "wafv2:*",
        "wellarchitected:*"
      ],
      "Resource": "*",
      "Condition": {
        "StringNotEquals": {
          "aws:RequestedRegion": [
            "us-east-1",
            "us-west-2"
          ]
        }
      }
    }
  ]
}

And paste it into the policy box. Make sure you paste over the starter policy in that text box, so it looks like this…

Scroll to the bottom and Create policy.

We need to attach the policy to root. If you are paying attention, this might create issues later because that means the Exceptions OU is also blocked. I’m okay with that for now, because I just thought about it after already recording the video and screenshots, and I’ve had a rough day because my cat ate something stupid and is in surgery right now and it’s stupid expensive and… anyway, we’ll fix that later — just keep it in mind. If you are doing this at work you might want to apply this directly to the OUs instead of to root.

Click the checkbox next to the RegionLockout policy, then Actions, and then Attach policy.

Select Root, then Attach policy.

All the work is done, but if you don’t trust me it’s pretty easy to test. First Sign out of the current account in the upper right corner. Then go back to the Identity Center tab where you logged in, pick the other account, and sign in there. Then pick a non-global service (Simple Queue Service is probably still on your login page, since we recently used it in a lab) and visit that service in us-west-1. See those errors? Swap to us-west-2 and watch them all melt away.

Remember, if you try this in your management account it won’t throw these errors, because SCPs never apply to management accounts.

Lab Key Points

  • Blocking regions is an important security control, but don’t break global services.

  • To do this we need to use two separate double negatives in our policy:

    • Deny + NotAction says “deny everything except this list of things”.

    • Deny + StringNotEquals says “deny all regions except these two regions”.

  • Cats can be really expensive pains in the ass. Now I have to cancel my birthday trip.

-Rich

Join the conversation

or to participate.