AWS IAM and Set Theory: Mastering Service Control Policies

Written by Colin Andrews.

This is the 2nd part in a series of blog posts about AWS IAM and Set Theory. The previous article covered how to write more powerful IAM policies using set theory.

If you are leveraging AWS in your organisation, then Service Control Policies are a critical security tool within your arsenal. They work by setting outer boundaries on the IAM actions allowed within your AWS Organisation’s member accounts. This could protect your critical infrastructure from IAM principals who have managed to apply overly permissive policies to themselves, or protect your data from access across boundaries you want maintained. Globally applying limits to permissions in your AWS environment can also have unexpected consequences, such as causing service outages or introducing impacts to developer productivity (by unintentionally denying access to services or actions), so building your understanding of this tool is important.

I will continue using terms defined in the first article as if they are fresh on your mind. I pretend that, having ravenously feasted upon the content of the former, you have rushed headlong to the buffet of the present. These terms include:

  • Set theory concepts: sets, complements, unions, and intersections

  • The IAM Permission Space: the set of all IAM actions in AWS

  • The Allow/Deny set: a set containing two elements, “Allow” and “Deny”

  • AWS' policy evaluation logic: a function mapping from the permission space to the Allow/Deny set

This article assumes some knowledge about AWS IAM and Organisations. For example, knowledge about

  • IAM roles

  • Identity and resource based policies

  • The components of a multi-account organisational structure, like

    • member account, and

    • organisational unit

Let’s jump right in.

What are Service Control Policies in AWS?

Service Control Policies, from here onward abbreviated to SCPs, are a crucial component of your organisation's cloud security posture. They are a feature of AWS Organisations that allow platform administrators to define the maximum permissions that IAM users and roles can use inside member accounts.

As the name suggests, SCPs are policy documents. They are written with the same syntax as regular IAM policies. They contain an Effect key, either an Action or NotAction key that describes a list of IAM actions, as well as Resource and/or Condition keys.

SCPs are deployed through the Organisations service. You choose what organisation level resource to attach the policy to, which can either be an organisation unit (OU) or an account.

We can think about the relationship between SCPs and IAM policies - as attached to roles/users - in terms of set theory. The AWS documentation actually states this:

The effective permissions [granted to an IAM principal] are the logical intersection between what is allowed by the SCP and what is allowed by the IAM and resource-based policies.

Notice the set theory word “intersection” in there? You all thought I was mad! You all called me crazy! Bringing together theoretical maths and modern security technology. But look who’s laughing now!

Ahem…

In taking our mathematical approach to the logic of granted permissions, you will recall that when discussing set intersections in the IAM Permission Space, we are actually more interested in the resultant mapping of permissions to the Allow/Deny set. That is to say, what actions are are ultimately allowed or denied after all policies have been evaluated.

Having SCPs in our AWS environment adds another set of policies that the policy evaluation logic needs to consider before deciding what actions are allowed. There are several ways to use SCPs in AWS, and how you feel about granting permissions will influence your preferred method. Either you will prefer writing allow lists, or deny lists.

Choosing between allow and deny lists for SCPs

I’ll admit that as a consultant I often spend a lot of my time sitting on the fence. It’s a handy place to be, because it allows me the ability to answer almost any question with “it depends,” and later I can pick an opinion that best suits the situation. Fence sitting is particularly useful when security enthusiasts start arguing about permission levels, with the room divided into two camps: “more” versus “less”. If you favour granting people “more” permissions, and choose to asses and remove only powerful/dangerous actions, then you probably like deny lists. If you favour “less” permissions, requiring that every performable action must have a valid reason for existing, then you probably like allow lists. What about me? It depends. In this article, I am favouring deny lists and intend to sway you to my opinion with two arguments:

First, deny lists in AWS are more flexible. They accept the NotAction keyword, and allow us to use Resource and Condition keys in the policy. Having access to condition keys is powerful, and later we’ll see how AWS Control Tower leverages them to secure multi-account environments.

Second, because of the NotAction key, we can write deny lists that are functionally equivalent to allow lists. This is thanks to the magic of set theory. Using complementary language to describe sets is very useful, particularly when the sets are large. It is often easier to say "everything but this one thing" instead of listing everything and omitting "that one thing".

