Hosting a Private Serverless Static Website on AWS

Matthew Taylor, Cloud Systems Development Lead at Cloudreach, recently completed a project that involved the creation of an internal training website. Here Matt explains how his team developed a serverless solution using AWS.

Recently, my team and I needed to launch a new, internal training website.

Rather than use Cloudreach’s existing wiki platform, we wanted to produce something new and exciting. However, we had no desire to run traditional web servers. So, we set out looking for a serverless solution.

After deciding to build our website on Hugo, we began researching how to host the site without any web servers. Typically, serverless web hosting on AWS involves storing content in an S3 bucket and then allowing web access either directly to the bucket or fronting the bucket with AWS CloudFront CDN. However, Hugo compiles content written in Markdown into themed HTML, so all we had to do was upload the Hugo compiled content into our S3 bucket.

There was one problem though: by default, web access to S3 or CloudFront is globally available to everyone on the web and we wanted our content to be only accessible by other Cloudreachers.

Serverless cookie issuing

A feature of CloudFront called "Signed Cookies" will, when enabled, only return web visitors the content they requested if their browser supplies three specific cookies; which have been cryptographically signed by AWS. CloudFront itself, however, cannot issue the cookies.

We knew that a Lambda function would be able to issue the cookies but we needed a secure way of allowing visitors to invoke the Lambda and receive cookies if they proved their identity. All whilst protecting our AWS account from bad actors on the Internet.

The solution was to have the visitor’s web browser use the AWS JavaScript library to establish an identity in AWS Cognito, which granted the visitor an IAM role. This IAM role is able to perform only one action: to execute the lambda function which can issue cookies. The Lambda function, written in node, uses the function "getSignedCookies", offered by the "aws-cloudfront-sign" package, to return the cryptographically signed cookies to the visitor’s browser.

Visitors could now access content in the private CloudFront distribution if they had been issued cookies.

Authentication and authorization

The next step was to restrict the site to only valid Cloudreach users. To begin, we needed to consider the authentication and authorization of our website visitors.

As all Cloudreachers have Google GSuite accounts, and are familiar with the Google login flow, we decided to use Google Sign-In. This meant Google handled the challenge of authentication for us. Our visitors would not require new, separate accounts for our website and we wouldn’t need to code an authentication backend.

Using Google Sign-In also made authorization easier as we could inspect the visitors’ email address and only allow visitors with an address ending "".

When a visitor clicks the Google login button, and selects their Google account, Google generates an encrypted token containing the visitor’s profile information. This token is returned to the browser and passed through to our Lambda function. The Lambda function sends the token back to Google for decryption and then inspects the visitor’s email address in the decrypted response. This series of events ensures that the visitors log in with a valid account, as Google will return an error if the encrypted token has been tampered with or is invalid.

Automated website content deployment

All the AWS resources were defined in CloudFormation and separate staging and production environments were deployed. A docker image, containing Hugo and the AWS CLI, was built for continuous integration and deployment.

When anyone in Cloudreach submits a merge request with new Markdown content, BitBucket Pipelines runs our docker container and deploys the rendered website into a unique address in the staging environment. Our reviewers only need to click this unique link and can view a full preview rendering of the website.

The BitBucket pipeline also reports into our slack channel, and the docker container posts the unique link for the reviewer to visit.

The same container also deploys to production once the reviewer approves the merge request.

Open source

Although the website content is for internal use, Cloudreach is open sourcing all of the infrastructure described in this blog post:

  • CloudFormation templates (written for use with Cloudreach Sceptre)
  • Lambda function for issuing CloudFront cookies and public html & javascript for invoking the login flow (forked from s3nator)
  • BitBucket Pipeline configuration
  • Dockerfile for CI/CD image containing Hugo & AWS CLI
  • Deployment instructions

The repository is available on GitHub at

Post submitted by Matthew Taylor, Cloud Systems Developer Lead at Cloudreach

  • sceptre
  • aws
  • open-source
  • Serverless
  • docker
  • aws-cloudfront
  • aws-lambda