Topics

Varnish with secure AWS S3 bucket as backend

Serving static contents from S3 is common, but using Varnish in front is a bit tricky. Especially if you want to keep the bucket secure and only serve from Varnish, here is a simple Varnish file to solve this problem.

First secure your bucket via IP policy:

{
  "Version": "2012-10-17",
  "Id": "S3PolicyId1",
  "Statement": [
    {
      "Sid": "IPAllow",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::example.bucket/*",
      "Condition": {
        "IpAddress": {
          "aws:SourceIp": [
            "5.6.7.8/32"  //varnish ip
          ]
        }
      }
    },
    {
      "Sid": "Explicit deny to ensure requests are allowed only from specific referer.",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::example.bucket/*",
      "Condition": {
        "StringNotLike": {
          "aws:Referer": [
            "https://example.com/*"
          ]
        }
      }
    }
  ]
}


The Varnish file:

vcl 4.0;

acl cloudflare {
    "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;
}

backend example {
  .host = "1.2.3.4";
  .port = "80";
}

backend s3_bucket {
  .host = "s3.bucket.us-east-1.amazonaws.com";
  .port = "80";
}

sub vcl_recv {

    if (req.http.host ~ "bucket.example.com") {
        if(req.method != "GET" && req.method != "HEAD") {
            return(synth(900)); //method not allowed
        }
        if(req.http.referer !~ "^https://example\.com"){
            return(synth(800)); //hotlinking
        }
        //clean up for S3
        unset req.http.cookie;
        unset req.http.cache-control;
        unset req.http.pragma;
        unset req.http.expires;
        unset req.http.etag;
        unset req.http.X-Forwarded-For;
        unset req.http.CF-Connecting-IP;

        //set correct backend
        set req.backend_hint = s3_bucket;
        set req.http.host = "s3.bucket.us-east-1.amazonaws.com";
        //done with this request, forward it to backend
        return(hash);
    }

    //normal request...
    //pick user IP from Cloudflare request
    if (client.ip ~ cloudflare && req.http.CF-Connecting-IP) {
        set req.http.X-Forwarded-For = req.http.CF-Connecting-IP;
    }

    if (req.http.host ~ "^(.*\.)?example\.com") {
            set req.backend_hint = example;
    }

    if (req.url ~ "\.(png|gif|jpg|ico|txt|swf|css|js)$") {
        unset req.http.Cookie;
    }

    return(hash);
}

sub vcl_backend_response {
    // strip the cookie before the image is inserted into cache
    if (bereq.url ~ "\.(png|gif|jpg|ico|txt|swf|css|js)$") {
        unset beresp.http.Set-Cookie;
        set beresp.ttl = 1d;
    }
}

sub vcl_deliver {
    //clean up for delivery
    unset resp.http.Via;
    unset resp.http.X-Whatever;
    unset resp.http.X-Powered-By;
    unset resp.http.X-Varnish;
    unset resp.http.Age;
    unset resp.http.Server;
    //s3 stuff
    unset resp.http.X-Amz-Id-2;
    unset resp.http.X-Amz-Meta-Group;
    unset resp.http.X-Amz-Meta-Owner;
    unset resp.http.X-Amz-Meta-Permissions;
    unset resp.http.X-Amz-Request-Id;

    if (req.http.host ~ "s3.bucket.us-east-1.amazonaws.com") {
        if(resp.status == 404){
            return(synth(700)); //not found

        //convert all other errors from S3 into "520 Unknown Error"
        }elseif(resp.status != 200){
            return(synth(600));
        }
    }
}


sub vcl_backend_error {
    set beresp.http.Content-Type = "text/html; charset=utf-8";
    synthetic( {"Error! Please retry later.."} );
    return(deliver);
}

sub vcl_synth {
    if (resp.status > 550) {
        set resp.http.Content-Type = "text/plain; charset=utf-8";
        if (resp.status == 900) {
            set resp.status = 405;
            synthetic({"Method Not Allowed"});
        }elseif (resp.status == 800) {
            set resp.status = 403;
            synthetic({"Hotlinking Not Allowed"});
        }elseif (resp.status == 700) {
            set resp.status = 404;
            synthetic({"Not Found"});
        }elseif (resp.status == 600) {
            set resp.status = 520;
            synthetic({"Unknown Error"});
        }
    }
    return(deliver);
}

By continuing to use the site, you agree to the use of cookies. more information

The cookie settings on this website are set to "allow cookies" to give you the best browsing experience possible. If you continue to use this website without changing your cookie settings or you click "Accept" below then you are consenting to this.

Close