General

AWS IAM Policies : Best Practices & How to Create an IAM Policy

AWS IAM Policies and Best Practices

Securely accessing cloud resources is one of the most critical requirements for IT teams. To achieve this, cloud administrators leverage web services that assist with managing access to cloud environments. 

In this article, we will explore the AWS IAM service, and more specifically, we will look into IAM policies, learn how they are structured, how to create them, and some best practices.

What is AWS IAM?

AWS Identity and Access Management(IAM) is a web service that assists with managing and controlling access and permissions to resources and other AWS services. Leveraging this service, we can set up the building blocks to control authentication and authorization to our AWS accounts. 

Before we continue talking about IAM, let’s define some terminology for its essential components that we will reference throughout the article. 

  • Authentication: The process of validating a user’s identity. Basically, verifying who someone claims to be. 
  • Authorization: Defining the level of permissions and access rights for a given user. Authorization happens after authentication and establishes what someone is allowed to do.
  • Principals: Persons or applications that try to perform actions on AWS. 
  • Users: Identities in IAM that correspond to users in an organization. They represent actual persons or applications and service accounts.
  • Groups: IAM users are grouped logically with IAM groups. This way, we can assign permissions to multiple users in a bulk fashion. A common pattern is to create groups according to different roles in an organization.
  • Roles: Roles provide temporary access to resources and aren’t associated with specific users. Instead, roles enable principals to temporarily assume a set of permissions to complete an operation.
  • Policies: To manage access on AWS we generate IAM policies that define levels of permissions and attach them to IAM identities(users, groups, roles) or AWS resources.
  • Requests: Principals attempt to perform actions on AWS by instantiating requests to AWS via the AWS API, Management Console, or CLI. 
  • Actions: Every request has an action definition that declares the specific operation requested. If authentication and authorization are cleared the action is approved.
  • Resources: A resource in this context can be considered any AWS object within a service.

Policy Types

IAM Policies are one of the most basic blocks of access management in AWS since they define the permissions of an identity or a resource. For every request, these policies are evaluated, and based on their definition; the requests are allowed or denied. Let’s look at the different types of policies that exist in AWS.  

  • Identity-based policies are policies attached to identities(users, groups, roles) and provide them with the required permissions. Identity-based policies could be either Managed policies or Inline policies. Managed policies are either prepared and managed by AWS for common use cases(AWS managed policies) or custom policies created by users(Customer managed policies) suitable for achieving fine-grained control. Inline policies are used when we need to make a policy part of a principal’s entity and maintain a strict one-to-one relationship between them. 
  • Resource-based policies are attached directly to resources and specify permissions for specific actions on the resource by some principals. 
  • IAM permissions boundaries define the maximum permissions for an IAM entity and are used as safeguards. 
  • Access control lists(ACLs) are attached to resources and control cross-account permissions for principals from other accounts.
  • Organizations Service Control Policies(SCPs) specify the maximum level of permissions for an organization’s accounts. These policies are used to limit the permissions that can be assigned within member accounts.
  • Session policies are advanced policies used during temporary sessions for roles or federated users.

Policy Document Structure

Now that we have seen the different types of IAM policies that exist in the context of AWS, let’s see how they are structured. Most of them are stored in JSON format and are attached to resources or identities. The only type of policy that uses a different format is ACL, but we won’t focus on ACLs in this article. 

Each JSON policy document might have optional informational elements at the top of the document and must have one or more statements. A statement contains all the necessary information about a permission that we would like to declare. 

Here’s a simple identity-based policy that allows the principal who has it attached to list the objects of a single S3 bucket named `this-is-an-example-s3-bucket-name`.

{
   "Version": "2012-10-17",
   "Statement": {
       "Sid": "ListObjectsInBucket",
       "Effect": "Allow",
       "Action": "s3:ListBucket",
       "Resource": "arn:aws:s3:::this-is-an-example-s3-bucket-name"
   }
}

IAM Policies are built using a combination of the below elements:

  • Version: Defines the version of the policy language. Always use the latest version.
  • Statement: This argument is used as a parent element for the different statements in the policy. 
  • Sid: This is an optional element that allows us to define a statement ID.
  • Effect: This element can have the values `Allow` or `Deny`.
  • Action: The list of actions related to the policy.
  • Resource: Defines the list of resources to which the policy is applied. For resource-based policies, this is optional since the policy applies to the resource that has it attached. 
  • Principal: Defines the identities that are allowed or denied access to resource-based policies. 
  • Condition: Defines some conditions under which the policy applies. This element is practical when we need to achieve custom rules for fine-grained access. 

There are options for resources, principals, and actions to define deny access controls by using NotPrincipal, NotAction, or NotResource. Note that these are mutually exclusive with their contrasting elements. For example, you can’t use both Resource and NotResource elements in a statement.

If you want to learn more about these elements and the JSON policy components, check out the IAM JSON policy reference.

