From AWS Security by Dylan Shields

This article deals with methods you can use to secure your AWS account.

Take 37% off AWS Security by entering fccshields into the discount code box at checkout at

The Wall between Accounts

Suppose you have two unrelated applications that run in the same AWS account. The first is a WordPress site, and the second is a Jenkins server. Both applications run on EC2. If Alice is a user who works on the WordPress site, and has full access to EC2, then an attacker with Alice’s credentials could attack both applications. In this scenario we say that both the WordPress site and the Jenkins server are in the blast radius of the Alice user. Figure 1 depicts this scenario. Ideally compromising a user which is meant for one application shouldn’t have any impact on the other. What can we do to reduce Alice’s blast radius?

Figure 1. When user accounts are compromised, all applications in the same account are potentially vulnerable, not only the ones involving the user.

One way to do this is to remove full EC2 access from Alice. But what if Alice needs to migrate the site to a larger instance size. She needs the permission to create and terminate EC2 instances. We can restrict those permissions down to specific ARNs, but if new instances are created, we don’t know what those ARNs will be yet. It can be complicated to restrict permissions to a subset of resources within a service, and it’s even more complicated when those resources change frequently. One way to manage this is attribute-based access control. But there’s an easier way. You can move the applications to separate AWS accounts.

A logical barrier between AWS accounts can be used to your advantage in this situation. If both applications run in separate accounts, and Alice’s credentials are compromised, there’s no risk to the Jenkins server. Even if Alice has full access to the EC2 service in the first account, the user can’t make any changes in the second account. Without some rather complicated setup, there’s no way for a user in one account to call AWS Actions in another account.

Figure 2. Separate AWS accounts provide logical separation of resources. The compromise of a user in one account is unlikely to have an impact on resources in a different account.

Multiple accounts come with tradeoffs you should consider. They provide good separation between the applications without having to write and manage complex IAM policies. On the other hand, it can be difficult to manage and track multiple accounts. In general, using multiple accounts is considered a best practice, but you should evaluate whether it makes sense for your organization.

Attribute-Based Access Control with Tags

Attribute-Based Access Control (ABAC) is a model that allows for dynamic permissions which depend on attributes of the caller and the resource. This is in contrast to Role-Based Access Control (RBAC) where all the resources a user can access are defined explicitly in the Identity Policies. In the Tagged Resources section, we’ll see how to use ABAC to grant a user permission to terminate only non-production EC2 instances. In the Tagged Principals section, we’ll investigate using ABAC to grant users permission to terminate EC2 instances only in the projects they work on.

Tagged Resources

Suppose we’ve production and non-production EC2 instances running in one account. We want to give a user permission to terminate any non-production EC2 instances. With the tools we have, this is tricky. We could add the ARNs of all the non-production resources to the Resource Block of the user’s Identity Policy, but then every time we create a new instance, we must update that policy.

This is a great use case for Attribute-Based Access Control, where we want to grant permissions based on an attribute of the resource. Namely, whether it’s a production instance or not. The way ABAC is implemented in IAM is through Tags. The first thing we need to do is add a Tag to every instance that says whether it’s a production instance. We’ll tag resources as either, Environment/Prod or Environment/NonProd. We can use the command below to tag an instance:

 $ aws ec2 create-tags \ #A
       --resources i-123abc \ #B
 --tags Key=Environment,Value=Prod #C

#A Add tags to EC2 resources

#B The resource ID to apply the tags

#C The tag Keys and Values

Now we can create an Identity Policy that uses that tag. We’ll do this using the Condition Block. If the Condition Block below is included, the policy only applies to resources which have the Environment/NonProd Tag.

