Mastering AWS CloudFormation
上QQ阅读APP看书,第一时间看更新

Understanding CloudFormation IAM permissions

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

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:

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

  2. 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)

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

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