Next, we will see an example of a more complex IAM policy with more than one statement using conditions.

{
   "Version": "2012-10-17",
   "Statement": [
       {
           "Sid": "DenyAllOutsideRequestedRegions",
           "Effect": "Deny",
           "NotAction": [
               "cloudfront:*",
               "iam:*",
               "route53:*",
               "support:*"
           ],
           "Resource": "*",
           "Condition": {
               "StringNotEquals": {
                   "aws:RequestedRegion": [
                       "eu-west-1",
                       "eu-central-1"
                   ]
               }
           }
       },
       {
           "Sid": "DenyAccessFromNonCorporateIPs",
           "Effect": "Deny",
           "Action": "*",
           "Resource": "*",
           "Condition": {
               "NotIpAddress": {
                   "aws:SourceIp": [
                       "192.0.1.0/24"
                   ]
               },
               "Bool": {"aws:ViaAWSService": "false"}
           }
       }
   ]
}

In the policy above, we define two separate statements. The first defines some rules to deny access based on the requested AWS region. More specifically, it denies all actions for regions not defined in the condition, except for the actions mentioned in the NotAction element.

The second one defines a policy to deny access to AWS based on the source IP of the requests. Here, since we have two conditions, they are combined with a logical `AND`. More specifically, the policy denies all AWS actions when requests originate from an IP that doesn’t come from the corporate IP range(in this example: 192.0.1.0/24) and when an AWS service isn’t making the call. 

Note that both policies don’t actually allow any actions and are used to restrict access to AWS resources. 

Finally, here’s a resource-based policy for an S3 bucket.

{
   "Version": "2012-10-17",
   "Statement": [
     {
       "Sid": "DenyS3AccessWithNoMFA",
       "Effect": "Deny",
       "Principal": "*",
       "Action": "s3:*",
       "Resource": "arn:aws:s3:::this-is-an-example-s3-bucket-name/example-directory/*",
       "Condition": { "Null": { "aws:MultiFactorAuthAge": true }}
     }
   ]
}

By associating this policy with the bucket `this_is_an_example_s3_bucket_name` every action on the bucket is denied if the request isn’t authenticated with a multi-factor authentication mechanism. Note the use of the Principal element here since we are using a resource-based policy.

Creating an IAM Policy

There are three main ways to create an IAM policy. We can either use the AWS console, the AWS CLI, or the AWS API. For this demo, we will go through the exercise of creating an IAM customer-managed policy via the AWS Console. 

The easiest option to create a policy is to use the helpful visual editor that the AWS console provides without having to write a JSON file and care about proper syntax. After signing in to the AWS Management Console, head to IAM and select Policies and Create Policy

From this screen, you can choose to either use the Visual editor or JSON.

create policy - choose a service

Let’s replicate our first example policy from above that allows listing the objects in an S3 bucket. For Service we select S3, for Actions choose ListBucket, and for Resources use the arn of the S3 bucket arn:aws:s3:::this-is-an-example-s3-bucket-name. Then, select Next: Tags.

Create policy - s3

Note that by default, the policy allows the actions chosen. To deny actions, you have to select Switch to deny permissions before selecting the actions.

create policy - switch to deny premissions

You can optionally add some tags to your customer-managed identity-based policy on the next page. For example, we could add tags for name, environment, department, etc. Then, select Next: Review.

create policy - add tags

Finally, add a Name to your policy and optionally a description for its purpose and select Create policy.

Create policy - review policy

Nice,  we have replicated the IAM policy we introduced in the first example.

If you feel comfortable with JSON syntax, you can instead use the JSON tab to define an IAM Policy like this:

Create policy - JSON

Another option using the AWS API under the hood would be to create your IAM resources with the Infrastructure as Code tool of your choice. Here’s an example of using Terraform to define the same policy.

resource "aws_iam_policy" "list_bucket_policy_example" {
 
 name        = "list_bucket_policy_example"
 path        = "/"
 description = "AWS IAM Policy example for listing the objects of a bucket"
 policy      = <<EOF
{
 "Version": "2012-10-17",
 "Statement": [
   {
     "Action": "s3:ListBucket",
     "Resource": "arn:aws:s3:::this-is-an-example-s3-bucket-name",
     "Effect": "Allow",
     "Sid": "ListObjectsInBucket"
   }
 ]
}
EOF
}

Validating IAM Policies

When creating or editing IAM policies using the AWS Management Console, their syntax and grammar are inspected to verify they comply with the IAM policy grammar. If there is any issue or error, you get notified accordingly and have to fix the policy. 

AWS provides IAM Access Analyzer with additional policy checks and recommendations to improve policies. When creating or editing a policy using the JSON tab, a policy validation pane below the policy provides different findings for the policy. There you get information for issues categorized as Security, Errors, Warnings, and Suggestions. Update your policy accordingly to resolve the findings.

Here’s an example of a malformed policy that triggers three separate findings.

