Using parameters and setting defaults

  • Try to parameterize as much as possible, making sure to cover settings that you expect users to customize. Some examples are CIDR blocks, FQDN names, host names, instance types, and storage volume sizes.
  • When setting defaults for instance types, make sure that the default is available in all (or the majority of) AWS Regions. To check availability, see the tables in the Amazon EC2 Pricing webpages.

Naming, labeling, and grouping

  • For parameter names, use Pascal case, and begin with an uppercase letter (e.g., KeyPairName).
  • Use parameter groupings and display labels. This AWS CloudFormation feature enables you to display parameters in an intuitive way. For example, you could place all the network-related information in a category called Network Configuration and your database configuration parameters in a category called Database Configuration. For instructions on setting up groups and labels, see the AWS::CloudFormation::Interface resource.
  • Name subnets (and their references) public or private as appropriate:
    • Public subnets have a direct Internet gateway route in the route table associated with them. Instances in this subnet can make inbound and outbound use of a public or Elastic IP address.
    • Private subnets have an indirect route to the Internet via a NAT gateway or NAT instance that resides in a public subnet. These instances are reachable only through their private IP address. (For more on this, see the security best practices section.)

Numbering parameters

  • For items that are entities, the parameter number should directly follow the entity name. For example, if you have two XYZ instances, they should be named XYZ1 and XYZ2.
  • If a feature is attached, extends, or complements the entity, its name should follow the entity name; for example, XYZ1EIP and XYZ2EIP.

Using parameter types

  • Use AWS-specific parameter types as much as possible. This enables users to pick values from a dropdown list. See the AWS documentation for a list.

Using the DependsOn attribute

  • Make sure to use the DependsOn attribute appropriately across resources to control the order of resource creation. Also, be mindful of the situation where it is required. For more information around these special cases, see the AWS CloudFormation documentation.

Adding Quick Start portability parameters

  • Include the standard parameters for the Quick Start S3 bucket name and key prefix. Set the default value for the key prefix to company-name/product-name/latest/, e.g., atlassian/jira/latest/.
...
   "QSS3BucketName": {
      "AllowedPattern": "^[0-9a-zA-Z]+([0-9a-zA-Z-]*[0-9a-zA-Z])*$",
      "ConstraintDescription": "Quick Start bucket name can include numbers, lowercase letters, uppercase letters, and hyphens (-). It cannot start or end with a hyphen (-).",
      "Default": "quickstart-reference",
      "Description": "S3 bucket name for the Quick Start assets. Quick Start bucket name can include numbers, lowercase letters, uppercase letters, and hyphens (-). It cannot start or end with a hyphen (-).",
      "Type": "String"
   },
   "QSS3KeyPrefix": {
      "AllowedPattern": "^[0-9a-zA-Z-/]*$",
      "ConstraintDescription": "Quick Start key prefix can include numbers, lowercase letters, uppercase letters, hyphens (-), and forward slash (/).",
      "Default": "microsoft/rdgateway/latest/",
      "Description": "S3 key prefix for the Quick Start assets. Quick Start key prefix can include numbers, lowercase letters, uppercase letters, hyphens (-), and forward slash (/).",
      "Type": "String"
   },
...
  • Use the AWS CloudFormation Fn::Sub function to join the Quick Start S3 bucket name and key prefix values. For example:
   "Fn::Sub": "https://${QSS3BucketName}.s3.amazonaws.com/${QSS3KeyPrefix}scripts/some-script.sh"
  • Use an IAM role with a policy that allows GetObject to ${QSS3BucketName}/${QSS3KeyPrefix}*; for example:
   "BastionHostRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
         "Policies": [
            {
               "PolicyDocument": {
                  "Version": "2012-10-17",
                  "Statement": [
                     {
                        "Action": [
                           "s3:GetObject"
                        ],
                        "Resource": {
                           "Fn::Sub": "arn:aws:s3:::${QSS3BucketName}/${QSS3KeyPrefix}*"
                        },
                        "Effect": "Allow"
                     }
                  ]
               },
               "PolicyName": "aws-quick-start-s3-policy"
            }
         ],
         "Path": "/",
         "AssumeRolePolicyDocument": {
            "Statement": [
               {
                  "Action": [
                     "sts:AssumeRole"
                  ],
                  "Principal": {
                     "Service": [
                        "ec2.amazonaws.com"
                     ]
                  },
                  "Effect": "Allow"
               }
            ],
            "Version": "2012-10-17"
         }
      }
   },
   "BastionHostProfile": {
      "Type": "AWS::IAM::InstanceProfile",
      "Properties": {
         "Roles": [
            {
               "Ref": "BastionHostRole"
            }
         ],
         "Path": "/"
      }
   },
  • Associate the instance with the instance profile from the IAM role; for example, based on the previous code:
   "BastionLaunchConfiguration": {
      "Type": "AWS::AutoScaling::LaunchConfiguration", or "Type": "AWS::EC2::Instance",
...
      "Properties": {
...
         "IamInstanceProfile": {
            "Ref": "BastionHostProfile"
         },
...
  • Use AWS CloudFormation authentication with the IAM role, and use cfn-init to access resources; for example:
"Metadata": {
  "AWS::CloudFormation::Authentication": {
     "S3AccessCreds": {
        "type": "S3",
        "roleName": {
           "Ref": "BastionHostRole"
        },
        "buckets": [
           {
              "Ref": "QSS3BucketName"
           }
        ]
     }
  },
  "AWS::CloudFormation::Init": {
     "config": {
        "files": {
           "/tmp/bastion_bootstrap.sh": {
              "source": {
                 "Fn::Sub": "https://${QSS3BucketName}.s3.amazonaws.com/${QSS3KeyPrefix}scripts/bastion_bootstrap.sh"
              },
              "mode": "000550",
              "owner": "root",
              "group": "root",
              "authentication": "S3AccessCreds"
           }
        },
...
  "UserData": {
     "Fn::Base64": {
        "Fn::Join": [
           "",
           [
              "#!/bin/bash\n",
              "export PATH=$PATH:/usr/local/bin\n",
              "which pip &> /dev/null\n",
              "if [ $? -ne 0 ] ; then\n",
              "    echo \"PIP NOT INSTALLED\"\n",
              "    [ `which yum` ] && $(yum install -y epel-release; yum install -y python-pip) && echo \"PIP INSTALLED\"\n",
              "    [ `which apt-get` ] && apt-get -y update && apt-get -y install python-pip && echo \"PIP INSTALLED\"\n",
              "fi\n",
              "pip install --upgrade pip &> /dev/null\n",
              "pip install awscli --ignore-installed six &> /dev/null\n",
              "easy_install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-latest.tar.gz\n",
...
              "cfn-init -v --stack ",
              {
                 "Ref": "AWS::StackName"
              },
              " --resource BastionLaunchConfiguration --region ",
              {
                 "Ref": "AWS::Region"
              },
              "\n",
              "cfn-signal -e $? --stack ",
              {
                 "Ref": "AWS::StackName"
              },
              " --resource BastionAutoScalingGroup --region ",
              {
                 "Ref": "AWS::Region"
              },
              "\n"
           ]
        ]
     }
  }