CloudFormation Best Practices

Todd Murchison edited this page Feb 3, 2015 · 1 revision

(my) Top Ten

  1. Validate your templates
  2. Use parameter constraints
  3. Use AWS-specific parameter types
  4. Use NoEcho to hide sensitive data
  5. Include an Outputs section
  6. Use CloudFormation::Init to install packages and customize the OS
  7. Use stack policies to protect critical resources
  8. Control stack access with IAM
  9. Verify service limits
  10. Log CloudFormation API calls with CloudTrail


Validate your templates

Validating your templates will help catch syntax and dependency issues before using them.

aws cloudformation validate-template \
 --template-body file://myCFN.template.json

Use parameter constraints

Parameter constraints can help catch invalid values before launching a stack.


"OwnerService"                  : {
  "Type"                        : "String",
  "Default"                     : "eng",
  "Description"                 : "Owner or service name.",
  "AllowedPattern"              : "[-a-z0-9]*",
  "ConstraintDescription"       : "Must be lowercase, no spaces, dashes ok."
}

"BastionCount" : {
  "Type"                        : "Number",
  "Default"                     : "1",
  "MinValue"                    : "0",
  "MaxValue"                    : "1",
  "Description"                 : "Enter '1' to launch a bastion host, or '0' to terminate."
}

Use AWS-specific parameter types

This example will list all the existing key pair names that are available to choose from in the region where you're launching the stack.


UserKeys" : {
  "Type"                        : "AWS::EC2::KeyPair::KeyName",
  "Description"                 : "Your instance key pair name."
}

This example allows you to choose one or more existing Subnet Ids.


"PubSubnets" : {
  "Type"                        : "List<AWS::EC2::Subnet::Id>,"
  "Description"                 : "Public Subnet IDs."
}

Use NoEcho to hide sensitive data

Sensitive information such as usernames and passwords will not be displayed.

Note: NoEcho is set to false by default.


"DBPassword" : {
  "NoEcho"                      : "true",
  "Description"                 : "Master password",
  "Type"                        : "String",
  "MinLength"                   : "8",
  "MaxLength"                   : "16"
}

Include an Outputs section

Organize and easily find important information. The Outputs section, when used with the Fn::GetAtt intrinsic function, returns the value of one or more attributes. This code snippet will return the public IP address of our NAT instance.


"Outputs" : {
  "NatId" : {
  "Description"                 : "NAT Id",
  "Value"                       : { "Ref" : "ec2NATInstance" }
  },
  "NatIP" : {
  "Description"                 : "NAT Public IP",
  "Value"                       : { "Fn::GetAtt" : [ "ec2NATInstance", "PublicIp" ] }
  }
}

Fn::GetAtt Attributes


Use CloudFormation::Init to install packages and customize the OS

This example configures iptables and ensures that it's running after a restart. It disables the sendmail service. cfn-init is called from the UserData section. Also see, cfn-hup.


"asBastionlaunch" : {
  "Type" : "AWS::AutoScaling::LaunchConfiguration",
    "Metadata" : {
    "Comment" : "Setup iptables and disable unwanted services",
      "AWS::CloudFormation::Init" : {
        "config" : {
          "commands" : {
            "iptables.sh" : {
              "command" : {
                "Fn::Join" : [ "",
                [ "/sbin/iptables -F\n", 
                  "/sbin/iptables -P INPUT DROP\n",
                  "/sbin/iptables -P FORWARD DROP\n",
                  "/sbin/iptables -P OUTPUT ACCEPT\n",
                  "/sbin/iptables -A INPUT -p tcp --dport 22 -j ACCEPT\n",
                  "/sbin/iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT\n",
                  "/sbin/iptables -A INPUT -i lo -j ACCEPT\n",
                  "/sbin/service iptables save\n" ]             
                ]
          } } },
          "services" : {
            "sysvinit" : {
              "sendmail" : {
                "enabled" : "false",
                "ensureRunning" : "false"
              },
              "iptables" : {
                "enabled" : "true",
                "ensureRunning" : "true"
          } } }
        }
      }
    },
  "Properties" : {
   ...
    "UserData" : {
      "Fn::Base64" : {
        "Fn::Join" : [ "",
        [ "#!/bin/bash -ex\n",
          "yum update -y aws-cfn-bootstrap\n",
          "yum update -y\n",

          "# Work on Metadata.\n",
          "/opt/aws/bin/cfn-init -v",
          " --stack ", { "Ref" : "AWS::StackName" },
          " --resource asBastionlaunch\n" ]
        ]
      }
    }
  }

Use stack policies to protect critical resources

The policy below prevents the replacement of our NAT server instances during a stack update.


{
  "Statement" : [
  {
    "Effect" : "Deny",
    "Action" : "Update:Replace",
    "Principal" : "*",
    "Resource" : "LogicalResourceId/*NATServer*"
    },
  {
    "Effect" : "Allow",
    "Action" : "Update:*",
    "Principal" : "*",
    "Resource" : "*"
   }
  ]
}

Control stack access with IAM

The following prevents an IAM user assigned the policy from updating or deleting stacks with "VPC" in the stack name.


{
  "Version" : "2012-10-17",
  "Statement" : [
    {
      "Action" : [
      "cloudformation:*"
      ],
      "Effect" : "Allow",
      "Resource" : "*"
    },
    {
      "Action" : [
      "cloudformation:DeleteStack",
      "cloudformation:UpdateStack"
      ],
      "Effect" : "Deny",
      "Resource" : "arn:aws:cloudformation:us-west-2:XXXXXXXX2745:stack/*VPC*"
    }
  ]
}

Verify service limits

Check resource quotas via Trusted Advisor >> Performance >> Service Limits

AWS Service Limits


Log CloudFormation API calls with CloudTrail

Enable CloudTrail in all regions. CloudTrail is a service that captures API calls. Using the information collected in CloudTrail, you can determine what requests were made to CloudFormation.