Yes No Header Background Image

How to Build IAM Policies in AWS

IAM policies are imperative when setting up permissions for your Amazon Web Services resources. Whether you are a one-person shop or part of a large organization, understanding how they work and how to set them up is crucial. If you don’t set up IAM policies properly, you will create security holes or you won’t have the correct permissions for your users.

Core Concepts

IAM policies can be used to allow or deny user access to specific actions and specific resources. For example, let’s say that we have employees who need the ability to launch EC2 instances, but we don’t want to give them the ability to terminate instances. So for this example, these are the actions we want to define:

  • Allow launching EC2 instances
  • Deny terminating EC2 instances

From there, how do we actually build this policy? There are a few different ways.

One way is to use a policy generator. Another way is to create the policy through the IAM console by going to Policies → Create Policy and then selecting an option.

AWS Console

If you go through the console, you’ll notice that there is also a policy generator that we can access from AWS. On top of that, we have the option to copy from existing AWS managed policies to get started, and finally, there is an option to create our own policies from scratch.

Since the syntax can be tedious to remember, it’s a good idea to use something like the generator, unless you know of another template that’s quite similar and can offer a head start.

IAM Policy generator

From the first generator linked, we can select the policy type as IAM Policy, and then we can add our policy statements. Statements make up a policy and we can have one or more in a single policy. Each of these statements describes one set of permissions. Since in the previous example we have two different permissions that we want to define, we can create two statements using the generator. The first statement will have an “Effect” of Allow for the “Amazon EC2” AWS Service. Then, the action is to allow launching (RunInstances) of EC2 instances.

We can find relevant actions and check their boxes to add them to our statement. For example, here we would have to have the “RunInstances” action to allowed. We may also want to include “StartInstances” and “StopInstances” so that our employee doesn’t leave the instance running even when they don’t need it. They don’t have to have termination access to simply stop the instances — these are two different actions.

As you scroll by all of these EC2 actions, you may start to realize that there are a lot of them. With so many different actions listed in your statement, you can encounter two issues: 1) You may hit the policy size limit, and 2) You may have a hard time seeing what is allowed and what isn’t allowed. But what if you want to allow all actions except to deny instance termination?

Instead of having a massive list of allowed actions in our policies, we can use the power of explicit denies. Explicit denies (the Deny “Effect”) always override allows. This means that if we allow all actions for the EC2 service, including the “TerminateInstances” action, we can override that by adding the deny statement.

So if you’re following along with the generator, select All Actions, and then for the Amazon Resource Name (ARN), we can simply put an asterisk (*) to mean all resources. Then, you can add the statement.

You may have noticed that there’s an option for Add Conditions right above Add Statement, but we’ll talk about that in a little bit.

The statement is as small as it gets: We have an effect of “Allow” for the action ec2:* on all resources. But we’re not done yet. We still have to deny the TerminateInstances action.

Now select Deny, under Effect, for the Amazon EC2 service, and set the action to “TerminateInstances” on all resources “*”. Go ahead and add that statement.

We are now ready to generate the policy. This will pop up a window with a JSON document, and this is our IAM policy. As we can see, this policy is quite small. That’s because we were able to use an explicit deny instead of having to list all of the allowed actions.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Stmt1469805995769",
      "Action": "ec2:*",
      "Effect": "Allow",
      "Resource": "*"
    },
    {
      "Sid": "Stmt1469806318715",
      "Action": [
        "ec2:TerminateInstances"
      ],
      "Effect": "Deny",
      "Resource": "*"
    }
  ]
}

As we discussed earlier, this policy contains the core elements: An action, effect, and resource. We also see at the top that a version was generated, which simply defines which policy language version to use. This is not a version that you create, and you should use the latest version (2012-10-17 as of August 2016) since you want to access the latest features. We then have the JSON array that contains all of our statements, and those statements have unique IDs.

Great, we have our policy, but what do we do with this policy now? Well, it depends. If this policy only applies to a single user, we can apply the policy directly to that user. Or if we plan on applying this policy to a group of users, we can apply this policy to a group.

Let’s take a look at both options.

Applying this to a user is straightforward. If we have a user, we can select that user and under the Permissions tab, we can click on Attach Policy. Search for the policy name, and attach it. Done.

