Securing Sensitive Craft CMS Assets with AWS Lambda

by Chris Chapman

Securing Sensitive Craft CMS Assets with AWS Lambda

During the early days of COVID-19, as more and more people were forced to work from home, one of our clients needed to quickly make their in-house Intranet externally available. Their Intranet is an essential resource for their employees and includes potentially sensitive files. One of the main challenges we needed to consider was how we could protect files and documents uploaded to the Intranet from being publicly available.

After evaluating our options, we decided that developing the Intranet into their existing Craft CMS based marketing site would be a quick, yet efficient approach. This would give their content editors an easy way to maintain Intranet content, while providing an authenticated way for users to access it.

We ultimately built the Intranet to take advantage of Craft CMS’ user system and an AWS S3 asset bucket delivered through CloudFront. This meant we already had a way to determine privileged access, and a hefty toolset from Amazon Web Services.

When an asset is uploaded to a typical Craft CMS volume, anyone with the URL can access it. The request never hits the application layer (i.e. Craft CMS or PHP) and there is not a way to reject requests at this level. One option could be generating tokenized URLs to facilitate the downloads, but this only provides obscurity without additional setup. Our ideal setup was having the public asset URLs allow or deny access based off if the user is currently authenticated with Craft CMS.

This is where AWS’ Lambda@Edge service comes into play. This is a feature of CloudFront which allows you to run serverless functions against requests. In other words, we are able to modify the response of HTTP requests depending on the request conditions.

At the Craft CMS level, we wrote a straightforward module to set a cookie whenever a user logs in. The cookie value contains a generated secret token stored in the application. This cookie is removed whenever the user logs out.

                                Event::on(
  User::class,
  User::EVENT_AFTER_LOGIN,
  function () {
    $this->setPrivateAssetCookie();
  }
);

Event::on(
  User::class,
  User::EVENT_AFTER_LOGOUT,
  function () {
    $this->removePrivateAssetCookie();
  }
);
                            

After that, we set up a Lambda function to handle the authorization. This checks if the requestor has a cookie matching a valid secret token mentioned above. If they do not, the response is modified to be a 403 forbidden error. Otherwise the request is left alone. This serves as a gatekeeper, and is configured to work with the CloudFront distribution and private S3 bucket. It runs on each and every request and results are not cached at edge locations. The serverless function also permits certain other requests, such as allowing the web server for Craft CMS asset transformations.

                                import {
  Callback,
  CloudFrontResponseEvent,
  CloudFrontResponseHandler,
  CloudFrontResultResponse,
  Context,
} from "aws-lambda";

// Check if the cookies contain a valid token for the assets.
export const isRequestAuthenticated = (
  cookies: { key?: string; value: string }[]
): boolean => {
  // ...
  return false;
};

// Check if the request is coming from a web server instance's IP address.
export const isBypassedIp = (ip: string): boolean => {
  // ...
  return false;
};

export const handler: CloudFrontResponseHandler = (
  event: CloudFrontResponseEvent,
  context: Context,
  callback: Callback<CloudFrontResultResponse>
) => {
  const { response, request } = event.Records[0].cf;

  if (
	!isRequestAuthenticated(request.headers.cookie) &&
	!isBypassedIp(request.clientIp)
  ) {
	response.body = "Forbidden";
	response.headers["content-type"] = [{ value: "text/html;charset=UTF-8" }];
	response.status = "403";
  }

  callback(null, response);
};
                            

Cloudfront events trigger lamba functions

We edit the behavior of the CloudFront distribution to associate the Lambda function at the origin response event.

Lambda function associaton

This is also where we choose to eliminate any caching at the distribution level. We then make sure that cookies are forwarded on to the Lambda function, which allows us to check the authorization token in our code above.

Cloudflare configuration

While the project may have been born out of necessity as a temporary and quick solution in an urgent situation, our client was so happy with the result that they made it a permanent replacement to their old Citrix/Wordpress Intranet site. Building the site in Craft not only means they have have one easy place to manage content, but we also have nearly unlimited capabilities for customizing the look and feel of it, so that it seamlessly carries over the branding and features of their marketing website.

Sometimes, necessity truly is the mother of invention!

Sign-up for our monthly newsletter.

Helpful resources and articles for your busy marketing team. Delivered to your inbox a couple of times per month.

We won't send you spam. Unsubscribe at any time.

More from the Blog:

The Best Virtual Halloween Activities on the Internet in 2020

The Best Virtual Halloween Activities on the Internet in 2020

Like everything else in 2020, Halloween might look a little different for some of us this year. At least we have the internet and this roundup of the best virtual Halloween activities it has to offer.

Read More

Why You Should Start Running A/B Tests on Your Website Now (And How to Do It Right)

Why You Should Start Running A/B Tests on Your Website Now (And How to Do It Right)

Setting up A/B testing is easy to do and can be done fairly inexpensively, so not doing it is like leaving free money on the table for your business. Here we break down how we set up A/B testing for our clients, in 4 simple steps.

Read More

Finally, stress-free web development.

Ready to partner with a team as interested in your success as you are? Reach out today for a no-pressure consultation.

Get Started

© Copyright Clearfire, Inc. Springfield, Illinois | Privacy Policy | hello@clearfirestudios.com | (217) 953-0321