Proving that allow and deny lists are the same is extremely useful, because it means I can pacify security enthusiasts on both sides of the fence.


The default way to engage with SCPs in AWS is to write deny lists. When you enable the SCP feature in your organisation management account, you will find a root level SCP called FullAWSAccess that appears to grant administrative access. This SCP gives us a base that allows all actions, and from this base we cut out subsets with deny statements.

When we write policies using the “Deny” and “Action” keywords, we are describing a set in the permissions space that we want to restrict access to. The complement of this set will then contain the allowable actions that IAM principals could use.

We can draw this scenario in a diagram. To illustrate the impact of SCPs on IAM policies, I have filled the IAM Permission Space with dots that represent individual actions. In our environment we have SCPs that deny the set of actions in A depicted by the red circle (e.g. ["amplify" : "*"]). We then have an IAM policy that allows actions in B. Notice that where A and B overlap, there are no dots representing allowed actions. This is because the SCP is denying those actions. Ultimately, the actions allowed to the IAM principal are those in the intersection of B with the complement of A (actions not in the red circle).

In order to write allow lists as SCPs, we first have to remove the FullAWSAccess SCP from all organisation roots and OUs. Then we write our SCPs using the “Allow” and “Action” keywords, and the set of allowable actions in the permission space is the set described by the SCPs (which is the complement to how the “Deny Action” SCPs worked).

Let’s draw an allow list in a diagram. Since the FullAWSAccess SCP has been removed, the dots representing actions have disappeared from most of our diagram. Instead, an SCP allowing actions in the set A is present, so the dots only appear in that circle. Then the IAM policy that allows actions in B is limited by the SCP, meaning only actions in the intersection of A and B are allowed.

The next scenario to investigate is using the “Deny” and “NotAction” keywords together.

You might recall from the previous article that having access to either the Action and NotAction keywords in IAM policies is what tipped us off that set theory would be a useful tool in our belt. Specifying an Action list had the effect of explicitly stating those permissions we wanted included in the policy, whereas using the NotAction keyword allowed us to implicitly describe a set of permissions. Indeed this was the key difference between the way the ReadOnlyAccess and PowerUserAccess policies worked.

In SCPs we can use the NotAction keyword only when the statement’s Effect is set to Deny. If the effect is set to Allow then we cannot use NotAction and must use Action. To understand why this is the case we need to consider a quote from the documentation:

For a permission to be allowed for a specific account, there must be an explicit Allow statement at every level from the root through each OU in the direct path to the account (including the target account itself).

Using the NotAction key implicitly describes sets in the IAM Permission Space, which if used with an Allow statement would contradict the intent of the quote. However, this means that we can use NotAction lists to implicitly deny sets of actions. This could be useful for taking out large swathes of the permission space with fewer lines in a policy.

Let’s draw an example in a diagram. Here, the SCP that allows all actions is still in place, but an SCP that denies actions not in A removes access to a great portion of the permission space. Subsequently, an IAM policy that allows actions B is limited to those actions that are also in A.

Notice that the results of the previous two examples both ended up with actions in the intersection of A and B being allowed to the IAM principal? In other words, writing an allow list using an “Allow Action” method had the same outcome as writing a deny list using “Deny NotAction”.

This is evidence that Allow lists and Deny lists can be made functionally equivalent. Which, besides being a fun party trick, is helpful to know when picking a strategy for applying SCPs to our environment.

Let’s have a example written out for us in JSON. For simplicity I have omitted any resource and condition keys to keep the text easy to read. In this first policy snippet we have an SCP that allows only S3 actions. Remember that to make this policy effective, we first have to remove the AWSFullAccess SCP from any organisation roots and OUs.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:*"
      ]
    }
  ]
}

In this next snippet, we achieve the same outcome but without the extra step of removing the AWSFullAccess SCP. Indeed, the full access SCP explicitly grants us access to all actions including S3, and then this snippet implicitly denies access to all actions except S3.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "NotAction": [
        "s3:*"
      ]
    }
  ]
}

The power of condition and resource keys

Earlier I mentioned that we can use resource and condition keys in our SCPs only if the Effect key is set to Deny. In fact, according to the AWS documentation:

  • An Allow statement in an SCP permits the Resource element to only have a "*" entry.

  • An Allow statement in an SCP can't have a Condition element at all.