Listing 1. Attribute-Based Access Control Policy For EC2 Instances.

   "Version": "2012-10-17",
   "Statement": [
       "Effect": "Allow",
       "Action": "ec2:TerminateInstances",
       "Resource": "*",
       "Condition": { #A
         "StringEquals": { #B
           "ec2:ResourceTag/Environment": "NonProd" #C

#A Use the condition block to apply only in certain situations

#B StringEquals matches keys to values

#C ec2:ResourceTag/Environment reads the value of the Environment tag on an EC2 resource

The StringEquals object in the Condition Block evaluates each key and checks whether it’s equal to the value. The key ec2:ResourceTag/Environment refers to the value of a Tag on an EC2 instance with the key Environment. You could change Environment in this case to the key of any other Tag you want to use.

Now if we create a new EC2 instance, all we need to do is tag it based on whether it’s a production instance, and the correct access controls are applied. We don’t need to make any additional changes to our policy whenever instances are created or destroyed.

Note that unfortunately only some AWS resources support this kind of access control based on tags. The table below shows some of the most popular services that support it:

Table 1. Services that support Tag-based Authorization

Elastic Compute Cloud (EC2)

EC2 Auto Scaling

Elastic Beanstalk

Elastic Container Registry

Elastic Load Balancing (ELB)

Elastic Block Store (EBS)

Elastic File System (EFS)


S3 Glacier

Simple Storage Service (S3)

Storage Gateway

Quantum Ledger Database (QLDB)

Relational Database Service (RDS)






Directory Service

Identity and Access Management

Secrets Manager


Machine Learning





Resource Groups

Systems Manager

Database Migration Service


Device Farm

API Gateway

App Mesh

Route 53 Resolver




Data Pipeline

Elastic MapReduce

AWS Glue

Amazon MQ

Step Functions

Simple Workflow Service

Ground Station




Simple Email Service




Tagged Principals

We’ve seen how we can apply controls based on attributes of the resource being accessed. Now we’ll see how we can do the same thing using attributes of the caller. Consider a scenario where multiple teams are working on separate projects within the same account. Can we set up the permissions in a way that a user who works on one project can’t terminate EC2 instances from another project?

Doing this requires knowing which project each user works on, and which project each EC2 instance belongs to. As before, we do this with Tags. We tag every user and EC2 instance with the project they work on. We can tag a user and an instance as being on the ABC project like this:

 $ aws iam tag-user \ #A
       --user-name Alice \
 --tags Key=Project,Value=ABC
 $ aws ec2 create-tags \ #B
       --resources i-123abc \
 --tags Key=Project,Value=ABC

#A Tag Alice as part of Project ABC

#B Tag an EC2 instance as also part of Project ABC

Once we do this for all users and instances, we can move onto constructing the policy. We’ll use the Condition Block of an Identity Policy to apply the attribute check. The following Condition Block applies the policy only to resources which have a Project tag value that matches the Project tag value on the user (or role) that the policy is attached to.

Listing 2. Attribute-Based Access Control Policy With Principal Tags.

   "Version": "2012-10-17",
   "Statement": [
       "Effect": "Allow",
       "Action": "ec2:TerminateInstances",
       "Resource": "*",
       "Condition": { #A
         "StringEquals": { #A
           "ec2:ResourceTag/Project": "${aws:PrincipalTag/Project}" #A
         } #A
       } #A

#A Only allow terminating instances on resource where the resource’s project tag matches the calling user’s project tag.

The `${aws:PrincipalTag/Project}` string evaluates to the value of the tag with key Project on the user the policy is attached to. You could replace Project with the key of any other tag you want to use. This Condition Block requires that you only apply the policy when the resource is an EC2 instance with a Project tag that matches the Project tag on the caller. We can add this Condition Block to an Identity Policy that allows terminating instances, and then attach that policy to all of our users. Once you’ve done this, you can try terminating some instances as a user from Project ABC. You can terminate Project ABC instances, but not the ones tagged Project XYZ.

Attribute-based Access Control is a huge convenience over having to constantly update policies when resources change. As long as you keep tags up to date on resources and users, you can write a single policy which can be attached to every user and still separates access based on projects.

That’s all for this article. If you want to learn more about the book, check it out on our browser-based liveBook reader here.