DEV Community

Cover image for Hands-on AWS CloudFormation - Part 5. IAM users, groups and roles
Samira Yusifova
Samira Yusifova

Posted on

Hands-on AWS CloudFormation - Part 5. IAM users, groups and roles

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 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

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:

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
Enter fullscreen mode Exit fullscreen mode

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

or you can use AWS CLI command:

aws iam list-policies --scope AWS
Enter fullscreen mode Exit fullscreen mode

Check out this link 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:

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
Enter fullscreen mode Exit fullscreen mode

Take a look at this GitHub ‘CustomerManagedPolicy’ template. Also check out AWS link for documentation.

Note, PolicyDocument is the only required field in Properties. It is simply a policy (a JSON document). Here 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:

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
Enter fullscreen mode Exit fullscreen mode

Take a look at this GitHub ‘InlinePolicy’ template. Also check out AWS link 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

Useful links

  • Check out the library of IAM identity-based policies here - it can help you to find the JSON policy document as a template for your own policies.
  • Check out IAM Policy Simulator which can be used to test and troubleshoot IAM and resource based policies.
  • Take a look at a formal Grammar 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.

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:

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
Enter fullscreen mode Exit fullscreen mode

Take a look at this GitHub User template. Also check out AWS link 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:

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
Enter fullscreen mode Exit fullscreen mode

Also you can add an inline policy document that is embedded in the specified IAM user, using Policies field:

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: '*'
Enter fullscreen mode Exit fullscreen mode

Take a look at this GitHub UserWithPolicies template.

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.

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
Enter fullscreen mode Exit fullscreen mode

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

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: '*'
Enter fullscreen mode Exit fullscreen mode

Take a look at this GitHub ‘Group’ template. Also check out AWS link 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:

Resources:
  myUserToGroupAddition:
    Type: 'AWS::IAM::UserToGroupAddition'
    Properties:
      GroupName: groupB # existing group name
      Users: # list of existing user names
          - userA
          - userB
Enter fullscreen mode Exit fullscreen mode

Take a look at this GitHub ‘AttachUsersToGroup’ template. Also check out AWS link 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:

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: '/'
Enter fullscreen mode Exit fullscreen mode

Take a look at this GitHub ‘Role’ template. Also check out AWS link 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:

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
Enter fullscreen mode Exit fullscreen mode

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

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: '*'
Enter fullscreen mode Exit fullscreen mode

Take a look at this GitHub ‘RoleWithPolicies’ template.

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:

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: '/'
Enter fullscreen mode Exit fullscreen mode

Take a look at this GitHub ‘InstanceProfile’ template. Also check out AWS link 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 and use them in stacks. Hopefully this hands-on guide will help you excel in the field of AWS CloudFormation.

Alt Text

Top comments (8)

Collapse
 
adeola2020git profile image
Adeola Adefolaju

Hello Samira,

Thanks for this great series.

I am a beginner to CloudFormation. I have a task to create 3 different IAM users with different policies using CloudFormation. I have tried to create them using a single template but it did not work. Do I create 3 different tamplates that would result in 3 different stacks and then nest the stacks? Or how do i achieve this place.

Your suggestion wll be higly appreciated. I have attached my template.

Thank you.

AWSTemplateFormatVersion: 2010-09-09
Description: Creating IAM Users via Cloudformation

Resources:
  AdminUser:
    Type: AWS::IAM::User  
    Properties:
      Policies:
      - PolicyName: admin-access
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action: "*"
            Resource: "*"
          - Effect: Allow
            Action: "iam:ChangePassword"
            Resource: "arn:aws:iam::*:user/${aws:username}"
          - Effect: Allow
            Action: "iam:GetAccountPasswordPolicy"
            Resource: "*"
      LoginProfile:
        Password: pA$$woRd
        PasswordResetRequired: true
      UserName: !Sub ${AWS::StackName}-${AWS::AccountId}

  AdminUserAccessKey:
    Type: AWS::IAM::AccessKey
    Properties:
      UserName: !Ref AdminUser

  EC2User:
    Type: AWS::IAM::User  
    Properties:
      Policies:
      - PolicyName: ec2-full-access
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action: 
            - ec2:*
            Resource: arn:aws:ec2:::*  
          - Effect: Allow
            Action: "iam:ChangePassword"
            Resource: "arn:aws:iam::*:user/${aws:username}"
          - Effect: Allow
            Action: "iam:GetAccountPasswordPolicy"
            Resource: "*"          
      LoginProfile:
        Password: pA$$woRd
        PasswordResetRequired: true
      UserName: !Sub ${AWS::StackName}-${AWS::AccountId}  

  EC2UserAccessKey:
    Type: AWS::IAM::AccessKey
    Properties:
      UserName: !Ref EC2User  

  S3User:
    Type: AWS::IAM::User  
    Properties:
      Policies:
      - PolicyName: list-bucket
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
            - s3:ListBucket
            Resource: arn:aws:s3:::* 
          - Effect: Allow
            Action: "iam:ChangePassword"
            Resource: "arn:aws:iam::*:user/${aws:username}"
          - Effect: Allow
            Action: "iam:GetAccountPasswordPolicy"
            Resource: "*"
      LoginProfile:
        Password: pA$$woRd
        PasswordResetRequired: true      
      UserName: !Sub ${AWS::StackName}-${AWS::AccountId}


  S3UserAccessKey:
    Type: AWS::IAM::AccessKey
    Properties:
      UserName: !Ref S3User    

Outputs:
  AccessKeyID:
    Value: !Ref AdminUserAccessKey 
  SecretAccessKey:
    Value: !GetAtt AdminUserAccessKey.SecretAccessKey
  User:
    Value: !Ref AdminUser
  Password: 
    Value: pA$$woRd

