AWS SAM custom domain fails when using !Ref - amazon-web-services

If I'm defining a custom domain as a standalone resource that I'm referencing with !Ref my deployment fails. But if I use the same details for a nested object it works. Any idea why?
Custom Domains only works if both DomainName and CertificateArn are provided.
ApiDomain:
Type: AWS::ApiGateway::DomainName
Properties:
DomainName: api.mydomain.com
CertificateArn: "arn:aws:acm:blah-blah"
Route53:
HostedZoneName: "mydomain.com."
MyApi:
Type: AWS::Serverless::Api
Properties:
StageName: Prod
Cors: "'*'"
Domain: !Ref ApiDomain
But if I nested the domain object - it magically works:
MyApi:
Type: AWS::Serverless::Api
Properties:
StageName: Prod
Cors: "'*'"
Domain:
DomainName: api.mydomain.com
CertificateArn: "arn:aws:acm:blah-blah"
Route53:
HostedZoneName: "mydomain.com."

You can't use your ApiDomain this way. The Domain property of the AWS::Serverless::Api resource requires a DomainConfiguration object. ApiDomain is a DomainName, which is not the same as a DomainConfiguration object. Besides DomainName, a DomainConfiguration also creates base path mappings and Route 53 records.
If you want to define your custom domain as a standalone resource, you will have to define the base path mappings and the Route 53 record as standalone resources as well.

Related

How to link custom domain name to multi region api gateway?

I have created a hosted zone resource via cloudformation. Now i want to create an api gateway via same template in two regions, say us-east-1 and us-west-1 .
customDomain:
Type: AWS::Route53::HostedZone
Properties:
Name: 'example.com'
I understand , i will have to create ssl certificate in both regions. so i have created another stack below. but how do i link , my example.com to api gateway on both the regions.
the error i'm getting now , if i deploy the stack below to second region is - create failed for Mycustomdomainconfig => "The domain name you provided already exists. (service: ApiGateway, ...Status Code 400...
Resources:
MyCertificate:
Type: "AWS::CertificateManager::Certificate"
Properties:
DomainName: example.com
ValidationMethod: DNS
DomainValidationOptions:
- DomainName: 'example.com'
HostedZoneId: 'abcde'
Myrecord:
Type: AWS::Route53::RecordSet
Properties:
Region: !Ref aws_region
SetIdentifier: !Sub "endpoint-${aws_region}"
HostedZoneId: 'abcde'
Name: 'example.com'
Type: A
AliasTarget:
DNSname: !GetAtt Mycustomdomainconfig.RegionalDomainName
HostedZoneId: !GetAtt Mycustomdomainconfig.RegionalHostedZoneId
Mycustomdomainconfig:
Type: AWS::ApiGateway::DomainName
Properties:
RegionalCertificationArn: !Ref MyCertificate
DomainName: 'example.com'
...
MyApiMapping:
Type: AWS::ApiGateway::BasePathMapping
Properties:
DomainName: !Ref Mycustomdomainconfig
RestApiId: !Ref MyApi
...
MyApi:
Type: AWS::Serverless::Api
Properties:
....

How to reference Cognito UserPoolDomain alias target within CloudFormation template?

