AWS Partner Network (APN) Blog

Automating Secure and Scalable Website Deployment on AWS with Amazon CloudFront and AWS CDK

By Adam Novotný, Presales Consultant – StormIT
By Radu Dobrinescu, Partner Solutions Architect – AWS

StormIT-AWS-Partners-2023
StormIT
StormIT-APN-Blog-CTA-2023

HTTPS is becoming a standard for websites because it’s important to provide secure communication between a user’s web browser and the website they are visiting. HTTPS encrypts the data exchanged, ensuring that sensitive information such as login credentials remain confidential and protected.

There is no easier way to run HTTPS-enabled static websites on Amazon Web Services (AWS) than by using Amazon CloudFront and Amazon Simple Storage Service (Amazon S3). These services provide a secure and efficient content delivery network (CloudFront) that caches and delivers website content closer to users, reducing latency and improving performance. Meanwhile, Amazon S3 offers scalable and reliable storage for hosting static website files.

StormIT is an AWS Advanced Tier Services Partner specializing in content delivery solutions, cloud migrations, serverless architectures and deployment, and DevOps. StormIT’s team of certified experts works with businesses to understand their unique cloud infrastructure needs and deliver innovative, scalable solutions that achieve their goals.

With a focus on reliability, security, and efficiency, StormIT’s cloud solutions are designed to help businesses of all sizes optimize their AWS Cloud operations. Whether your application architecture is a simple three-tier web application or complex set of workloads, StormIT provides deployment services to meet your application and business needs.

In this post, we’ll look at automating website deployment on AWS using AWS Cloud Development Kit (AWS CDK) and TypeScript. We’ll use the architecture that combines CloudFront as the content delivery network, AWS Certificate Manager for secure certificate provisioning, S3 for reliable website hosting, and Amazon Route 53 as the domain name system (DNS). You can also check out StormIT’s video about deploying a static website with HTTPS using CDK.

Overview

StormIT provides customers with advice on how to store, transform, and deliver content in the AWS Cloud. Amazon CloudFront is the secure and efficient content delivery network (CDN) that StormIT uses to improve performance and decrease costs for clients. Learn more in StormIT’s blog post CloudFront Pricing: How to Approach it and Save Money?

Infrastructure as code (IaC) is an approach that allows developers to manage and provision their infrastructure through code, using tools like AWS CloudFormation, Terraform, or AWS Cloud Development Kit (AWS CDK), which provides a higher level and object-oriented abstraction on top of CloudFormation, making it easier to define and manage AWS resources using popular programming languages like TypeScript, Python, or Java.

Basically, every time you deploy the AWS CDK stack a CloudFormation template is created to define and provision the necessary AWS resources for the website infrastructure. The template can be found in the AWS Management Console and also managed there, if needed.

About the Architecture

This architecture contains Amazon CloudFront as a content delivery network, AWS Certificate Manager for certificate provisioning, Amazon S3 for website hosting, and Amazon Route 53 as a DNS for hostname resolution. Together, these components create a secure, scalable, and high-performance website deployment solution.

Please be aware that all content stored in the S3 bucket is publicly available. To maintain data security, ensure that only static website content is stored here, avoiding any unintentional exposure of sensitive information.

StormIT-HTTPS-Static-Website-1

Figure 1 – AWS architecture diagram.

The architecture works by utilizing CloudFront as a content delivery network to efficiently distribute static website content, improving performance for users. AWS Certificate Manager is used to provision TLS/SSL certificates, enabling secure HTTPS communication, while S3 hosts the static website files and serves as the origin for CloudFront.

Amazon Route 53 acts as a DNS to associate the website’s domain name with the CloudFront distribution, allowing users to access the website using the domain name.

Although it can be fairly easy to provide this type of architecture via the AWS console, when something is done manually it can be more prone to errors. It’s often better to try to automate things, as you can use them across many customers with just a small changes or update.

This type of architecture can be used for many use cases, such as ReactJS website deployment or other static website deployment. It’s also a great example of AWS CDK usage for someone interested in learning more or using it for their own projects.

TypeScript CDK Code for the Deployment

Prerequisites

Starting a New CDK Project

The simplest way to start is by using the default CDK project:

  • Create a new folder with the name “cdk-example-s3-cloudfront” and navigate to it.
  • Run this code to initialize a new CDK project in TypeScript. This should also install all the dependencies.

cdk init app --language=typescript

Account and Region Setup

