When setting up a CloudFront distribution with a custom SSL/TLS certificate, a common challenge is issuing the certificate in a specific AWS region, such as the US East (N. Virginia) region (us-east-1). This is the process of creating a custom SSL certificate in one region and using it for a CloudFront distribution deployed in another region.

To achieve this, we’ll employ two AWS CDK stacks: one to request and store the certificate in the US East region, and another to deploy CloudFront in any other region using the certificate ARN stored in the Parameter Store. We’ll also make use of AWS CDK and custom resources to streamline the process.

Step 1: Creating a Custom Resource to Read SSM Parameter
We begin by creating a custom resource using AWS CDK that allows us to read an SSM parameter value. The custom resource is essential for retrieving the SSL certificate ARN from the Parameter Store in the US East region.


// custom-resource.ts

import { Construct } from "constructs";
import { AwsCustomResource } from "aws-cdk-lib/custom-resources";
import * as iam from "aws-cdk-lib/aws-iam";
import * as ssm from "aws-cdk-lib/aws-ssm";

interface SSMParameterReaderProps {
  parameterName: string;
  region: string;
}

export class SSMParameterReader extends AwsCustomResource {
  constructor(scope: Construct, name: string, props: SSMParameterReaderProps) {
    const { parameterName, region } = props;

    const ssmAwsSdkCall = {
      service: "SSM",
      action: "getParameter",
      parameters: {
        Name: parameterName,
      },
      region,
      physicalResourceId: { id: Date.now().toString() }, 
     };

    super(scope, name, {
      onUpdate: ssmAwsSdkCall,
      policy: {
        statements: [
          new iam.PolicyStatement({
            resources: ["*"],
            actions: ["ssm:GetParameter"],
            effect: iam.Effect.ALLOW,
          }),
        ],
      },
    });
  }

  public getParameterValue(): string {
    return this.getResponseField("Parameter.Value").toString();
  }
}

Step 2: Requesting and Storing the Certificate
Next, we create the first AWS CDK stack responsible for requesting and storing the SSL certificate in the US East region.


// certificate-stack.ts

import * as cdk from "aws-cdk-lib";

export class CertificateStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // Replace DOMAIN_NAME with our domain name
    const DOMAIN_NAME = "example.com";

    const hostedZone = route53.HostedZone.fromLookup(
      this,
      "website-hostedzone",
      { domainName: DOMAIN_NAME }
    );

    const certificate = new acm.Certificate(this, "website-certificate", {
      domainName: `*.${DOMAIN_NAME}`,
      subjectAlternativeNames: [DOMAIN_NAME],
      validation: acm.CertificateValidation.fromDns(hostedZone),
    });

    new ssm.StringParameter(this, "sslArn", {
      parameterName: "sslArn",
      stringValue: certificate.certificateArn,
    });
  }
}

Step 3: Deploying CloudFront with the Certificate
In the second AWS CDK stack, we deploy the CloudFront distribution in any other region, using the SSL certificate ARN imported from the Parameter Store.

// cloudfront-stack.ts

import { Construct } from "constructs";
import * as cdk from "aws-cdk-lib";
import * as certManager from "aws-cdk-lib/aws-certificatemanager";
import * as cloudfront from "aws-cdk-lib/aws-cloudfront";
import * as s3 from "aws-cdk-lib/aws-s3";
import * as route53 from "aws-cdk-lib/aws-route53";
import * as origins from "aws-cdk-lib/aws-cloudfront-origins";

export class MainStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // Replace DOMAIN_NAME with our domain name
    const DOMAIN_NAME = "example.com";
    const SSL_CERT_REGION = "us-east-1"; // Replace with the region where the certificate is issued

    // ...
    // Create other resources as needed (e.g., S3 bucket for our website)
    // ...

    const awsSSLCertARN = new SSMParameterReader(this, "sslArn", {
      parameterName: "sslArn",
      region: SSL_CERT_REGION,
    }).getParameterValue();

    const certificate = certManager.Certificate.fromCertificateArn(
      this,
      "Certificate",
      awsSSLCertARN
    );

    const siteDistribution = new cloudfront.Distribution(
      this,
      "SiteDistribution",
      {
        defaultRootObject: "index.html",
        domainNames: [`www.${DOMAIN_NAME}`, DOMAIN_NAME],
        certificate,
        defaultBehavior: {
          origin: new origins.S3Origin(s3Bucket, {
            originAccessIdentity: originAccessIdentity,
          }),
          // allows both http and https request
          viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.ALLOW_ALL,
        },
      }
    );

    // ...
    // Setup other CloudFront and route53 configurations as needed
    // ...
  }
}

Instantiate Both Stacks
Finally, we instantiate both stacks with different regions.



// app.ts

import * as cdk from "aws-cdk-lib";

// Replace ANY_OTHER_REGION with the region where we want to deploy CloudFront
const ANY_OTHER_REGION = "us-west-2";

const certificateManagerStack = new CertificateStack(app, "intermediateStack", {
  env: {
    account: process.env.CDK_DEFAULT_ACCOUNT,
    region: "us-east-1",
  },
});

const mainStack = new MainStack(app, "mainStack", {
  env: {
    account: process.env.CDK_DEFAULT_ACCOUNT,
    region: ANY_OTHER_REGION,
  },
});
mainStack.addDependency(certificateManagerStack);

With this setup, the CertificateStack will issue the certificate in the US East region and store the certificate ARN in the Parameter Store. The MainStack will then read the certificate ARN from the Parameter Store in the region of our choice and use it to deploy CloudFront with the custom SSL/TLS certificate.

By following these steps, we can now use a custom SSL certificate issued in one region and apply it to CloudFront distributions in different regions. This approach allows for greater flexibility and efficiency in managing SSL certificates for our CloudFront distributions across various AWS regions.