I want to create A record for Cognito UserPoolDomain alias target (Cognito auto-generated Cloudfront distribution) within CloudFormation template.
I didn't find in docs how to reference alias target of UserPoolDomain?
Should I create a Cloudfront distribution and then somehow pass it to Cognito?
This is snippet of my CloudFormation template and what I try to achieve:
...
AuthUserPoolDomain:
Type: AWS::Cognito::UserPoolDomain
Properties:
UserPoolId: !Ref AuthUserPool
Domain: auth.example.com
CustomDomainConfig:
CertificateArn: !Ref CertificateArn
AuthRecordSets:
Type: AWS::Route53::RecordSetGroup
Properties:
HostedZoneName: example.com.
RecordSets:
- Name: auth.example.com
Type: A
AliasTarget:
DNSName: ???
EvaluateTargetHealth: false
HostedZoneId: Z2FDTNDATAQYW2 # This is always the hosted zone ID when you create an alias record that routes traffic to a CloudFront distribution.
...
Please help!
As a workaround solution I've created a new CloudFormation template that accept Cognito target alias as a parameter (CognitoDistribution).
After Cognitio UserPoolDoman is created I run this to find distribution
aws cognito-idp describe-user-pool-domain --domain auth.example.com \
| jq -r '.DomainDescription.CloudFrontDistribution'
and then I create DNS record with this template
AWSTemplateFormatVersion: 2010-09-09
Description: >-
Add CNAME record for Cognito UserPoolDomain alias target (CloudFront distribution).
It is not part of same template as AWS::Cognito::UserPoolDomain because UserPoolDomain does not return distribution ref.
Parameters:
HostedZoneName:
Type: String
UserPoolDomain:
Type: String
CognitoTargetAlias:
Type: String
Resources:
AuthRecordSets:
Type: AWS::Route53::RecordSetGroup
Properties:
HostedZoneName: !Sub "${HostedZoneName}."
RecordSets:
- Name: !Ref UserPoolDomain
Type: A
AliasTarget:
DNSName: !Ref CognitoTargetAlias
EvaluateTargetHealth: false
# This is always the hosted zone ID when you create an alias record that routes traffic to a CloudFront distribution.
HostedZoneId: Z2FDTNDATAQYW2
not clean solution but at least I don't need to do it in console

How to set up Custom Domain names with Route53 in AWS SAM Cloud-Formation

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.

Custom Domain Name for WebSockets API with Serverless

I'm managing a REST API for an app with Serverless and want to extend this setup with a WebSockets API in the same region. Everything should be handled with the same certificate, but different subdomains.
At first I created a new custom domain with sls create_domain --stage=....
Then I tried to add it to the new WebSockets stack, but ended with this error:
Error: Failed to find CloudFormation resources for ...
I found it on Github that this seems to be not supported by CloudFormation right now so Serverless does not support it.
So I tried to attach my stage to the custom domain name manually in the UI:
Mixing of REST APIs and HTTP APIs on the same domain name can only be
accomplished through API Gateway's V2 DomainName interface. Currently,
WebSocket APIs can only be attached to a domain name with other
WebSocket APIs. This must also occur through API Gateway's V2
DomainName interface.
More confusion arises, as it's not even the same domain name in this case. The new domain name was sockets.<DOMAIN>.com and the existing one was api.<DOMAIN>.com. Or do different subdomains are falling into 'same domain name'?
Nevertheless I tried to create the custom domain again via the apigatewayv2 CLI:
aws apigatewayv2 create-domain-name --domain-name <DOMAIN> --domain-name-configurations file://domain-configuration.json --region eu-west-1
domain-configuration.json:
[
{
"ApiGatewayDomainName": "<DOMAIN>",
"CertificateArn": "arn:aws:acm:us-east-1:<ACCOUNT_ID>:certificate/<CERT_ID>",
"CertificateName": "<DOMAIN>",
"DomainNameStatus": "AVAILABLE",
"EndpointType": "EDGE",
"SecurityPolicy": "TLS_1_2"
}
]
But this results in the following error:
An error occurred (BadRequestException) when calling the CreateDomainName operation: Invalid certificate ARN: arn:aws:acm:us-east-1:924441585974:certificate/b88f0a3f-1393-4a16-a876-9830852b5207. Certificate must be in 'eu-west-1'.
My current state was that API Gateway only allows custom certificates to be located in us-east-1, so this error confuses me even more.
Summary: I'm completely stuck on how to get a custom domain name attached to my WebSocket API stage. I'm happy about every hint in the right direction!
Found a solution with a custom CloudFormation resource template:
resources:
Resources:
WebSocketDomainName:
Type: AWS::ApiGatewayV2::DomainName
Properties:
DomainName: <domain-name>
DomainNameConfigurations:
- EndpointType: 'REGIONAL'
CertificateArn: <cert-arn>
WebSocketMapping:
Type: AWS::ApiGatewayV2::ApiMapping
Properties:
ApiId: <api-id>
DomainName: !Ref WebSocketDomainName
Stage: <stage-name>
DNSRecord:
Type: AWS::Route53::RecordSet
Properties:
HostedZoneName: <hosted-zone-name>.
TTL: '900'
ResourceRecords:
- !GetAtt [ WebSocketDomainName, RegionalDomainName ]
Name: <domain-name>
Type: CNAME
Edit: Now working with serverless-domain-manager! 🎉
custom:
customDomain:
rest:
domainName: rest.serverless.foo.com
stage: ci
basePath: api
certificateName: '*.foo.com'
createRoute53Record: true
endpointType: 'regional'
securityPolicy: tls_1_2
http:
domainName: http.serverless.foo.com
stage: ci
basePath: api
certificateName: '*.foo.com'
createRoute53Record: true
endpointType: 'regional'
securityPolicy: tls_1_2
websocket:
domainName: ws.serverless.foo.com
stage: ci
basePath: api
certificateName: '*.foo.com'
createRoute53Record: true
endpointType: 'regional'
securityPolicy: tls_1_2
Thanks #tpschmidt for the helpful response!
Just to add, in case you want a BasePathMapping for your websocket domain, add ApiMappingKey to the Cloudformation provided above. E.g.:
WebSocketMapping:
Type: AWS::ApiGatewayV2::ApiMapping
Properties:
ApiId: <api-id>
DomainName: !Ref WebSocketDomainName
Stage: <stage-name>
ApiMappingKey: <stage-name> #(e.g. prod)
I had to make a couple of minor adjustments to have a Lambda in a private VPC with WebSockets support and a custom domain.
The CertificateArn must be from ACM and cannot be a certificate uploaded to IAM.
I used an A-record instead of a CNAME
WebSocketDomainName:
Type: AWS::ApiGatewayV2::DomainName
Properties:
DomainName: !Ref DomainName
DomainNameConfigurations:
- EndpointType: REGIONAL
CertificateArn: <your-certificate-arn>
# This maps /staging or /prod to just the domain name
WebSocketMapping:
Type: AWS::ApiGatewayV2::ApiMapping
Properties:
ApiId: <your-api-id>
DomainName: !Ref WebSocketDomainName
Stage: <your-stage>
Route53DNS:
Type: AWS::Route53::RecordSetGroup
Properties:
HostedZoneName: !Ref Route53HostedZone
RecordSets:
- Name: !Ref DomainName
Type: A
AliasTarget:
HostedZoneId: ZLY8HYME6SFDD # This means eu-west-1 ApiGateWay
DNSName:
!GetAtt [WebSocketDomainName, RegionalDomainName]
For your HostedZoneId check: https://docs.aws.amazon.com/general/latest/gr/apigateway.html

