Add AWS::Route53::RecordSet DnsRecord to a serverless Cloudfront Distribution - aws-cloudformation

I found this on how to associate a route53 dns record with a S3 bucket in a serverless.yml file.
I've tried to adapt that to the case of deploying a cloudfront distrib
DnsRecord:
Type: "AWS::Route53::RecordSet"
Properties:
AliasTarget:
DNSName: <cloudfrontdistribution id>
HostedZoneId: Z21DNDUVLTQW6Q
HostedZoneName: ${self:custom.appFQDN}.
Name:
Ref: WebAppCloudFrontDistribution
Type: 'CNAME'
but am struggling with how to get the distribution id as a ref rather than a fixed string.
How would I do this?

To set up an AliasTarget, you actually just need to provide the CloudFront DNS name for the DNSName parameter, not the distribution ID. You can do this with:
!GetAtt WebAppCloudFrontDistribution.DomainName
I'm assuming that WebAppCloudFrontDistribution is the logical ID of an AWS::CloudFront::Distribution resource in your template and not a parameter. If this is actually a parameter, just set the value of the parameter to the DNS name listed for the distribution in the AWS console dashboard for CloudFront.
There are some other things you'll need to fix in your template:
HostedZoneName should be the name of the Route53 hosted zone, not the FQDN you want to use. Personally, I prefer to use the HostedZoneId property for AWS::Route53::RecordSet resources instead since it's clearer what the meaning of this property is, but to each their own. (Note: HostedZoneId property for the AWS::Route53::RecordSet resource should be the HostedZoneId for YOUR hosted zone, not the same value as the AliasTarget HostedZoneId.)
Name should be the DNS name that you want to be a CNAME for the CloudFront distribution resource.
I know it's a bit weird, but with alias targets, you have to set the type to either "A" (for IPv4) or "AAAA" (IPv6). I recommend doing both - you can do this by creating a duplicate of your AWS::Route53::RecordSet resource but set type to "AAAA" instead of "A".
Finally, note that in order for this to work, you will also need to make sure to add the FQDN as an alternate name for the CloudFront distribution resource - you can set this using the "Aliases" property of the "DistributionConfig" property of the distribution resource in your template, or by configuring this manually for the distribution settings in the AWS console if you're not creating the resource in this template.

I struggled to create a AWS::Route53::RecordSet with CloudFormation producing unspecific, unhelpful error messages of the type "The resource failed to create". The key for me was to use HostedZoneId rather than HostedZoneName to specify the parent "hosted zone". This is what I ended up with:
NaaaaaComDNSEntry:
Type: 'AWS::Route53::RecordSet'
DependsOn: NaaaaaComCloudFront
Properties:
AliasTarget:
DNSName: !GetAtt NaaaaaComCloudFront.DomainName
# For CloudFront, HostedZoneId is always Z2FDTNDATAQYW2, see:
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-aliastarget.html#cfn-route53-aliastarget-hostedzoneid
HostedZoneId: Z2FDTNDATAQYW2
# HostedZoneId is for ID for 'naaaaa.com.'; In theory its valid to use `HostedZoneName` OR `HostedZoneId`
# but in practice the recordset always failed to create if I used `HostedZoneName`
HostedZoneId: ZABCDEFGHIJK5M
Name: 'www.naaaaa.com.'
Type: 'A'

This is what my working config looks like in serverless templates:
DnsRecord:
Type: "AWS::Route53::RecordSet"
Properties:
AliasTarget:
DNSName:
Fn::GetAtt:
- CloudFrontDistribution
- DomainName
# Looks like it is always the same for CloudFront distribs.
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-aliastarget.html
# https://docs.aws.amazon.com/general/latest/gr/rande.html#cf_region
HostedZoneId: ${self:custom.zoneId}
HostedZoneName: ${self:custom.secondLevelDomain}.
Name: ${self:custom.appFQDN}
Type: 'A'
And
CloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
...
Aliases:
- ${self:custom.appFQDN}
Also courtesy of an example by Tom McLaughlin:
https://github.com/ServerlessOpsIO/serverless-zombo.com/blob/master/serverless.yml

Related

Get attribute of EC2 created via LaunchConfiguration

