AWS Lambda (Part 3) - Custom Domain and Valid Certificate using AWS SAM
6 min read

AWS Lambda (Part 3) - Custom Domain and Valid Certificate using AWS SAM

Deployed a Lambda function to the AWS cloud, with a custom domain and automatically provision a valid certificate using SAM and CloudFormation.

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 a RestApiGateway 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 the RestApiGateway 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.