AWS Resource Provisioning with Attribute Based Access Control (ABAC) – What You Need To Know

Lior Zatlavi By Lior Zatlavi

What to pay attention to when using ABAC in order to avoid unnecessary security gaps.

Everyone knows that IAM conditions are a very effective tool for configuring granular permissions on policies in AWS. One of their use cases in defining policies is to assign conditions that use resource tags for access control, aka ABAC (or Attribute Based Access Control). ABAC offers highly dynamic control of the actions that principals can perform on resources - by manipulating tags on the resources or principals.

In the past, concerns have been raised about using tags to manage permissions in AWS. In the past year, some have called the mechanism not mature enough. AWS is moving forward, gradually adding increasing support for tags and their use for access control.

After participating in a recent reddit thread discussion on ABAC, I thought it would be useful to write a post to accomplish two things:

  • Demonstrate how to use ABAC as an enabler for allowing principals to create resources as long as they are tagged with certain tags (so are compelled by certain policies)
  • Highlight what a security practitioner who decides to make this move should pay attention to so as to avoid unnecessary security gaps

I don’t necessarily recommend using this mechanism but realize many people want to. This post is meant to help such endeavors and build awareness as to where this mechanism may fall short -- and suggest how to use ABAC responsibly.

Here's the scenario

Suppose you’re using ABAC in AWS and want to allow certain principals who are allowed to create resources do so provided the new resources are provisioned with certain tags that have certain values. In this case, policies such as Service Control Policies, Resource-based Policies, Permission Boundaries and IAM Policies will apply, giving you more control on what these principals can actually do.

Let’s say we want to allow a certain user to be able to create only IAM Roles that have a tag called “resourceGroup” with the value “groupA”. This will allow us, for example, to limit the kind of actions these Roles can perform on certain resources using Service Control Policies or even Resource-based policies (by the way, you can also use this tag to actually create a resource group in AWS’s Resource Group tool).

We would then attach the following policy to the user:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "allowCreateRoleWithRGEqGroupA",
            "Effect": "Allow",
            "Action": [
                "iam:CreateRole",
                "iam:TagRole"
            ],
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "aws:RequestTag/resourceGroup": "groupA"
                }
            }
        }
    ]
}

If the user attempts to create a Role without the tag, for example:

aws iam create-role --role-name TestRole --assume-role-policy-document "file://trustPolicy.json"

The user will get an AccessDenied error. However, if the user simply adds the necessary tag to the cli call:

aws iam create-role --role-name TestRole --assume-role-policy-document "file://trustPolicy.json" --tags '{"Key": "resourceGroup", "Value": "groupA"}'

The action should work.

It’s important to ensure two things:

  • that the user can’t remove the tag (the user’s removal of the tag after creating the Role would override our efforts to compel the Roles the user creates to have the tag). In our example, this means the user can’t perform the action iam:UntagRole. Note that the user is able to tag the role using iam:TagRole.
  • that the aws:RequestTag applies to the iam:TagRole permissions - so that, even upon trying, the user will not be able to override the value of the tag

Let's get even more agile

To make things more interesting, we can use a condition that limits the user to creating a Role only by using the “resourceGroup” tag with the user’s “team” tag value. This dynamic configuration gives us much more agility - for example, we can apply this condition to multiple users of various groups and control the kind of Roles they can create by simply changing the tags applied to each user.

In this case, the policy will look something like this:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "allowCreateRoleWithRGEqTeam",
            "Effect": "Allow",
            "Action": [
                "iam:CreateRole",
                "iam:TagRole"
            ],
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "aws:RequestTag/resourceGroup": "${aws:PrincipalTag/team}"
                }
            }
        }
    ]
}

Now, if the user has been assigned a “team” tag, it will have to attach a “resourceGroup” tag with the same value as its“team” tag to each Role it creates.

You can also use AWS Organizations Tag policies to further control the tag - for example, to enforce specific capitalization or make sure its value is from a specific range.

So… where’s the catch?

You may think such granularity is great - however, using ABAC in AWS has problems. Last November, Summit Route did a nice job of surveying the state of ABAC in AWS - most of it still applies, and we highly recommend reading it.

An important problem cited by Summit Route that I’d like to highlight, as the example in this post makes it clear, is the mechanism’s lack of support for all resources. For ABAC in AWS to work, it’s necessary not just for resources to support tags, they also need to support tagging on create (that is, allow creation of the tag when the resource is created) and also to support aws:RequestTag, which allows verification that certain tags with certain values are attached to the request of creating the resource.

Although a “global” condition key, aws:RequestTag does not apply to all resources, even those that support tags. For example, lambda:CreateFunction doesn’t support this condition key - and delegating creation of Lambda functions to developers is a natural use case to be tempted to apply the mechanism to.

It can be very inconvenient to discover during implementation of your access control strategy that some resources you wish to allow a user to create under this condition don’t support it. You could find a “hackish” way around it - such as forcing the user to create resources with a certain name pattern - however, that’s not a solid foundation on which to build an access control policy.

It can be even less convenient should you apply the above condition to a statement meant to allow a principal to create multiple types of resources (e.g. roles and Lambda functions), only to find out the hard way - because you didn’t check first - that some of the resources don’t support aws:RequestTag. The condition will cause denial of the action to create the resource. This is because the condition key is not supported and, of course, is never part of the request even if the expected tagging is done on creation. Bottom line: you wind up granting fewer privileges to the principal than expected.

Going forward

As mentioned, AWS continues to add support to more and more services for this key. For example, support for the aws:RequestTag condition key for the iam:CreateRole action (demonstrated in this post) was apparently added since Summit Route’s work published (as it cites iam:CreateRole as an action for which restricting tagging on create is not supported). However, when designing an access control strategy, one should never assume new functionality will be added when it’s not currently available.

I strongly recommend that you first check the AWS documentation for support of the actions for the resources to which you plan to apply your access control policy. Also, be sure to manually test that the creation of the resources actually works.