AWS Cloudformation Cross stack ref for Security group - error - amazon-web-services

I have below 2 stacks
1) Stack 1 - this is network stack, defines vpc, subnets and security group
2) Stack 2 - this stack defines ec2 instance
Network stack exports following
WebServerSG:
Description : "Web Server Security Group"
Value: !GetAtt InstanceSecurityGroup.GroupId
Export:
Name: !Sub ${AWS::StackName}-WebServerSG
The ec2 instance stack accepts a parameter "NetworkStack" and uses the network stack to refer to the security group as follows
"Resources" : {
"WebServerInstance": {
"Type": "AWS::EC2::Instance",
"Properties": {
"ImageId" : { "Fn::FindInMap" : [ "AWSRegionArch2AMI", { "Ref" : "AWS::Region" },
{ "Fn::FindInMap" : [ "AWSInstanceType2Arch", { "Ref" : "InstanceType" }, "Arch" ] } ] },
"InstanceType" : { "Ref" : "InstanceType" },
"SubnetId" : {"Fn::ImportValue" : {"Fn::Sub" : "${NetworkStack}-SubnetADMZ"}},
"SecurityGroupIds" : {"Fn::ImportValue" : {"Fn::Sub" : "${NetworkStack}-WebServerSG"}},
"KeyName" : { "Ref" : "KeyName" }
}
}
},
The ec2 instance stack fails with the error , "Value of property SecurityGroupIds must be of type List of String"
I tried to use SecurityGroups instead , but received similar error

Even if you want to specify only 1 security group, CloudFormation wants a list. The solution here is to make a list of one element, the element being the imported security group. In your case it will look like this (notice the brackets):
...
"SecurityGroupIds" : [{"Fn::ImportValue" : {"Fn::Sub" : "${NetworkStack}-WebServerSG"}}],
...

Laurent Jalbert Simard answer is correct. Fn:ImportValue should be enclosed in [] to provide imported value as list.

Related

AWS CloudFormation: How to output a machine's PublicIP?

I wrote a CloudFormation template which creates a linux docker host.
I want to display the PublicIP of the machine under the "Outputs" section.
This is the relevant portion of the template:
"Outputs" : {
"ServerAddress" : {
"Value" : { "Fn::GetAtt" : [ "Server", "PublicDnsName" ] },
"Description" : "Server Domain Name"
},
"SecurityGroup" : {
"Value" : { "Fn::GetAtt" : [ "ServerSecurityGroup", "GroupId" ] },
"Description" : "Server Security Group Id"
},
"PublicIp" : {
"Value" : { "Fn::GetAtt" : [ "ServerPublicIp", "PublicIp" ]},
"Description" : "Server's PublicIp Address"
},
}
I've read in the official AWS documentation about using "Fn::GetAtt" and tried to implement it in my template, but when I try to create the stack I get the following error:
Error
Template validation error: Template error: instance of Fn::GetAtt references undefined resource ServerPublicIp
As far as I understand, the first part in the GetAtt line is a LogicalName (which I can choose?) and the second one is the real attribute as appears on the above link.
So my question is how to display the PublicIP of the server under the Outputs section?
Assuming your have an EC2 instance resource in your template named Server:
"Server" : {
"Type" : "AWS::EC2::Instance",
"Properties" : {
}
}
You output the public IP address referencing it's resource name:
"Outputs" : {
"PublicIp" : {
"Value" : { "Fn::GetAtt" : [ "Server", "PublicIp" ]},
"Description" : "Server's PublicIp Address"
}
}
As mentioned in the docs, the outputs can optionally be exported for cross-stack references. In case that is your use-case:
JSON:
"Outputs" : {
"PublicIp" : {
"Value" : { "Fn::GetAtt" : ["Server", "PublicIp"]},
"Description" : "Server Public IP"
"Export" : {
"Name" : {"Fn::Sub": "${AWS::StackName}-PublicIP"}
}
}
}
YAML:
Outputs:
PublicIp:
Description: Server Public IP
Value: !GetAtt Server.PublicIp
Export:
Name: !Sub "${AWS::StackName}-PublicIp"
See also:
AWS::EC2::Instance properties and return values in the docs.

AWS specific parameters and EC2 SecurityGroupIds List String Error