First, we prepare the environment file where we can specify which AWS account to use. In this case, we’ll use the default AWS account you should have configured in the AWS CLI.

  • Navigate to the file “bin/cdk-example-s3-cloudfront.ts”
  • Replace the code; if you want to use a different region than eu-west-1 , change it accordingly.
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { CdkExampleS3CloudfrontStack } from '../lib/cdk-example-s3-cloudfront-stack';
const app = new cdk.App();
new CdkExampleS3CloudfrontStack(app, 'CdkExampleS3CloudfrontStack', {
  env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: 'eu-west-1' },
});

Website Files

Because we’ll be deploying the website to an Amazon S3 bucket, we’ll need to prepare files for our website. Here, we use an example “index.html” file as a website and a special “index.html” file for errors. Normally, you can put your website files into the “html-website” folder.

  • Create a folder in a root called “html-website.”
  • Create a file in this folder called “index.html” and put your sample code in there.
  • Create a folder called “error” in the “html-website” folder.
  • Create a file called “index.html” in “html-website/error” directory and put the code for your error page in there.

Infrastructure Code

Next, we’ll change the file with the infrastructure provisioning code. Here is some information about this code:

  • This code is a TypeScript AWS CDK stack that deploys an HTTPS-enabled static website to S3 with Amazon CloudFront.
  • It also creates a TLS/SSL certificate for HTTPS and sets up an Amazon Route 53 alias record for the CloudFront distribution.
  • You need to have your own domain name in Amazon Route 53 before using this code.
  • Essentially, this code automates the process of setting up a secure and scalable static website infrastructure on AWS.

Following is a detailed code snippet to provision this infrastructure:

  • Navigate to the file “lib/cdk-example-s3-cloudfront-stack.ts”.
  • Copy the following code, replacing what’s currently in this file with it.
import * as route53 from 'aws-cdk-lib/aws-route53';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as acm from 'aws-cdk-lib/aws-certificatemanager';
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';
import * as targets from 'aws-cdk-lib/aws-route53-targets';
import * as cloudfront_origins from 'aws-cdk-lib/aws-cloudfront-origins';
import * as s3deploy from 'aws-cdk-lib/aws-s3-deployment';

import { BlockPublicAccess, BucketAccessControl } from 'aws-cdk-lib/aws-s3';

import { CfnOutput, Duration, RemovalPolicy, Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';

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

  // 1. Define the domain name by changing'stormit.link'.
  const domainName = 'stormit.link';
  const siteDomain = 'www' + '.' + domainName;

    // 1.1 Create a Route 53 hosted zone (optional - you will need to update the NS records).
    /*
    const hostedZone = new route53.PublicHostedZone(this, 'MyHostedZone', {
        zoneName: domainName,
        });
          
    new CfnOutput(this, 'Site', { value: 'https://' + siteDomain });
    */

    // 1.2 Find the current hosted zone in Route 53 
      const zone = route53.HostedZone.fromLookup(this, 'Zone', { domainName: domainName });
      console.log(zone);
    
  // 2. Create a TLS/SSL certificate for HTTPS
        const certificate = new acm.DnsValidatedCertificate(this, 'SiteCertificate', {
          domainName: domainName,
          subjectAlternativeNames: ['*.' + domainName],
              hostedZone: zone,
              region: 'us-east-1', // Cloudfront only checks this region for certificates
        });

    // 2.1 The removal policy for the certificate can be set to 'Retain' or 'Destroy'
        certificate.applyRemovalPolicy(RemovalPolicy.DESTROY)
        new CfnOutput(this, 'Certificate', { value: certificate.certificateArn });

  // 3. Create an S3 bucket to store content, and set the removal policy to either 'Retain' or 'Destroy'
    // Please be aware that all content stored in the S3 bucket is publicly available.
        const siteBucket = new s3.Bucket(this, 'SiteBucket', {
          bucketName: siteDomain,
          publicReadAccess: true,
          removalPolicy: RemovalPolicy.DESTROY,
          autoDeleteObjects: true,
          blockPublicAccess: BlockPublicAccess.BLOCK_ACLS,
          accessControl: BucketAccessControl.BUCKET_OWNER_FULL_CONTROL,
          websiteIndexDocument: 'index.html',
          websiteErrorDocument: 'error/index.html'})
          new CfnOutput(this, 'Bucket', { value: siteBucket.bucketName });

  // 4. Deploy CloudFront distribution
        const distribution = new cloudfront.Distribution(this, 'SiteDistribution', {
          certificate: certificate,
          defaultRootObject: "index.html",
          domainNames: [siteDomain, domainName],
          minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021,
          errorResponses:[
            {
              httpStatus: 404,
              responseHttpStatus: 404,
              responsePagePath: '/error/index.html',
              ttl: Duration.minutes(30),
            }
          ],
          defaultBehavior: {
            origin: new cloudfront_origins.S3Origin(siteBucket),
            compress: true,
            allowedMethods: cloudfront.AllowedMethods.ALLOW_GET_HEAD_OPTIONS,
            viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
          }
        });

        new CfnOutput(this, 'DistributionId', { value: distribution.distributionId });

  // 5. Create a Route 53 alias record for the CloudFront distribution
        //5.1  Add an 'A' record to Route 53 for 'www.example.com'
        new route53.ARecord(this, 'WWWSiteAliasRecord', {
          zone,
          recordName: siteDomain,
          target: route53.RecordTarget.fromAlias(new targets.CloudFrontTarget(distribution))
        });
        //5.2 Add an 'A' record to Route 53 for 'example.com'
        new route53.ARecord(this, 'SiteAliasRecord', {
          zone,
          recordName: domainName,
          target: route53.RecordTarget.fromAlias(new targets.CloudFrontTarget(distribution))
        });

    //6. Deploy the files from the 'html-website' folder to an S3 bucket
        new s3deploy.BucketDeployment(this, 'DeployWebsite', {
          sources: [s3deploy.Source.asset('./html-website')],
          destinationBucket: siteBucket,
        });
  }
}

