A funny way to restrict access to website hosted on S3

S3 is a popular choice when you want to host a static website. Often, “a website” means a public website. But not all websites are made to be public. If you tried googling the options to restrict access to the website, you’ve probably seen the recipes like restricting based on IP addresses or even 3-rd party services.

IP address-based approach doesn’t work for me, because I often work from home and I definitely don’t want to whitelist my ISP’s subnets. The 3-rd party services are by default to be avoided when it’s about security. So I had to look for alternatives.

S3 bucket policies seemed like a good area to learn more about. One of the optional IAM policy elements is Condition. It allows you to specify under what circumstances the policy is applied. In an example I’ve mentioned previously, they use Condition to allow access when the request comes from the IP address (aws:SourceIp) that belongs to a specific subnet.

I was curious what other keys similar to aws:SourceIp are there, and discovered this list of Global Condition Keys. Among the others, there are:

  • aws:CurrentTime – you can restrict access to your website based on current time.
  • aws:Referer – restrict access based on the Referer request header. If only you could make your browser send this header when you access the website, it would be a way to go. But I have no idea how to make Chrome do that.
  • aws:SourceIp – we’ve seen this one already.
  • aws:UserAgent – this one maps to User-Agent request header. You can allow access for Chrome and forbid for IE.

The last one looked promising, because I knew that faking the User-Agent sometimes can be useful. I thought that there should be Chrome extensions that allow you to exactly this.

Condition Operators are how you check if the value matches the criteria you have. In my case, I want to allow access if aws:UserAgent contains some predefined secret substring, so I’m going to use StringLike operator for this. Here’s what my bucket policy looks like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"Version": "2008-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::loki2302-dummy-bucket1/*",
"Condition": {
"StringLike": {
"aws:UserAgent": "*secret123*"
}
}
}
]
}

So, only if HTTP request has the User-Agent header and the value contains secret123, access will be granted. Here’s a demo (I’m using User-Agent Switcher for Google Chrome):

And after switching to a custom User-Agent that has my secret substring secret123:

I definitely don’t recommend this approach for production, but in some cases it should be good enough for development sandboxes.