We already know that CloudFormation performs API calls when we create or update the stack. Now the question is, does CloudFormation have the same powers as a root user?
When you work with production-grade AWS accounts, you need to control access to your environment for both humans (yourself and your coworkers) and machines (build systems, AWS resources, and so on). That is why controlling access for CloudFormation is important.
By default, when the user runs stack creation, they invoke the API method cloudformation:CreateStack
. CloudFormation will use that user's access to invoke other API methods during the stack creation.
This means that if our user has an IAM policy with an allowed action ec2:*
, but attempts to create an RDS instance with CloudFormation, the stack will fail to create with an error, User is unauthorized to perform this action
.
Let's try this. We will create an IAM role with ec2:*
, assume that role, and try to create the same bucket stack:
Important note
We already have an IAM user Admin
in our AWS account and we will add that user as a principal.
MyIamRole.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: "This is a dummy role"
Resources:
IamRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Sid: AllowAssumeRole
Effect: Allow
Principal:
AWS:
- !Join
- ""
- - "arn:aws:iam::"
- !Ref "AWS::AccountId"
- ":user/Admin"
Action: "sts:AssumeRole"
ManagedPolicyArns:
- "arn:aws:iam::aws:policy/AmazonEC2FullAccess"
- "arn:aws:iam::aws:policy/AWSCloudformationFullAccess"
Outputs:
IamRole:
Value: !GetAtt IamRole.Arn
If we create this stack, assume that role, and try to create the previous mybucket
stack, it will fail to create with an error. Let's take a look:
$ aws cloudformation create-stack \
--stack-name iamrole \
--capabilities CAPABILITY_IAM \
--template-body file://IamRole.yaml
$ IAM_ROLE_ARN=$(aws cloudformation describe-stacks \
--stack-name iamrole \
--query "Stacks[0].Outputs[?OutputKey=='IamRole'].OutputValue" \
--output text)
$ aws sts assume-role --role-arn $IAM_ROLE_ARN \
--role-session-name tmp
# Here goes the output of the command. I will store the access credentials in the env vars
$ export AWS_ACCESS_KEY_ID=…
$ export AWS_SECRET_ACCESS_KEY=…
$ export AWS_SESSION_TOKEN=…
$ aws cloudformation create-stack \
--stack-name mybucket \
--template-body file://MyBucket.yaml
We will see the following error on the AWS console:
Figure 1.1 – CloudFormation console – stack events
On the other hand, we cannot provide everyone with an AdminAccess
policy, so we need to find a way to use CloudFormation with the necessary permissions while only letting CloudFormation use those permissions.
CloudFormation supports service roles. Service roles are the IAM roles that are used by different AWS services (such as EC2, ECS, Lambda, and so on). CloudFormation service roles are used by CloudFormation during stacks and StackSets operations—creation, update, and deletion:
- Let's create a specific role for CloudFormation:
CfnIamRole.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: "This is a CFN role"
Resources:
IamRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Sid: AllowAssumeRole
Effect: Allow
Principal:
Service: "cloudformation.amazonaws.com"
Action: "sts:AssumeRole"
ManagedPolicyArns:
- "arn:aws:iam::aws:policy/AdministratorAccess"
Outputs:
IamRole:
Value: !GetAtt IamRole.Arn
- We create this stack for the service role and obtain the CloudFormation role ARN:
$ aws cloudformation create-stack \
--stack-name cfniamrole \
--capabilities CAPABILITY_IAM \
--template-body file://CfnIamRole.yaml
$ IAM_ROLE_ARN=$(aws cloudformation describe-stacks \
--stack-name cfniamrole \
--query "Stacks[0].Outputs[?OutputKey=='IamRole'].OutputValue" \
--output text)
- Now we run the creation of the stack, which will use our role, specifying the
Role ARN
:$ aws cloudformation create-stack \
--stack-name mybucket \
--template-body file://MyBucket.yaml \
--role-arn $IAM_ROLE_ARN
- After a while, we can verify that our stack has been created, and we see our bucket!
$ aws s3 ls
# Output
2019-10-16 14:14:24 mybucket-mybucket-jqjpr6wmz19q
Before we continue, don't forget to clean your account:
$ for i in mybucket iamrole cfniamrole; do aws cloudformation delete-stack --stack-name $i ; done
Important note
Note that in the preceding example, we provide the CloudFormation role with an AdminPolicy
(the one that is provided by AWS by default).
In production-grade systems, we want to allow CloudFormation only those privileges that are required for the stack.
There are two permission schemas that are being applied for CloudFormation roles:
- We have a certain list of services that we can use (for example, EC2, RDS, VPC, DynamoDB, S3, and so on).
- Each template/stack combination will use only those services it needs—for example, if we declare Lambda functions with Simple Notification Service (SNS), then we should create the role with policies only for Lambda and SNS.