Quick Bits: Enabling hotlink protection for S3 on AWS

After messing around with static sites using Storage Zones on bunny.net (afflink) and discovering it’s a pretty solid platform for doing so, I decided to head over to AWS after a bit of an absence to see if I could wrestle S3 into doing static-website-things in a nicer way too. One of the tasks was to allow specific referrers, which bunny.net makes fairly easy to do.

A few web searches eventually led me to https://stackoverflow.com/questions/62959832/prevent-image-hotlinking-with-s3-and-cloudfront which had a good solution, though it requires a referrer on each request or it will block.

I wanted to allow empty referrers, so it took a slight bit of tweaking. I figured I’d better save it somewhere for the next time I need it. That somewhere turned out to be here.

  1. In AWS S3, select your bucket.
  2. Select the Permissions tab.
  3. Scroll down to your Bucket policy and hit Edit.

If you’ve just gone through the basic static website setup (tutorials abound elsewhere), you probably have a basic policy that looks like this:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadGetObject",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::BUCKET-NAME/*"
    }
  ]
}

 

We’re going to modify it to look more like the following:

AWS S3 hotlink protection via a bucket policy

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadGetObject",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::BUCKET-NAME/*"
    },
    {
      "Sid": "Allow access only from my website",
      "Effect": "Deny",
      "Principal": {
        "AWS": "*"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::BUCKET-NAME/*",
      "Condition": {
        "StringNotLike": {
          "aws:Referer": [
            "https://dev.mywebsite.com:8080/*",
            "https://staging.mywebsite.com/*",
            "https://www.mywebsite.com/*"
          ]
        },
        "Null": {
          "aws:Referer": "false"
        }
      }
    }
  ]
}

I kept the example referrers from the original stackoverflow link in case you’re getting tripped up and trying to refer to that page as well.

Don’t forget to change BUCKET-NAME to match your actual bucket name (you’ll get an error otherwise). Don’t forget the comma (highlighted in the picture above).

How and Why It Works

There are 2 conditions that must both be TRUE for the policy to DENY access:

  1. Referer string not like the 3 examples.
  2. Referer string exists (if it’s null, then this condition becomes false).

If you don’t want to allow access without a referer, then you can remove the Null section and will get something resembling the original stackoverflow link above.

 

Expanding the above to certain files only!

If you have a more typical “static” website, you probably have your HTML and IMAGES intermingled, and thus, blocking access to your entire site including the HTML based on the referer could be a big problem.

We’ll assume you have certain files that you don’t want hotlinked. I identified a couple options to handle this… tags as one possibility and wildcard resources as the other. There could be others.

I went with wildcard resources as it’s straightforward and resources seem to be one of the few places that a leading wildcard in a filename works. Essentially, change this line:

"Resource": "arn:aws:s3:::BUCKET-NAME/*",

… (within our Deny section) into something resembling this…

"Resource": [
  "arn:aws:s3:::BUCKET-NAME/*.jpg",
  "arn:aws:s3:::BUCKET-NAME/*.png",
  "arn:aws:s3:::BUCKET-NAME/*.webp",
  "arn:aws:s3:::BUCKET-NAME/*.avif"
],

And… that’s it!

This write-up was done primarily due to a lack of examples showing up in search results. It should be robust enough, but if you’ve come across other methods (perhaps even better ones), feel free to share via the comment box below!

Leave a Comment

You can use an alias and fake email. However, if you choose to use a real email, "gravatars" are supported. You can check the privacy policy for more details.