Malformed policy example

We get an Error finding because we don’t specify a correct ARN field for the Resource element.

Next, on the Warnings tab, we get notified that we omitted the top-level Version element.

Validating policy - Warnings tab

Finally, on the Suggestions tab, we get notified that our Action element includes no actions.

Validating policy - Suggestions tab

Testing IAM Policies

Another handy tool for AWS administrators and users who manage IAM policies is the IAM Policy Simulator. Using this simulator, you can troubleshoot issues with different policy types and try to understand why some requests are allowed or denied. 

The IAM Policy Simulator console provides a testing playground for IAM policies and an easy way to test which actions are allowed or denied to specific principals for specific resources. The simulator doesn’t actually make the requests, so it’s a safe space to experiment and get information. You can even test new policies that don’t yet exist on our account and simulate real-world scenarios by defining complex conditions. Finally, since the returned output is a message with the result and relevant information, you can identify which statement in a policy denies or allows access. 

For example, we have created an IAM user test-user and attached the policy ListBucketContents that we created earlier. We can use the AWS Policy Simulator to validate that this user can indeed list the objects of the example bucket.

AWS Policy Simulator

After defining all the parameters for the simulation, we choose Run Simulation and check that the Permission result is allowed. Check out the detailed Troubleshooting IAM Policies guide for issues with IAM policies.

Versioning IAM Policies

A valuable feature of IAM Policies that you should be aware of is that AWS automatically keeps five versions of each policy. Whenever you make changes to a policy and save it, a new version is created instead of overwriting the existing one. 

When the limit of five kept versions is reached, you can select which older version to remove. This feature is convenient because it allows us to quickly revert to a previous policy version in case of issues.

IAM Policies Best Practices

  • Follow Least Privilege Principles: When creating IAM policies grant only the necessary permissions to perform the job. Specify specific actions, resources, and principles and add custom conditions to achieve the required controls. 
  • Generate policies based on access activity: Although it’s considered a best practice, it might be challenging to implement fine-grained policies that grant the least privilege when starting with IAM policies. For this reason, you can initially use policies that allow more permissions than they should and then refine them by generating a new policy based on the access activity of an IAM entity. This is a more pragmatic approach to achieving the least privilege for inexperienced users that makes the journey to improving security and access management smoother. 
  • Get started with managed policies: It’s totally ok to start with AWS Managed policies as you start experimenting and learning. These policies cover common use cases and help your team set up necessary permissions to start quickly. When you feel comfortable enough with IAM, consider restricting your policies by creating customer-managed policies that follow the least privileged approach.
  • Assign permissions with roles and groups & avoid inline policies: It’s considered best to avoid assigning permissions directly to users or using inline policies. For easier access management and better security controls, attach policies to groups or roles. 
  • Validate policies: Every time you create or edit policies, validate them using the AWS helper tools, as we have seen in the section Validating IAM Policies.
  • Use IAM Roles for EC2 instances(EC2 Instance Profiles): For apps that run on EC2 instances, attach the necessary permissions to IAM roles and specify the role as a launch parameter for the instance. 
  • Use policy conditions: Leverage policy conditions to achieve complex policies with custom rules. By combining different conditions in policies, we can comply with specific requirements according to our organization’s standards. Check out the Policy Elements: Condition reference page for more information.
  • Test IAM policies with IAM Policy Simulator: Test existing and new policies quickly and without affecting any environment with the IAM Policy Simulator. Use it to understand your policies better and troubleshoot different issues.
  • Review IAM policies on a regular basis: Cloud environments aren’t static; they evolve over time. For this reason, IAM policies should be reviewed and updated to reflect changes. Following best practices and least privilege, it’s not something that you configure once and forget but requires continuous effort. 
  • Tag IAM policies: Tag your IAM policies to add metadata to them the same way you tag other AWS resources. 
  • Control access to resources with tags: If you have a proper tagging strategy for your AWS resources, you can create elaborate IAM policies that control access to them based on tags. This method becomes handy when you want to separate access to resources based on owners or teams.
  • Use IaC to create your IAM policies: In this tutorial, we have seen how to create IAM policies from the AWS Console, and this might be ok as a starting point, but eventually, you should treat your IAM resources as infrastructure components and apply the same Infrastructure as Code principles as any other AWS resource.

Key Points

We have explored IAM concepts in the context of AWS, and more specifically, we focused on IAM policies. We saw how to create and test policies, assign necessary permissions, and combine different IAM mechanisms to achieve efficient access management controls on AWS.

Here you can learn more about Spacelift integration with AWS, and here about the usage of policies with Spacelift.

Thank you for reading, and I hope you enjoyed this article as much as I did.

The most flexible management platform for Infrastructure as Code

Spacelift is a sophisticated SaaS product for Infrastructure as Code that helps DevOps develop and deploy new infrastructures or changes quickly and with confidence.

Start free trial