Outputs:
  AccessKeyID:
    Value: !Ref EC2UserAccessKey 
  SecretAccessKey:
    Value: !GetAtt EC2UserAccessKey.SecretAccessKey
  User:
    Value: !Ref EC2User
  Password: 
    Value: pA$$woRd

Outputs:
  AccessKeyID:
    Value: !Ref S3UserAccessKey 
  SecretAccessKey:
    Value: !GetAtt S3UserAccessKey.SecretAccessKey
  User:
    Value: !Ref S3User
  Password: 
    Value: pA$$woRd
Enter fullscreen mode Exit fullscreen mode
Collapse
 
tiamatt profile image
Samira Yusifova • Edited

Hi, sorry for the late reply. Hm, that's a great question! Unfortunately there is no concept of a loop in AWS CloudFormation.

Some ideas to come up with a workaround:

1) Use AWS CLI instead of CloudFormation - here is a great example to store the list of users in Excel file and create multiple IAM users with a single shell script on the Linux

2) Use Terraform instead of CloudFormation - here is a great example to store the list of users as Terraform variable and the loop thru the list to create multiple IAM users inside foreach loop.

3) You can still use CloudFormation but specify each and every user in a template.

Let's look at your code.

a) You see, you are using AdminUser, EC2User and S3User as IAM users (AWS::IAM::User). I would suggest you to create them as three IAM groups (not users) in separate template.

b) Then create another template for multiple users.

Something like the following:

a) template for groups:

AWSTemplateFormatVersion: '2010-09-09'

## =================== DESCRIPTION =================== ##
Description: >-
  AWS CloudFormation sample template. 
  Create a list of IAM Groups

## =================== RESOURCES =================== ##
Resources: 
  # group 1
  AdminGroup:
    Type: AWS::IAM::Group
    Properties: 
      GroupName: AdminGroup 
      Policies:
        - add your policies here
  # group 2
  Ec2AdminGroup:
    Type: AWS::IAM::Group
    Properties: 
      GroupName: Ec2AdminGroup 
      Policies:
        - add your policies here
  # group 3
  S3AdminGroup:
    Type: AWS::IAM::Group
    Properties: 
      GroupName: S3AdminGroup 
      Policies:
        - add your policies here


## =================== OUTPUT =================== ##
Outputs:
  # group 1
  outputName:
    Description: Admin group name
    Value: !Ref AdminGroup
  outputARN:
    Description: Admin group ARN
    Value: !GetAtt AdminGroup.Arn
  # group 2
  outputName:
    Description: Ec2Admin group name
    Value: !Ref Ec2AdminGroup
  outputARN:
    Description: Ec2Admin group ARN
    Value: !GetAtt Ec2AdminGroup.Arn 
  # group 3
  outputName:
    Description: S3Admin group name
    Value: !Ref S3AdminGroup
  outputARN:
    Description: S3Admin group ARN
    Value: !GetAtt S3AdminGroup.Arn  
Enter fullscreen mode Exit fullscreen mode

b) template for users:

AWSTemplateFormatVersion: 2010-09-09
Description: Creating IAM Users via Cloudformation

Resources:
  # user A
  LeonardoDiCaprio:
    Type: AWS::IAM::User  
    Properties:
      UserName: Leonardo DiCaprio
      LoginProfile:
        Password: pA$$woRd
        PasswordResetRequired: true
      Groups: Ec2AdminGroup
  LeonardoDiCaprioAccessKey:
    Type: AWS::IAM::AccessKey
    Properties:
      UserName: !Ref LeonardoDiCaprio

  # user B
  ChristianBale:
    Type: AWS::IAM::User  
    Properties:
      UserName: Christian Bale
      LoginProfile:
        Password: pA$$woRd
        PasswordResetRequired: true
      Groups: Ec2AdminGroup
  ChristianBaleAccessKey:
    Type: AWS::IAM::AccessKey
    Properties:
      UserName: !Ref ChristianBale  

  # add more users 

Outputs:
  # not a good practice to expose access keys as CloudFormation output - it's better to give a user enough permission to access their access keys 
  LeonardoDiCaprioAccessKeyID:
    Value: !Ref LeonardoDiCaprioAccessKey 
  LeonardoDiCaprioSecretAccessKey:
    Value: !GetAtt LeonardoDiCaprioAccessKey.SecretAccessKey
  ChristianBaleAccessKeyID:
    Value: !Ref ChristianBaleAccessKey 
  ChristianBaleSecretAccessKey:
    Value: !GetAtt ChristianBaleAccessKey.SecretAccessKey  
  # add more users' outputs
Enter fullscreen mode Exit fullscreen mode

If you know better solution, please let me know!

Collapse
 
madhuvinod23 profile image
madhuvinod23

Great series Samira.. Content is relevant even for novices..

Collapse
 
tiamatt profile image
Samira Yusifova

It means the world to me! Thanks a lot!

Collapse
 
camilo_martinez_f9e33dc55 profile image
Camilo Martinez

Hi Samira

Do you have any suggestion to create grant privileges in a group for developers?

Collapse
 
tiamatt profile image
Samira Yusifova • Edited

Hi, sorry for the delayed response. Well, there is not a one size fits all approach. It depends on your company's priorities and needs. Do you need to give your developers an access to EC2 and RDS only? Or you are ok to give them full access to AWS services and resources (using AdministratorAccess build-in policy). However, as a rule of thumb, I would suggest you to follow:

  • Security best practices in IAM link
  • Business use cases for IAM link
  • Delegate permission management to developers by using IAM permissions boundaries link
Collapse
 
satizzzzz profile image
Sathish Kumar

Awesome series of CloudFormation :) . Thanks

Collapse
 
tiamatt profile image
Samira Yusifova

Thank you so much, Sathish! I am so glad you found the information useful!