overview

Article 2: Using AWS CloudFront to enable HTTPS on top of S3

2022-02-10

Goal

In this second article we will have a look at how to enable HTTPS on top of S3. HTTPS encrypts the communication between the server and the client. The main reasons why we want to have HTTPS are:

You could argue that it's not 100% necessary, because at the moment we just deliver static content, which doesn't hold any sensitive data.

What do we need to do?

  1. get a SSL certificate
  2. setup an AWS CloudFront distribution using S3 as content source
  3. switch our domain in Route53 to use CloudFront instead of S3
  4. summary

Implementation

1. Get a SSL certificate

The first thing we need to encrypt traffic is a SSL certificate. SSL is a mechanism that's used to encrypt the traffic between browser and Server. The SSL certificate tells the browser that the content it receives from the comes from the domain you typed into the browser address bar. Thus making sure that the website was sent out by the owner of the domain. Kaspersky has a nice explanation of SSL and SSL certificates. Head over there is you want more information.

Luckily AWS can issue SSL certificates for domains that you created within your AWS account, which we fortunately did. This is the only thing that you need to do, to create a valid SSL certificate inside your AWS account.

const certificate = new certificatemanager.DnsValidatedCertificate(
  this,
  "fanderl_rocks_tls_certificate",
  {
    domainName: wwwFullDomainName,
    hostedZone: hostedZone,
    region: "us-east-1" // this needs to us-east-1, otherwise the certifcate can't be used in cloudfront
  }
)

This issues a new DNS validated SSL certificate into AWS Certifcate Manager. It ensures that we own the domain by checking the CNAME record of our domain.

2. Setup an AWS CloudFront distribution using S3 as content source

AWS CloudFront is a content delivery network with the main purpose of providing edge locations that serve static content which are physically closer to the customers computer. This speeds up download for a better user experience. The main reason we use it is, that S3 does not provide HTTPS support out of the box. It's intended to be used in combination with CloudFront to deliver static web assets. The other big benefit is, that our S3 buckets don't need to be public anymore. So we have a single entry point for our web content, which is CloudFront. In order to set this up, we need a CloudFront distribution:

const distribution = new cloudfront.CloudFrontWebDistribution(this, 'fanderlf_rocks_cloudfront_distribution', {
  viewerCertificate,
  originConfigs: [
    {
      s3OriginSource: {
        s3BucketSource: subdomain_bucket,
        originAccessIdentity: cloudfrontOAI
      },
      behaviors: [{
        compress: true,
        allowedMethods: cloudfront.CloudFrontAllowedMethods.GET_HEAD_OPTIONS,
        isDefaultBehavior: true,
        viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS
      }]
    }
  ]
})

The CloudFront distribution points to our S3 bucket and redirects traffic from HTTP to HTTPS. The viewerCertificate needs an implementation of the ICertificate interface the we provide like so:

const viewerCertificate = cloudfront.ViewerCertificate.fromAcmCertificate({
  certificateArn: certificate.certificateArn,
  env: {
    region,
    account
  },
  node: this.node,
  stack: this,
  metricDaysToExpiry: () =>
    new cloudwatch.Metric({
      namespace: "TLS Viewer Certificate Validity",
      metricName: "TLS Viewer Certificate Expired",
    }),
  applyRemovalPolicy: () => RemovalPolicy.DESTROY
}, {
  sslMethod: cloudfront.SSLMethod.SNI,
  securityPolicy: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021,
  aliases: [wwwFullDomainName]
})

As the content of our S3 will not be publicly accessible anymore, we need to give CloudFront access to the files in S3. This is done by configuring the origin access identity like this:

const cloudfrontOAI = new cloudfront.OriginAccessIdentity(this, 'cloudfront-OAI', {
  comment: `OAI for ${this.stackName}`
});

subdomain_bucket.addToResourcePolicy(new iam.PolicyStatement({
  actions: ['s3:GetObject'],
  resources: [subdomain_bucket.arnForObjects('*')],
  principals: [new iam.CanonicalUserPrincipal(cloudfrontOAI.cloudFrontOriginAccessIdentityS3CanonicalUserId)]
}));

I couldn't figure out how to setup CloudFront with the S3 redirect that we had configured in the static S3 approach. So I also uploaded the files to the subdomain S3 bucket and added a cache invalidation for all files, whenever we upload new files. This prevents CloudFront from still delivering old files, when we uploaded new ones.

new s3Deployment.BucketDeployment(this, 'fanderl_rocks_static_website_subdomain_content', {
  sources: [s3Deployment.Source.asset('../../content')],
  destinationBucket: subdomain_bucket,
  distribution: distribution,
  distributionPaths: ['/*']
});

Switch our domain in Route53 to use CloudFront instead of S3

The last thing we need to do, is to point Route53 to CloudFront instead of S3:

new route53.ARecord(this, 'fanderl_rocks_subdmain_a_record', {
  zone: hostedZone,
  recordName: wwwFullDomainName,
  target: route53.RecordTarget.fromAlias(new targets.CloudFrontTarget(distribution))
});

Summary

Our webpage is now accessible only via HTTPS under https://www.fanderl.rocks. If somebody tries to access it using HTTP, they will be redirected to HTTPS.

Impressum - last commit - 361d2043beae44e38fd3d23af373ef57b5fc4d0e