Applying this to groups is basically the same thing. If you don’t have a group, go ahead and create one. Otherwise, click on the group and attach the policy under permissions. You can then add users to this group, and all users will inherit this policy.

The neat thing here is that we can still apply policies to individual users, even if they are part of a group. This allows us to explicitly deny permissions they shouldn’t have but other users in the group can have, or to give more permissions to that specific users that others should not have.

That’s it! This covers how to create basic IAM policies and how to apply those policies to users or groups. However, oftentimes we require more advanced policies. Otherwise it would just be too easy, right?

Advanced Examples and Concepts

Let’s take a look at how we can create policies that cover more useful scenarios instead of just being limited to the basics. This isn’t meant to be a comprehensive list of all the available options, but simply an introduction to what is possible.

We briefly mentioned the “Add Conditions” option earlier. Conditions can be added to policies, and can take our policies to the next level, as well as give us more fine grain control.

Building on top of our previous example, our users are now able to launch EC2 instances, but those resources can be launched across all of the different regions available to us in AWS. To make it easier to keep track of all those resources, we’ve been tasked with restricting which region these users can launch EC2 resources in. How can we implement this? Conditions.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Stmt1469805995769",
      "Action": "ec2:*",
      "Effect": "Allow",
      "Condition": {
        "StringEquals": {
          "ec2:Region": "us-east-1"
        }
      },
      "Resource": "*"
    }
  ]
}

For this example policy, I took what we built earlier and simply added a Condition element (or Condition block). With conditions, we can build expressions with boolean condition operators like equal, greater than, etc…

We have multiple options here. We can have string condition operators like StringEquals, StringLike, and the like. We can also have numeric condition operators like NumericEquals, NumericLessThan, and more. In addition to these, we can use date conditions, boolean conditions, and many more.

In the example, we specify that any EC2 actions that we perform must be in the us-east-1 region. Otherwise, the requests get denied.

But wait a minute, we don’t have any explicit denies blocking other regions, so why would that automatically deny them? Because explicit denies are not the only denies. We also have default denies. If we don’t allow actions, then AWS denies them by default. As soon as we allow that action, that allow overrides the default deny as we’ve seen. In this case, since we don’t allow actions for other regions, actions against other regions are denied by default.

Example Using Variables

Let’s take a look at another example using conditions with variables, and we’ll look at this specifically for Amazon S3. Variables are extremely useful if we don’t know the exact resource name when we write the policy. This can happen when applying a policy to a group, where we need to specify placeholders that are replaced when the policy gets evaluated.

For example, let’s say that we have a “developers” bucket in Amazon S3, with folders inside of that bucket for each individual developer. The folder name is the “username-userid” of the user. Instead of having to manually add a policy to each user with their exact folder name, we can create one policy and apply it to the entire group — all thanks to variables.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": ["s3:ListBucket"],
      "Effect": "Allow",
      "Resource": ["arn:aws:s3:::developers"],
      "Condition": {"StringLike": {"s3:prefix": ["${aws:username}-{aws:userid}/*"]}}
    },
    {
      "Action": [
        "s3:GetObject",
        "s3:PutObject"
      ],
      "Effect": "Allow",
      "Resource": ["arn:aws:s3:::developers/${aws:username}-{aws:userid}/*"]
    }
  ]
}

When this policy is evaluated, the policy variables are swapped out with the values coming from the actual request. If user “jeff” with id “123456789” sends the “s3:ListBucket” request on the developers bucket and the jeff-123456789 folder, then that user will have access to list objects inside of that folder. If they do it on another folder, they will be denied access. That way, each developer can only see his or her own file, and no one else’s. The same applies to “s3:GetObject” and “s3:PutObject”.

There are many more variables available for us to use.

Conclusion

This post’s aim was to get you familiar with IAM policies so that you can start using them in your Amazon Web Services account. Of course, there are a number of different scenarios that we could have talked about and that you may run into, but hopefully this gives you the foundation from which to grow your knowledge of IAM policies.

Whether you are building simple or complex IAM policies, you now know the basic structure of a policy and how to get started. Good luck!

Christophe Limpalair

Christophe is the Director of Quick Training at the Linux Academy, and prior to that, he created AWS training content. He is passionate about constantly learning and helping others learn. Whenever he's not working on Quick Training, he's usually hosting the ScaleYourCode podcast.

Leave a Reply

Your email address will not be published. Required fields are marked *