├── 20140130_cfn ├── images.zip ├── img │ ├── video.png │ └── hangout-qa.png ├── index.html ├── s3-role-authentication.json └── README.md ├── 20131220_cfn ├── img │ ├── video.png │ ├── federation.png │ ├── hangout-qa.png │ ├── new-console.png │ ├── vs_editor.png │ ├── reinvent_video.png │ └── stack-to-template.png └── README.md ├── 20140116_cfn ├── img │ ├── video.png │ └── hangout-qa.png ├── wp-config.php.tpl ├── wp_mustache.cfn.json └── README.md ├── 20140213_cfn ├── img │ ├── video.png │ └── vpc-scaffold.png ├── README.md └── vpc-scaffold.cfn.json └── README.md /20140130_cfn/images.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-hangouts/HEAD/20140130_cfn/images.zip -------------------------------------------------------------------------------- /20131220_cfn/img/video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-hangouts/HEAD/20131220_cfn/img/video.png -------------------------------------------------------------------------------- /20140116_cfn/img/video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-hangouts/HEAD/20140116_cfn/img/video.png -------------------------------------------------------------------------------- /20140130_cfn/img/video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-hangouts/HEAD/20140130_cfn/img/video.png -------------------------------------------------------------------------------- /20140213_cfn/img/video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-hangouts/HEAD/20140213_cfn/img/video.png -------------------------------------------------------------------------------- /20131220_cfn/img/federation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-hangouts/HEAD/20131220_cfn/img/federation.png -------------------------------------------------------------------------------- /20131220_cfn/img/hangout-qa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-hangouts/HEAD/20131220_cfn/img/hangout-qa.png -------------------------------------------------------------------------------- /20131220_cfn/img/new-console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-hangouts/HEAD/20131220_cfn/img/new-console.png -------------------------------------------------------------------------------- /20131220_cfn/img/vs_editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-hangouts/HEAD/20131220_cfn/img/vs_editor.png -------------------------------------------------------------------------------- /20140116_cfn/img/hangout-qa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-hangouts/HEAD/20140116_cfn/img/hangout-qa.png -------------------------------------------------------------------------------- /20140130_cfn/img/hangout-qa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-hangouts/HEAD/20140130_cfn/img/hangout-qa.png -------------------------------------------------------------------------------- /20140213_cfn/img/vpc-scaffold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-hangouts/HEAD/20140213_cfn/img/vpc-scaffold.png -------------------------------------------------------------------------------- /20131220_cfn/img/reinvent_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-hangouts/HEAD/20131220_cfn/img/reinvent_video.png -------------------------------------------------------------------------------- /20131220_cfn/img/stack-to-template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amazon-archives/aws-hangouts/HEAD/20131220_cfn/img/stack-to-template.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hangouts 2 | ## 2014 3 | ### [AWS CloudFormation Office Hours](20140213_cfn/README.md) (2014-02-13) 4 | ### [AWS CloudFormation Office Hours](20140130_cfn/README.md) (2014-01-30) 5 | ### [AWS CloudFormation Office Hours](20140116_cfn/README.md) (2014-01-16) 6 | ## 2013 7 | ### [AWS CloudFormation: A Year in Review](20131220_cfn/README.md) (2013-12-20) -------------------------------------------------------------------------------- /20140116_cfn/wp-config.php.tpl: -------------------------------------------------------------------------------- 1 | g]4ll6~,6G|R'); 9 | define('SECURE_AUTH_KEY', 'gTFTI|~rYHY)|mlu:Cv7RN]GQ^3ngyUbw;L0o!12]0c-ispR<-yt3qj]xjquz^&9'); 10 | define('LOGGED_IN_KEY', 'Jd:HG9M)1p5t2v6uF~D`,.o1pzS)F8[bM9i['); 14 | define('LOGGED_IN_SALT', '~K;rSQ^+{P5/k|=!]k%RXAF-Y@XMY6GSp+wJ5{(|rCzaWjZ%/'); 15 | define('NONCE_SALT', ',Bs_*Y9:b/1Z:apVLHtz35uim|okkA,b|Jt[-&Nla=T{ 2 | 3 | 4 | UNDER CONSTRUCTION 5 | 38 | 39 | 40 |
41 | 42 | CLASSIC HTML 43 | 44 |
45 | 46 | -------------------------------------------------------------------------------- /20140130_cfn/s3-role-authentication.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | 4 | "Description": "An example of using IAM roles to securely download from S3. See the guide at https://github.com/evandbrown/aws-hangouts/tree/master/20140130_cfn **WARNING** This template creates an Amazon EC2 instance. You will be billed for the AWS resources used if you create a stack from this template.", 5 | 6 | "Parameters": { 7 | 8 | "S3Bucket": { 9 | "Type": "String", 10 | "Description": "An S3 bucket in your account that contains index.html and images.zip" 11 | } 12 | 13 | }, 14 | 15 | "Mappings" : { 16 | "AWSRegion2AMI" : { 17 | "us-east-1" : { "id" : "ami-bba18dd2" }, 18 | "us-west-2" : { "id" : "ami-ccf297fc" }, 19 | "us-west-1" : { "id" : "ami-a43909e1" }, 20 | "eu-west-1" : { "id" : "ami-5256b825" }, 21 | "ap-southeast-1" : { "id" : "ami-b4baeee6" }, 22 | "ap-southeast-2" : { "id" : "ami-5ba83761" }, 23 | "ap-northeast-1" : { "id" : "ami-0d13700c" }, 24 | "sa-east-1" : { "id" : "ami-c99130d4" } 25 | } 26 | }, 27 | 28 | "Resources": { 29 | "InstanceRole": { 30 | "Type": "AWS::IAM::Role", 31 | "Properties": { 32 | "AssumeRolePolicyDocument": { 33 | "Statement": [ 34 | { 35 | "Effect": "Allow", 36 | "Principal": { 37 | "Service": [ "ec2.amazonaws.com" ] 38 | }, 39 | "Action": [ "sts:AssumeRole" ] 40 | } 41 | ] 42 | }, 43 | "Path": "/" 44 | } 45 | }, 46 | "RolePolicies": { 47 | "Type": "AWS::IAM::Policy", 48 | "Properties": { 49 | "PolicyName": "S3Download", 50 | "PolicyDocument": { 51 | "Statement": [ 52 | { 53 | "Action": [ "s3:GetObject" ], 54 | "Effect": "Allow", 55 | "Resource": [ { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "S3Bucket" }, "/index.html"]] }, 56 | { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "S3Bucket" }, "/images.zip"]] }] 57 | } 58 | ] 59 | }, 60 | "Roles": [ { "Ref": "InstanceRole" } ] 61 | } 62 | }, 63 | "InstanceProfile": { 64 | "Type": "AWS::IAM::InstanceProfile", 65 | "Properties": { 66 | "Path": "/", 67 | "Roles": [ { "Ref": "InstanceRole" } ] 68 | } 69 | }, 70 | "SecurityGroup" : { 71 | "Type" : "AWS::EC2::SecurityGroup", 72 | "Properties" : { 73 | "GroupDescription" : "HTTP Access", 74 | "SecurityGroupIngress" : [{ 75 | "CidrIp" : "0.0.0.0/0", 76 | "FromPort" : "80", 77 | "ToPort" : "80", 78 | "IpProtocol" : "tcp" 79 | }] 80 | } 81 | }, 82 | "WebServer" : { 83 | "Type" : "AWS::EC2::Instance", 84 | "Properties" : { 85 | "IamInstanceProfile" : { "Ref" : "InstanceProfile" }, 86 | "InstanceType" : "t1.micro", 87 | "SecurityGroups" : [ { "Ref" : "SecurityGroup" } ], 88 | "ImageId" : { "Fn::FindInMap" : [ "AWSRegion2AMI", { "Ref" : "AWS::Region" }, "id" ] }, 89 | "UserData" : { "Fn::Base64" : { "Fn::Join" : ["", [ 90 | "#!/bin/bash -v\n", 91 | "yum update -y aws-cfn-bootstrap\n", 92 | 93 | "# Helper function\n", 94 | "function error_exit\n", 95 | "{\n", 96 | " /opt/aws/bin/cfn-signal -s false -r \"$1\" '", { "Ref" : "WaitHandle" }, "'\n", 97 | " exit 1\n", 98 | "}\n", 99 | 100 | "/opt/aws/bin/cfn-init -s ", { "Ref" : "AWS::StackId" }, " -r WebServer ", 101 | " --region ", { "Ref" : "AWS::Region" }, " || error_exit 'Failed to run cfn-init'\n", 102 | "/opt/aws/bin/cfn-signal -e $? '", { "Ref" : "WaitHandle" }, "'\n"]]}} 103 | }, 104 | "Metadata" : { 105 | "AWS::CloudFormation::Authentication" : { 106 | "S3AccessCreds" : { 107 | "type" : "S3", 108 | "roleName" : { "Ref" : "InstanceRole" }, 109 | "buckets" : [{ "Ref" : "S3Bucket" }] 110 | } 111 | }, 112 | "AWS::CloudFormation::Init" : { 113 | "config" : { 114 | "packages" : { "yum" : { "httpd" : "" } }, 115 | "files" : { 116 | "/var/www/html/index.html" : { 117 | "source" : { "Fn::Join" : ["", ["https://", { "Ref" : "S3Bucket" }, ".s3.amazonaws.com/index.html"]] }, 118 | "mode" : "000400", 119 | "owner" : "apache", 120 | "group" : "apache", 121 | "authentication" : "S3AccessCreds" 122 | } 123 | }, 124 | "sources" : { 125 | "/var/www/html" : { "Fn::Join" : ["", ["https://", { "Ref" : "S3Bucket" }, ".s3.amazonaws.com/images.zip"]] } 126 | }, 127 | "services" : { 128 | "sysvinit" : { 129 | "httpd" : { 130 | "enabled" : "true", 131 | "ensureRunning" : "true" 132 | } 133 | } 134 | } 135 | } 136 | } 137 | } 138 | }, 139 | "WaitHandle" : { 140 | "Type" : "AWS::CloudFormation::WaitConditionHandle" 141 | }, 142 | "WaitCondition" : { 143 | "Type" : "AWS::CloudFormation::WaitCondition", 144 | "DependsOn" : "WebServer", 145 | "Properties" : { 146 | "Handle" : {"Ref" : "WaitHandle"}, 147 | "Timeout" : "300" 148 | } 149 | } 150 | }, 151 | "Outputs" : { 152 | "WebsiteURL" : { 153 | "Description" : "Website URL", 154 | "Value" : { "Fn::Join" : ["", ["http://", { "Fn::GetAtt" : [ "WebServer", "PublicDnsName" ]}]]} 155 | } 156 | } 157 | } -------------------------------------------------------------------------------- /20140116_cfn/wp_mustache.cfn.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion" : "2010-09-09", 3 | 4 | "Description" : "AWS CloudFormation Sample Template WordPress_Single_Instance: WordPress is web software you can use to create a beautiful website or blog. This template installs a single-instance WordPress deployment using a local MySQL database to store the data. It demonstrates using the AWS CloudFormation bootstrap scripts to install packages and files at instance launch time. **WARNING** This template creates an Amazon EC2 instance. You will be billed for the AWS resources used if you create a stack from this template.", 5 | 6 | "Parameters" : { 7 | 8 | "KeyName" : { 9 | "Description" : "Name of an existing EC2 KeyPair to enable SSH access to the instances", 10 | "Type" : "String" 11 | }, 12 | 13 | "InstanceType" : { 14 | "Description" : "WebServer EC2 instance type", 15 | "Type" : "String", 16 | "Default" : "m1.small", 17 | "AllowedValues" : [ "t1.micro","m1.small","m1.medium","m1.large","m1.xlarge","m2.xlarge","m2.2xlarge","m2.4xlarge","m3.xlarge","m3.2xlarge","c1.medium","c1.xlarge","cc1.4xlarge","cc2.8xlarge","cg1.4xlarge"], 18 | "ConstraintDescription" : "must be a valid EC2 instance type." 19 | }, 20 | 21 | "DBName": { 22 | "Default": "wordpress", 23 | "Description" : "The WordPress database name", 24 | "Type": "String", 25 | "MinLength": "1", 26 | "MaxLength": "64", 27 | "AllowedPattern" : "[a-zA-Z][a-zA-Z0-9]*", 28 | "ConstraintDescription" : "must begin with a letter and contain only alphanumeric characters." 29 | }, 30 | 31 | "DBUsername": { 32 | "Default": "admin", 33 | "NoEcho": "true", 34 | "Description" : "The WordPress database admin account username", 35 | "Type": "String", 36 | "MinLength": "1", 37 | "MaxLength": "16", 38 | "AllowedPattern" : "[a-zA-Z][a-zA-Z0-9]*", 39 | "ConstraintDescription" : "must begin with a letter and contain only alphanumeric characters." 40 | }, 41 | 42 | "DBPassword": { 43 | "Default": "admin", 44 | "NoEcho": "true", 45 | "Description" : "The WordPress database admin account password", 46 | "Type": "String", 47 | "MinLength": "1", 48 | "MaxLength": "41", 49 | "AllowedPattern" : "[a-zA-Z0-9]*", 50 | "ConstraintDescription" : "must contain only alphanumeric characters." 51 | }, 52 | 53 | "DBRootPassword": { 54 | "NoEcho": "true", 55 | "Description" : "Root password for MySQL", 56 | "Type": "String", 57 | "MinLength": "1", 58 | "MaxLength": "41", 59 | "AllowedPattern" : "[a-zA-Z0-9]*", 60 | "ConstraintDescription" : "must contain only alphanumeric characters." 61 | }, 62 | "SSHLocation" : { 63 | "Description" : " The IP address range that can be used to SSH to the EC2 instances", 64 | "Type": "String", 65 | "MinLength": "9", 66 | "MaxLength": "18", 67 | "Default": "0.0.0.0/0", 68 | "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})", 69 | "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x." 70 | } 71 | }, 72 | 73 | "Mappings" : { 74 | "AWSInstanceType2Arch" : { 75 | "t1.micro" : { "Arch" : "64" }, 76 | "m1.small" : { "Arch" : "64" }, 77 | "m1.medium" : { "Arch" : "64" }, 78 | "m1.large" : { "Arch" : "64" }, 79 | "m1.xlarge" : { "Arch" : "64" }, 80 | "m2.xlarge" : { "Arch" : "64" }, 81 | "m2.2xlarge" : { "Arch" : "64" }, 82 | "m2.4xlarge" : { "Arch" : "64" }, 83 | "m3.xlarge" : { "Arch" : "64" }, 84 | "m3.2xlarge" : { "Arch" : "64" }, 85 | "c1.medium" : { "Arch" : "64" }, 86 | "c1.xlarge" : { "Arch" : "64" }, 87 | "cc1.4xlarge" : { "Arch" : "64HVM" }, 88 | "cc2.8xlarge" : { "Arch" : "64HVM" }, 89 | "cg1.4xlarge" : { "Arch" : "64HVM" } 90 | }, 91 | 92 | "AWSRegionArch2AMI" : { 93 | "us-east-1" : { "32" : "ami-31814f58", "64" : "ami-1b814f72", "64HVM" : "ami-0da96764" }, 94 | "us-west-2" : { "32" : "ami-38fe7308", "64" : "ami-30fe7300", "64HVM" : "NOT_YET_SUPPORTED" }, 95 | "us-west-1" : { "32" : "ami-11d68a54", "64" : "ami-1bd68a5e", "64HVM" : "NOT_YET_SUPPORTED" }, 96 | "eu-west-1" : { "32" : "ami-973b06e3", "64" : "ami-953b06e1", "64HVM" : "NOT_YET_SUPPORTED" }, 97 | "ap-southeast-1" : { "32" : "ami-b4b0cae6", "64" : "ami-beb0caec", "64HVM" : "NOT_YET_SUPPORTED" }, 98 | "ap-southeast-2" : { "32" : "ami-b3990e89", "64" : "ami-bd990e87", "64HVM" : "NOT_YET_SUPPORTED" }, 99 | "ap-northeast-1" : { "32" : "ami-0644f007", "64" : "ami-0a44f00b", "64HVM" : "NOT_YET_SUPPORTED" }, 100 | "sa-east-1" : { "32" : "ami-3e3be423", "64" : "ami-3c3be421", "64HVM" : "NOT_YET_SUPPORTED" } 101 | } 102 | }, 103 | 104 | "Resources" : { 105 | 106 | "WebServer": { 107 | "Type": "AWS::EC2::Instance", 108 | "Metadata" : { 109 | "AWS::CloudFormation::Init" : { 110 | "config" : { 111 | "packages" : { 112 | "yum" : { 113 | "httpd" : [], 114 | "php" : [], 115 | "php-mysql" : [], 116 | "mysql" : [], 117 | "mysql-server" : [], 118 | "mysql-devel" : [], 119 | "mysql-libs" : [] 120 | } 121 | }, 122 | 123 | "sources" : { 124 | "/var/www/html" : "http://wordpress.org/latest.tar.gz" 125 | }, 126 | 127 | "files" : { 128 | "/tmp/setup.mysql" : { 129 | "content" : { "Fn::Join" : ["", [ 130 | "CREATE DATABASE ", { "Ref" : "DBName" }, ";\n", 131 | "CREATE USER '", { "Ref" : "DBUsername" }, "'@'localhost' IDENTIFIED BY '", { "Ref" : "DBPassword" }, "';\n", 132 | "GRANT ALL ON ", { "Ref" : "DBName" }, ".* TO '", { "Ref" : "DBUsername" }, "'@'localhost';\n", 133 | "FLUSH PRIVILEGES;\n" 134 | ]]}, 135 | "mode" : "000644", 136 | "owner" : "root", 137 | "group" : "root" 138 | }, 139 | 140 | "/var/www/html/wordpress/wp-config.php" : { 141 | "source" : "http://s3.amazonaws.com/evbrown/public/wp-config.php.tpl", 142 | "mode" : "000644", 143 | "owner" : "root", 144 | "group" : "root", 145 | "context" : { 146 | "DBUsername" : {"Ref" : "DBUsername"}, 147 | "DBPassword" : {"Ref" : "DBPassword"}, 148 | "DBName" : {"Ref" : "DBName"} 149 | } 150 | } 151 | }, 152 | "services" : { 153 | "sysvinit" : { 154 | "httpd" : { "enabled" : "true", "ensureRunning" : "true" }, 155 | "mysqld" : { "enabled" : "true", "ensureRunning" : "true" }, 156 | "sendmail" : { "enabled" : "false", "ensureRunning" : "false" } 157 | } 158 | } 159 | } 160 | } 161 | }, 162 | "Properties": { 163 | "ImageId" : { "Fn::FindInMap" : [ "AWSRegionArch2AMI", { "Ref" : "AWS::Region" }, 164 | { "Fn::FindInMap" : [ "AWSInstanceType2Arch", { "Ref" : "InstanceType" }, "Arch" ] } ] }, 165 | "InstanceType" : { "Ref" : "InstanceType" }, 166 | "SecurityGroups" : [ {"Ref" : "WebServerSecurityGroup"} ], 167 | "KeyName" : { "Ref" : "KeyName" }, 168 | "UserData" : { "Fn::Base64" : { "Fn::Join" : ["", [ 169 | "#!/bin/bash -v\n", 170 | "yum update -y aws-cfn-bootstrap\n", 171 | 172 | "# Helper function\n", 173 | "function error_exit\n", 174 | "{\n", 175 | " /opt/aws/bin/cfn-signal -e 1 -r \"$1\" '", { "Ref" : "WaitHandle" }, "'\n", 176 | " exit 1\n", 177 | "}\n", 178 | 179 | "# Install Apache Web Server, MySQL, PHP and WordPress\n", 180 | "/opt/aws/bin/cfn-init -s ", { "Ref" : "AWS::StackId" }, " -r WebServer ", 181 | " --region ", { "Ref" : "AWS::Region" }, " || error_exit 'Failed to run cfn-init'\n", 182 | 183 | "# Setup MySQL root password and create a user\n", 184 | "mysqladmin -u root password '", { "Ref" : "DBRootPassword" }, "' || error_exit 'Failed to initialize root password'\n", 185 | "mysql -u root --password='", { "Ref" : "DBRootPassword" }, "' < /tmp/setup.mysql || error_exit 'Failed to create database user'\n", 186 | 187 | "# Setup correct file ownership\n", 188 | "chown -R apache:apache /var/www/html/wordpress\n", 189 | 190 | "# All is well so signal success\n", 191 | "/opt/aws/bin/cfn-signal -e 0 -r \"WordPress setup complete\" '", { "Ref" : "WaitHandle" }, "'\n" 192 | 193 | ]]}} 194 | } 195 | }, 196 | 197 | "WaitHandle" : { 198 | "Type" : "AWS::CloudFormation::WaitConditionHandle" 199 | }, 200 | 201 | "WaitCondition" : { 202 | "Type" : "AWS::CloudFormation::WaitCondition", 203 | "DependsOn" : "WebServer", 204 | "Properties" : { 205 | "Handle" : {"Ref" : "WaitHandle"}, 206 | "Timeout" : "300" 207 | } 208 | }, 209 | 210 | "WebServerSecurityGroup" : { 211 | "Type" : "AWS::EC2::SecurityGroup", 212 | "Properties" : { 213 | "GroupDescription" : "Enable HTTP access via port 80 and SSH access", 214 | "SecurityGroupIngress" : [ 215 | {"IpProtocol" : "tcp", "FromPort" : "80", "ToPort" : "80", "CidrIp" : "0.0.0.0/0"}, 216 | {"IpProtocol" : "tcp", "FromPort" : "22", "ToPort" : "22", "CidrIp" : { "Ref" : "SSHLocation"}} 217 | ] 218 | } 219 | } 220 | }, 221 | 222 | "Outputs" : { 223 | "WebsiteURL" : { 224 | "Value" : { "Fn::Join" : ["", ["http://", { "Fn::GetAtt" : [ "WebServer", "PublicDnsName" ]}, "/wordpress"]] }, 225 | "Description" : "WordPress Website" 226 | } 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /20140116_cfn/README.md: -------------------------------------------------------------------------------- 1 | AWS CloudFormation Hangout: January 16, 2014 2 | ======================================================== 3 | Thanks for joining AWS Developer Community Manager Evan Brown (@evandbrown on [GitHub](http://github.com/evandbrown) and [Twitter](http://twitter.com/evandbrown); or e-mail evbrown (at) amazon.com) for the first Cloudformation Hangout of 2014. The Hangout starts at 9:00 AM PST on Thursday, January 16, and is scheduled for 30 minutes. 4 | 5 | [Check out the index](../README.md) for a list of our previous Hangouts, including detailed agenda and recordings. 6 | 7 | ## The Recording 8 | Apologies for the video issues! Fortunately audio is intact, and we think we've identified the problem to avoid in future events. Watch the recording of the live session - including Q&A - below: 9 | 10 | [![](img/video.png)](https://plus.google.com/events/c2fl0tchd40roion9bdsr2g3c50) 11 | 12 | ## Agenda Overview 13 | * New features since the last Hangout 14 | * Feature Highlight: Using {{ mustache }} in your CloudFormation templates 15 | * Focus on the Forums: Top posts and answers of the week 16 | * Your Q&A 17 | 18 | ## About the Q&A 19 | Q&A is enabled for this Hangout. To ask your question, click the Ask a new question button in the bottom-right corner of your screen, like so: 20 | 21 | ![](img/hangout-qa.png) 22 | 23 | You can enter your questions at any point during the hangout. Keep in mind that it takes about 50 seconds before the audio and video to make it through all the tubes to your computer, so by the time you've typed your question we may be on another topic. But don't worry! We've got the final half of the Hangout reserved just for Q&A. 24 | 25 | ## New Features 26 | Here's what's been added to CloudFormation in the last 4 weeks: 27 | 28 | 1. **CloudFormation** - Up to 60 parameters and 60 outputs per template 29 | 2. **Auto Scaling** - Create a group from a running instance; use provisioned IOPS volumes 30 | 3. **SQS** - Update queues and queue policies 31 | 4. **AWS Marketplace** - If you are looking for turnkey solutions, trials or want to sell your own, [AWS Marketplace](https://aws.amazon.com/marketplace) now supports products packaged as CloudFormation templates. 32 | 33 | Check out the release notes at [http://aws.amazon.com/releasenotes/AWS-CloudFormation](http://aws.amazon.com/releasenotes/AWS-CloudFormation) for more information. 34 | 35 | ## Feature Highlight: {{ mustache }} 36 | Did you know that CloudFormation supports [mustache](http://mustache.github.io/) templates? Let's use WordPress as an example to see that feature in action. 37 | 38 | We can use CloudFormation to launch and bootstrap a WordPress environment with [this sample template](https://s3.amazonaws.com/cloudformation-templates-us-east-1/WordPress_Single_Instance_With_RDS.template). Looking more closely at the template, we see it's using [AWS::CloudFormation::Init](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-init.html) to install and configure WordPress on each instance. Specifically, on line 133 we see the template is creating the important `wp-config.php` file that contains Wordpress configuration information, including database hostname, username, and password: 39 | 40 | ```json 41 | ... 42 | "files" : { 43 | "/var/www/html/wordpress/wp-config.php" : { 44 | "content" : { "Fn::Join" : ["", [ 45 | "=1.2.10) 101 | * Params are now passed in a simpler "key1=value1;key2=value2" format 102 | * Passing a CommaDelimistedList param example: 103 | 104 | aws cloudformation create-stack --stack-name demo \ 105 | --template-body file://demo.json \ 106 | --parameters ParameterKey=PublicSubnets,ParameterValue='subnet1\,subnet2\,subnet3' 107 | 108 | --- 109 | 110 | ### [ Create an SQS Queue for each Instance in an ASG](https://forums.aws.amazon.com/thread.jspa?threadID=143546&tstart=0#) 111 | Two options: 112 | 113 | 1. Configure Auto Scaling to notify you when an Instance is added or removed. Handle that notification and use API to create or delete a queue. ( [Documentation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-as-group.html#cfn-as-group-notificationconfiguration)): 114 | 115 | ```json 116 | ... 117 | "AutoScalingGroup" : { 118 | "Type" : "AWS::AutoScaling::AutoScalingGroup", 119 | "Properties" : { 120 | "AvailabilityZones" : { "Fn::GetAZs" : ""}, 121 | "LaunchConfigurationName" : { "Ref" : "LaunchConfig" }, 122 | "MinSize" : "1", 123 | "MaxSize" : "3", 124 | "LoadBalancerNames" : [ { "Ref" : "ElasticLoadBalancer" } ], 125 | "NotificationConfiguration" : { 126 | "TopicARN" : { "Ref" : "NotificationTopic" }, 127 | "NotificationTypes" : [ "autoscaling:EC2_INSTANCE_LAUNCH","autoscaling:EC2_INSTANCE_LAUNCH_ERROR","autoscaling:EC2_INSTANCE_TERMINATE", "autoscaling:EC2_INSTANCE_TERMINATE_ERROR"] 128 | } 129 | } 130 | } 131 | ... 132 | ``` 133 | 134 | 2. Create an SQS Queue via SDK/CLI/API on each Instance via UserData (**caveat**: consider how you will delete this Queue when an Instance goes away): 135 | 136 | ```json 137 | ... 138 | "AutoScalingLaunchConfig": { 139 | "Type": "AWS::AutoScaling::LaunchConfiguration", 140 | "Properties": { 141 | "ImageId" : "...", 142 | "InstanceType" : { "Ref" : "InstanceType" }, 143 | ... 144 | "UserData" : { "Fn::Base64" : { "Fn::Join" : ["", [ 145 | "#!/bin/bash -v\n", 146 | "aws sqs create-queue ..." 147 | ]]}} 148 | } 149 | }, 150 | ... 151 | ``` 152 | 153 | --- 154 | 155 | ### [Reference Account ID in a Template/Stack](https://forums.aws.amazon.com/thread.jspa?threadID=101106&tstart=0#) 156 | 157 | * The `AccountId` pseudo parameter was added several months ago: `{"Ref" : "AWS::AccountId"}` 158 | 159 | --- 160 | 161 | ### [CloudFormation Events to an SQS Queue via SNS](https://forums.aws.amazon.com/thread.jspa?threadID=143617&tstart=0) 162 | 163 | When you launch a CloudFormation stack, you can provide an SNS Topic that CloudFormation will publish an event stream to. You may want those messages to flow into an SQS queue for offline processing by subscribing that Queue as an Endpoint to your Topic. In addition to the subscription, be sure to add a Queue Policy to your Queue granting SendMessage permissions to your topic. For more details, including an example Queue Policy, see [the documentation](http://docs.aws.amazon.com/sns/latest/dg/SendMessageToSQS.html) 164 | 165 | --- 166 | 167 | ### [Increase Size of C:\ on Stack Creation](https://forums.aws.amazon.com/thread.jspa?threadID=142940&tstart=0) 168 | Use the `BlockDeviceMappings` property of the `AWS::EC2::Instance` or `AWS::AutoScaling::LaunchConfiguration` Resources to set the size. 169 | 170 | ```json 171 | ... 172 | "Ec2Instance" : { 173 | "Type" : "AWS::EC2::Instance", 174 | "Properties" : { 175 | "ImageId" : { "Ref" : "ImageId" }, 176 | "InstanceType" : { "Ref" : "InstanceType" }, 177 | "SecurityGroups" : [{ "Ref" : "Ec2SecurityGroup" }], 178 | "BlockDeviceMappings" : [ 179 | { 180 | "DeviceName" : "/dev/sda1", 181 | "Ebs" : { "VolumeSize" : "50" } 182 | },{ 183 | "DeviceName" : "/dev/sdm", 184 | "Ebs" : { "VolumeSize" : "100" } 185 | } 186 | ] 187 | } 188 | }, 189 | ... 190 | ``` 191 | 192 | For a complete example, see [the documentation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/quickref-ec2.html#scenario-ec2-bdm) 193 | 194 | ## Q&A -------------------------------------------------------------------------------- /20131220_cfn/README.md: -------------------------------------------------------------------------------- 1 | AWS CloudFormation: A Year in Review (Plus Q&A!) Hangout 2 | ======================================================== 3 | Thanks for joining our first CloudFormation hangout! Developer Community Manager Evan Brown (@evandbrown on [GitHub](http://github.com/evandbrown) and [Twitter](http://twitter.com/evandbrown)) will spend the first 15 minutes of the Hangout reviewing the major features that were introduced for CloudFormation in 2013, and the final 15 minutes answering your questions. 4 | 5 | ## The Recording 6 | Watch the recording of the live session - including Q&A - below: 7 | 8 | [![](img/video.png)](https://plus.google.com/events/c7hl5l8kamtacnteveco0daasps) 9 | 10 | ## About the Q&A 11 | Q&A is enabled for this Hangout. To ask your question, click the Ask a new question button in the bottom-right corner of your screen, like so: 12 | 13 | ![](img/hangout-qa.png) 14 | 15 | You can enter your questions at any point during the hangout. Keep in mind that it takes about 50 seconds before the audio and video to make it through all the tubes to your computer, so by the time you've typed your question we may be on another topic. But don't worry! We've got the final half of the Hangout reserved just for Q&A. 16 | 17 | ## What is CloudFormation 18 | AWS CloudFormation lets you create and manage a collection of related AWS resources, provisioning and updating them in an orderly and predictable fashion. Declare and manage your complete application environment, including VPCs, EC2 Instances in Auto Scaling Groups, RDS Databases, and more, all in a template file: 19 | 20 | ![](img/stack-to-template.png) 21 | 22 | Store your template files in version control (e.g., git) and use them to launch identical stacks multiple times in multiple AWS Regions around the world. 23 | 24 | ## Last Night 25 | ### Updated CloudFormation Management Console 26 | Adds features like auto-refreshing stack events, a new stack launch and upate wizard that includes alphabetical ordering of input parameters. 27 | 28 | ![](img/new-console.png) 29 | 30 | You can toggle back to the previous console if you like. Please click the _feedback_ link and give us your feedback. 31 | 32 | ### New Features for Existing Resources: 33 | * **ELB Cross-Zone Load Balancing** 34 | * **Elastic Beanstalk Worker Tier** 35 | 36 | ## Expanded Service Coverage 37 | 38 | ### Available in GovCloud 39 | Launched in GovCloud (US) in August. A variety of [complete sample templates are available](http://aws.amazon.com/cloudformation/aws-cloudformation-templates/aws-cloudformation-templates-govcloud-us). 40 | 41 | ### Support for New Resources 42 | CloudFormation added support for a number of new AWS Resources and features in 2013, including: 43 | 44 | #### ElastiCache Redis 45 | 46 | "RedisCluster" : { 47 | "Type": "AWS::ElastiCache::CacheCluster", 48 | "Properties": { 49 | "Engine" : "redis", 50 | "NumCacheNodes" : "1" 51 | ... 52 | } 53 | } 54 | 55 | * [Sample template](https://s3.amazonaws.com/cloudformation-templates-us-east-1/ElastiCache_Redis.template) 56 | 57 | * Run from [CLI](http://aws.amazon.com/cli): 58 | 59 | aws cloudformation create-stack \ 60 | --stack-name redis-sample \ 61 | --template-url https://s3.amazonaws.com/cloudformation-templates-us-east-1/ElastiCache_Redis.template\ 62 | --parameters ... 63 | 64 | --- 65 | 66 | #### EBS-Optimized EC2 Instances 67 | 68 | "OptimizedInstance" : { 69 | "Type" : "AWS::AutoScaling::LaunchConfiguration", 70 | "Properties" : { 71 | "EbsOptimized" : "true", 72 | } 73 | } 74 | 75 | * [Sample template](https://s3.amazonaws.com/cloudformation-templates-us-east-1/EC2WithEBSPIOPs.template) 76 | * [Run in Console](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#cstack=sn%7Eebsoptimized%7Cturl%7Ehttps://s3.amazonaws.com/cloudformation-templates-us-east-1/EC2WithEBSPIOPs.template) 77 | 78 | --- 79 | 80 | #### Rolling Updates for Auto Scaling Groups 81 | Invoked when you change an ASG's Launch Configuration or Subnet group membership. 82 | 83 | "ASG1" : { 84 | "Type" : "AWS::AutoScaling::AutoScalingGroup", 85 | "Properties" : {...} 86 | "UpdatePolicy" : { 87 | "AutoScalingRollingUpdate" : { 88 | "MaxBatchSize" : "2", 89 | "MinInstancesInService" : "6", 90 | "PauseTime" : "PT12M5S" 91 | } 92 | } 93 | } 94 | 95 | * [Sample template](https://s3.amazonaws.com/cloudformation-templates-us-east-1/AutoScalingRollingUpdates.template) 96 | * [Documentation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-updatepolicy.html) 97 | 98 | --- 99 | 100 | #### RDS Read Replicas 101 | 102 | "ReplicaDB" : { 103 | "Type" : "AWS::RDS::DBInstance", 104 | "Properties" : { 105 | "SourceDBInstanceIdentifier" : { "Ref" : "MasterDB" }, 106 | } 107 | } 108 | 109 | * [Sample template](view-source:https://s3.amazonaws.com/cloudformation-templates-us-east-1/RDS_MySQL_With_Read_Replica.template) 110 | * [Documentation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-rds-database-instance.html#cfn-rds-dbinstance-sourcedbinstanceidentifier) 111 | 112 | --- 113 | 114 | ### Expanded VPC support 115 | Including: 116 | 117 | * `AssociatePublicIpAddress` property for `AWS::EC2::NetworkInterface` type 118 | * `PrivateIpAddresses` for the `AWS::EC2::Instance` type 119 | * `EnableDnsSupport` and `EnableDnsHostnames` properties on` AWS::EC2::VPC` resource type 120 | * `AWS::ElastiCache::*` resource types and properties 121 | 122 | * [Documentation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc.html) 123 | 124 | ## Developer Experience 125 | ### Conditionals 126 | Create resources conditionally based on input parameters or mappings. 127 | 128 | This snippet attaches an EBS volume conditionally, only when the `EBSVolumeDeviceName` != "No Volume", and sets the `Iops` property only if the instance type is EBSOptimized (as discovered in the `InstanceConfig` map): 129 | 130 | { 131 | "Parameters" : { 132 | "EBSVolumeDeviceName" : { 133 | "Description" : "Device name to attach an EBS volume (Default no EBS volume attached)", 134 | "Type" : "String", 135 | "Default" : "No Volume" 136 | }, 137 | }, 138 | "Mappings" : { 139 | "InstanceConfig" : { 140 | "t1.micro" : { "Arch" : "PV64", "EBSOptimized" : "false" }, 141 | "m1.large" : { "Arch" : "PV64", "EBSOptimized" : "true" } 142 | } 143 | }, 144 | "Conditions" : { 145 | "AttachVolume" : { "Fn::Not" : [{ "Fn::Equals" : [ { "Ref" : "EBSVolumeDeviceName" }, "No Volume" ]}]}, 146 | "IsEBSOptimized" : { "Fn::Equals" : [ { "Fn::FindInMap" : [ "InstanceConfig", { "Ref" : "InstanceType" }, "EBSOptimized" ]}, "true" ] } 147 | }, 148 | "Resources" : { 149 | "EBSVolume": { 150 | "Type": "AWS::EC2::Volume", 151 | "Condition" : "AttachVolume", 152 | "Properties": { 153 | "VolumeType" : { "Fn::If" : ["IsEBSOptimized", "io1", {"Ref" : "AWS::NoValue"}]}, 154 | "Iops" : { "Fn::If" : ["IsEBSOptimized", { "Ref" : "IOPs" }, {"Ref" : "AWS::NoValue"}]} 155 | } 156 | } 157 | } 158 | } 159 | 160 | Speaking of Conditionals, the [AWS Toolkit for Microsoft Visual Studio](http://aws.amazon.com/visualstudio/) includes a CloudFormation plugin that provides IntelliSense editing for templates. Here's a shot of the editor in action: 161 | 162 | ![](img/vs_editor.png) 163 | 164 | [Norm Johanson](http://blogs.aws.amazon.com/net/blog/author/Norm+Johanson) recently wrote [a nice post about](http://blogs.aws.amazon.com/net/post/TxLCYWVVGA2IY1/Resource-Condition-Support-in-the-AWS-CloudFormation-Editor) the editor and its new support for Conditional Resources. 165 | 166 | * [Documentation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-conditions.html) 167 | * [Sample template](https://s3.amazonaws.com/cloudformation-templates-us-east-1/EC2InstanceWithEBSVolumeConditionalIOPs.template) 168 | * [VS Template Editor Intro Video](http://www.youtube.com/watch?v=3QydWLiCqWk) 169 | 170 | --- 171 | 172 | ### User-Defined Names 173 | Specify the name (i.e., Physical ID) of your choice for several AWS resources, including CloudWatch alarms, DynamoDB tables, Elastic Beanstalk applications and environments, S3 buckets, SNS topics, SQS queues, ElastiCache clusters, ELBs, and RDS db instances. 174 | 175 | "MyQueue": { 176 | "Type": "AWS::SQS::Queue", 177 | "Properties" : { 178 | "QueueName" : "TheRealNameOfTheQueue" 179 | } 180 | } 181 | 182 | 183 | * [Documentation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-name.html) 184 | 185 | ### Custom Resources 186 | Custom Resources allow you to write custom code and define your own Resources that are part of a CloudFormation stack. Two of our CloudFormation engineers gave a deep, fantastic talk on the subject at re:Invent and released a framework to make development even easier. I encourage you to watch the video on YouTube: 187 | 188 | [![](img/reinvent_video.png)](http://www.youtube.com/watch?v=ZhGMaw67Yu0#t=10) 189 | 190 | * [Examples](https://github.com/awslabs/aws-cfn-custom-resource-examples) 191 | * [Documentation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref.html) 192 | 193 | ## Stack Management 194 | ### IAM Policies for Stacks 195 | Express permissions for IAM Users and Groups that grant access to Actions (e.g., _DeleteStack_) and Resources (e.g., a specific CloudFormation Stack). For example: 196 | 197 | { 198 | "Version":"2012-10-17", 199 | "Statement":[{ 200 | "Effect":"Deny", 201 | "Action":[ 202 | "cloudformation:DeleteStack", 203 | "cloudformation:UpdateStack" 204 | ], 205 | "Resource":"arn:aws:cloudformation:us-east-1:123456789012:stack/MyProductionStack/*" 206 | }] 207 | } 208 | 209 | * [Documentation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-iam-template.html) 210 | 211 | --- 212 | 213 | ### Stack Policies: Protect Resources from Updates 214 | Stack Policies are applied at the CloudFormation stack level (similar to how Bucket Policies are applied to S3 Buckets) and protect individual resources in your stack from being updated or deleted _during stack update events_. Stack Policies should be used only as a fail-safe to prevent accidental updates; use IAM policies to control access to Stacks and actions. 215 | 216 | { 217 | "Statement" : [ 218 | { 219 | "Effect" : "Deny", 220 | "Action" : "Update:*", 221 | "Principal": "*", 222 | "Resource" : "LogicalResourceId/ProductionDatabase" 223 | }, 224 | { 225 | "Effect" : "Allow", 226 | "Action" : "Update:*", 227 | "Principal": "*", 228 | "Resource" : "*" 229 | } 230 | ] 231 | } 232 | 233 | * [Documentation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/protect-stack-resources.html) 234 | 235 | --- 236 | 237 | ### Federated Identity and Security Token Service (STS) Support 238 | Make calls to CloudFormation APIs or use the CloudFormation Management Console with STS credentials or as a Federated Identity user. Here's a picture: 239 | 240 | ![](img/federation.png) 241 | 242 | I can retrieve a session token from STS and use the automatically-expiring credentials to make API calls to CloudFormation: 243 | 244 | $> aws sts get-session-token 245 | { 246 | "Credentials": { 247 | "SecretAccessKey": "ZpC-------------Tf/p", 248 | "SessionToken": "AQoDYXdzELL//////////wEa8AEeI6Tbr4DUvHHxirqtRA9irfnG/f+/MnOW6vtZ7j++gUg0goK0d7gAgxJ3OlQU=", 249 | "Expiration": "2013-12-20T12:26:44Z", 250 | "AccessKeyId": "ASI----------WKIQ" 251 | } 252 | } 253 | 254 | * [Informative Blog Post](http://aws.typepad.com/aws/2013/10/federated-users-temp-creds-aws-cloudformation.html) 255 | * [Documentation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-iam-template.html) 256 | 257 | --- 258 | 259 | ## Performance 260 | 261 | ### Parallel Stack Processing 262 | CloudFormation creates, updates and deletes resources in parallel in order to improve performance of these operations. For example, provisioning a RAID 0 setup, which involves the creation of multiple EBS volumes, is now faster because CloudFormation can provision the volumes in parallel. CloudFormation automatically determines which resources in a template can be created in parallel. 263 | 264 | Use [the DependsOn attribute](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-attribute-dependson.html) for control over the order of resource creation. 265 | 266 | --- 267 | 268 | ### Nested Stack Updates 269 | CloudFormation allows nesting a stack as a resource inside a template (i.e., stacks in stacks in stacks). With Nested Stack Updates, updates initiated on a top-level stack will also updated its nested stacks automatically. Only stacks whose templates have changed will be updated. 270 | 271 | Here's an example of a nested stack: 272 | 273 | { 274 | "Resources" : { 275 | "myStack" : { 276 | "Type" : "AWS::CloudFormation::Stack", 277 | "Properties" : { 278 | "TemplateURL" : "https://s3.amazonaws.com/cloudformation-templates-us-east-1/S3_Bucket.template", 279 | "TimeoutInMinutes" : "60" 280 | } 281 | } 282 | } 283 | } 284 | 285 | * [Documentation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/quickref-stack.html) 286 | 287 | ## Q&A! 288 | 289 | ## These Are a Few Of My Favorite Links 290 | 291 | * [AWS Application Management Blog](http://blogs.aws.amazon.com/application-management) 292 | * [@AWSCloudFormer](http://twitter.com/AWSCloudFormer) - CloudFormation on Twitter 293 | * [The Documentation](http://aws.amazon.com/documentation/cloudformation/) 294 | * [re:Invent Session Videos](http://blogs.aws.amazon.com/application-management/post/TxZ8FZHFP6EYOQ/AWS-re-Invent-2013-Sessions-Now-Available) 295 | 296 | ## Feedback 297 | Was this Hangout helpful? Are you interested in more of them for 2014? Let me know! [evbrown@amazon.com](mailto:evbrown@amazon.com) 298 | -------------------------------------------------------------------------------- /20140213_cfn/README.md: -------------------------------------------------------------------------------- 1 | AWS CloudFormation Office Hours: February 13, 2014 2 | ======================================================== 3 | Thanks for joining AWS Developer Community Manager Evan Brown for CloudFormation Office Hours. The Hangout starts at 9:00 AM PST on Thursday, February 13, and is scheduled for 45 minutes. RSVP for the event at [https://plus.google.com/events/cj7ssql3r1475rf6q9q4vv205no](https://plus.google.com/events/cj7ssql3r1475rf6q9q4vv205no) 4 | 5 | [Check out the index](../README.md) for a list of our previous Hangouts, including code samples and recordings. 6 | 7 | ## The Recording 8 | Below is the recording of the February 13 session, including Q&A: 9 | 10 | [![](img/video.png)](https://plus.google.com/events/cj7ssql3r1475rf6q9q4vv205no) 11 | 12 | ## Agenda Overview 13 | * New features since the last Hangout 14 | * Community agenda items 15 | * Feature Highlight: VPC 16 | * Your Q&A 17 | 18 | ## About the Q&A 19 | Q&A is enabled for this Hangout. To ask your question, click the Ask a new question button in the bottom-right corner of your screen. 20 | 21 | You can enter your questions at any point during the hangout. Keep in mind that it takes about 50 seconds before the audio and video to make it through all the tubes to your computer, so by the time you've typed your question we may be on another topic. But don't worry! We've got the final half of the Hangout reserved just for Q&A. 22 | 23 | ## Community Agenda Suggestions 24 | * Add your suggested agenda items here! Check out [Pull Requests](https://github.com/evandbrown/aws-hangouts/pulls?direction=desc&page=1&sort=created&state=closed) from our last Office Hours to see how others have done it. 25 | 26 | ## New Features 27 | 28 | * **Amazon Redshift** - You can now model a Redshift cluster configuration in a CloudFormation template file and have CloudFormation launch the cluster with a few clicks or CLI commands. The template enables you to version control, replicate, or share your Redshift configuration. [Here is a sample CloudFormation template](https://s3.amazonaws.com/cloudformation-templates-us-east-1/Redshift.template) that provisions a Redshift cluster, and here's a snippet: 29 | 30 | ```json 31 | "Resources": { 32 | "RedshiftCluster": { 33 | "Type": "AWS::Redshift::Cluster", 34 | "Properties": { 35 | "ClusterType": { "Ref": "ClusterType" }, 36 | "NumberOfNodes": { 37 | "Fn::If": [ 38 | "IsMultiNodeCluster", 39 | { 40 | "Ref": "NumberOfNodes" 41 | }, 42 | { 43 | "Ref": "AWS::NoValue" 44 | } 45 | ] 46 | }, 47 | "NodeType": { "Ref": "NodeType" }, 48 | "DBName": { "Ref": "DatabaseName" }, 49 | "MasterUsername": { "Ref": "MasterUsername" }, 50 | "MasterUserPassword": { "Ref": "MasterUserPassword" }, 51 | "ClusterParameterGroupName": { "Ref": "RedshiftClusterParameterGroup" } 52 | }, 53 | "DeletionPolicy": "Snapshot" 54 | }, 55 | ... 56 | } 57 | ``` 58 | 59 | Note the use of ``Fn::If` to conditionally set the `NumberOfNodes` property. Check out [the documentation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/conditions-section-structure.html) or [our previous Hangout](https://github.com/evandbrown/aws-hangouts/blob/master/20131220_cfn/README.md#conditionals) for more details on Conditional resource creation. 60 | 61 | * **AWS Elastic Beanstalk** - Previously, you could use AWS CloudFormation to provision an Elastic Beanstalk application as part of a CloudFormation template and stack. Now, you can use AWS CloudFormation to update that application as well as provisioning it, by updating the associated CloudFormation template and stack. 62 | 63 | ## Feature Highlight: Declare an Amazon VPC with CloudFormation 64 | We'll walk through building a VPC with public and private subnets, including: 65 | 66 | * Provisioning NAT and Bastion instances 67 | * Making Security Group changes by updating the template and stack 68 | * Extending our template to work in multiple regions 69 | * Choosing stack outputs to simplify deploying an app into the VPC 70 | 71 | ### First Things First 72 | Let's run the sample with the AWS CLI before we start: 73 | 74 | ``` 75 | aws cloudformation create-stack \ 76 | --stack-name "vpc-demo" \ 77 | --template-body file://vpc-scaffold.cfn.json \ 78 | --parameters ParameterKey=KeyName,ParameterValue=evbrown-amazon \ ParameterKey=VPCAvailabilityZone1,ParameterValue=eu-west-1a \ ParameterKey=VPCAvailabilityZone2,ParameterValue=eu-west-1c \ 79 | --region eu-west-1 80 | ``` 81 | 82 | If you run this yourself, be sure to set the KeyName parameter to the name of your EC2 Key Pair. 83 | 84 | ### Template Scaffold 85 | Our sample VPC template - [vpc-scaffold.cfn.json](vpc-scaffold.cfn.json) - defines the following Parameters, Resources, and Outputs: 86 | 87 | **Parameters** 88 | 89 | * BastionInstanceType 90 | * KeyName 91 | * NATInstanceType 92 | * NetworkName 93 | * SSHFrom 94 | * VPCAvailabilityZone1 95 | * VPCAvailabilityZone2 96 | 97 | **Resources** 98 | 99 | * BastionHost 100 | * InstanceSecurityGroup 101 | * NATDevice 102 | * PrivateSubnet1 103 | * PrivateSubnet2 104 | * PublicSubnet1 105 | * PublicSubnet2 106 | * VPC 107 | * ... 108 | 109 | > The `vpc-scaffold.cfn.json` template we're working with is over 800 lines. Fortunately the excellent tool `jq` ("like `sed` for JSON data", available at [http://stedolan.github.io/jq/](http://stedolan.github.io/jq/)) makes quick work of digesting CloudFormation templates. I'm sure there's an even better way to do this, but I parsed each block with something like: 110 | 111 | >`cat vpc-scaffold.cfn.json | jq '.Resources | to_entries [] | "* "+.key' -r` 112 | 113 | ### Visualize It 114 | Here's an architecture diagram (with a lot of the lower-level resources left out) of the resulting VPC: 115 | 116 | ![](img/vpc-scaffold.png) 117 | 118 | ### Key Considerations 119 | 120 | Here are a few tey things to note about this VPC: 121 | 122 | #### Multi-AZ (2 AZs, to be precise) 123 | This template is designed to run in precisely 2 Availability Zones in whichever Region it's deployed, and you must provide those two AZs in the input params `VPCAvailabilityZone1` and `VPCAvailabilityZone2`. 124 | 125 | Why are we explicitly requiring AZs to be input as parameters? After all, CloudFormation does provide the intrinsic function `Fn::GetAZs` that returns all Availability Zones for a Region, and you can even use `Fn::Select` to retrieve the first two zones: 126 | 127 | { "Fn::Select" : [ "0", { "Fn::GetAZs" : "" } ] } 128 | { "Fn::Select" : [ "1", { "Fn::GetAZs" : "" } ] } 129 | 130 | This method works great for EC2 Classic, however `Fn::GetAZs` does not guarantee that a returned AZ is VPC-enabled. It's a good idea to explicitly provide AZs you know support VPC as input Parameters, and that's why we did it in this example: 131 | 132 | ```json 133 | Parameters: { 134 | ... 135 | "VPCAvailabilityZone1": { 136 | "Description": "One of two Availability Zones that will be used to create subnets.", 137 | "Type": "String", 138 | "MinLength": "1", 139 | "MaxLength": "255" 140 | }, 141 | "VPCAvailabilityZone2": { 142 | "Description": "Two of two Availability Zones that will be used to create subnets. Must be different than VPCAvailabilityZone2.", 143 | "Type": "String", 144 | "MinLength": "1", 145 | "MaxLength": "255" 146 | } 147 | ... 148 | } 149 | ``` 150 | 151 | #### Multi-Region Support 152 | 153 | We'd like our template to work in any of the 8 public AWS Regions so we can replicate our infrastructure around the world in a reliable and consistent way. CloudFormation Mappings provide a convenient mechanism to - you guessed it! - map keys to values, and it's very common to use Region names to map to Regionally-specific values. 154 | 155 | Here we map out the regionally-specific AMI required to launch the NAT Instance in the `AWSNATAMI` map: 156 | 157 | ```json 158 | "Mappings": { 159 | ... 160 | "AWSNATAMI": { 161 | "us-east-1": { "AMI": "ami-c6699baf" }, 162 | "us-west-2": { "AMI": "ami-52ff7262" }, 163 | "us-west-1": { "AMI": "ami-3bcc9e7e" }, 164 | "eu-west-1": { "AMI": "ami-0b5b6c7f" }, 165 | "ap-southeast-1": { "AMI": "ami-02eb9350" }, 166 | "ap-southeast-2": { "AMI": "ami-ab990e91" }, 167 | "ap-northeast-1": { "AMI": "ami-14d86d15" }, 168 | "sa-east-1": { "AMI": "ami-0439e619" } 169 | }, 170 | ... 171 | } 172 | ``` 173 | 174 | Last week @anthroprose posed [a question via Pull Request](https://github.com/evandbrown/aws-hangouts/pull/3) (did you know you can suggest agenda items via PR? You totally can; you should give it a shot!) around declaring different CIDR ranges for VPCs and Subnets based on region. You might increment the second octet in your VPC CIDR for each region (i.e., 10.1.0.0/16 = us-east-1; 10.2.0.0/16 = us-west-2; etc.) This might serve as a convention so you understand which VPC and Region an Instance is in based on its IP addrses, and would also be important if you're connecting multiple VPCs in different regions to our own datacenter (like a inverse of the [CloudHub pattern](http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPN_CloudHub.html)). To meet this requirement we've declared the `Region2VpcCidr` mapping and entered keys indicating CIDR block for VPCs and both Public and Private Subnets by Region: 175 | 176 | ```json 177 | "Mappings": { 178 | ... 179 | "Region2VpcCidr" : { 180 | "us-east-1": { 181 | "VpcCidr": "10.1.0.0/16", 182 | "PublicSubnet1Cidr": "10.1.100.0/24", 183 | "PublicSubnet2Cidr": "10.1.101.0/24", 184 | "PrivateSubnet1Cidr": "10.1.200.0/24", 185 | "PrivateSubnet2Cidr": "10.1.201.0/24" 186 | }, 187 | "us-west-1": { 188 | "VpcCidr": "10.2.0.0/16", 189 | "PublicSubnet1Cidr": "10.2.100.0/24", 190 | "PublicSubnet2Cidr": "10.2.101.0/24", 191 | "PrivateSubnet1Cidr": "10.2.200.0/24", 192 | "PrivateSubnet2Cidr": "10.2.201.0/24" 193 | }, 194 | "us-west-2": { 195 | "VpcCidr": "10.3.0.0/16", 196 | "PublicSubnet1Cidr": "10.3.100.0/24", 197 | "PublicSubnet2Cidr": "10.3.101.0/24", 198 | "PrivateSubnet1Cidr": "10.3.200.0/24", 199 | "PrivateSubnet2Cidr": "10.3.201.0/24" 200 | }, 201 | ... 202 | }, 203 | } 204 | ``` 205 | 206 | Later in the template when we declare the VPC and Subnet resources, we simply lookup (`Fn::FindInMap`) the correct CIDR range from the map: 207 | 208 | ```json 209 | "Resources": { 210 | "VPC": { 211 | "Type": "AWS::EC2::VPC", 212 | "Properties": { 213 | "CidrBlock": { 214 | "Fn::FindInMap": ["Region2VpcCidr", { "Ref" : "AWS::Region" }, "VpcCidr"] 215 | }, 216 | ... 217 | } 218 | }, 219 | "PublicSubnet1": { 220 | "Type": "AWS::EC2::Subnet", 221 | "Properties": { 222 | "CidrBlock": { 223 | "Fn::FindInMap": ["Region2VpcCidr", { "Ref" : "AWS::Region" }, "PublicSubnet1Cidr"] 224 | }, 225 | "AvailabilityZone": { "Ref": "VPCAvailabilityZone1" }, 226 | ... 227 | } 228 | }, 229 | ... 230 | } 231 | ``` 232 | 233 | #### 2 Public and 2 Private Subnets 234 | This one's pretty straighforward. We create 2 public subnets (1 in each AZ) and 2 private subnets (also 1 in each AZ). 235 | 236 | #### Private Subnets have outbound Internat access (TCP 80 and 443) via NATDevice (EC2 Instance) 237 | We define a route table, then a private route, and associate the private subnets with the table: 238 | 239 | ```json 240 | "PrivateRouteTable": { 241 | "Type": "AWS::EC2::RouteTable", 242 | "Properties": { 243 | "VpcId": { 244 | "Ref": "VPC" 245 | }, 246 | "Tags": [{ 247 | "Key": "Network", 248 | "Value": { "Fn::Join" : ["-", [{ "Ref": "NetworkName" }, { "Ref": "AWS::Region" }] ]} 249 | }, { 250 | "Key": "Network", 251 | "Value": "Private" 252 | }] 253 | } 254 | }, 255 | "PrivateRoute": { 256 | "Type": "AWS::EC2::Route", 257 | "Properties": { 258 | "RouteTableId": { 259 | "Ref": "PrivateRouteTable" 260 | }, 261 | "DestinationCidrBlock": "0.0.0.0/0", 262 | "InstanceId": { 263 | "Ref": "NATDevice" 264 | } 265 | } 266 | }, 267 | "PrivateSubnet1RouteTableAssociation": { 268 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 269 | "Properties": { 270 | "SubnetId": { 271 | "Ref": "PrivateSubnet1" 272 | }, 273 | "RouteTableId": { 274 | "Ref": "PrivateRouteTable" 275 | } 276 | } 277 | }, 278 | 279 | "PrivateSubnet2RouteTableAssociation": { 280 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 281 | "Properties": { 282 | "SubnetId": { 283 | "Ref": "PrivateSubnet2" 284 | }, 285 | "RouteTableId": { 286 | "Ref": "PrivateRouteTable" 287 | } 288 | } 289 | }, 290 | ``` 291 | 292 | Notice that the `PrivateRoute` indicates traffice should route through `NATInstance`. That's the EC2 Instance we declared that's configured to run NAT. Notice that we get the AMI from a map (and also that SourceDestCheck is set to false...required for NAT/PAT): 293 | 294 | ```json 295 | "NATDevice": { 296 | "Type": "AWS::EC2::Instance", 297 | "Properties": { 298 | "InstanceType": { 299 | "Ref": "NATInstanceType" 300 | }, 301 | "SubnetId": { 302 | "Ref": "PublicSubnet1" 303 | }, 304 | "SourceDestCheck": "false", 305 | "ImageId": { 306 | "Fn::FindInMap": ["AWSNATAMI", { 307 | "Ref": "AWS::Region" 308 | }, "AMI"] 309 | }, 310 | "SecurityGroupIds": [{ 311 | "Ref": "NATSecurityGroup" 312 | }] 313 | } 314 | }, 315 | ``` 316 | 317 | The security group for our NAT Instance is configured to allow TCP 80 and 443 from the CIDR range of our private subnets. We retrieve those ranges from a map: 318 | 319 | ```json 320 | "NATSecurityGroup": { 321 | "Type": "AWS::EC2::SecurityGroup", 322 | "Properties": { 323 | "SecurityGroupIngress": [{ 324 | "IpProtocol": "tcp", 325 | "FromPort": "80", 326 | "ToPort": "80", 327 | "CidrIp": { 328 | "Fn::FindInMap": ["Region2VpcCidr", { "Ref" : "AWS::Region" }, "PrivateSubnet1Cidr"] 329 | } 330 | }, { 331 | "IpProtocol": "tcp", 332 | "FromPort": "443", 333 | "ToPort": "443", 334 | "CidrIp": { 335 | "Fn::FindInMap": ["Region2VpcCidr", { "Ref" : "AWS::Region" }, "PrivateSubnet1Cidr"] 336 | } 337 | }], 338 | ... 339 | } 340 | }, 341 | ``` 342 | 343 | #### Bastion EC2 Instance for SSH access to Instances in Private Subnet 344 | The `BastionHost` resource is an EC2 instance configured to allow SSH access from the public Internet, restricted to the CIDR range you specify in the `SSHFrom` input parameter. Though we've set the default for this param to 0.0.0.0/0, you should always override it with your specific source CIDR range. 345 | 346 | #### InstanceSecurityGroup configured to allow TCP 22 from Bastion Instance 347 | Although this stack builds out a VPC, we'll eventually run EC2 Instances and applications inside of it, and we want to be able to SSH to these new Instances from the Bastion. We've gone ahead and declared the `InstanceSecurityGroup` with rules to allow `BastionHost` to connect on TCP 22. We can simply attach this group to future instances, ensuring SSH access from the Bastion: 348 | 349 | ```json 350 | "InstanceSecurityGroup": { 351 | "Type": "AWS::EC2::SecurityGroup", 352 | "Properties": { 353 | "VpcId": { 354 | "Ref": "VPC" 355 | }, 356 | "SecurityGroupIngress": [{ 357 | "IpProtocol": "tcp", 358 | "FromPort": "22", 359 | "ToPort": "22", 360 | "SourceSecurityGroupId": { 361 | "Ref": "BastionSecurityGroup" 362 | } 363 | }], 364 | "SecurityGroupEgress": [{ 365 | "IpProtocol": "tcp", 366 | "FromPort": "22", 367 | "ToPort": "22", 368 | "SourceSecurityGroupId": { 369 | "Ref": "BastionSecurityGroup" 370 | } 371 | }] 372 | } 373 | }, 374 | ``` 375 | 376 | #### Outputs 377 | Finally, we'll need information from this stack to launch EC2 instances and applications into the VPC in the future. We've included in the Outputs everything we might need to launch an ELB, Elastic Beanstalk app, RDS database, or Auto Scaling group into this VPC at any point in the future, including: 378 | 379 | * Bastion 380 | * InstanceSecurityGroup 381 | * PrivateSubnet1 382 | * PrivateSubnet2 383 | * PublicSubnet1 384 | * PublicSubnet2 385 | * VPCAvailabilityZone1 386 | * VPCAvailabilityZone2 387 | * VPCId 388 | 389 | ### Version Controlled Security! 390 | Let's say we want to modify the `InstanceSecurityGroup` to open TCP 443. Let's add Ingress and Egress rules, commit the template to git, and update the stack with the CLI: 391 | 392 | ``` 393 | aws cloudformation update-stack \ 394 | --stack-name "vpc-demo" \ 395 | --template-body file://vpc-scaffold.cfn.json \ 396 | --parameters ParameterKey=KeyName,ParameterValue=evbrown-amazon \ ParameterKey=VPCAvailabilityZone1,ParameterValue=eu-west-1a \ ParameterKey=VPCAvailabilityZone2,ParameterValue=eu-west-1c \ 397 | --region eu-west-1 398 | ``` 399 | 400 | 401 | ## Q&A 402 | 403 | ## Get in Touch 404 | You can find CloudFormation on [Twitter](http://twitter.com/awscloudformer). Evan is on [GitHub](http://github.com/evandbrown), [Twitter](http://twitter.com/evandbrown) or at evbrown (at) amazon.com. 405 | 406 | Check out the Application Management Blog at [http://blogs.aws.amazon.com/application-management](http://blogs.aws.amazon.com/application-management) for weekly technical posts about CloudFormation, Elastic Beanstalk, and OpsWorks. 407 | 408 | -------------------------------------------------------------------------------- /20140130_cfn/README.md: -------------------------------------------------------------------------------- 1 | AWS CloudFormation Office Hours: January 30, 2014 2 | ======================================================== 3 | Thanks for joining AWS Developer Community Manager Evan Brown for CloudFormation Office Hours. The Hangout starts at 9:00 AM PST on Thursday, January 30, and is scheduled for 30 minutes. 4 | 5 | This week we're joined by [Adam Thomas](http://www.linkedin.com/pub/adam-thomas/12/215/621/), a Software Development Engineer at AWS, and Chetan Dandekar, Senior Product Manager for CloudFormation. You'll learn from Adam how to securely download content from S3 onto your EC2 Instances when using `cfn-init`, and he'll hang around to help answer questions. 6 | 7 | [Check out the index](../README.md) for a list of our previous Hangouts, including detailed agenda and recordings. 8 | 9 | ## The Recording 10 | Below the recording of the January 30 session, including Q&A: 11 | 12 | [![](img/video.png)](https://plus.google.com/events/cl5s04iq8t39861dvh30fv2h58c) 13 | 14 | ## Agenda Overview 15 | * New features since the last Hangout 16 | * Community agenda items 17 | * Feature Highlight: Adam Thomas covers using AWS::CloudFormation::Authentication for secure file and sources downloads with `cfn-init` 18 | * Your Q&A 19 | 20 | ## About the Q&A 21 | Q&A is enabled for this Hangout. To ask your question, click the Ask a new question button in the bottom-right corner of your screen, like so: 22 | 23 | ![](img/hangout-qa.png) 24 | 25 | You can enter your questions at any point during the hangout. Keep in mind that it takes about 50 seconds before the audio and video to make it through all the tubes to your computer, so by the time you've typed your question we may be on another topic. But don't worry! We've got the final half of the Hangout reserved just for Q&A. 26 | 27 | ## New Features 28 | On January 27 CloudFormation added 2 new features: 29 | 30 | 1. **Auto Scaling scheduled actions support**: You can scale the number of Amazon EC2 instances in an Auto Scaling group based on a schedule. By using a schedule, you can scale applications in response to predictable load changes. For more information, see [AWS::AutoScaling::ScheduledAction](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-as-scheduledaction.html). 31 | 32 | 2. **Amazon DynamoDB secondary indexes**: You can create local and global secondary indexes for DynamoDB databases. By using secondary indexes, you can efficiently access data with attributes other than the primary key. For more information, see [AWS::DynamoDB::Table](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html). 33 | 34 | 3. **Amazon SQS dead letter queues**: You can specify a dead letter queue, where messages are sent when the source queue fails to process on them. For more information, see [AWS::SQS::Queue](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sqs-queues.html). 35 | 36 | You can always find the latest release notes at [http://aws.amazon.com/releasenotes/AWS-CloudFormation](http://aws.amazon.com/releasenotes/AWS-CloudFormation) 37 | 38 | ## Community Agenda Suggestions 39 | 40 | Kudos to the [alanwill](https://github.com/alanwill), [anthroprose](https://github.com/anthroprose), and [ringods](https://github.com/ringods) who forked the repo, added in some really great suggestions and sent Pull Requests! Let's look at their suggestions: 41 | 42 | * Non-disruptive application upgrade: how to go from v1 to v2 of your app without downtime? Is it possible to deploy v2 of your app on new instances and flip the DNS all using CloudFormation? 43 | 44 | > Short answer is yes: if you manage DNS in Route 53 and CloudFormation with [AWS::Route53::RecordSet](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-recordset.html), you can modify your CloudFormation template to resolve a record to - for example - a new ELB where v2 of your app has been deployed. If you're using [Weighted Record Sets](http://docs.aws.amazon.com/Route53/latest/DeveloperGuide/WeightedResourceRecordSets.html), you can introduce a new version of your application incrementally by adjusting the weight (i.e., distribution) of your Route 53 records in CloudFormation and update the stack periodically. 45 | > 46 | > Also consider that AWS Elastic Beanstalk provides an API to facilitate CNAME swap. See [Zero Downtime Deployment](http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/using-features.CNAMESwap.html) for more information. 47 | 48 | * How would I make a scalable way of passing Subnet information from one template to another for use with ELBs? ELB Subnets require "Type: A list of strings" however Template Paremeters can be String, Number, or CSV. Some of my regions have 3 AZs, some have 4. Passing subnets with a CSV such as: "ELBSubnets" : { "Fn::Join" : [",", [{ "Ref" : "PublicSubnet0" }, { "Ref" : "PublicSubnet1" }, { "Ref" : "PublicSubnet2" }]]} does not work. For now I need 1 template for 3 subnet ELBs and 1 template for 4 subnet ELBs. 49 | 50 | > CloudFormation supports a few different [input Parameter types](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/concept-parameters.html), including String and CommaDelimitedList. If you wanted to pass in a set of VPC Subnet IDs, you could use the CommaDelimitedList and Ref it from the ElasticLoadBalance resource: 51 | 52 | ```json 53 | { 54 | "Parameters": { 55 | "Subnets": { 56 | "Type": "CommaDelimitedList" 57 | } 58 | }, 59 | "Resources": { 60 | "ElasticLoadBalancer": { 61 | "Type": "AWS::ElasticLoadBalancing::LoadBalancer", 62 | "Properties": { 63 | "Subnets": { 64 | "Ref": "Subnets" 65 | }, 66 | "Listeners": [{ 67 | "LoadBalancerPort": "80", 68 | "InstancePort": "80", 69 | "Protocol": "HTTP" 70 | }] 71 | } 72 | } 73 | } 74 | } 75 | ``` 76 | 77 | * Scalable Subnet Patterns & Best Practices for Multiple Regions & Environments (Production/Staging) 78 | 79 | > This is a good idea to dedicate more time to. We'll cover this in a Feature Focus segment of an upcoming Office Hours soon. 80 | 81 | * Security groups that reference themselves. When you have a cluster using the same security group and each node in the cluster needs to communicate with other nodes in the cluster there's a need to create a Security Group rule with ports where the source is the same security group ID. How do you do this with CloudFormation? 82 | 83 | > By default, EC2 Instances in the same SG are not allowed to communicate with eachother. You need to add a self-referencing rule in the Security Group the Instances are running in. Because you can't Ref a Security Group resource in CloudFormation (because the resource doens't exist yet), the [AWS::EC2::SecurityGroupIngress](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group-ingress.html) resource type exists to allow you to append ingress rules after resource creation. In this example we declare the `SGBase` resource, and Ref it from the `SGBaseIngress`: 84 | 85 | ```json 86 | { 87 | "AWSTemplateFormatVersion": "2010-09-09", 88 | "Resources": { 89 | "SGBase": { 90 | "Type": "AWS::EC2::SecurityGroup", 91 | "Properties": { 92 | "GroupDescription": "Base Security Group", 93 | "SecurityGroupIngress": [ 94 | { 95 | "IpProtocol": "tcp", 96 | "CidrIp": "0.0.0.0/0", 97 | "FromPort": "22", 98 | "ToPort": "22" 99 | } 100 | ] 101 | } 102 | }, 103 | "SGBaseIngress": { 104 | "Type": "AWS::EC2::SecurityGroupIngress", 105 | "Properties": { 106 | "GroupName": { "Ref": "SGBase" }, 107 | "IpProtocol": "tcp", 108 | "FromPort": "80", 109 | "ToPort": "80", 110 | "SourceSecurityGroupName": { "Ref": "SGBase" } 111 | } 112 | } 113 | } 114 | } 115 | ``` 116 | 117 | * Please suggest agenda topics you'd like to see us cover! Fork this repo, add your items to this list, then submit a Pull Request! If we merge your PR, we'll cover the topic in this session! 118 | 119 | ## Feature Highlight: AWS::CloudFormation::Authentication 120 | 121 | CloudFormation produces a helper script named [cfn-init](http://aws.amazon.com/developertools/4026240853893296) that performs one function: getting your application onto a CloudFormation-created EC2 instance. Cfn-init can do a lot of things (see the [documentation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-init.html) for more), but one of the most used features is downloading files and sources. The difference a file and a source is that a source is extracted once it has been downloaded. 122 | 123 | There is a lot of documentation about AWS::CloudFormation::Authentication; two good resources are the [official documentation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-authentication.html) and Evan's [blog](http://blogs.aws.amazon.com/application-management/post/Tx3LKFZ27CWZBKO/Authenticated-File-Downloads-with-CloudFormation) on the subject. Instead of going over every kind of authentication, let's look at the most common kind: IAM Roles with downloads from S3. 124 | 125 | I've created a [sample template](s3-role-authentication.json) that we'll use as a demonstration of S3 role downloads. Since we can't create inline S3 content in templates, this requires a little bit of setup: 126 | 127 | 1. Create an S3 bucket in your region of choice 128 | 2. Upload [an index page](index.html) as `index.html` and some [classy images](images.zip) as `images.zip` 129 | 3. Create a stack using the [sample template](s3-role-authentication.json). The only parameter is the name of your S3 Bucket. 130 | 4. When the stack hits CREATE_COMPLETE, click on the link in the Outputs tab. 131 | 132 | Reminder: this sample uses AWS resources, so it may cost you money if you exceed the [free tier](http://aws.amazon.com/free/) 133 | 134 | The mechanics of the template are pretty straightforward. First, we have to create a role and an instance profile: 135 | 136 | ```json 137 | "InstanceRole": { 138 | "Type": "AWS::IAM::Role", 139 | "Properties": { 140 | "AssumeRolePolicyDocument": { 141 | "Statement": [ 142 | { 143 | "Effect": "Allow", 144 | "Principal": { 145 | "Service": [ "ec2.amazonaws.com" ] 146 | }, 147 | "Action": [ "sts:AssumeRole" ] 148 | } 149 | ] 150 | }, 151 | "Path": "/" 152 | } 153 | }, 154 | "RolePolicies": { 155 | "Type": "AWS::IAM::Policy", 156 | "Properties": { 157 | "PolicyName": "S3Download", 158 | "PolicyDocument": { 159 | "Statement": [ 160 | { 161 | "Action": [ "s3:GetObject" ], 162 | "Effect": "Allow", 163 | "Resource": [ { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "S3Bucket" }, "/index.html"]] }, 164 | { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "S3Bucket" }, "/images.zip"]] }] 165 | } 166 | ] 167 | }, 168 | "Roles": [ { "Ref": "InstanceRole" } ] 169 | } 170 | }, 171 | "InstanceProfile": { 172 | "Type": "AWS::IAM::InstanceProfile", 173 | "Properties": { 174 | "Path": "/", 175 | "Roles": [ { "Ref": "InstanceRole" } ] 176 | } 177 | } 178 | ``` 179 | 180 | A few things to take away from this snippet: 181 | 1. The IAM role is scoped to the [least privilege](http://docs.aws.amazon.com/IAM/latest/UserGuide/IAMBestPractices.html#grant-least-privilege) necessary to download the files. You only need GetObject for the objects you intend to download -- ListBucket, for instance, is not necessary. 182 | 2. An IAM Instance Profile is created, which allows us to associate the IAM Profile to the EC2 instance. This will allow cfn-init to make authenticated requests to S3. 183 | 184 | Next, let's look at the EC2 instance definition. 185 | ```json 186 | "WebServer" : { 187 | "Type" : "AWS::EC2::Instance", 188 | "Properties" : { 189 | "IamInstanceProfile" : { "Ref" : "InstanceProfile" }, 190 | "InstanceType" : "t1.micro", 191 | "SecurityGroups" : [ { "Ref" : "SecurityGroup" } ] 192 | ... 193 | } 194 | ... 195 | } 196 | ``` 197 | 198 | The only real takeaway here is the IamInstanceProfile property, which lets the instance access the role we defined previously. 199 | 200 | Finally, we set up the EC2 instance's software configuration in Metadata. Cfn-init will read this metadata when determining how to set up the instance. 201 | 202 | ```json 203 | "Metadata" : { 204 | "AWS::CloudFormation::Authentication" : { 205 | "S3AccessCreds" : { 206 | "type" : "S3", 207 | "roleName" : { "Ref" : "InstanceRole" }, 208 | "buckets" : [{ "Ref" : "S3Bucket" }] 209 | } 210 | }, 211 | "AWS::CloudFormation::Init" : { 212 | "config" : { 213 | "files" : { 214 | "/var/www/html/index.html" : { 215 | "source" : { "Fn::Join" : ["", ["https://", { "Ref" : "S3Bucket" }, ".s3.amazonaws.com/index.html"]] }, 216 | "mode" : "000400", 217 | "owner" : "apache", 218 | "group" : "apache", 219 | "authentication" : "S3AccessCreds" 220 | } 221 | }, 222 | "sources" : { 223 | "/var/www/html" : { "Fn::Join" : ["", ["https://", { "Ref" : "S3Bucket" }, ".s3.amazonaws.com/images.zip"]] } 224 | }, 225 | "services" : { 226 | "sysvinit" : { 227 | "httpd" : { 228 | "enabled" : "true", 229 | "ensureRunning" : "true" 230 | } 231 | } 232 | } 233 | } 234 | } 235 | } 236 | ``` 237 | 238 | In the `AWS::CloudFormation::Authentication` section, we configure the credentials available to cfn-init. This can include HTTP basic usernames and passwords, AWS Access Keys and Secret Keys, and IAM roles. Let's take a closer look at the credential: 239 | ```json 240 | "S3AccessCreds" : { 241 | "type" : "S3", 242 | "roleName" : { "Ref" : "InstanceRole" }, 243 | "buckets" : [{ "Ref" : "S3Bucket" }] 244 | } 245 | ``` 246 | The `S3` type is, as you would expect, used to authenticate against S3. `roleName` refers to the role linked to the EC2 instance (not the instance profile). `buckets` takes a list of bucket names; cfn-init will authenticate any request to the listed buckets with this credential automatically. 247 | 248 | Next, let's look at an authenticated file download: 249 | ```json 250 | "files" : { 251 | "/var/www/html/index.html" : { 252 | "source" : { "Fn::Join" : ["", ["https://", { "Ref" : "S3Bucket" }, ".s3.amazonaws.com/index.html"]] }, 253 | "mode" : "000400", 254 | "owner" : "apache", 255 | "group" : "apache", 256 | "authentication" : "S3AccessCreds" 257 | } 258 | } 259 | ``` 260 | The `authentication` key tells cfn-init to explicitly use S3AccessCreds. In this case, it is superfluous -- since we added `{ "Ref" : "S3Bucket" }` to the list of buckets in the definition of S3AccessCreds, this configuration will do nothing. In a more practical scenario, you could use the `authentication` key to override the default authentication behavior configured by the `buckets` setting. 261 | 262 | Finally, the source: 263 | ```json 264 | "sources" : { 265 | "/var/www/html" : { "Fn::Join" : ["", ["https://", { "Ref" : "S3Bucket" }, ".s3.amazonaws.com/images.zip"]] } 266 | } 267 | ``` 268 | Here, we don't specify an `authentication` key. That's because `sources` does not support it. You *must* use the `buckets` setting in the authentication configuration to make an authenticated `sources` download. 269 | 270 | ### Troubleshooting Authentication 271 | 272 | Authentication can be tricky to get right, and the failure modes are not obvious. 273 | 274 | 1. Check the logs! 275 | 276 | /var/log/cfn-init.log is your primary source for cfn-init troubleshooting. You should be able to determine which error code S3 is returning, and which file it's failing on. If you want more output, you can run cfn-init with -v for verbose output 277 | 278 | 2. Check your nesting 279 | 280 | The most common mistake people make is nesting `AWS::CloudFormation::Authentication` beneath `AWS::CloudFormation::Init`. They should be siblings (meaning both should be direct children of `Metadata`), else cfn-init will not be able to find the configuration 281 | 282 | 3. Check your case 283 | 284 | S3 Object Keys are case sensitive; mismatching case will cause your downloads to fail. 285 | 286 | 3. Know your S3 error codes 287 | 288 | 1. Error code 403: Your user does not have permission, or the key you requested in a *non-public bucket* does not exist 289 | 290 | `[ERROR] Unhandled exception during build: Failed to retrieve https://adamthom-hangouts-demo.s3.amazonaws.com/index.html2: HTTP Error 403 :...` 291 | 292 | 2. Error code 404: The key you requested in a *publicly-listable bucket* does not exist 293 | 294 | `[ERROR] Error encountered during build of config: Failed to retrieve https://adamthom-hangouts-demo.s3.amazonaws.com/index.html2:` 295 | `HTTP Error 404 : NoSuchKeyThe specified key does not exist.` 296 | `index.html2...` 297 | 298 | or the bucket itself does not exist: 299 | 300 | `[ERROR] HTTP Error 404 : NoSuchBucketThe specified bucket does not exist` 301 | `adamthom-hangouts-demozzzz...` 302 | 303 | ## Q&A 304 | 305 | ## Get in Touch 306 | You can find CloudFormation on [Twitter](http://twitter.com/awscloudformer). Evan is on [GitHub](http://github.com/evandbrown), [Twitter](http://twitter.com/evandbrown) or at evbrown (at) amazon.com. 307 | 308 | Check out the Application Management Blog at http://blogs.aws.amazon.com/application-management for weekly technical posts about CloudFormation, Elastic Beanstalk, and OpsWorks. 309 | 310 | -------------------------------------------------------------------------------- /20140213_cfn/vpc-scaffold.cfn.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | 4 | "Description": "Provision a VPC (across two AZs), a Bastion and NAT instance, and a generic Security Group with ingress access to the NAT instance and from the Bastion instance. Output the VPC, Subnet, and SG IDs.", 5 | 6 | "Parameters": { 7 | "KeyName": { 8 | "Description": "Name of an existing EC2 KeyPair to enable SSH access to the Elastic Beanstalk and Bastion hosts", 9 | "Type": "String", 10 | "MinLength": "1", 11 | "MaxLength": "255", 12 | "AllowedPattern": "[\\x20-\\x7E]*", 13 | "ConstraintDescription": "can contain only ASCII characters." 14 | }, 15 | 16 | "SSHFrom": { 17 | "Description": "Lockdown SSH access to the bastion host (default can be accessed from anywhere)", 18 | "Type": "String", 19 | "MinLength": "9", 20 | "MaxLength": "18", 21 | "Default": "0.0.0.0/0", 22 | "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})", 23 | "ConstraintDescription": "must be a valid CIDR range of the form x.x.x.x/x." 24 | }, 25 | 26 | "BastionInstanceType": { 27 | "Description": "Bastion Host EC2 instance type", 28 | "Type": "String", 29 | "Default": "m1.small", 30 | "AllowedValues": ["t1.micro", "m1.small", "m1.medium", "m1.large", "m1.xlarge", "m2.xlarge", "m2.2xlarge", "m2.4xlarge", "m3.xlarge", "m3.2xlarge", "c1.medium", "c1.xlarge", "cc1.4xlarge", "cc2.8xlarge", "cg1.4xlarge"], 31 | "ConstraintDescription": "must be a valid EC2 instance type." 32 | }, 33 | 34 | "NATInstanceType": { 35 | "Description": "NET Device EC2 instance type", 36 | "Type": "String", 37 | "Default": "m1.small", 38 | "AllowedValues": ["t1.micro", "m1.small", "m1.medium", "m1.large", "m1.xlarge", "m2.xlarge", "m2.2xlarge", "m2.4xlarge", "m3.xlarge", "m3.2xlarge", "c1.medium", "c1.xlarge", "cc1.4xlarge", "cc2.8xlarge", "cg1.4xlarge"], 39 | "ConstraintDescription": "must be a valid EC2 instance type." 40 | }, 41 | "VPCAvailabilityZone1": { 42 | "Description": "One of two Availability Zones that will be used to create subnets.", 43 | "Type": "String", 44 | "MinLength": "1", 45 | "MaxLength": "255" 46 | }, 47 | "VPCAvailabilityZone2": { 48 | "Description": "Two of two Availability Zones that will be used to create subnets. Must be different than VPCAvailabilityZone2.", 49 | "Type": "String", 50 | "MinLength": "1", 51 | "MaxLength": "255" 52 | }, 53 | "NetworkName": { 54 | "Description": "The name of the network that will be used to tag all resources in the VPC", 55 | "Type": "String", 56 | "Default": "Default" 57 | } 58 | }, 59 | 60 | "Mappings": { 61 | "Region2VpcCidr" : { 62 | "us-east-1": { 63 | "VpcCidr": "10.1.0.0/16", 64 | "PublicSubnet1Cidr": "10.1.100.0/24", 65 | "PublicSubnet2Cidr": "10.1.101.0/24", 66 | "PrivateSubnet1Cidr": "10.1.200.0/24", 67 | "PrivateSubnet2Cidr": "10.1.201.0/24" 68 | }, 69 | "us-west-1": { 70 | "VpcCidr": "10.2.0.0/16", 71 | "PublicSubnet1Cidr": "10.2.100.0/24", 72 | "PublicSubnet2Cidr": "10.2.101.0/24", 73 | "PrivateSubnet1Cidr": "10.2.200.0/24", 74 | "PrivateSubnet2Cidr": "10.2.201.0/24" 75 | }, 76 | "us-west-2": { 77 | "VpcCidr": "10.3.0.0/16", 78 | "PublicSubnet1Cidr": "10.3.100.0/24", 79 | "PublicSubnet2Cidr": "10.3.101.0/24", 80 | "PrivateSubnet1Cidr": "10.3.200.0/24", 81 | "PrivateSubnet2Cidr": "10.3.201.0/24" 82 | }, 83 | "eu-west-1": { 84 | "VpcCidr": "10.3.0.0/16", 85 | "PublicSubnet1Cidr": "10.3.100.0/24", 86 | "PublicSubnet2Cidr": "10.3.101.0/24", 87 | "PrivateSubnet1Cidr": "10.3.200.0/24", 88 | "PrivateSubnet2Cidr": "10.3.201.0/24" 89 | }, 90 | "sa-east-1": { 91 | "VpcCidr": "10.4.0.0/16", 92 | "PublicSubnet1Cidr": "10.4.100.0/24", 93 | "PublicSubnet2Cidr": "10.4.101.0/24", 94 | "PrivateSubnet1Cidr": "10.4.200.0/24", 95 | "PrivateSubnet2Cidr": "10.4.201.0/24" 96 | }, 97 | "ap-northeast-1": { 98 | "VpcCidr": "10.5.0.0/16", 99 | "PublicSubnet1Cidr": "10.5.100.0/24", 100 | "PublicSubnet2Cidr": "10.5.101.0/24", 101 | "PrivateSubnet1Cidr": "10.5.200.0/24", 102 | "PrivateSubnet2Cidr": "10.5.201.0/24" 103 | }, 104 | "ap-southeast-1": { 105 | "VpcCidr": "10.6.0.0/16", 106 | "PublicSubnet1Cidr": "10.6.100.0/24", 107 | "PublicSubnet2Cidr": "10.6.101.0/24", 108 | "PrivateSubnet1Cidr": "10.6.200.0/24", 109 | "PrivateSubnet2Cidr": "10.6.201.0/24" 110 | }, 111 | "ap-southeast-2": { 112 | "VpcCidr": "10.7.0.0/16", 113 | "PublicSubnet1Cidr": "10.7.100.0/24", 114 | "PublicSubnet2Cidr": "10.7.101.0/24", 115 | "PrivateSubnet1Cidr": "10.7.200.0/24", 116 | "PrivateSubnet2Cidr": "10.7.201.0/24" 117 | } 118 | }, 119 | 120 | "AWSNATAMI": { 121 | "us-east-1": { 122 | "AMI": "ami-c6699baf" 123 | }, 124 | "us-west-2": { 125 | "AMI": "ami-52ff7262" 126 | }, 127 | "us-west-1": { 128 | "AMI": "ami-3bcc9e7e" 129 | }, 130 | "eu-west-1": { 131 | "AMI": "ami-0b5b6c7f" 132 | }, 133 | "ap-southeast-1": { 134 | "AMI": "ami-02eb9350" 135 | }, 136 | "ap-southeast-2": { 137 | "AMI": "ami-ab990e91" 138 | }, 139 | "ap-northeast-1": { 140 | "AMI": "ami-14d86d15" 141 | }, 142 | "sa-east-1": { 143 | "AMI": "ami-0439e619" 144 | } 145 | }, 146 | 147 | "AWSInstanceType2Arch": { 148 | "t1.micro": { 149 | "Arch": "64" 150 | }, 151 | "m1.small": { 152 | "Arch": "64" 153 | }, 154 | "m1.medium": { 155 | "Arch": "64" 156 | }, 157 | "m1.large": { 158 | "Arch": "64" 159 | }, 160 | "m1.xlarge": { 161 | "Arch": "64" 162 | }, 163 | "m2.xlarge": { 164 | "Arch": "64" 165 | }, 166 | "m2.2xlarge": { 167 | "Arch": "64" 168 | }, 169 | "m2.4xlarge": { 170 | "Arch": "64" 171 | }, 172 | "m3.xlarge": { 173 | "Arch": "64" 174 | }, 175 | "m3.2xlarge": { 176 | "Arch": "64" 177 | }, 178 | "c1.medium": { 179 | "Arch": "64" 180 | }, 181 | "c1.xlarge": { 182 | "Arch": "64" 183 | }, 184 | "cc1.4xlarge": { 185 | "Arch": "64Cluster" 186 | }, 187 | "cc2.8xlarge": { 188 | "Arch": "64Cluster" 189 | }, 190 | "cg1.4xlarge": { 191 | "Arch": "64GPU" 192 | } 193 | }, 194 | 195 | "AWSRegionArch2AMI": { 196 | "us-east-1": { 197 | "32": "ami-a0cd60c9", 198 | "64": "ami-aecd60c7", 199 | "64Cluster": "ami-a8cd60c1", 200 | "64GPU": "ami-eccf6285" 201 | }, 202 | "us-west-2": { 203 | "32": "ami-46da5576", 204 | "64": "ami-48da5578", 205 | "64Cluster": "NOT_YET_SUPPORTED", 206 | "64GPU": "NOT_YET_SUPPORTED" 207 | }, 208 | "us-west-1": { 209 | "32": "ami-7d4c6938", 210 | "64": "ami-734c6936", 211 | "64Cluster": "NOT_YET_SUPPORTED", 212 | "64GPU": "NOT_YET_SUPPORTED" 213 | }, 214 | "eu-west-1": { 215 | "32": "ami-61555115", 216 | "64": "ami-6d555119", 217 | "64Cluster": "ami-67555113", 218 | "64GPU": "NOT_YET_SUPPORTED" 219 | }, 220 | "ap-southeast-1": { 221 | "32": "ami-220b4a70", 222 | "64": "ami-3c0b4a6e", 223 | "64Cluster": "NOT_YET_SUPPORTED", 224 | "64GPU": "NOT_YET_SUPPORTED" 225 | }, 226 | "ap-southeast-2": { 227 | "32": "ami-b3990e89", 228 | "64": "ami-bd990e87", 229 | "64Cluster": "NOT_YET_SUPPORTED", 230 | "64GPU": "NOT_YET_SUPPORTED" 231 | }, 232 | "ap-northeast-1": { 233 | "32": "ami-2a19aa2b", 234 | "64": "ami-2819aa29", 235 | "64Cluster": "NOT_YET_SUPPORTED", 236 | "64GPU": "NOT_YET_SUPPORTED" 237 | }, 238 | "sa-east-1": { 239 | "32": "ami-f836e8e5", 240 | "64": "ami-fe36e8e3", 241 | "64Cluster": "NOT_YET_SUPPORTED", 242 | "64GPU": "NOT_YET_SUPPORTED" 243 | } 244 | } 245 | }, 246 | 247 | "Resources": { 248 | 249 | "VPC": { 250 | "Type": "AWS::EC2::VPC", 251 | "Properties": { 252 | "CidrBlock": { 253 | "Fn::FindInMap": ["Region2VpcCidr", { "Ref" : "AWS::Region" }, "VpcCidr"] 254 | }, 255 | "Tags": [{ 256 | "Key": "Network", 257 | "Value": { "Fn::Join" : ["-", [{ "Ref": "NetworkName" }, { "Ref": "AWS::Region" }] ]} 258 | }, { 259 | "Key": "Scheme", 260 | "Value": "PublicAndPrivate" 261 | }] 262 | } 263 | }, 264 | 265 | "PublicSubnet1": { 266 | "Type": "AWS::EC2::Subnet", 267 | "Properties": { 268 | "VpcId": { 269 | "Ref": "VPC" 270 | }, 271 | "CidrBlock": { 272 | "Fn::FindInMap": ["Region2VpcCidr", { "Ref" : "AWS::Region" }, "PublicSubnet1Cidr"] 273 | }, 274 | "AvailabilityZone": { 275 | "Ref": "VPCAvailabilityZone1" 276 | }, 277 | "Tags": [{ 278 | "Key": "Network", 279 | "Value": { "Fn::Join" : ["-", [{ "Ref": "NetworkName" }, { "Ref": "AWS::Region" }] ]} 280 | }, { 281 | "Key": "Network", 282 | "Value": "Public" 283 | }] 284 | } 285 | }, 286 | 287 | "PublicSubnet2": { 288 | "Type": "AWS::EC2::Subnet", 289 | "Properties": { 290 | "VpcId": { 291 | "Ref": "VPC" 292 | }, 293 | "CidrBlock": { 294 | "Fn::FindInMap": ["Region2VpcCidr", { "Ref" : "AWS::Region" }, "PublicSubnet2Cidr"] 295 | }, 296 | "AvailabilityZone": { 297 | "Ref": "VPCAvailabilityZone2" 298 | }, 299 | "Tags": [{ 300 | "Key": "Network", 301 | "Value": { "Fn::Join" : ["-", [{ "Ref": "NetworkName" }, { "Ref": "AWS::Region" }] ]} 302 | }, { 303 | "Key": "Network", 304 | "Value": "Public" 305 | }] 306 | } 307 | }, 308 | 309 | "InternetGateway": { 310 | "Type": "AWS::EC2::InternetGateway", 311 | "Properties": { 312 | "Tags": [{ 313 | "Key": "Network", 314 | "Value": { "Fn::Join" : ["-", [{ "Ref": "NetworkName" }, { "Ref": "AWS::Region" }] ]} 315 | }, { 316 | "Key": "Network", 317 | "Value": "Public" 318 | }] 319 | } 320 | }, 321 | 322 | "GatewayToInternet": { 323 | "Type": "AWS::EC2::VPCGatewayAttachment", 324 | "Properties": { 325 | "VpcId": { 326 | "Ref": "VPC" 327 | }, 328 | "InternetGatewayId": { 329 | "Ref": "InternetGateway" 330 | } 331 | } 332 | }, 333 | 334 | "PublicRouteTable": { 335 | "Type": "AWS::EC2::RouteTable", 336 | "Properties": { 337 | "VpcId": { 338 | "Ref": "VPC" 339 | }, 340 | "Tags": [{ 341 | "Key": "Network", 342 | "Value": { "Fn::Join" : ["-", [{ "Ref": "NetworkName" }, { "Ref": "AWS::Region" }] ]} 343 | }, { 344 | "Key": "Network", 345 | "Value": "Public" 346 | }] 347 | } 348 | }, 349 | 350 | "PublicRoute": { 351 | "Type": "AWS::EC2::Route", 352 | "DependsOn": "GatewayToInternet", 353 | "Properties": { 354 | "RouteTableId": { 355 | "Ref": "PublicRouteTable" 356 | }, 357 | "DestinationCidrBlock": "0.0.0.0/0", 358 | "GatewayId": { 359 | "Ref": "InternetGateway" 360 | } 361 | } 362 | }, 363 | 364 | "PublicSubnet1RouteTableAssociation": { 365 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 366 | "Properties": { 367 | "SubnetId": { 368 | "Ref": "PublicSubnet1" 369 | }, 370 | "RouteTableId": { 371 | "Ref": "PublicRouteTable" 372 | } 373 | } 374 | }, 375 | 376 | "PublicSubnet2RouteTableAssociation": { 377 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 378 | "Properties": { 379 | "SubnetId": { 380 | "Ref": "PublicSubnet2" 381 | }, 382 | "RouteTableId": { 383 | "Ref": "PublicRouteTable" 384 | } 385 | } 386 | }, 387 | 388 | "PublicNetworkAcl": { 389 | "Type": "AWS::EC2::NetworkAcl", 390 | "Properties": { 391 | "VpcId": { 392 | "Ref": "VPC" 393 | }, 394 | "Tags": [{ 395 | "Key": "Network", 396 | "Value": { "Fn::Join" : ["-", [{ "Ref": "NetworkName" }, { "Ref": "AWS::Region" }] ]} 397 | }, { 398 | "Key": "Network", 399 | "Value": "Public" 400 | }] 401 | } 402 | }, 403 | 404 | "InboundHTTPPublicNetworkAclEntry": { 405 | "Type": "AWS::EC2::NetworkAclEntry", 406 | "Properties": { 407 | "NetworkAclId": { 408 | "Ref": "PublicNetworkAcl" 409 | }, 410 | "RuleNumber": "100", 411 | "Protocol": "6", 412 | "RuleAction": "allow", 413 | "Egress": "false", 414 | "CidrBlock": "0.0.0.0/0", 415 | "PortRange": { 416 | "From": "80", 417 | "To": "80" 418 | } 419 | } 420 | }, 421 | 422 | "InboundHTTPSPublicNetworkAclEntry": { 423 | "Type": "AWS::EC2::NetworkAclEntry", 424 | "Properties": { 425 | "NetworkAclId": { 426 | "Ref": "PublicNetworkAcl" 427 | }, 428 | "RuleNumber": "101", 429 | "Protocol": "6", 430 | "RuleAction": "allow", 431 | "Egress": "false", 432 | "CidrBlock": "0.0.0.0/0", 433 | "PortRange": { 434 | "From": "443", 435 | "To": "443" 436 | } 437 | } 438 | }, 439 | 440 | "InboundSSHPublicNetworkAclEntry": { 441 | "Type": "AWS::EC2::NetworkAclEntry", 442 | "Properties": { 443 | "NetworkAclId": { 444 | "Ref": "PublicNetworkAcl" 445 | }, 446 | "RuleNumber": "102", 447 | "Protocol": "6", 448 | "RuleAction": "allow", 449 | "Egress": "false", 450 | "CidrBlock": { 451 | "Ref": "SSHFrom" 452 | }, 453 | "PortRange": { 454 | "From": "22", 455 | "To": "22" 456 | } 457 | } 458 | }, 459 | 460 | "InboundEmphemeralPublicNetworkAclEntry": { 461 | "Type": "AWS::EC2::NetworkAclEntry", 462 | "Properties": { 463 | "NetworkAclId": { 464 | "Ref": "PublicNetworkAcl" 465 | }, 466 | "RuleNumber": "103", 467 | "Protocol": "6", 468 | "RuleAction": "allow", 469 | "Egress": "false", 470 | "CidrBlock": "0.0.0.0/0", 471 | "PortRange": { 472 | "From": "1024", 473 | "To": "65535" 474 | } 475 | } 476 | }, 477 | 478 | "OutboundPublicNetworkAclEntry": { 479 | "Type": "AWS::EC2::NetworkAclEntry", 480 | "Properties": { 481 | "NetworkAclId": { 482 | "Ref": "PublicNetworkAcl" 483 | }, 484 | "RuleNumber": "100", 485 | "Protocol": "6", 486 | "RuleAction": "allow", 487 | "Egress": "true", 488 | "CidrBlock": "0.0.0.0/0", 489 | "PortRange": { 490 | "From": "0", 491 | "To": "65535" 492 | } 493 | } 494 | }, 495 | 496 | "PublicSubnet1NetworkAclAssociation": { 497 | "Type": "AWS::EC2::SubnetNetworkAclAssociation", 498 | "Properties": { 499 | "SubnetId": { 500 | "Ref": "PublicSubnet1" 501 | }, 502 | "NetworkAclId": { 503 | "Ref": "PublicNetworkAcl" 504 | } 505 | } 506 | }, 507 | 508 | "PublicSubnet2NetworkAclAssociation": { 509 | "Type": "AWS::EC2::SubnetNetworkAclAssociation", 510 | "Properties": { 511 | "SubnetId": { 512 | "Ref": "PublicSubnet2" 513 | }, 514 | "NetworkAclId": { 515 | "Ref": "PublicNetworkAcl" 516 | } 517 | } 518 | }, 519 | 520 | "PrivateSubnet1": { 521 | "Type": "AWS::EC2::Subnet", 522 | "Properties": { 523 | "VpcId": { 524 | "Ref": "VPC" 525 | }, 526 | "AvailabilityZone": { 527 | "Ref": "VPCAvailabilityZone1" 528 | }, 529 | "CidrBlock": { 530 | "Fn::FindInMap": ["Region2VpcCidr", { "Ref" : "AWS::Region" }, "PrivateSubnet1Cidr"] 531 | }, 532 | "Tags": [{ 533 | "Key": "Network", 534 | "Value": { "Fn::Join" : ["-", [{ "Ref": "NetworkName" }, { "Ref": "AWS::Region" }] ]} 535 | }, { 536 | "Key": "Network", 537 | "Value": "Private" 538 | }] 539 | } 540 | }, 541 | 542 | "PrivateSubnet2": { 543 | "Type": "AWS::EC2::Subnet", 544 | "Properties": { 545 | "VpcId": { 546 | "Ref": "VPC" 547 | }, 548 | "AvailabilityZone": { 549 | "Ref": "VPCAvailabilityZone2" 550 | }, 551 | "CidrBlock": { 552 | "Fn::FindInMap": ["Region2VpcCidr", { "Ref" : "AWS::Region" }, "PrivateSubnet2Cidr"] 553 | }, 554 | "Tags": [{ 555 | "Key": "Network", 556 | "Value": { "Fn::Join" : ["-", [{ "Ref": "NetworkName" }, { "Ref": "AWS::Region" }] ]} 557 | }, { 558 | "Key": "Network", 559 | "Value": "Private" 560 | }] 561 | } 562 | }, 563 | 564 | "PrivateRouteTable": { 565 | "Type": "AWS::EC2::RouteTable", 566 | "Properties": { 567 | "VpcId": { 568 | "Ref": "VPC" 569 | }, 570 | "Tags": [{ 571 | "Key": "Network", 572 | "Value": { "Fn::Join" : ["-", [{ "Ref": "NetworkName" }, { "Ref": "AWS::Region" }] ]} 573 | }, { 574 | "Key": "Network", 575 | "Value": "Private" 576 | }] 577 | } 578 | }, 579 | 580 | "PrivateSubnet1RouteTableAssociation": { 581 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 582 | "Properties": { 583 | "SubnetId": { 584 | "Ref": "PrivateSubnet1" 585 | }, 586 | "RouteTableId": { 587 | "Ref": "PrivateRouteTable" 588 | } 589 | } 590 | }, 591 | 592 | "PrivateSubnet2RouteTableAssociation": { 593 | "Type": "AWS::EC2::SubnetRouteTableAssociation", 594 | "Properties": { 595 | "SubnetId": { 596 | "Ref": "PrivateSubnet2" 597 | }, 598 | "RouteTableId": { 599 | "Ref": "PrivateRouteTable" 600 | } 601 | } 602 | }, 603 | 604 | "PrivateRoute": { 605 | "Type": "AWS::EC2::Route", 606 | "Properties": { 607 | "RouteTableId": { 608 | "Ref": "PrivateRouteTable" 609 | }, 610 | "DestinationCidrBlock": "0.0.0.0/0", 611 | "InstanceId": { 612 | "Ref": "NATDevice" 613 | } 614 | } 615 | }, 616 | 617 | "PrivateNetworkAcl": { 618 | "Type": "AWS::EC2::NetworkAcl", 619 | "Properties": { 620 | "VpcId": { 621 | "Ref": "VPC" 622 | }, 623 | "Tags": [{ 624 | "Key": "Network", 625 | "Value": { "Fn::Join" : ["-", [{ "Ref": "NetworkName" }, { "Ref": "AWS::Region" }] ]} 626 | }, { 627 | "Key": "Network", 628 | "Value": "Private" 629 | }] 630 | } 631 | }, 632 | 633 | "InboundPrivateNetworkAclEntry": { 634 | "Type": "AWS::EC2::NetworkAclEntry", 635 | "Properties": { 636 | "NetworkAclId": { 637 | "Ref": "PrivateNetworkAcl" 638 | }, 639 | "RuleNumber": "100", 640 | "Protocol": "6", 641 | "RuleAction": "allow", 642 | "Egress": "false", 643 | "CidrBlock": "0.0.0.0/0", 644 | "PortRange": { 645 | "From": "0", 646 | "To": "65535" 647 | } 648 | } 649 | }, 650 | 651 | "OutBoundPrivateNetworkAclEntry": { 652 | "Type": "AWS::EC2::NetworkAclEntry", 653 | "Properties": { 654 | "NetworkAclId": { 655 | "Ref": "PrivateNetworkAcl" 656 | }, 657 | "RuleNumber": "100", 658 | "Protocol": "6", 659 | "RuleAction": "allow", 660 | "Egress": "true", 661 | "CidrBlock": "0.0.0.0/0", 662 | "PortRange": { 663 | "From": "0", 664 | "To": "65535" 665 | } 666 | } 667 | }, 668 | 669 | "PrivateSubnet1NetworkAclAssociation": { 670 | "Type": "AWS::EC2::SubnetNetworkAclAssociation", 671 | "Properties": { 672 | "SubnetId": { 673 | "Ref": "PrivateSubnet1" 674 | }, 675 | "NetworkAclId": { 676 | "Ref": "PrivateNetworkAcl" 677 | } 678 | } 679 | }, 680 | 681 | "PrivateSubnet2NetworkAclAssociation": { 682 | "Type": "AWS::EC2::SubnetNetworkAclAssociation", 683 | "Properties": { 684 | "SubnetId": { 685 | "Ref": "PrivateSubnet2" 686 | }, 687 | "NetworkAclId": { 688 | "Ref": "PrivateNetworkAcl" 689 | } 690 | } 691 | }, 692 | 693 | "NATIPAddress": { 694 | "Type": "AWS::EC2::EIP", 695 | "DependsOn": "GatewayToInternet", 696 | "Properties": { 697 | "Domain": "vpc", 698 | "InstanceId": { 699 | "Ref": "NATDevice" 700 | } 701 | } 702 | }, 703 | 704 | "NATDevice": { 705 | "Type": "AWS::EC2::Instance", 706 | "Properties": { 707 | "InstanceType": { 708 | "Ref": "NATInstanceType" 709 | }, 710 | "SubnetId": { 711 | "Ref": "PublicSubnet1" 712 | }, 713 | "SourceDestCheck": "false", 714 | "ImageId": { 715 | "Fn::FindInMap": ["AWSNATAMI", { 716 | "Ref": "AWS::Region" 717 | }, "AMI"] 718 | }, 719 | "SecurityGroupIds": [{ 720 | "Ref": "NATSecurityGroup" 721 | }] 722 | } 723 | }, 724 | 725 | "NATSecurityGroup": { 726 | "Type": "AWS::EC2::SecurityGroup", 727 | "Properties": { 728 | "GroupDescription": "Enable internal access to the NAT device", 729 | "VpcId": { 730 | "Ref": "VPC" 731 | }, 732 | "SecurityGroupIngress": [{ 733 | "IpProtocol": "tcp", 734 | "FromPort": "80", 735 | "ToPort": "80", 736 | "CidrIp": { 737 | "Fn::FindInMap": ["Region2VpcCidr", { "Ref" : "AWS::Region" }, "PrivateSubnet1Cidr"] 738 | } 739 | }, { 740 | "IpProtocol": "tcp", 741 | "FromPort": "443", 742 | "ToPort": "443", 743 | "CidrIp": { 744 | "Fn::FindInMap": ["Region2VpcCidr", { "Ref" : "AWS::Region" }, "PrivateSubnet1Cidr"] 745 | } 746 | }], 747 | "SecurityGroupEgress": [{ 748 | "IpProtocol": "tcp", 749 | "FromPort": "80", 750 | "ToPort": "80", 751 | "CidrIp": "0.0.0.0/0" 752 | }, { 753 | "IpProtocol": "tcp", 754 | "FromPort": "443", 755 | "ToPort": "443", 756 | "CidrIp": "0.0.0.0/0" 757 | }] 758 | } 759 | }, 760 | 761 | "InstanceSecurityGroup": { 762 | "Type": "AWS::EC2::SecurityGroup", 763 | "Properties": { 764 | "GroupDescription": "SG that EB instances will launch into.", 765 | "VpcId": { 766 | "Ref": "VPC" 767 | }, 768 | "SecurityGroupIngress": [{ 769 | "IpProtocol": "tcp", 770 | "FromPort": "22", 771 | "ToPort": "22", 772 | "SourceSecurityGroupId": { 773 | "Ref": "BastionSecurityGroup" 774 | } 775 | }], 776 | "SecurityGroupEgress": [{ 777 | "IpProtocol": "tcp", 778 | "FromPort": "22", 779 | "ToPort": "22", 780 | "SourceSecurityGroupId": { 781 | "Ref": "BastionSecurityGroup" 782 | } 783 | }] 784 | } 785 | }, 786 | 787 | "BastionIPAddress": { 788 | "Type": "AWS::EC2::EIP", 789 | "DependsOn": "GatewayToInternet", 790 | "Properties": { 791 | "Domain": "vpc", 792 | "InstanceId": { 793 | "Ref": "BastionHost" 794 | } 795 | } 796 | }, 797 | 798 | "BastionHost": { 799 | "Type": "AWS::EC2::Instance", 800 | "Properties": { 801 | "InstanceType": { 802 | "Ref": "BastionInstanceType" 803 | }, 804 | "KeyName": { 805 | "Ref": "KeyName" 806 | }, 807 | "SubnetId": { 808 | "Ref": "PublicSubnet1" 809 | }, 810 | "ImageId": { 811 | "Fn::FindInMap": ["AWSRegionArch2AMI", { 812 | "Ref": "AWS::Region" 813 | }, { 814 | "Fn::FindInMap": ["AWSInstanceType2Arch", { 815 | "Ref": "BastionInstanceType" 816 | }, "Arch"] 817 | }] 818 | }, 819 | "SecurityGroupIds": [{ 820 | "Ref": "BastionSecurityGroup" 821 | }] 822 | } 823 | }, 824 | 825 | "BastionSecurityGroup": { 826 | "Type": "AWS::EC2::SecurityGroup", 827 | "Properties": { 828 | "GroupDescription": "Enable access to the Bastion host", 829 | "VpcId": { 830 | "Ref": "VPC" 831 | }, 832 | "SecurityGroupIngress": [{ 833 | "IpProtocol": "tcp", 834 | "FromPort": "22", 835 | "ToPort": "22", 836 | "CidrIp": { 837 | "Ref": "SSHFrom" 838 | } 839 | }], 840 | "SecurityGroupEgress": [{ 841 | "IpProtocol": "tcp", 842 | "FromPort": "22", 843 | "ToPort": "22", 844 | "CidrIp": { 845 | "Fn::FindInMap": ["Region2VpcCidr", { "Ref" : "AWS::Region" }, "PrivateSubnet1Cidr"] 846 | } 847 | }, { 848 | "IpProtocol": "tcp", 849 | "FromPort": "22", 850 | "ToPort": "22", 851 | "CidrIp": { 852 | "Fn::FindInMap": ["Region2VpcCidr", { "Ref" : "AWS::Region" }, "PrivateSubnet2Cidr"] 853 | } 854 | }] 855 | } 856 | } 857 | }, 858 | 859 | "Outputs": { 860 | 861 | "Bastion": { 862 | "Description": "IP Address of the Bastion host", 863 | "Value": { 864 | "Ref": "BastionIPAddress" 865 | } 866 | }, 867 | "InstanceSecurityGroup" : { 868 | "Description" : "The ID of a VPC Security Group that has ingress access to the NAT instance.", 869 | "Value" : { "Ref" : "InstanceSecurityGroup" } 870 | }, 871 | "VPCId" : { 872 | "Description" : "A VPC ID.", 873 | "Value" : { "Ref" : "VPC" } 874 | }, 875 | "PrivateSubnet1" : { 876 | "Description" : "A private VPC subnet ID.", 877 | "Value" : { "Ref" : "PrivateSubnet1" } 878 | }, 879 | "PrivateSubnet2" : { 880 | "Description" : "A private VPC subnet ID. Must be in a different AZ than PrivateSubnet1", 881 | "Value" : {"Ref" : "PrivateSubnet2" } 882 | }, 883 | "PublicSubnet1" : { 884 | "Description" : "A public VPC subnet ID.", 885 | "Value" : { "Ref" : "PublicSubnet1" } 886 | }, 887 | "PublicSubnet2" : { 888 | "Description" : "A public VPC subnet ID. Must be in a different AZ than PrivateSubnet1", 889 | "Value" : { "Ref" : "PublicSubnet2" } 890 | }, 891 | "VPCAvailabilityZone1" : { 892 | "Description": "The AZ that (Public|Private)Subnet1 is launched into.", 893 | "Value": { "Ref": "VPCAvailabilityZone1"} 894 | }, 895 | "VPCAvailabilityZone2" : { 896 | "Description": "The AZ that (Public|Private)Subnet2 is launched into.", 897 | "Value": { "Ref": "VPCAvailabilityZone2"} 898 | } 899 | } 900 | } 901 | --------------------------------------------------------------------------------