I have a rather annoying issue which I am unable to resolve and will do my best to explain.
The following cut down example works in which I am able to reference a parameter and assign the security groups to my instance via the SecurityGroupIds property:
"Parameters" : {
"pDefaultSg" : {
"Description" : "AWS2 VPC default security groups",
"Type" : "List<AWS::EC2::SecurityGroup::Id>",
"Default" : "sg-245xxxxx,sg-275xxxxx,sg-235xxxxx"
}
}
"Resources" : {
"ec2Instance" : {
"Type" : "AWS::EC2::Instance",
"Properties" : {
"SecurityGroupIds" : { "Ref" : "pDefaultSg" }
}
}
The issue begins when I also want to add a second value to the SecurityGroupIds property referencing a security group resource instantiated within the same template:
"Resources" : {
"ec2Instance" : { ...
"SecurityGroupIds" : [ { "Ref" : "pDefaultSg" }, { "Fn::GetAtt" : "sgDb", "GroupId" } ],
....
"sgDb" : {
"Type" : "AWS::EC2::SecurityGroup",
"Properties" : { ...
I am then unable to avoid the following error causing the Cloudformation stack to rollback:
Value of property SecurityGroupIds must be of type List of String
I would really appreciate any pointers.
Many Thanks
The issue is that when pDefaultSg is accessed via the Ref intrinsic function it returns a list, therefore your SecurityGroupIds Property looks like
[["sg-245xxxxx","sg-275xxxxx","sg-235xxxxx"],"sg-1234DB"]
The solution is to change your SecurityGroupIds Property to Fn::Join the pDefaultSg List to a comma separated string followed by the sgDb:
"SecurityGroupIds": [
{"Fn::Join":
[",",
{"Ref": "pDefaultSg"}
]
},
{ "Fn::GetAtt" : ["sgDb", "GroupId"] }
]

How to name an Auto Scaling Group in a CloudFormation template?

I have a CloudFormation template that creates an auto scaling group (among other things). How can I give the auto scaling group a name in the template?
The AWS docs do not mention anything (http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-as-group.html), and its possible to do if I create it trough the AWS website. (I need to give a group a name because I need to find this group from another script)
EDIT: I've tried to add a tag called "Name", but it still does not work:
"Resources": {
"MyServerGroup" : {
"Type" : "AWS::AutoScaling::AutoScalingGroup",
"Properties" : {
"AvailabilityZones" : { "Fn::GetAZs" : ""},
"LaunchConfigurationName" : { "Ref" : "LaunchConfig" },
"MinSize" : { "Ref" : "ServerCount" },
"MaxSize" : { "Ref" : "ServerCount" },
"DesiredCapacity" : { "Ref" : "ServerCount" },
"LoadBalancerNames" : [ { "Ref" : "ElasticLoadBalancerName" } ],
"Tags" : [ {
"Key" : "Name",
"Value" : { "Ref" : "ServerName" },
"PropagateAtLaunch" : "true"
} ]
},
"CreationPolicy": {
"ResourceSignal": {
"Count": "2",
"Timeout": "PT5M"
}
}
},
The name column in the AWS console still displays something like "MyStackName-MyServerGroup-345MH3NF34N7E", and in the Tags field I can see the key-value pair for the Name tag that I added.
Although naming the AutoScalingGroup (ASG) doesn't seem to be possible, you can export the name assigned when the ASG is created by the CloudFormation (CF) template by using the following:
Outputs:
AutoScalingGroupName:
Description: "AutoScalingGroup Name"
Export:
Name:
Fn::Sub: ${AWS::StackName}-autoscalinggroupname
Value: { Ref: AutoScalingGroup }
The ASG name can then be used in other CF templates, although this is perhaps not what the OP means by "find this group from another script".
CloudFormation now has a property called: AutoScalingGroupName
Description: "The name of the Auto Scaling group. Minimum length of 1. Maximum length of 255. Must follow the following pattern: [\u0020-\uD7FF\uE000-\uFFFD\uD800\uDC00-\uDBFF\uDFFF\r\n\t]*"
Type: String
Required: No
see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-as-group.html
Create a tag with for the ASG with the key "Name" (the capital N is important). This will be used for the Name column in the console.
Note that you could find the ASG by searching for a well known tag other than Name from your other script. Tags are a great way to search for resources.
Clearly from the AWS Tagging Auto Scaling Groups and Instances doc, you can not set value of tag prefixed with aws:.
I guess, value you have read from console "MyStackName-MyServerGroup-345MH3NF34N7E" is of tag aws:autoscaling:groupName. So its obvious you can not set Auto Scaling Group Name in CloudFormation Template.
Unfortunately, from my experience, you can not pass tag value in CloudFormation template. But best, you can use AWS SDK to read tag value from launched EC2 instances.
The attribute "AutoscalingGroupName" within AWS::AutoScaling::AutoScalingGroup entity works.
Sample template :
"Autoscaling1"
{
"Type" : "AWS::AutoScaling::AutoScalingGroup",
"Properties" : {
"AutoScalingGroupName" : String,
"AvailabilityZones" : [ String, ... ],
"Cooldown" : String,
"DesiredCapacity" : String,
"HealthCheckGracePeriod" : Integer,
"HealthCheckType" : String,
"InstanceId" : String,
"LaunchConfigurationName" : String,
"LifecycleHookSpecificationList" : [ LifecycleHookSpecification, ... ],
"LoadBalancerNames" : [ String, ... ],
"MaxSize" : String,
"MetricsCollection" : [ MetricsCollection, ... ],
"MinSize" : String,
"NotificationConfigurations" : [ NotificationConfiguration, ... ],
"PlacementGroup" : String,
"Tags" : [ TagProperty, ... ],
"TargetGroupARNs" : [ String, ... ],
"TerminationPolicies" : [ String, ... ],
"VPCZoneIdentifier" : [ String, ... ]
}
}
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-as-group.html#cfn-autoscaling-autoscalinggroup-autoscalinggroupname

Can I use public security groups in cloud formation for ec2 launch configs?

Can I use public security groups in cloud formation for ec2 launch configs ?
I am trying to simply direct the cloudformation stack to use an existing set and it fails to create those resources.
I tried to create those security groups as a rolling set (ssh1,ssh2...) for each stack but no such luck either.
It would be really cool if your answer would include a code snippet as to how to do this exactly. thanks.
You can refer the sample template for deep understanding.
Amazon EC2 instance in a security group
Here is the part you are interesting.
"Resources" : {
"EC2Instance" : {
"Type" : "AWS::EC2::Instance",
"Properties" : {
"InstanceType" : { "Ref" : "InstanceType" },
"SecurityGroups" : [ { "Ref" : "InstanceSecurityGroup" } ], # <--Here is the refer
"KeyName" : { "Ref" : "KeyName" },
"ImageId" : { "Fn::FindInMap" : [ "AWSRegionArch2AMI", { "Ref" : "AWS::Region" },
{ "Fn::FindInMap" : [ "AWSInstanceType2Arch", { "Ref" : "InstanceType" }, "Arch" ] } ] }
}
},
"InstanceSecurityGroup" : {
"Type" : "AWS::EC2::SecurityGroup",
"Properties" : {
"GroupDescription" : "Enable SSH access via port 22",
"SecurityGroupIngress" : [ {
"IpProtocol" : "tcp",
"FromPort" : "22",
"ToPort" : "22",
"CidrIp" : { "Ref" : "SSHLocation"}
} ]
}
}
},
Above sample to create a new security group, and assign it to a new ec2 instance. If you have exist security group, then you needn't the part InstanceSecurityGroup, and assign the real security group name to ec2 instance at:
"SecurityGroups" : "REAL Security Group Name",

how to construct a string of physical subnet ids to create db subnet group on the fly in a cloudformation script?

I'm trying to build a CLoudFormation script that launches an instance and a db into a vpc at the same time. the issue is the db requires two AZ's so i create a second subnet and now i just need to reference the two subnet physical ids in a 'MyDBSubnetGroup' var. I can get the logical IDs for the subnets i created but dont know how to ref those physical IDs. ANyone know? THanks!!
Heres my code:
"MyDBSubnetGroup" : {
"Type" : "AWS::RDS::DBSubnetGroup",
"Properties" : {
"DBSubnetGroupDescription" : "Subnets available for the RDS DB Instance",
"SubnetIds" : { "Fn::Join" : [ " ", [{"Ref" : "PublicSubnetAZ1"}, ", ", {"Ref" : "PublicSubnetAZ2"}, " " ]]}
}
},
I run into the same issue, after working with AWS support I understood that List of String does not mean what we initially thought. Also, if you want to place the DB inside a VPC you must not use AWS::RDS::DBSecurityGroup objects.
Here is a full sample, it took me a while to get it working:
"dbSubnetGroup" : {
"Type" : "AWS::RDS::DBSubnetGroup",
"Properties" : {
"DBSubnetGroupDescription" : "Availability Zones for RDS DB",
"SubnetIds" : [ { "Ref" : "subnetPrivate1" },
{ "Ref" : "subnetPrivate2" } ]
}
},
"dbInstance" : {
"Type" : "AWS::RDS::DBInstance",
"Properties" : {
"DBInstanceIdentifier" : { "Fn::Join" : [ "",
[ { "Ref" : "AWS::StackName" },
"DB" ] ] },
"DBName" : "dbname",
"DBSubnetGroupName" : { "Ref" : "dbSubnetGroup" },
"MultiAZ" : "true",
"AllocatedStorage" : "8",
"BackupRetentionPeriod" : "0",
"DBInstanceClass" : "db.m1.medium",
"Engine" : "postgres",
"MasterUserPassword" : "masteruserpassword",
"MasterUsername" : "masterusername",
"VPCSecurityGroups" : [ { "Ref" : "sgVpc" }, { "Ref" : "sgDB" } ]
}
},
If you map the subnet ids you can access them with something like this.
"AWSRegionSubnet":{
"us-east-1":{
"RDSSubnets":[
"subnet-aaaaaaaa",
"subnet-bbbbbbbb"
]
},
"us-west-2":{
"RDSSubnets":[
"subnet-cccccccc",
"subnet-dddddddd"
]
}
}
"RDSSubnet":{
"Type":"AWS::RDS::DBSubnetGroup",
"Properties":{
"DBSubnetGroupDescription":"Some cool notes here",
"SubnetIds":{
"Fn::FindInMap":[
"AWSRegionSubnet",
{
"Ref":"AWS::Region"
},
"RDSSubnets"
]
}
}
}