The following code is my attempt at creating a SPA (Single Page Application) CloudFormation template. I understand there are probably many flaws but I can't conceptually understand how to break the circular dependency error I am getting. In my head, it only makes sense that Route53 depends on CloudFront because it needs to know the AliasTarget, it also makes sense that CloudFront needs to depend on Certificate Manager because it needs AcmCertificateArn and CertificateManager needs to depend on Route53 (for obvious reasons but I have a feeling someone is going to tell me this is where I break the chain).
AWSTemplateFormatVersion: '2010-09-09'
Description: Creates an S3 bucket configured for hosting a static website, and a Route
53 DNS record pointing to the bucket
Parameters:
DomainName:
Type: String
Description: The DNS name of an existing Amazon Route 53 hosted zone e.g. jevsejev.io
AllowedPattern: (?!-)[a-zA-Z0-9-.]{1,63}(?<!-)
ConstraintDescription: must be a valid DNS zone name.
FullDomainName:
Type: String
Description: The full domain name e.g. development.jevsejev.io
AllowedPattern: (?!-)[a-zA-Z0-9-.]{1,63}(?<!-)
ConstraintDescription: must be a valid DNS zone name.
Resources:
Route53:
Type: AWS::Route53::RecordSet
DependsOn:
- Cloudfront
Properties:
HostedZoneName: !Ref 'DomainName'
RecordSets:
Name: !Ref 'FullDomainName'
Type: A
AliasTarget:
DNSName: !GetAtt [Cloudfront, WebsiteURL]
CertificateManager:
Type: AWS::CertificateManager::Certificate
DependsOn:
- Route53
Properties:
DomainName: !Ref 'FullDomainName'
Cloudfront:
Type: AWS::CloudFront::Distribution
DependsOn:
- S3
Properties:
DistributionConfig:
Origins:
- DomainName: !GetAtt [S3, WebsiteURL]
Id: S3Origin
CustomOriginConfig:
HTTPPort: '80'
HTTPSPort: '443'
OriginProtocolPolicy: http-only
Enabled: true
HttpVersion: 'http2'
DefaultRootObject: index.html
Aliases:
- !Ref 'FullDomainName'
DefaultCacheBehavior:
AllowedMethods:
- GET
- HEAD
Compress: true
TargetOriginId: S3Origin
ForwardedValues:
QueryString: true
Cookies:
Forward: none
ViewerProtocolPolicy: redirect-to-https
PriceClass: PriceClass_All
ViewerCertificate:
AcmCertificateArn: !Ref CertificateManager
SslSupportMethod: sni-only
S3:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref 'FullDomainName'
AccessControl: PublicRead
WebsiteConfiguration:
IndexDocument: index.html
I also have this problem and just get fixed.
Hope this it's helpful for people
The Certificate part once you have this property DomainValidationOptions and use your own HostedZone Id as Value of HostedZoneId, Certificate will get created without hanging there.
Below are my code for reference:
CldoudFrontHostedZoneId is a fixed value from AWS documents
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-aliastarget.html
DefaultHostedZoneId is my HostedZone Id
Parameters:
DefaultHostedZoneId:
Description: Default own HostedZone Id
Type: String
Default: xxxxxxxxxxxxxx
CldoudFrontHostedZoneId:
Description: AWS only use this hosted zone Id for cloudfront
Type: String
Default: Z2FDTNDATAQYW2
Resources:
StagingCloudFrontCertificate:
Type: AWS::CertificateManager::Certificate
Properties:
DomainName: yyyyy.xxxxxxxx.company.com
ValidationMethod: DNS
DomainValidationOptions:
- DomainName: yyyyy.xxxxxxxx.company.com
HostedZoneId: !Ref DefaultHostedZoneId
StagingCloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Origins:
- DomainName: !GetAtt StagingPublishedS3.DomainName
Id: !Ref StagingPublishedS3
S3OriginConfig:
OriginAccessIdentity: !Join ['', ['origin-access-identity/cloudfront/', !Ref StagingCloudFrontOriginAccessIdentity]]
Enabled: 'true'
Comment: Staging CloudFront Distribution
HttpVersion: http2
IPV6Enabled: true
PriceClass: PriceClass_100
Aliases:
- yyyyy.xxxxxxxx.company.com
ViewerCertificate:
MinimumProtocolVersion: TLSv1.2_2021
AcmCertificateArn: !Ref StagingCloudFrontCertificate
SslSupportMethod: sni-only
DefaultCacheBehavior:
AllowedMethods:
- GET
- HEAD
- OPTIONS
CachePolicyId: !Ref AWSmanagedCachePolicy
# ForwardedValues:
# QueryString: false
ViewerProtocolPolicy: 'redirect-to-https'
TargetOriginId: !Ref StagingPublishedS3
TrustedKeyGroups:
- !Ref StagingCloudFrontKeyGroup
CacheBehaviors:
- AllowedMethods:
- GET
- HEAD
PathPattern: /favicon.ico
TargetOriginId: !Ref StagingPublishedS3
ViewerProtocolPolicy: 'redirect-to-https'
CachePolicyId: !Ref AWSmanagedCachePolicy
StagingCloudfrontDNS:
Type: AWS::Route53::RecordSetGroup
Properties:
HostedZoneName: xxxxxxxx.company.com.
Comment: Zone apex alias targeted to CloudFront
RecordSets:
- Name: yyyyy.xxxxxxxx.company.com.
Type: A
AliasTarget:
HostedZoneId: !Ref CldoudFrontHostedZoneId
DNSName: !GetAtt StagingCloudFrontDistribution.DomainName
- Name: yyyyy.xxxxxxxx.company.com.
Type: AAAA
AliasTarget:
HostedZoneId: !Ref CldoudFrontHostedZoneId
DNSName: !GetAtt StagingCloudFrontDistribution.DomainName
Related
I've setup a CloudFront distribution in CloudFormation and I'm building an AWS WAF ACL to act as a firewall for it. To associate the ACL to the CloudFront distribution, I've added a AWS::WAFv2::WebACLAssociation entry which requires the ARN of the CloudFront distribution for the ResourceArn entry. However, I can't seem to find out how to get the CloudFront distribution ARN from the official documentation. I thought I could use !Ref however it used the CloudFront ID as per the documentation instead of the ARN.
How do I reference the CloudFront distribution ARN from the WebACLAssociation entry?
Example below (other resources omitted for brevity):
---
AWSTemplateFormatVersion: 2010-09-09
Description: CloudFront
Parameters:
# ...
CloudFront:
Type: AWS::CloudFront::Distribution
DependsOn:
- IssuedCertificate
- S3Bucket
Properties:
DistributionConfig:
Origins:
- DomainName: !Sub
- ${S3Bucket}.${S3WebEndpoint}
- {
S3Bucket: !Ref S3Bucket,
S3WebEndpoint:
!FindInMap [RegionMap, !Ref "AWS::Region", websiteendpoint],
}
Id: S3origin
CustomOriginConfig:
OriginProtocolPolicy: http-only
Enabled: "true"
Comment: !Sub Distribution for ${DomainName}
HttpVersion: http2
Aliases:
- !Ref DomainName
DefaultCacheBehavior:
AllowedMethods:
- GET
- HEAD
- OPTIONS
TargetOriginId: S3origin
Compress: True
DefaultTTL: 604800
ForwardedValues:
QueryString: "false"
Cookies:
Forward: none
ViewerProtocolPolicy: redirect-to-https
PriceClass: PriceClass_100
ViewerCertificate:
AcmCertificateArn: !Ref Certificate
SslSupportMethod: sni-only
# ...
AWSWAF:
Type: AWS::WAFv2::WebACL
Properties:
Name: allowlist
Description: Allowlist
Scope: CLOUDFRONT
DefaultAction:
Block: {}
Rules:
- Name: ipset-rule
Priority: 0
Action:
Allow: {}
Statement:
IPSetReferenceStatement:
Arn: # <ARN>
VisibilityConfig:
SampledRequestsEnabled: true
CloudWatchMetricsEnabled: true
MetricName: ipset-metrics
VisibilityConfig:
SampledRequestsEnabled: true
CloudWatchMetricsEnabled: true
MetricName: allowlist-metrics
AWSWAFAssociation:
Type: AWS::WAFv2::WebACLAssociation
Properties:
ResourceArn: !Ref CloudFront
WebACLArn: !Ref AWSWAF
There is no direct Attribute for the same but you can construct it:
arn:aws:cloudfront::${AWS::AccountId}:distribution/${CloudFront}
Turns out I had been approaching the problem wrong all along. Diving into the docs, I found that AWS details how to deploy an ACL for a CloudFront distribution here under the ResouceArn entry.
To fix this issue, all I had to do was add the following to the CloudFront distribution DistributionConfig and remove the WebACLAssociation entry:
WebACLId: !GetAtt AWSWAF.Arn
So the final CloudFront entry looked like this:
CloudFront:
Type: AWS::CloudFront::Distribution
DependsOn:
- IssuedCertificate
- S3Bucket
Properties:
DistributionConfig:
Origins:
- DomainName: !Sub
- ${S3Bucket}.${S3WebEndpoint}
- {
S3Bucket: !Ref S3Bucket,
S3WebEndpoint:
!FindInMap [RegionMap, !Ref "AWS::Region", websiteendpoint],
}
Id: S3origin
CustomOriginConfig:
OriginProtocolPolicy: http-only
Enabled: "true"
Comment: !Sub Distribution for ${DomainName}
HttpVersion: http2
Aliases:
- !Ref DomainName
DefaultCacheBehavior:
AllowedMethods:
- GET
- HEAD
- OPTIONS
TargetOriginId: S3origin
Compress: True
DefaultTTL: 604800
ForwardedValues:
QueryString: "false"
Cookies:
Forward: none
ViewerProtocolPolicy: redirect-to-https
PriceClass: PriceClass_100
ViewerCertificate:
AcmCertificateArn: !Ref Certificate
SslSupportMethod: sni-only
WebACLId: !GetAtt AWSWAF.Arn
I am creating custom domain for api gateway using yaml. Here is my sample yaml.
TestAppApi:
Type: AWS::Serverless::Api
Properties:
StageName: !Ref ApiStageName
EndpointConfiguration: REGIONAL
TracingEnabled: TRUE
MethodSettings:
- LoggingLevel: INFO
ResourcePath: "/*" # allows for logging on any resource
HttpMethod: "*" # allows for logging on any method
DataTraceEnabled: TRUE
DomainName:
Type: AWS::ApiGateway::DomainName
Properties:
RegionalCertificateArn: !Ref Certificate
DomainName: !Ref ApiDomain
SecurityPolicy: TLS_1_2
EndpointConfiguration:
Types:
- REGIONAL
UrlMapping:
Type: AWS::ApiGateway::BasePathMapping
DependsOn:
- TestAppApiStage
- DomainName
Properties:
DomainName:
!Ref ApiDomain
RestApiId:
Ref: TestAppApi
Stage: !Ref ApiStageName
Domain:
Type: AWS::Route53::RecordSetGroup
DependsOn:
- TestAppApi
- UrlMapping
- DomainName
Properties:
HostedZoneId: !Ref ApiDomainHostedZoneId
RecordSets:
- Name: !Ref ApiDomain
Type: A
AliasTarget:
DNSName: !GetAtt DomainName.RegionalDomainName
HostedZoneId: !GetAtt DomainName.RegionalHostedZoneId
I am passing ApiDomain as parameter and it works as expected when my ApiDomain is 'dev.test.com.au' where test.com.au is my registered domain. But if I change my ApiDomain to something like 'dev-$env:USERNAME.test.com.au' and then I get the following error.
Invalid domain name identifier specified(Service:AmazonApiGateway;Status Code: 404;
Error Code: NotFoundException;Request ID: fbf8ce40-cce0-4a0f-9714-c649d35a3ee4; Proxy: null)
Apparently this error has something to do with the dependency issue according to this thread https://github.com/aws/serverless-application-model/issues/192# but it works with one name but not the other. And anyways I have added all the dependencies and still invalid domain name identifier error.
Any help will be appreciated!
Don't have capital letters in your domain name people. My ApiDomain parameter has capital letters. Domain name creation automatically converts it to lower letters. But baseurlmapping is left to find ApiDomain with capital letter and hence the issue. Needed to read documentation more clearly but anyhow posting it here in case somebody experiences this silly issue.
ORIGINAL QUESTIONS: How to get RegionalDomainName out of a AWS APIGateway DomainName in SAM Cloud-formation
EDIT: I changed the question to hopefully get more traffic to this answer as it answers several questions not just my original one.
I am getting the following error when I try and deploy my stack:
resource DomainName does not support attribute type regionalDomainName in Fn::GetAtt
My yml file looks like the following:
PublicApi:
Type: AWS::Serverless::Api
Properties:
Name: PublicApi
...
EndpointConfiguration: REGIONAL
DomainName:
Type: AWS::ApiGateway::DomainName
Properties:
RegionalCertificateArn: "arn:aws:acm:${Region}:XXXXXXXXXXX:certificate/XXXXXXXXXXXXX"
DomainName: !Sub ${Stage}.${name}
EndpointConfiguration:
Types:
- REGIONAL
myDNSRecord:
Type: AWS::Route53::RecordSet
Properties:
HostedZoneId : Z1UJRXOUMOOFQ8
Name: !Sub ${Stage}.${name}
AliasTarget:
HostedZoneId: Z1UJRXOUMOOFQ8
DNSName: !GetAtt DomainName.regionalDomainName
Type: A
UrlMapping:
Type: AWS::ApiGateway::BasePathMapping
DependsOn:
- PublicApi
Properties:
DomainName: !Ref DomainName
RestApiId: !Ref PublicApi
Stage: !Ref Stage
The following is from the DomainName documentation:
"RegionalDomainName
The domain name associated with the regional endpoint for this custom domain name. You set up this association by adding a DNS record that points the custom domain name to this regional domain name."
I am confused as to what I need to do to make this DomainName Regional.
I have also been getting the following Errors in different iterations, that I thought this should fix:
"Cannot import certificates for EDGE while REGIONAL is active."
Any help on this front would be much appreciated!
I found from several different forums different solutions to different problems and finally came up with a working model. I hope this helps someone out in the future.
PublicApi:
Type: AWS::Serverless::Api
Properties:
Name: PublicApi
StageName: ApiStage
...
EndpointConfiguration: REGIONAL
DomainName:
Type: AWS::ApiGateway::DomainName
Properties:
RegionalCertificateArn: "arn:aws:acm:u${Region}:XXXXXXXX:certificate/XXXXXXXX"
DomainName: stage.example.com
SecurityPolicy: TLS_1_2
EndpointConfiguration:
Types:
- REGIONAL
LambdaDNS:
Type: AWS::Route53::RecordSetGroup
Properties:
HostedZoneName:
Ref: example.com.
RecordSets:
- Name:
Fn::Sub: stage.example.com.
Type: A
AliasTarget:
HostedZoneId: Z1UJRXOUMOOFQ8
DNSName:
Fn::GetAtt:
- DomainName
- RegionalDomainName
UrlMapping:
Type: AWS::ApiGateway::BasePathMapping
DependsOn:
- PublicApi
- PublicApiStage
Properties:
DomainName:
Ref: DomainName
RestApiId:
Ref: PublicApi
Stage: ApiStage
The key bit for me ended up being the DependsOn in the BasePathMapping.
I am trying to use Route53 and Custom Domains to link a URL I have and the API gateway that is a Lambda Proxy underneath it.
This is what I have so far in my SAM file:
MyApiCertificate:
Type: 'AWS::CertificateManager::Certificate'
Properties:
DomainName: example.com
MyApiDomainName:
Type: 'AWS::ApiGateway::DomainName'
Properties:
RegionalCertificateArn: !Ref MyApiCertificate
DomainName: example.com
MyApiBasePathMapping:
Type: 'AWS::ApiGateway::BasePathMapping'
Properties:
RestApiId: !Ref PublicApi
DomainName: !Ref MyApiDomainName
BasePath: /
Stage: stage
MyApiRoute53RecordSetGroup:
Type: AWS::Route53::RecordSetGroup
Properties:
HostedZoneName: example.com
RecordSets:
- Name: example.com
Type: A
AliasTarget:
EvaluateTargetHealth: false
HostedZoneId: !GetAtt MyApiDomainName.DistributionHostedZoneId
DNSName: !GetAtt MyApiDomainName.DistributionDomainName
I would like to make this of type CNAME and have the URL be stage.example.com
The following Code does not work.
MyApiCertificate:
Type: 'AWS::CertificateManager::Certificate'
Properties:
DomainName: example.com
MyApiDomainName:
Type: 'AWS::ApiGateway::DomainName'
Properties:
RegionalCertificateArn: !Ref MyApiCertificate
DomainName: example.com
MyApiBasePathMapping:
Type: 'AWS::ApiGateway::BasePathMapping'
Properties:
RestApiId: !Ref PublicApi
DomainName: !Ref MyApiDomainName
BasePath: /
Stage: stage
MyApiRoute53RecordSetGroup:
Type: AWS::Route53::RecordSetGroup
Properties:
HostedZoneName: example.com
RecordSets:
- Name: stage.example.com
Type: CNAME
AliasTarget:
EvaluateTargetHealth: false
HostedZoneId: !GetAtt MyApiDomainName.DistributionHostedZoneId
DNSName: !GetAtt MyApiDomainName.DistributionDomainName
I am getting this error: Cannot import certificates for REGIONAL while EDGE is active. (Service: AmazonApiGateway; Status Code: 400; Error Code: BadRequestException; Request ID: XXXXXXXXXXXXXXXXXXXXXXXX)
Although nothing I have is an EDGE Api Gateway and Nothing has the name I want (Stage.example.com)
Any help on this issue would be appreciated!
I came across the same error using Terraform, and the issue was that I was specifying regional_certificate_arn rather than certificate_arn. API Gateway defaults to edge-optimized, which means you're specifying a RegionalCertificateArn with an edge-optimized API here - try using CertificateArn instead.
I wanted to deploy a static website using cloudformation but I am having issues creating the record set. The stack creation was successful until the hosted zone in resource section. I'm not sure why there is an issue creating the record set for a hosted zone.
Error: The following resource(s) failed to create: [RecordSet].
---
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Assuming that you already have a Hosted Zone registered with Amazon Route 53, this Cfn template is to create a static site'
# Metadata:
# 'AWS::CloudFormation::Interface':
# ParameterGroups:
# - Label:
# default: 'HostedZone name'
# Parameters:
# - HostedZoneName
Parameters:
HostedZoneName:
Description: "The DNS name of an existing Amazon Route 53 hosted zone"
Type: String
AllowedPattern: "(?!-)[a-zA-Z0-9-.]{1,63}(?<!-)"
Default: "thecloudcrew.net"
Resources:
S3Bucket:
Type: AWS::S3::Bucket
Properties:
AccessControl: PublicRead
BucketName: !Ref HostedZoneName
WebsiteConfiguration:
IndexDocument: index.html
ErrorDocument: error.html
LoggingConfiguration:
DestinationBucketName: !Ref S3LoggingBucket
LogFilePrefix: logs
WWWS3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub
- www.${domain}
- { domain: !Ref HostedZoneName}
WebsiteConfiguration:
RedirectAllRequestsTo:
HostName: !Ref HostedZoneName
S3LoggingBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub
- ${domain}.logs
- { domain: !Ref HostedZoneName}
AccessControl: LogDeliveryWrite
HostedZone:
Type: "AWS::Route53::HostedZone"
Properties:
HostedZoneConfig:
Comment: "My Hosted zone for thecloudcrew.net"
HostedZoneTags:
-
Key: Name
Value: thecloudcrew
Name: !Ref HostedZoneName
RecordSet: #FIXME
Type: "AWS::Route53::RecordSet"
Properties:
AliasTarget:
DNSName: s3-website.us-east-2.amazonaws.com
HostedZoneId: Z2O1EMRO9K5GLX
Comment: "RecordSet for static website"
HostedZoneId: !Ref HostedZone #TODO
Name: !Ref HostedZone
Type: A
#Region: 'us-east-2'
# SetIdentifier: String
# TTL: String
# Weight: Integer
You're missing the Type: A property, you have to replace HosteZoneName by HostedZoneId. Also, remove Region it has no use in Route53 which is global.