Usage

If you don’t have a domain name yet, you’ll need to get one. This code is also prepared for you to already having a Route 53 hosted zone with your domain name.

Configure your desired settings in “lib/cdk-example-s3-cloudfront-stack.ts”. The domain name can be set as below:

const domainName = 'your.domain';

Next, deploy the stack. This will deploy the S3 bucket, AWS Certificate Manager certificate, CloudFront distribution, and Route 53 records:

cdk deploy

Cleanup

Delete the stack and all associated resources:

cdk destroy

A Deeper Look into the CDK TypeScript Code

The main file “cdk-example-s3-cloudfront-stack.ts that’s a base for this CDK stack is separated into multiple sections, and every section has its own usage.

Let’s look into every section of this code:

  • Define the domain name by changing “stormit.link”. It’s possible this is the only change you’ll need to do, but be aware that a Route 53 public hosted zone with the same domain name must be created before using this code as-is.
    • (Optional) Create a Route 53 hosted zone (you will need to update the name server records). This is commented out as it’s optional, but if uncommented this step will create a Route 53 hosted zone for the specified domain name. Then, you’ll have to quickly change the DNS NS records and it can be tricky to get this working.
    • Find the current hosted zone in Route 53. This step finds the Route 53 hosted zone for the specified domain name and assigns it to the variable used in the next steps.
  • Create a TLS/SSL certificate for HTTPS. This step involves creating a TLS/SSL certificate for the specified domain name and its subdomains using AWS Certificate Manager. This certificate is a crucial part of enabling HTTPS encryption.
    • If you come across any information regarding the deprecation of the “DnsValidatedCertificate” function, note that we still use it because AWS does not yet have a suitable replacement. This is because we must specify the certificate’s region is us-east-1 (North Virginia), which is required by CloudFront. The service does not recognize certificates from other regions.
    • The removal policy for the certificate can be set to “Retain” or “Destroy.” This step sets the removal policy for the certificate to Destroy, but if needed it can be commented out so when you destroy the stack it will retain.
  • Create an S3 bucket to store content, and set the removal policy to either Retain or Destroy. This step creates an S3 bucket for the website’s content and sets the bucket’s removal policy to Destroy. The user can choose to set the removal policy to Retain if they want to keep the bucket and its contents even if the stack is deleted.
  • Deploy CloudFront distribution. This step creates a CloudFront distribution for the website using the S3 bucket and TLS/SSL certificate created in the previous steps. This CloudFront is also created with optimal settings for distributing static content and caching content in its points of presence, also known as POPs.
  • Create a Route 53 alias record for the CloudFront distribution. This step creates two Route 53 “A” records, one for the domain name and one for its subdomain “www,” pointing to the CloudFront distribution created in the previous step.
  • Deploy the files from the “html-website” folder in GitHub to an S3 bucket. This step deploys the contents of the “html-website” folder to the S3 bucket created earlier. The user can change the folder name and source location to their desired folder and source location.

Conclusion

This example shows how AWS CDK can be used to create an Amazon S3 bucket in a few lines of code using TypeScript. It also demonstrates the benefits of using AWS CDK, such as the ability to define infrastructure as code and leverage familiar programming languages to manage AWS resources.

With AWS CDK, developers can focus on the logic and functionality of their applications, while AWS manages the underlying infrastructure.

If you want to know more about the services StormIT offers, get in touch for a quick discussion about your project.

.
StormIT-APN-Blog-Connect-2023
.


StormIT – AWS Partner Spotlight

StormIT is an AWS Partner specializing in content delivery solutions, cloud migrations, serverless architectures, architecture deployment, and DevOps.

Contact StormIT | Partner Overview