🚨 - Update added Tagging 🚀
There are plenty of tools to provision AWS resources, I prefer to use CloudFormation for my daily work. Here are my recommendations that can help you to use CloudFormation more effectively and securely throughout its entire workflow.
When using Continuous Integration and Continuous Deployment patterns for your service, you might want to spin up your CloudFormation Stack more than once in a region or account. Sooner or later you might end up with resources using the same name (eg. IAM Roles, etc.) since these resources are global.
❗️ Try to avoid using names in general and use tags or add a prefix to all your resources.
AWSTemplateFormatVersion: 2010-09-09
Parameters:
Prefix:
Type: String
Resources:
Role:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${Prefix}-role
Validate your templates against the AWS CloudFormation Resource Specification and check your templates for insecure infrastructure - you can use the following tools to do that automatically.
cfn-lint is an open-source command-line tool that validates CloudFormation YAML/JSON templates against the AWS CloudFormation Resource Specification and additional checks. It includes checking valid values for resource properties and best practices.
cfn_nag is an open source command-line tool that performs static analysis of CloudFormation templates. It will search for insecure infrastructure like:
⚠️ If you want to suspend certain findings you can do that by adjusting the metadata of the resource of the template eg:
AWSTemplateFormatVersion: 2010-09-09
Resources:
MyRule:
Type: 'AWS::IAM::Role'
Metadata:
cfn_nag:
rules_to_suppress:
- id: F3
reason: "Complies with own Coding Guidelines"
- id: W11
reason: "Complies with own Coding Guidelines"
- id: W28
reason: "Should never be replaced"
taskcat is a tool that tests AWS CloudFormation templates. It deploys your CloudFormation template in multiple AWS Regions and generates a report with a pass/fail grade for each region.
To avoid false statements and the following errors in deployments use AWS-Specific Parameter Types for existing resources (such as existing Virtual Private Cloud IDs or an EC2 key-pair name), for not existing resources define allowed patterns or allowed values for the parameter.
AWSTemplateFormatVersion: 2010-09-09
Parameters:
BackupFrequency:
Description: Frequency of the Backup
ConstraintDescription: Must be a valid selection
Type: String
Default: Daily
AllowedValues:
- Daily
- Weekly
- None
AccountId:
Type: String
Description: >-
The account number.
ConstraintDescription: Must be a valid Account number
Default: '123456789101'
MinLength: 12
MaxLength: 12
AllowedPattern: '^\d{12}$'
Group resources by their technical context, not the AWS service name that will help you keep the overview of your template(s) and it will help you to make changes to a particular set of resources by using their own process and schedule without affecting other resources.
Use one Master stack and trigger all other stacks as nested stacks. That way you can mix and match different templates but use nested stacks to create a single, unified stack.
Try to use the short-form syntax for all intrinsic functions - there are just a few cases where it's not possible to use short-form syntax. It is way much easier to read the short-form syntax instead of reading the syntax for the full function name.
🚨 Wrong:
Arn:
Fn::GetAtt:
- CostReporterLambda
- Arn
✅ Right:
Arn: !GetAtt CostReporterLambda.Arn
💡 If you use the short form and immediately include another function in the valueToEncode parameter, use the full function name for at least one of the functions eg:
UserData:
Fn::Base64: !Sub |
#!/bin/bash
If you have more than two intrinsic functions successively you can do something like that (😁 yes I know that is not the normal case but someday it might be your case) eg:
UserData: !Base64
Fn::If:
- UseWindows
- Fn::Join:
- ''
- - |
<powershell>
For readability of code, it is better to use !GetAtt in short notation within !Sub.
🚨 Wrong: !Join ['', ['arn:aws:s3:::', !GetAtt [Example, ParameterValue], /*]]
✅ Right: !Sub arn:aws:s3:::${Example.ParameterValue}/*
If you need to deal with several interconnected Ifs
pay attention to the tab stops, otherwise your template will not be invoked. In the following example we have four interconnected if conditions:
SubnetIds:
!If
- UseAZ1
- - Fn::ImportValue: !Sub ${NetworkStackName}-PrivateSubnetAID
- !If
- UseAZ2
- - Fn::ImportValue: !Sub ${NetworkStackName}-PrivateSubnetAID
- Fn::ImportValue: !Sub ${NetworkStackName}-PrivateSubnetBID
- !If
- UseAZ3
- - Fn::ImportValue: !Sub ${NetworkStackName}-PrivateSubnetAID
- Fn::ImportValue: !Sub ${NetworkStackName}-PrivateSubnetBID
- Fn::ImportValue: !Sub ${NetworkStackName}-PrivateSubnetCID
- !If
- UseAZ4
- - Fn::ImportValue: !Sub ${NetworkStackName}-PrivateSubnetAID
- Fn::ImportValue: !Sub ${NetworkStackName}-PrivateSubnetBID
- Fn::ImportValue: !Sub ${NetworkStackName}-PrivateSubnetCID
- Fn::ImportValue: !Sub ${NetworkStackName}-PrivateSubnetDID
- !Ref AWS::NoValue
You can use the Resource Tags property to apply tags to resources, which can help you identify and categorize those resources. 🚨 There are two different notations to tag your aws resources within the template. I think thats because multiple teams are working on providing automations for cloudformation. In the following you are see the two different notations how tags need to be assigned in your template.
Notation 1
Info:
- Need to be used for example in Ec2 or DynamoDB.
- This property type is an array of key-value pairs.
Tags:
- Key: YOURKEY
Value: YOURVALUE
--------------------------------------------------------------
Notation 2
Info:
- Need to be used for example in Signer or SecurityHub.
- This property type is processed as a map of key-value pairs.
Tags:
- YOURKEY: YOURVALUE
--------------------------------------------------------------
If you need to use settings/parameters which you need more than once, I for example, always store them in the AWS Parameter Store and query them in my other templates via the AWS::SSM::Parameter::Value<String>
Parametertype in other templates. For Parameters which I just need to use once, I just handover them via input.