All Articles

HTTPs on CloudFront using Certificate Manager and aws-cdk

The static website deployed with aws-cdk to CloudFront post shows how to deploy static content with aws-cdk. We still miss a custom domain configuration for our website though. The following example shows how to:

  • setup a custom domain name for a CloudFront distribution
  • enable https using AWS issued trusted certificate

Get a trusted certificate from AWS Certificate Manager using aws-cdk

Using the example from previous post as a base, inside infrastructure directory, let’s add additional package:

npm i --save @aws-cdk/aws-certificatemanager

CloudFront can only use AWS Certificate Manager issued certificates inside us-east-1 region (N. Virginia). We can create our FrontendStack in us-east-1 region. Alternatively we can create another stack with the certificate only. Below I show how to use the second option.

...
import { ValidationMethod } from "@aws-cdk/aws-certificatemanager";
import * as cm from "@aws-cdk/aws-certificatemanager";

const domainName = "fast-static-website.miensol.pl";

export class CertificateStack extends cdk.Stack {
  constructor(scope: cdk.Construct) {
    super(scope, "CertificateStack", {
      env: { region: "us-east-1" },
    });

    const certificate = new cm.Certificate(this, "CustomDomainCertificate", {
      domainName: domainName,
      validationMethod: ValidationMethod.DNS,
    });

    const certificateArn = certificate.certificateArn;
    new CfnOutput(this, "CertificateArn", {
      value: certificateArn,
    });
  }
}
...

We request a certificate for a given domain name in us-east-1 region. Before AWS Certificate Manager issues a trusted TLS certificate it needs to verify that you own the domain. The validation happens using either:

  • email: AWS CM sends a message to admin email addresses for the domain
  • DNS: AWS CM requests a particular DNS record configuration

In the example above I use fast-static-website.miensol.pl with DNS validation method. Since I own the domain I can prove that to AWS Certificate Manager.

We also update the bin/infrastructure.ts file to include our new stack:

#!/usr/bin/env node
import "source-map-support/register";
import * as cdk from "@aws-cdk/core";
import { CertificateStack, FrontendStack } from "../lib/frontend-stack";

const app = new cdk.App();
new CertificateStack(app);
new FrontendStack(app);

Let’s trigger next deployment with:

npm run cdk deploy '*'

The process will wait for a certificate request validation. The requested DNS record will show up in the deployment command output:

> npm run cdk deploy
...
1/5 | 6:03:37 PM | CREATE_IN_PROGRESS   | AWS::CertificateManager::Certificate
 | CustomDomainCertificate (CustomDomainCertificateXXXXX) Content of DNS Record is: {Name: XXXXXX.fast-static-website.miensol.pl.,Type: CNAME,Value: XXXXX.auiqqraehs.acm-validations.aws.}
1/5 Currently in progress: CustomDomainCertificateXXXXX

You can also find the request in Certificate Manager inside AWS Web console. After you configure the DNS record, the process will continue. In the command output you should see a message similar to:

CertificateStack.CertificateArn = arn:aws:acm:us-east-1:XXXXXXX:certificate/XXXXX-XXXXX-XXXX-XXXX-XXXXXXXX

Please note the above value, we will need it in the following step.

Attach a certificate to CloudFront distribution using aws-cdk

We need to tell the CloudFront distribution to use the new certificate. Let’s modify the FrontendStack to accept parameters:

interface FrontendProps {
  certificateArn: string;
}

export class FrontendStack extends cdk.Stack {
  constructor(scope: cdk.Construct, props: FrontendProps) {
    super(scope, "FrontendStack");

    const frontBucket = new s3.Bucket(this, "FrontendBucket", {
      websiteIndexDocument: "index.html",
      publicReadAccess: true,
    });

    const distribution = new cloudfront.CloudFrontWebDistribution(
      this,
      "Distribution",
      {
        viewerCertificate: {
          aliases: [domainName],
          props: {
            acmCertificateArn: props.certificateArn,
            sslSupportMethod: "sni-only",
          },
        },
...

In the viewerCertificate section we specify domain name as well as certificate CloudFront distribution should use. Next we pass CertificateStack.CertificateArn value noted above as a parameter:

const app = new cdk.App();
new CertificateStack(app);
new FrontendStack(app, {
  certificateArn:
    "arn:aws:acm:us-east-1:XXXXXXX:certificate/XXXXX-XXXXX-XXXX-XXXX-XXXXXXXX",
});

The deployment will take a while to complete since the CF distribution has to update. We can use that time to configure fast-static-website.miensol.pl DNS record. The record needs to point to FrontendStack.DistributionDomainName. I use CloudFlare so in my case this means creating a CNAME record: cloudflare cname entry

We can now load our static website using HTTPS!

certificate

It is a shame that it is not possible to create cross region references using aws-cdk:

There is not, unfortunately. You’re going to have to thread the API URL across regions manually. That means:

Deploy the ApiStackIreland first. Note the API URL (maybe make it an Output so that it’s easy to get to). Deploy the EdgeStack and plug in the value of API URL from the first step. There are a number of ways you could do this, and honestly I’m not sure which one to recommend.

I guess a pragmatic one would be to use this.node.getContext() and pass the value on the command-line when deploying the second stack (using the -c KEY=VALUE fflag), or put it into cdk.json under the context: {} section.

The full working example is available in the github repository.