AWS Lambda (Part 3) - Custom Domain and Valid Certificate using AWS SAM
Intro
I wrote about getting started with Serverless and AWS Lambda; Part 1 - Getting Started with Serverless, and Part 2 - Local Serverless development with AWS SAM, and just wanted to expand on this a bit in one area.
You may recall that when we deployed our Lambda function to the AWS cloud, we were presented with a URL like this: https://0a6197ftak.execute-api.eu-west-2.amazonaws.com/Prod/hello
Wouldn’t it be nice if we could have a custom domain, for example, https://hello.example.com as well as a valid certificate?
This is possible, and is what I want to expand on with this post.
Prerequisites
- HelleWorld example
- AWS Route 53 Hosted Zone
HelloWorld Example
Follow Part 2 - Local Serverless development with AWS SAM to get set up.
AWS Route 53 Hosted Zone
Have AWS Route 53 manage the DNS for a domain you own in order to specify how you want to route traffic for the domain and subdomains.
If the domain was purchased through AWS, the Domain’s Name Servers and a Start of Authority (SOA) record for the zone will automatically be created for the zone.
If you want AWS Route 53 as the DNS service for an existing domain you will have to add the AWS Name Servers (NS) record and a start of authority (SOA) record with you Domain provider.
For more information please see: https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/MigratingDNS.html
Once a Zone is created, obtain the Host Zone ID, either through the AWS Console or use the aws cli
. This will be required in a later step.
From the AWS Console you'll find it here:
Using the AWS CLI, it will look something like this.
(Note: the cut
command is required, but feel free the | cut ...
if its not available)
domain=wimi.rocks
aws route53 list-hosted-zones-by-name \
--dns-name ${domain} \
--query "HostedZones[].Id" \
--output text --no-cli-pager | cut -d'/' -f3
AWS SAM Specification
The AWS SAM specification is used to define a serverless application within an AWS SAM template. These can consist of resources types, resource properties, data types, resource attributes, intrinsic functions and API Gateways.
Furthermore, the AWS SAM templates are an extension of AWS CloudFormation templates, with some additional components that make them easier to work with. For the full reference of the AWS CloudFormation templates, see AWS CloudFormation Template Reference in the AWS CloudFormation User Guide.
Updating the SAM Template
Now that we know we can use AWS CloudFormation within the SAM Template, we need to make a few changes in order for the deployment to associate a DNS record, and provision a certificate for the API Gateway endpoint and the act as the trigger to the Lambda Function.
The following section outlines the changes required to the AWS SAM template, which you’ll find in the route of the project in the template.yaml
file.
Parameters
First, we’ll add the Parameters
section. Parameters are specified or passed to the template and referenced within the template during runtime. This allows the ability to dynamically specify values rather than having to hardcode values in the template.
We need a Fully Qualified Domain Name (www.example.com) to use the Lambda function. This is essentially where the Lambda function will be triggered from.
Next we need to add the AWS Hosted Zone ID in the format "Z111111QQQQQQQ". This is the zone for the domain and is required as CloudFormation will create DNS records; firstly, a CNAME record used for performing DNS validation when generating the Certificate, and secondly, an A record used by the API Gateway.
Parameters:
FQDN:
Type: String
Description: Fully qualified domain name (www.example.com) to use for the Lambda function.
ZoneId:
Type: String
Description: AWS Hosted Zone ID in the format "Z111111QQQQQQQ".
Default: none
Resources
Two additional resources are required:
- The
GenerateCertificate
resource which will define the properties for the certificate we require to be generated. - The Rest API Gateway which was essentially created implicitly when it was defined as part of the
HelloWorldFunction
resource. However, because we want to explicitly specify some properties aRestApiGateway
resource is required. We can now explicitly define the properties for the API Gateway to make use of the generated certificate as well as the FQDN.
These resources can be added above the already present HelloWorldFunction
.
Resources:
GenerateCertificate: # Creates a valid certificate for the HTTP API endpoint under the custom domain
Type: AWS::CertificateManager::Certificate
Properties:
DomainName: !Ref FQDN
ValidationMethod: DNS
DomainValidationOptions:
- DomainName: !Ref FQDN
HostedZoneId: !Ref ZoneId
RestApiGateway: # Creates a HTTP API endpoint under the custom domain
Type: AWS::Serverless::Api
Properties:
StageName: Prod
Domain:
DomainName: !Ref FQDN
CertificateArn: !Ref GenerateCertificate
Route53:
HostedZoneId: !Ref ZoneId
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
# ......
Two more changes are required for the HelloWorldFunction
, specifically for the HelloWorld
event’s properties:
- Firstly, add a property
RestApiId
. This is the reference to the Rest API Gateway ID which is created by theRestApiGateway
resource that we specified. It is useful in the way that we can reference other created resources from within the same template. This is the CloudFormation magic 🧙‍♂️. - Secondly, update the path by removing the
hello
from the URI. This is really just for demonstrative purposes. Feel free to leave it as is or update to something else.
Resources:
GenerateCertificate:
Type: AWS::CertificateManager::Certificate
Properties:
# .....
RestApiGateway:
Type: AWS::Serverless::Api
Properties:
# .....
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world/
Handler: app.lambda_handler
Runtime: python3.9
Architectures:
- x86_64
Events:
HelloWorld:
Type: Api
Properties:
RestApiId: !Ref RestApiGateway # Add the RestApiId property
Path: / # Update the path
Method: get
Outputs
Lastly, now that we have a fully qualified domain name to access the API Gateway, the value being returned for where the API Gateway endpoint can be accessed should be updated.
NOTE: if you used a different Path:
in the HelloWorldFunction
properties, be sure to add it to the updated value.
Outputs:
# .....
HelloWorldApi:
Description: "API Gateway endpoint URL for Prod stage for Hello World function"
Value: !Sub "https://${FQDN}/" # Updated to the FQDN
HelloWorldFunction:
Description: "Hello World Lambda Function ARN"
Value: !GetAtt HelloWorldFunction.Arn
HelloWorldFunctionIamRole:
Description: "Implicit IAM Role created for Hello World function"
Value: !GetAtt HelloWorldFunctionRole.Arn
Deploy Changes
Now that we have made the changes it is as simple as running sam deploy
with the --guided
flag. The --guided
flag is needed as we need to specify the parameters; FQDN and the ZoneID. But for the rest, it is exactly the same as before.
Keep in mind that the deploy takes somewhat longer because the Certificate creation requires DNS validation which takes some time to prepare and process.
Here is the full workflow of the deploy:
Output
The output will now contain the FQDN we specified during deploy, and if the deployment was successful we'll be able to access our function using the ULR in the output.
Accessing the custom URL should now return the response from the function as well as over a secure connection using a valid certificate for the FQDN.
Cleanup
Same as before, to remove the stack from your AWS account, run the sam delete
command.
I have noticed that the CNAME record which is created to do the DNS validation for the certificate is not removed and you would have to manually removed the record from the Route 53 Host Zone.
That should do it for now,
If you enjoyed the post, please consider to subscribe so that you receive future content in your inbox :)
Psssst, worried about sharing your email address and would rather want to hide it? Consider using a service I created to help with that: mailphantom.io
Also, if you have any questions, comments, or suggestions please feel free to Contact Me.