Using the condition and resource keys in IAM policies deserves a blog post all to itself. However, we can build our understanding of their use in SCPs without needing mountains of theory. There are very likely active implementations of these policies in your AWS environment right now.

If your organisation started working with multi-account structures in the last 5 years, it is extremely likely that you use a service called AWS Control Tower. If you started using multi account structures earlier, I would highly recommend a comparison between it and your current solution. Personally, I think it is a great service, and only getting better.

Control Tower is a managed service for building multi-account AWS environments. The service works to hide the complexity of creating and managing accounts, and sets up some preliminary SCPs in your environment to protect certain resources and data.

Let’s learn more about this service and jump right under the hood! Control Tower acts on resources inside your accounts by making use of an IAM role called AWSControlTowerExecution. This is an administrative role that the service uses to deploy resources. For example, in your security accounts this role is responsible for configuring CloudTrail and associated S3 buckets. What’s brilliant about the Control Tower service is how it protects its own resources from you by implementing clever SCPs.

Here’s a snippet from one such policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Condition": {
        "ArnNotLike": {
          "aws:PrincipalARN": "arn:aws:iam::*:role/AWSControlTowerExecution"
        }
      },
      "Action": [
        "s3:DeleteBucket"
      ],
      "Resource": [
        "arn:aws:s3:::aws-controltower*"
      ],
      "Effect": "Deny",
      "Sid": "GRAUDITBUCKETDELETIONPROHIBITED"
    }
  ]
}

This SCP is saying, “deny everyone the S3 action DeleteBucket on any bucket that starts with the name aws-controltower*, with the exception of principals using the AWSControlTowerExecution role."

This is my favourite type of policy logic. It demonstrates the thought that has gone into building the Control Tower service. You could implement similar logic in your own SCPs. Perhaps you have a base layer of roles and resources in each account that are managed by a centralised team. Using resource and condition statements inspired by Control Tower is an effective way to protect those items.

To summarise our investigation so far: using Deny lists for SCPs can be functionally equivalent to Allow lists, and additionally grants us greater freedoms to use the resource and condition keys in our policies.

Of course, I believe appreciating both options is the best way to proceed because after all, the security of your AWS organisation should not boil down to a debate about “more” versus “less”. It should contain nuance, and make use the right tools for the job.

Now, if you think I’m just going to let words like ArnNotLike and the presence of an asterisk in arn:aws:s3:::aws-controltower* just slip by without comment, then oh-dear you are sorely mistaken.

The presence of ArnNotLike in the previous policy’s condition statement implies the existence of an ArnLike key. Given that these two words can be used to describe sets of ARNs, that then implies the existence of the “ARN Space”, which is the set of all possible ARNs!

Indeed, you can now consider a corresponding “space” for each key that we could place in the condition statement of a policy. All of which could be considered as subsets of the much larger “IAM Condition Space”.

Following directly on from this, the Resource key allows us to define a set of resources that are affected by the policy. We could also use a NotResource key to implicitly describe a set of resources (which is true in regular IAM policies. However, in SCPs we cannot use the NotResource key). This means we can think of sets in the “AWS Resource Space!”

It’s sets all the way down!

How to choose a strategy?

If you are concerned with setting a global permissions boundary for your AWS environment, then SCPs are the right tool for the job. Making a decision about how they should be applied and maintained in your environment is an important choice.

In this article I present an argument which prefers writing “deny” lists over “allow” lists for SCPs. We utilised our understanding of set theory and the IAM permission space to analyse the differences between these approaches, and identified the greater flexibility that deny lists offer, particularly in relation to the use of the NotAction, condition and resource keys.

Further evidence for the use of deny lists is provided by the AWS Control Tower service, which utilises these types of SCPs to protect its own resources inside your accounts.

Whether you prefer granting “more” or “less” permissions is a choice I leave to you, but what I hope you have learned from this article is that, with the power of set theory, you are not limited to a binary choice between “allow” and “deny” to achieve your goals.

Acknowledgments

I would like to thank my colleagues at Mantel Group for their invaluable feedback and input to this article. In particular I would like to thank Sarah Pelham for her detailed review.

08/19/2024