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.
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
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:
or you can use AWS CLI command:
aws iam list-policies --scope AWS
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
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
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:
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
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
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: '*'
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
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: '*'
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
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: '/'
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
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: '*'
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: '/'
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.
Top comments (8)
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.
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
andS3User
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:
b) template for users:
If you know better solution, please let me know!
Great series Samira.. Content is relevant even for novices..
It means the world to me! Thanks a lot!
Hi Samira
Do you have any suggestion to create grant privileges in a group for developers?
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:
Awesome series of CloudFormation :) . Thanks
Thank you so much, Sathish! I am so glad you found the information useful!