Hands-on AWS CloudFormation - Part 5. IAM users, groups and roles
Tags: aws, cloud, security, cloudformation
Published: 2021-01-04 07:02:14

In the “Hands-on AWS CloudFormation” series we continue to create small templates by provisioning different types of AWS resources with AWS CloudFormation. In the end of this series we can turn the small templates into building blocks for *full stack templates*. For example, in [Part 4](https://dev.to/tiamatt/hands-on-aws-cloudformation-part-4-create-vpc-with-private-and-public-subnets-85d) we’ve learned how to create a VPC with private and public subnets - ultimately, it will help us to create a secure, highly reliable and fault-tolerant system using multiple EC2 instances in a private network and ancillary services such as Auto Scaling and Elastic Load Balancing. But to get to the final results we need to take a few baby steps at a time. That being said, for today’s lesson we will cover the IAM part of AWS using CloudFormation.

## **Don’t ignore IAM** 

AWS IAM is something you need to take seriously if you are working in the AWS space. Think of it as an essential gate-keeper of AWS. This is the place where you would administer authentication and authorization for AWS’s environments and services.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/wewxsyxuvgn7gfxy9wnt.jpg)

It’s admirable that AWS IAM follows *an incredibly granular approach* in providing permissions and access control within your environments. With that, let's take advantage of the great cloud platform and  create the basic components of IAM with CloudFormation, such as:
* Policies
* Users
* Groups
* Roles

## **Creating IAM policies**

We manage access in AWS by creating policies and attaching them to **IAM identities** (users, groups of users, and roles) or AWS resources. **Policies** specify a set of permissions. Permissions in the policies determine whether the request is allowed or denied. 

There are three types of IAM policies:
* AWS Managed Policy
* Customer Managed Policy
* Inline Policy

### AWS Managed Policy

**AWS Managed Policy** is a standalone policy that is created and administered **by AWS**. AWS managed policies could be reused between IAM entities (users, groups, or roles) and **cannot be modified**. 

Here is an example of how you can attach AWS managed policy to a new role:

```YAML
Resources:
  myRole: # a new role
    Type: 'AWS::IAM::Role'
    Properties: 
      # ... some code here ...
      ManagedPolicyArns: # list of ARNs of IAM managed policies that you want to attach to the role
        - arn:aws:iam::aws:policy/AWSCloud9Administrator # provides administrator access to AWS Cloud9
```
You can get a needed AWS managed policy via AWS Management Console by navigating to IAM -> Policies, then filter by Policy type checking ‘AWS managed’ checkbox:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ffispi2uc8gvif30nzju.png)

or you can use AWS CLI command:

```YAML
aws iam list-policies --scope AWS
```
Check out [this link](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html) if you need to install AWS CLI.



### Customer Managed Policy

**Customer Managed Policy** is a standalone policy  that is created **by a user**. Customer managed policies could be reused between IAM entities and **can be modified**.

Let’s create a customer managed policy that gives read only access to EC2 instance:

```YAML
Resources:
  myCustomerManagedPolicyForEC2:
    Type: 'AWS::IAM::ManagedPolicy'
    Properties:
      ManagedPolicyName: customerManagedEC2ReadOnlyPolicy # give a name to this policy
      Description: Customer managed policy for read only access to EC2 instance
      Path: '/'
      PolicyDocument: # (required) JSON policy document 
        Version: '2012-10-17'
        Statement: # allow read only access to EC2 instance
          - Effect: Allow
            Action: 
              - 'ec2:Describe'
            Resource: '*'
      # IAM entities (Groups, Roles, and Users) are optional properties
      Users: # attach this policy to the list of existing users
          - userA
          - userB
      Groups: # attach this policy to the list of existing groups
          - groupA
          - groupB
      Roles: # attach this policy to the list of existing roles
          - roleA
          - roleB
```
Take a look at this [GitHub ‘CustomerManagedPolicy’ template](https://github.com/Tiamatt/Mastering-AWS-CloudFormation/blob/main/IAM/Policies-CustomerManagedPolicy.yaml). Also check out [AWS link](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-managedpolicy.html) for documentation.

Note, **PolicyDocument** is the only required field in Properties. It is simply a policy (a JSON document). [Here](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_examples.html) is my favorite link to the great list of example policies. Usually you need to provide policies in JSON format in IAM. However, for AWS CloudFormation templates formatted in YAML, you can provide the policy in JSON or YAML format. AWS CloudFormation always *converts a YAML policy to JSON format* before submitting it to IAM.

What is the **Path**? If you are using the IAM API or AWS CLI to create IAM resources, you can also give some resources an optional path, e.g. ‘*/companyA/departmentB/projectC/*’ to match your company's organizational structure. You could then create a policy to allow all users in that path to access the policy simulator API.

### Inline Policy

**Inline Policy** is a policy that is created **by a user** and **embedded directly** to IAM entities. Inline policies **cannot be reused** in different IAM entities as it emphasizes direct one-to-one relationship between entity and the policy itself. Once the entity is deleted, inline policies attached to it get removed as well. 

Let’s create an inline policy that gives read only access to all S3 buckets:

```YAML
Resources:
  myInlinePolicyForS3ReadOnly:
    Type: 'AWS::IAM::Policy'
    Properties:
      PolicyName: inlineS3ReadOnlyPolicy # (required) give a name to this policy
      PolicyDocument: # (required) JSON policy document
        Version: '2012-10-17'
        Statement: # allow read only access to all S3 buckets
          - Effect: Allow
            Action:
              - 's3:Get*'
              - 's3:List*'
            Resource: '*'
      # Note, Groups, Roles, and Users fields are optional. However, you must specify at least one of these fields
      Users: # attach this policy to the list of existing users
          - userA
          - userB
      Groups: # attach this policy to the list of existing groups
          - groupA
          - groupB
      Roles: # attach this policy to the list of existing roles
          - roleA
          - roleB
```
Take a look at this [GitHub ‘InlinePolicy’ template](https://github.com/Tiamatt/Mastering-AWS-CloudFormation/blob/main/IAM/Policies-InlinePolicy.yaml). Also check out [AWS link](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-policy.html) for documentation.

Note, **PolicyName** and **PolicyDocument** are the only required fields in Properties. **Roles**, **Users**, and **Groups** fields are optional. But you must specify **at least one** of these fields. Thus, if you run the stack ignoring all three fields (Groups, Roles, and Users), CloudFormation will roll it back notifying of an error:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/26rxyne6mxqpdnabdpcp.png)

### Useful links
* Check out the library of  IAM identity-based policies [here](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_examples.html) - it can help you to find the JSON policy document as a template for your own policies.
* Check out [IAM Policy Simulator](https://policysim.aws.amazon.com) which can be used to test and troubleshoot IAM and resource based policies.
* Take a look at a formal [Grammar](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_grammar.html) for the language used to create JSON policies in IAM.
* If you need to use the guide for a visual editor to create and modify your IAM policies, check out this [article](https://aws.amazon.com/blogs/security/use-the-new-visual-editor-to-create-and-modify-your-aws-iam-policies/).

## **Creating IAM users and groups**

**IAM user** is a person that needs to interact with your AWS resources or services either from the AWS Console or with the AWS CLI. When you create a new user, no credentials are assigned, and the user does not have any permission to access your AWS resources.

Now, let’s create some IAM users with AWS Cloudformation:

```YAML
Resources:
  myUser:
    Type: 'AWS::IAM::User'
    Properties:
      UserName: userA # give a name to this user
      LoginProfile: # specify a password for this user
        Password: pa$$w0rd
        PasswordResetRequired: true # make this user to set a new password on next sign-in
      Path: '/'
      Groups: # attach this user to the list of existing groups
          - groupA
          - groupB
```
Take a look at this [GitHub User template](https://github.com/Tiamatt/Mastering-AWS-CloudFormation/blob/main/IAM/Users-User.yaml).  Also check out [AWS link](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-user.html) for documentation.

**LoginProfile** contains the user's password. You can also use **PasswordResetRequired** to specify whether the user is required to set a new password on next sign-in.

As mentioned before, by default any new IAM user is created with **no access** to any AWS services (non-explicit deny). You can set permissions by adding needed policies to the user (please make sure to follow the standard security advice of granting least privilege).

In order to attach IAM managed policies to a user, use **ManagedPolicyArns** field:

```YAML
Resources:
  myUser:
    Type: 'AWS::IAM::User'
    Properties:
      UserName: userB # give a name to this user
      ManagedPolicyArns: # list of ARNs of IAM managed policies that you want to attach to the user
        - arn:aws:iam::aws:policy/AWSCloud9Administrator # provides administrator access to AWS Cloud9
        - arn:aws:iam::111111111111:policy/myCustomerManagedPolicy # use your own customer managed policy
```
Also you can add an inline policy document that is embedded in the specified IAM user, using **Policies** field:

```YAML
Resources:
  myUser:
    Type: 'AWS::IAM::User'
    Properties:
      UserName: userC # give a name to this user
      Policies: # list of inline policy documents that are embedded in the user
        - PolicyName: inlineS3ReadOnlyPolicy # give a unique name to this policy
          PolicyDocument: # JSON policy document
            Version: '2012-10-17'
            Statement: # allow read only access to all S3 buckets
              - Effect: Allow
                Action:
                  - 's3:Get*'
                  - 's3:List*'
                Resource: '*'
```
Take a look at this [GitHub UserWithPolicies template](https://github.com/Tiamatt/Mastering-AWS-CloudFormation/blob/main/IAM/Users-UserWithPolicies.yaml).

But what if you need to give Admin permissions to a hundred of users? Are you going to attach a new policy to each and every user? If so, what if tomorrow you want to change Admin permissions to  something else? The easiest way to do that is to create an IAM group called Admins and give that group the types of permissions that administrators typically need. Then add users to that group. Those added users will then automatically have all of their group’s permissions. Without a group, listing permissions for every single user will be a huge hassle.

**Group** is a collection of IAM users. Groups are useful when we want to manage permissions for a group of users.

If you already have some groups, you can attach them to *userA* specifying a list of groups in **Groups** field. 

```YAML
Resources:
  myUser:
    Type: 'AWS::IAM::User'
    Properties:
      UserName: userD # give a name to this user
      Groups: # attach this user to the list of existing groups
          - groupA
          - groupB
```

If not, then let’s create a new group:

```YAML
Resources:
  myGroup:
    Type: 'AWS::IAM::Group'
    Properties:
      GroupName: ApiDevelopers # give a name to this group
      Path: '/'
      ManagedPolicyArns: # list of ARNs of IAM managed policies that you want to attach to the group
        - arn:aws:iam::aws:policy/AWSCloud9Administrator # provide administrator access to AWS Cloud9
        # - arn:aws:iam::111111111111:policy/customerManagedBlahBlahPolicy # use your own customer managed policy specifying its ARN
      Policies: # list of inline policy documents that are embedded in the group
        - PolicyName: inlineCloudWatchLogsPolicy # give a unique name to this policy
          PolicyDocument: # JSON policy document
            Version: '2012-10-17'
            Statement: # provide write permissions to CloudWatch Logs
              - Effect: Allow
                Action:
                  - 'logs:CreateLogGroup'
                  - 'logs:CreateLogStream*'
                  - 'logs:PutLogEvents'
                Resource: '*'
```
Take a look at this [GitHub ‘Group’ template](https://github.com/Tiamatt/Mastering-AWS-CloudFormation/blob/main/IAM/Groups-Group.yaml).  Also check out [AWS link](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-group.html) for documentation.

You can specify IAM managed policies using **ManagedPolicyArns** field and inline policies using **Policies** field.

The last step is to learn how to attach multiple existing users to an existing group. For that you might need to use *AWS::IAM::UserToGroupAddition*:

```YAML
Resources:
  myUserToGroupAddition:
    Type: 'AWS::IAM::UserToGroupAddition'
    Properties:
      GroupName: groupB # existing group name
      Users: # list of existing user names
          - userA
          - userB
```
Take a look at this [GitHub ‘AttachUsersToGroup’ template](https://github.com/Tiamatt/Mastering-AWS-CloudFormation/blob/main/IAM/Groups-AttachUsersToGroup.yaml).  Also check out [AWS link](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-iam-addusertogroup.html ) for documentation.

Note, a group does not have security credentials as well as cannot access and manage AWS’s resources - it just helps to manage user permissions.

## **Creating IAM roles**

**IAM roles** allow you to delegate access to users or services that normally don't have access to your organization's AWS resources. IAM users or AWS services can assume a role to obtain **temporary** security credentials that can be used to make AWS API calls. Consequently, you don't have to share long-term credentials or define permissions for each entity that requires access to a resource.

Creating a new role is similar to delegate access permissions to those trusted entities without having to share access keys. A role cannot make direct requests to AWS services, but the entity it attached to.

Let’s create a simple role for an EC2 instance:

```YAML
Resources:
  myRole:
    Type: 'AWS::IAM::Role'
    Properties: 
      RoleName: roleA # give a name to this role
      Description: IAM role for EC2 instance
      AssumeRolePolicyDocument: # (required) only one trust policy with a role
        Version: '2012-10-17'
        Statement: 
          - 
            Effect: Allow
            Principal: 
              Service: 
                - 'ec2.amazonaws.com'
            Action: 
              - 'sts:AssumeRole'
      MaxSessionDuration: 3600 # in seconds, 1 hour
      Path: '/'
```
Take a look at this [GitHub ‘Role’ template](https://github.com/Tiamatt/Mastering-AWS-CloudFormation/blob/main/IAM/Roles-Role.yaml). Also check out [AWS link](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html) for documentation.

Note, **AssumeRolePolicyDocument** is the only required field in Properties. It is the trust policy that is associated with this role. **Trust policies** define which entities can assume the role. But you can associate *only one* trust policy with a role.

**MaxSessionDuration** (in seconds) helps you to set the maximum session duration for a new role. If you do not specify a value for this setting, the default maximum of one hour (3600 seconds) is applied. This setting can have a value from 1 hour to 12 hours.

Similar to users, you can set permissions for the role by adding needed policies to it (and again, please follow the standard security advice of granting least privilege).

In order to attach IAM managed policies to the role, use **ManagedPolicyArns** field:

```YAML
myRole:
    Type: 'AWS::IAM::Role'
    Properties: 
      RoleName: roleB # give a name to this role
      AssumeRolePolicyDocument: # (required) only one trust policy with a role
      # ... some code here ...
      ManagedPolicyArns: # list of ARNs of IAM managed policies that you want to attach to the role
        - arn:aws:iam::aws:policy/AWSCloud9Administrator # provides administrator access to AWS Cloud9
        - arn:aws:iam::111111111111:policy/myCustomerManagedPolicy # use your own customer managed policy
```

Also you can add an inline policy document that is embedded in the role, using **Policies** field:

```YAML
myRole:
    Type: 'AWS::IAM::Role'
    Properties: 
      RoleName: roleB # give a name to this role
      AssumeRolePolicyDocument: # (required) only one trust policy with a role
      # ... some code here …
      Policies: # list of inline policy documents that are embedded in the role
        - PolicyName: inlineS3ReadOnlyPolicy # give a unique name to this policy
          PolicyDocument: # JSON policy document
            Version: '2012-10-17'
            Statement: # allow read only access to all S3 buckets
              - Effect: Allow
                Action:
                  - 's3:Get*'
                  - 's3:List*'
                Resource: '*'
```

Take a look at this [GitHub ‘RoleWithPolicies’ template](https://github.com/Tiamatt/Mastering-AWS-CloudFormation/blob/main/IAM/Roles-RoleWithPolicies.yaml).

## **Creating Instance Profile**

You should use an **Instance Profile** to pass an IAM role to an EC2 instance. But what is the difference between an AWS role and an instance profile?

* **Roles** are designed to be 'assumed' by other principals which do define 'who am I?', such as users, AWS services, and EC2 instances.

* **Instance profile**, on the other hand, defines 'who am I?' Just like an IAM user represents a person, an instance profile represents EC2 instances. The only permissions an EC2 instance profile has is the power to assume a role.

Here is how you can create a new instance profile in order to attach your role to EC2 instance:

```YAML
Resources: 
  myInstanceProfile: 
    Type: 'AWS::IAM::InstanceProfile'
    Properties: 
      InstanceProfileName: instanceProfileA
      Roles: # (required) existing role name to associate with the instance profile 
      # Note: only one role can be assigned to an EC2 instance at a time, but type is 'List of String'
        - roleD
      Path: '/'
``` 
Take a look at this [GitHub ‘InstanceProfile’ template](https://github.com/Tiamatt/Mastering-AWS-CloudFormation/blob/main/IAM/Roles-InstanceProfile.yaml). Also check out [AWS link](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-instanceprofile.html) for documentation.

Note, there are some limitations - an instance profile can contain *only one IAM role*, although a role can be included in multiple instance profiles. This limit of one role per instance profile cannot be increased. You can remove the existing role and then add a different role to an instance profile.

## **Review**

AWS IAM provides a number of security features to consider as you develop and implement your own security policies.

In this article, we walked through how to start creating IAM policies and IAM identities (users, groups, and roles) using AWS CloudFormation. You can download and use IAM templates from [my GitHub account](https://github.com/Tiamatt/Mastering-AWS-CloudFormation/tree/main/IAM) and use them in stacks. Hopefully this hands-on guide will help you excel in the field of AWS CloudFormation.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/nqhbrw19lmsf4dbn3n3y.jpg)