CloudFormation fails with Route53 RecordSet and ApiGatewayV2 Domain

I am using Serverless to create a WebSocket API with AWS. I have added the following additional CloudFormation resources inside the serverless.yml file:
Resources:
ApiGatewayDomain:
Type: AWS::ApiGatewayV2::DomainName
Properties:
DomainName: ${opt:id}.example.com
DomainNameConfigurations:
- EndpointType: REGIONAL
CertificateArn: arn:aws:acm:eu-central-1:<REDACTED>:certificate/<REDACTED>
ApiGatewayBasePathMapping:
Type: AWS::ApiGatewayV2::ApiMapping
Properties:
DomainName: # ${opt:id}.example.com
Ref: ApiGatewayDomain
ApiId:
Ref: "WebsocketsApi"
Stage: ${opt:stage, 'dev'}
DependsOn:
- WebsocketsDeployment${sls:instanceId}
- WebsocketsDeploymentStage
Route53RecordSet:
Type: AWS::Route53::RecordSet
Properties:
Type: A
HostedZoneId: <REDACTED>
Name:
Ref: ApiGatewayDomain
AliasTarget:
DNSName: # <REDACTED>.execute-api.eu-central-1.amazonaws.com.
Fn::Join:
- ""
-
- Fn::GetAtt: [ApiGatewayDomain, RegionalDomainName]
- "."
HostedZoneId: # Z1U9ULNL0V5AJ3
Fn::GetAtt: [ApiGatewayDomain, RegionalHostedZoneId]
DependsOn:
- ApiGatewayDomain
I want to achieve, that the deployment of a stack registers a custom api gateway domain (works), sets up the path mapping (works) and adds a Route53RecordSet (only works with concrete values).
I cannot figure out what I am doing wrong and always get an internal error from Route53. If I comment out the Fn::Join and Fn::GetAtt blocks for DNSName and HostedZoneId and use concrete values, everything works as expected. Am I missing something or is this simply an error with CloudFormation and Route53?