I would like to get the PrivateIP attribute of EC2s that i create via LaunchConfiguration.
I need that attribute so that i can assign a type A dns record to the instance for other purposes.
Here is my code:
Resources:
webLaunchConfig:
Type: 'AWS::AutoScaling::LaunchConfiguration'
Properties:
ImageId: !Ref webEc2AMI
InstanceType: !Ref ec2WebInstanceType
SecurityGroups: !Ref webEc2SG
UserData:
'Fn::Base64': !Sub >
#!/bin/bash -xe
apt update -y
dnsWebServerName:
Type: 'AWS::Route53::RecordSet'
Properties:
HostedZoneId: !Ref hostedZoneId
Comment: DNS name for my db server.
Name: !Ref dnsWebServerNamePar
Type: A
TTL: '900'
ResourceRecords:
- !GetAtt webLaunchConfig.PrivateIp
... and when i try to launch it i get this error:
Template contains errors.: Template error: resource webLaunchConfig
does not support attribute type PrivateIp in Fn::GetAtt
... indicating me that what i am trying to do is not supported. Though there must be a way to achieve this.
Do you know how to do it? Or a workaround for this?
Sadly you can't do this. AWS::AutoScaling::LaunchConfiguration is only a blueprint of an instance to be launched. Thus it does not provide information about instance PrivateIp. The get the PrivateIp you have to actually launch the instance.
To do so you have to use AWS::EC2::Instance. However AWS::EC2::Instance does not support launching from ``AWS::AutoScaling::LaunchConfiguration. So either you have to change your LaunchConfigurationintoLaunchTemplateor just create instance directly usingAWS::EC2::Instance` rather then any templates.

CloudFormation - DHCPOptions - Array of DomainNameServers doesn't maintain order

I have a DHCPOptions defined in my CloudFormation template as so:
DhcpOptionSet:
Type: AWS::EC2::DHCPOptions
DependsOn:
- DnsInstance
- DnsSecondaryInstance
Properties:
DomainName: test.local
DomainNameServers:
- !GetAtt DnsInstance.PrivateIp
- !GetAtt DnsSecondaryInstance.PrivateIp
- AmazonProvidedDNS
Tags:
- Key: Name
Value: test-option-set
However, when CF creates the DHCP OptionSet, the order of the Name Servers doesn't match what I've defined in the template. It seems that no matter what order I put them in for my template, they end up as:
DnsSecondaryInstance.PrivateIp,AmazonProvidedDNS,DnsInstance.PrivateIp
This causes the resolv.conf on servers to be in the wrong order.
Is there anyway to ensure the ordering of the OptionSet?
What worked for me was to specify the list of DNS address as a comma separated list. The technique is described in the description of the DomainNameServers parameter under https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-dhcp-options.html which states:
If specifying more than one domain name server, specify the IP addresses in a single parameter, separated by commas.
To be more specific, assuming the DNS IP addresses are 10.10.0.1, 10.10.0.2, 10.10.0.3.
Properties:
DomainNameServers:
- 10.10.0.1, 10.10.0.2, 10.10.0.3

Specify HostedZone NameServers as CloudFormation Outputs

I am creating a CFN stack for a number of domains. The domain are not with the AWS registry, but a third-party one.
I want to have the list of nameservers from the SOA as part of the stack Outputs. However, as they aren't returned as a string but, according to the docs, a "set", I can't figure out how to extract and return them.
Details:
According to the docs for AWS::Route53::HostedZone, you can obtain the list of nameservers with
Return Values
[...]
Fn::GetAtt
Fn::GetAtt returns a value for a specified attribute of this type. The
following are the available attributes and sample return values.
NameServers
Returns the **set** of name servers for the specific hosted zone. For example: ns1.example.com.
This attribute is not supported for private hosted zones.
So, I tried to do:
Resources:
MyZone:
Type: 'AWS::Route53::HostedZone'
Properties:
Name: my.domain.
...
Outputs:
MyZone:
Value: !Ref MyZone
MyZoneServers:
Value: !GetAtt MyZone.NameServers
but that gives:
An error occurred (ValidationError) when calling the UpdateStack operation: Template format error: The Value field of every Outputs member must evaluate to a String.
When I only output the zone ref, it works just fine and get the Z... string for the zone.
I've tried various other tricks and approaches, mostly with various intrinsic functions such as !Split, !Select, etc. Nowhere can I seem to find what this "set" is: a list? a comma-separated string? (in which case !Split should work)
I could retrieve the nameservers via the describe function of Route53 after the stack is created, but my feeling is that I'm missing something totally obvious so don't want to add that extra step.
The set of nameservers is an array of strings. In order to output it you need to use !Join like this:
Resources:
MyZone:
Type: 'AWS::Route53::HostedZone'
Properties:
Name: my.domain.
...
Outputs:
MyZone:
Value: !Ref MyZone
MyZoneServers:
Value: !Join [',', !GetAtt MyZone.NameServers] # or any other delimiter that suits you
You should see the following Outputs:

AWS CloudFormation function call fails: Fn::ImportValue must not depend on any resources, imported values, or Fn::GetAZs

I have a cloud formation template (mainVPC) that creates few Subnets in a VPC and exports the subnets with names "PrivateSubnetA", "PrivateSubnetB" ...
I have a different cloud formation template that creates DBSubnetGroup. I want to use "PrivateSubnetA", "PrivateSubnetB" as default values if user does not provide data. CloundFormation does not support imported values in parameters. So I put some default value (XXXX) and had a condition section to see if the user has provided some input
Conditions:
userNotProvidedSubnetA: !Equals
- !Ref PrivateSubnetA
- XXXX
userNotProvidedSubnetB: !Equals
- !Ref PrivateSubnetB
- XXXX
This helps me in figuring out if the user has provided data. Now I want to use default values, if the user has not provided values, else use user-provided values.
below is code for that
DBSubnetGroup:
Type: 'AWS::RDS::DBSubnetGroup'
Properties:
DBSubnetGroupDescription: RDS Aurora Cluster Subnet Group
SubnetIds:
- !If
- userNotProvidedSubnetA
- Fn::ImportValue:
!Sub '${fmMainVpc}-PrivateSubnetA'
- !Ref PrivateSubnetA
- !If
- userNotProvidedSubnetB
- Fn::ImportValue:
!Sub '${fmMainVpc}-PrivateSubnetB'
- !Ref PrivateSubnetB
This fails with the error "Template error: the attribute in Fn::ImportValue must not depend on any resources, imported values, or Fn::GetAZs".
ImportValue is not used anywhere else in the template.
Is there a way for using exported values as default values ( the default values cannot be hardcoded, they come as exported values from a run of another stack), while providing an option for the users to provide their own values (to create resources).
Thanks.
This can also be caused by having a reference inside Fn::ImportValue to a parameter be misnamed. For example, if I have the following parameter NetworkStackName defined and I mis-reference it in the Fn::ImportValue statement (as NetworkName), I will get this error. I would need to change the NetworkName to match the value in Parameters, NetworkStackName to fix the error.
Parameters:
NetworkStackName:
Type: String
Default: happy-network-topology
Resources:
MySQLDatabase:
Type: AWS::RDS::DBInstance
Properties:
Engine: MySQL
DBSubnetGroupName:
Fn::ImportValue:
!Sub "${NetworkName}-DBSubnetGroup"
I had a problem where I needed to get my artifact bucket name from my prerequisite stack, I tried this:
Fn::ImportValue:
- 'arn:aws:s3:::${ArtifactStore}/*'
turns out you can do this and it will work. Hope his helps someone out one day!
- !Sub
- 'arn:aws:s3:::${BucketName}/*'
- BucketName : !ImportValue 'ArtifactStore'
Currently, Cloudformation didn't support dynamic default value. It's not possible to have a dynamic default value for CloudFormation. As the template has not executed at the time all parameters are being collected. However, you can use SSM parameter for as the workaround, something like below.
Parameters
PagerDutyUrl:
Type: AWS::SSM::Parameter::Value<String>
Description: The Pagerduty url
Going back to your current cloudformation, I am thinking that value ${fmMainVpc} might not be initialized correctly.
I'm my case, I had the follow resource:
# removed for brevity
Subnets:
- !ImportValue: parent-stack-subnet-a
- !ImportValue: parent-stack-subnet-b
I forgot to remove the : when changing the syntax from Fn::ImportValue to the shorthand !ImportValue. Confusing error message, but removing the : resolved it because that was incorrect usage on my part.

IAM nested stack fails to complete due to undefined resource policies

I have created a nested IAM stack, which constists of 3 templates:
- iam-policies
- iam-roles
-iam user/groups
the masterstack template looks like this:
Resources:
Policies:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://s3.amazonaws.com/xxx/iam/iam_policies.yaml
UserGroups:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://s3.amazonaws.com/xxx/iam/iam_user_groups.yaml
Roles:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: https://s3.amazonaws.com/xxx/iam/iam_roles.yaml
The policy ARNs are exported via Outputs section like:
Outputs:
StackName:
Description: Name of the Stack
Value: !Ref AWS::StackName
CodeBuildServiceRolePolicy:
Description: ARN of the managed policy
Value: !Ref CodeBuildServiceRolePolicy
in the Role template the policies ARNs are imported like
CodeBuildRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${EnvironmentName}-CodeBuildRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Action:
- 'sts:AssumeRole'
Effect: Allow
Principal:
Service:
- codebuild.amazonaws.com
Path: /
ManagedPolicyArns:
- !GetAtt
- Policies
- Outputs.CodeBuildServiceRolePolicy
But when I try create the stack, it fails saying the Roles stack cannot be created because
Template error: instance of Fn::GetAtt references undefined resource Policies
How can I force the creation of the policies first so the second and third template can use the policies to create roles and user/ groups? Or is the issue elsewhere?
merci A
Your question,
How can I force the creation of the policies first so the second and
third template can use the policies to create roles and user/ groups?
Or is the issue elsewhere?
You can use "DependsOn" attribute. It automatically determines which resources in a template can be parallelized and which have dependencies that require other operations to finish first. You can use DependsOn to explicitly specify dependencies, which overrides the default parallelism and directs CloudFormation to operate on those resources in a specified order.
In your case second and third template DependsOn Policies
More details : DependsOn
The reason on why you aren't able to access the outputs is that, you haven't exposed the outputs for other stacks.
Update your Outputs with the data you want to export. Ref - Outputs for the same.
Then, use the function Fn::ImportValue in the dependent stacks to consume the required data. Ref - ImportValue for the same.
Hope this helps.