|
| 1 | +vcl 4.1; |
| 2 | + |
| 3 | +import std; |
| 4 | + |
| 5 | +backend default { |
| 6 | + .host = "webserver"; |
| 7 | + .port = "90"; |
| 8 | + .connect_timeout = 2s; |
| 9 | +} |
| 10 | + |
| 11 | +# Add hostnames, IP addresses and subnets that are allowed to purge content |
| 12 | +acl purge { |
| 13 | + "webserver"; |
| 14 | + "drupal"; |
| 15 | + "localhost"; |
| 16 | + "127.0.0.1"; |
| 17 | + "::1"; |
| 18 | +} |
| 19 | + |
| 20 | +sub vcl_recv { |
| 21 | + # Announce support for Edge Side Includes by setting the Surrogate-Capability header |
| 22 | + set req.http.Surrogate-Capability = "Varnish=ESI/1.0"; |
| 23 | + |
| 24 | + # Remove empty query string parameters |
| 25 | + # e.g.: www.example.com/index.html? |
| 26 | + if (req.url ~ "\?$") { |
| 27 | + set req.url = regsub(req.url, "\?$", ""); |
| 28 | + } |
| 29 | + |
| 30 | + # Remove port number from host header |
| 31 | + set req.http.Host = regsub(req.http.Host, ":[0-9]+", ""); |
| 32 | + |
| 33 | + # Sorts query string parameters alphabetically for cache normalization purposes. |
| 34 | + set req.url = std.querysort(req.url); |
| 35 | + |
| 36 | + # Remove the proxy header to mitigate the httpoxy vulnerability |
| 37 | + # See https://httpoxy.org/ |
| 38 | + unset req.http.proxy; |
| 39 | + |
| 40 | + # Add X-Forwarded-Proto header when using https |
| 41 | + if (!req.http.X-Forwarded-Proto && (std.port(server.ip) == 443)) { |
| 42 | + set req.http.X-Forwarded-Proto = "https"; |
| 43 | + } |
| 44 | + |
| 45 | + # Ban logic to remove multiple objects from the cache at once. Tailored to Drupal's cache invalidation mechanism |
| 46 | + if(req.method == "BAN") { |
| 47 | + if(!client.ip ~ purge) { |
| 48 | + return(synth(405, "BAN not allowed for this IP address")); |
| 49 | + } |
| 50 | + |
| 51 | + if (req.http.Purge-Cache-Tags) { |
| 52 | + ban("obj.http.Purge-Cache-Tags ~ " + req.http.Purge-Cache-Tags); |
| 53 | + } |
| 54 | + else { |
| 55 | + ban("obj.http.x-url ~ " + req.url + " && obj.http.x-host == " + req.http.host); |
| 56 | + } |
| 57 | + |
| 58 | + return (synth(200, "Ban added.")); |
| 59 | + } |
| 60 | + |
| 61 | + # Purge logic to remove objects from the cache |
| 62 | + if(req.method == "PURGE") { |
| 63 | + if(!client.ip ~ purge) { |
| 64 | + return(synth(405,"PURGE not allowed for this IP address")); |
| 65 | + } |
| 66 | + return (purge); |
| 67 | + } |
| 68 | + |
| 69 | + # Only handle relevant HTTP request methods |
| 70 | + if ( |
| 71 | + req.method != "GET" && |
| 72 | + req.method != "HEAD" && |
| 73 | + req.method != "PUT" && |
| 74 | + req.method != "POST" && |
| 75 | + req.method != "PATCH" && |
| 76 | + req.method != "TRACE" && |
| 77 | + req.method != "OPTIONS" && |
| 78 | + req.method != "DELETE" |
| 79 | + ) { |
| 80 | + return (pipe); |
| 81 | + } |
| 82 | + |
| 83 | + # Remove tracking query string parameters used by analytics tools |
| 84 | + if (req.url ~ "(\?|&)(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=") { |
| 85 | + set req.url = regsuball(req.url, "&(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=([A-z0-9_\-\.%25]+)", ""); |
| 86 | + set req.url = regsuball(req.url, "\?(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=([A-z0-9_\-\.%25]+)", "?"); |
| 87 | + set req.url = regsub(req.url, "\?&", "?"); |
| 88 | + set req.url = regsub(req.url, "\?$", ""); |
| 89 | + } |
| 90 | + |
| 91 | + # Only cache GET and HEAD requests |
| 92 | + if ((req.method != "GET" && req.method != "HEAD") || req.http.Authorization) { |
| 93 | + return(pass); |
| 94 | + } |
| 95 | + |
| 96 | + # Mark static files with the X-Static-File header, and remove any cookies |
| 97 | + # X-Static-File is also used in vcl_backend_response to identify static files |
| 98 | + if (req.url ~ "^[^?]*\.(7z|avi|bmp|bz2|css|csv|doc|docx|eot|flac|flv|gif|gz|ico|jpeg|jpg|js|less|mka|mkv|mov|mp3|mp4|mpeg|mpg|odt|ogg|ogm|opus|otf|pdf|png|ppt|pptx|rar|rtf|svg|svgz|swf|tar|tbz|tgz|ttf|txt|txz|wav|webm|webp|woff|woff2|xls|xlsx|xml|xz|zip)(\?.*)?$") { |
| 99 | + set req.http.X-Static-File = "true"; |
| 100 | + unset req.http.Cookie; |
| 101 | + return(hash); |
| 102 | + } |
| 103 | + |
| 104 | + # Don't cache the following pages |
| 105 | + if (req.url ~ "^/status.php$" || |
| 106 | + req.url ~ "^/update.php$" || |
| 107 | + req.url ~ "^/cron.php$" || |
| 108 | + req.url ~ "^/admin$" || |
| 109 | + req.url ~ "^/admin/.*$" || |
| 110 | + req.url ~ "^/flag/.*$" || |
| 111 | + req.url ~ "^.*/ajax/.*$" || |
| 112 | + req.url ~ "^.*/ahah/.*$" || |
| 113 | + req.url ~ "^/\.well-known/acme-challenge/") { |
| 114 | + return (pass); |
| 115 | + } |
| 116 | + |
| 117 | + # Remove all cookies except the session & NO_CACHE cookies |
| 118 | + if (req.http.Cookie) { |
| 119 | + set req.http.Cookie = ";" + req.http.Cookie; |
| 120 | + set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";"); |
| 121 | + set req.http.Cookie = regsuball(req.http.Cookie, ";(S?SESS[a-z0-9]+|NO_CACHE)=", "; \1="); |
| 122 | + set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", ""); |
| 123 | + set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", ""); |
| 124 | + |
| 125 | + if (req.http.cookie ~ "^\s*$") { |
| 126 | + unset req.http.cookie; |
| 127 | + } else { |
| 128 | + return(pass); |
| 129 | + } |
| 130 | + } |
| 131 | + return(hash); |
| 132 | +} |
| 133 | + |
| 134 | +sub vcl_hash { |
| 135 | + # Create cache variations depending on the request protocol |
| 136 | + hash_data(req.http.X-Forwarded-Proto); |
| 137 | +} |
| 138 | + |
| 139 | +sub vcl_backend_response { |
| 140 | + # Inject URL & Host header into the object for asynchronous banning purposes |
| 141 | + set beresp.http.x-url = bereq.url; |
| 142 | + set beresp.http.x-host = bereq.http.host; |
| 143 | + |
| 144 | + # Serve stale content for 2 minutes after object expiration |
| 145 | + # Perform asynchronous revalidation while stale content is served |
| 146 | + set beresp.grace = 120s; |
| 147 | + |
| 148 | + # If the file is marked as static we cache it for 1 day |
| 149 | + if (bereq.http.X-Static-File == "true") { |
| 150 | + unset beresp.http.Set-Cookie; |
| 151 | + set beresp.ttl = 1d; |
| 152 | + } |
| 153 | + |
| 154 | + # If we dont get a Cache-Control header from the backend |
| 155 | + # we default to 1h cache for all objects |
| 156 | + if (!beresp.http.Cache-Control) { |
| 157 | + set beresp.ttl = 1h; |
| 158 | + } |
| 159 | + |
| 160 | + # Parse Edge Side Include tags when the Surrogate-Control header contains ESI/1.0 |
| 161 | + if (beresp.http.Surrogate-Control ~ "ESI/1.0") { |
| 162 | + unset beresp.http.Surrogate-Control; |
| 163 | + set beresp.do_esi = true; |
| 164 | + } |
| 165 | +} |
| 166 | + |
| 167 | +sub vcl_deliver { |
| 168 | + # Cleanup of headers |
| 169 | + unset resp.http.x-url; |
| 170 | + unset resp.http.x-host; |
| 171 | + unset req.http.X-Static-File; |
| 172 | +} |
0 commit comments