12

I am trying to have Cloudflare to act as CDN for files hosted on S3, in a way that nobody can access the files directly. For example:

S3 bucket: cdn.mydomain.com.s3.amazonaws.com

CDN (Cloudflare): cdn.mydomain.com

What I want is to be able to access cdn.mydomain.com/file.jpg (Cloudflare) but not cdn.mydomain.com.s3.amazonaws.com/file.jpg (S3).

Right now I have a CNAME configured on Cloudflare that points to my bucket, and the following CORS:

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedOrigin>*</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
        <AllowedHeader>Authorization</AllowedHeader>
    </CORSRule>
</CORSConfiguration>

If I try to access any file, via S3 or CDN, I get permission denied. If I make a file public (aka grantee Everyone), I can then access that file via S3 and CDN.

I have tried changing the AllowedOrigin with *.mydomain.com, but no luck.

rlcabral
  • 1,496
  • 15
  • 39

3 Answers3

18

I found the solution. The article at CloudFlare's support center doesn't mention this.

You have to edit the bucket policy, not the CORS. And instead of allowing your domain, like that article says, to have access to the bucket, you have to allow CloudFlare IP's. For the reference, here is the list of IP's: https://www.cloudflare.com/ips

Here is the bucket policy sample to work with CloudFlare:

    {
        "Sid": "SOME_STRING_ID_HERE",
        "Effect": "Allow", // or deny
        "Principal": {"AWS": "*"}, // or whatever principal you want
        "Action": "s3:GetObject", // or whatever action you want
        "Resource": "arn:aws:s3:::cdn.mydomain.com/*", // or whatever resource you want
        "Condition": {
            "IpAddress": {
                "aws:SourceIp": [
                    "103.21.244.0/22",
                    "103.22.200.0/22",
                    "103.31.4.0/22",
                    "104.16.0.0/12",
                    "108.162.192.0/18",
                    "131.0.72.0/22",
                    "141.101.64.0/18",
                    "162.158.0.0/15",
                    "172.64.0.0/13",
                    "173.245.48.0/20",
                    "188.114.96.0/20",
                    "190.93.240.0/20",
                    "197.234.240.0/22",
                    "198.41.128.0/17",
                    "199.27.128.0/21"
                ]
            }
        }
    }
rlcabral
  • 1,496
  • 15
  • 39
  • Can you edit your answer? I'm getting this is not a valid jason, even mixing with first answer. I found out there's a part missing and also comments are not allowed. – Marcelo Agimóvel Sep 13 '18 at 11:17
  • Well, the comments were only to explain it here. You can just remove them. I still have this code in use in the bucket policy, and it is working fine. Be sure you check the list of IP's on Cloudflare. They added IPv6 months ago. – rlcabral Sep 24 '18 at 13:36
  • this works for me. now only cloudflare can read , directly access will return access denied – Nick Chan Abdullah Aug 09 '19 at 23:47
  • Hi @NickChanAbdullah.. Whether you changed your bucket public access turned off or it was Public only ? – Sreejith Jul 21 '20 at 08:36
  • @Sreejith Bucket is not public. Not even admin can direct access the bucket. In order to access it, you need to remove `s3.amazonaws.com/` part from the link. So, 1. Lock the bucket; 2. Set bucket policy to allow CloudFlare like I showed in my answer; 3. Create a `CNAME` entry on CloudFlare, for example with name `cdn`, target pointing to your bucket (`bucketname.s3.amazonaws.com`); 4. Access any file in that bucket using `cdn.yourdomain.com/filename`; 5. Profit; Requests are going through and cached by CloudFlare, so it won't be hitting S3 all the time, which saves money. – rlcabral Jul 23 '20 at 16:32
  • @rlcabral Thanks for the reply. Step 1, 2 , 3 and 4 are set as mentioned. But in bit bucket (Block public access under permission) I have to switch off the Block all public access for this to work. And it makes the bucket to be public under Access. Can you please explain what exactly I have to do to Lock the bucket. – Sreejith Jul 24 '20 at 04:08
  • 1
    @Sreejith Access Control List. On Public Access > Everyone, I revoked all permissions. Just uncheck everything: List objects, Write objects... This will block everyone from accessing the bucket, including you. But the cdn way will still work. – rlcabral Aug 03 '20 at 18:51
3

The accepted solution doesn't exactly work. It just allows access to CloudFlare. For that solution to work, you must explicitly deny everything elsewhere in the policy. This bucket policy is updated for Cloudflare's most recent IP addresses (including IPv6) and it also denies all access not from a Cloudflare IP address out of the box.

{
    "Id": "Policy1517260196123",
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "A string ID here",
            "Action": "s3:*",
            "Effect": "Deny",
            "Resource": "arn:aws:s3:::yourbucket.example.com/*",
            "Condition": {
                "NotIpAddress": {
                    "aws:SourceIp": [
                        "103.21.244.0/22",
                        "103.22.200.0/22",
                        "103.31.4.0/22",
                        "104.16.0.0/12",
                        "108.162.192.0/18",
                        "131.0.72.0/22",
                        "141.101.64.0/18",
                        "162.158.0.0/15",
                        "172.64.0.0/13",
                        "173.245.48.0/20",
                        "188.114.96.0/20",
                        "190.93.240.0/20",
                        "197.234.240.0/22",
                        "198.41.128.0/17",
                        "2400:cb00::/32",
                        "2405:8100::/32",
                        "2405:b500::/32",
                        "2606:4700::/32",
                        "2803:f800::/32",
                        "2c0f:f248::/32",
                        "2a06:98c0::/29"
                    ]
                }
            },
            "Principal": {
                "AWS": "*"
            }
        }
    ]
}
Phil Rukin
  • 450
  • 2
  • 17
Ben Cooper
  • 1,288
  • 2
  • 10
  • 21
  • Well, I forgot to mention that the bucket was not public. So no one could access it anyway. Your approach works too. It just assumes that the bucket is public, then your policies block access to it if `NotIpAddress`. Same idea ;) – rlcabral Jan 30 '18 at 20:05
  • 2
    Hi @rlcabral, I tried to make my bucket Private but doing so, I am not able to access the objects in S3 bucket using cloudflare. Can you please let me know how to make the S3 bucket private and access the content in bucket through cloudflare ? I want to keep the bucket private and provide the access through cloudflare only – Sreejith Jul 21 '20 at 04:46
2

It's better to use your domain name as the entry guard to s3, rather than a list of IPs.

Go to your site's S3 Console
Select the Properties Panel
Under Permission
Choose Add CORS Configuration
Add your CORSRules for your domain names

S3-CORS-Config example

More CORS configuration can be found here


My mistake, this answer only forbids other sites from downloading your assets via legitimate web browsers, cannot stop people from downloading them via different user agents.

tu4n
  • 4,200
  • 6
  • 36
  • 49