From 310d192134d1f5295c463c177106c78ea4d2e076 Mon Sep 17 00:00:00 2001 From: Robert van Dijk Date: Thu, 24 Oct 2024 16:50:52 +0200 Subject: [PATCH] Update varnish config --- apps/varnish-proxy/configmap.yaml | 326 ++++++++++++++++-------------- 1 file changed, 173 insertions(+), 153 deletions(-) diff --git a/apps/varnish-proxy/configmap.yaml b/apps/varnish-proxy/configmap.yaml index a90885a..c76a827 100644 --- a/apps/varnish-proxy/configmap.yaml +++ b/apps/varnish-proxy/configmap.yaml @@ -5,197 +5,217 @@ metadata: namespace: default data: default.vcl: | - vcl 4.0; - import directors; + vcl 4.1; + import std; - # Assumed 'wordpress' host, this can be docker servicename backend default { .host = "website"; .port = "80"; } + # Add hostnames, IP addresses and subnets that are allowed to purge content acl purge { "localhost"; - "127.0.0.1"; + "127.0.0.1"; + "::1"; + "10.20.0.0/16"; } - - sub vcl_recv { - # Healthcheck - if(req.url == "/healthcheck") { - return(synth(200,"OK")); + sub vcl_recv { + # Remove empty query string parameters + # e.g.: www.example.com/index.html? + if (req.url ~ "\?$") { + set req.url = regsub(req.url, "\?$", ""); } - # Only a single backend - set req.backend_hint= default; - - # Setting http headers for backend - set req.http.X-Forwarded-For = client.ip; - set req.http.X-Forwarded-Proto = "https"; - - # Unset headers that might cause us to cache duplicate infos - unset req.http.Accept-Language; - unset req.http.User-Agent; - - # The purge...no idea if this works - if (req.method == "PURGE") { - if (!client.ip ~ purge) { - return(synth(405,"Not allowed.")); - } - return (purge); - } - if ( std.port(server.ip) == 6080) { + # Remove port number from host header + set req.http.Host = regsub(req.http.Host, ":[0-9]+", ""); - set req.http.x-redir = "https://" + req.http.host + req.url; - return (synth(750, "Moved permanently")); - } + # Sorts query string parameters alphabetically for cache normalization purposes + set req.url = std.querysort(req.url); - # drop cookies and params from static assets - if (req.url ~ "\.(gif|jpg|jpeg|swf|ttf|css|js|flv|mp3|mp4|pdf|ico|png)(\?.*|)$") { - unset req.http.cookie; - set req.url = regsub(req.url, "\?.*$", ""); - } + # Remove the proxy header to mitigate the httpoxy vulnerability + # See https://httpoxy.org/ + unset req.http.proxy; - # drop tracking params - if (req.url ~ "\?(utm_(campaign|medium|source|term)|adParams|client|cx|eid|fbid|feed|ref(id|src)?|v(er|iew))=") { - set req.url = regsub(req.url, "\?.*$", ""); - } + # Only a single backend + set req.backend_hint= default; - # pass wp-admin urls - if (req.url ~ "(wp-login|wp-admin)" || req.url ~ "preview=true" || req.url ~ "xmlrpc.php") { - return (pass); - } + # Setting http headers for backend + set req.http.X-Forwarded-For = client.ip; + set req.http.X-Forwarded-Proto = "https"; - # pass wp-admin cookies - if (req.http.cookie) { - if (req.http.cookie ~ "(wordpress_|wp-settings-)") { - return(pass); + # Unset headers that might cause us to cache duplicate infos + unset req.http.Accept-Language; + unset req.http.User-Agent; + + # Healthcheck + if(req.url == "/healthcheck") { + return(synth(200,"OK")); + } + + # Add X-Forwarded-Proto header when using https + if (!req.http.X-Forwarded-Proto) { + if(std.port(server.ip) == 443 || std.port(server.ip) == 8443) { + set req.http.X-Forwarded-Proto = "https"; } else { - unset req.http.cookie; + set req.http.X-Forwarded-Proto = "http"; } - } - } - - - - sub vcl_backend_response { - # retry a few times if backend is down - if (beresp.status == 503 && bereq.retries < 3 ) { - return(retry); - } - - if (bereq.http.Cookie ~ "(UserID|_session)") { - # if we get a session cookie...caching is a no-go - set beresp.http.X-Cacheable = "NO:Got Session"; - set beresp.uncacheable = true; - return (deliver); - - } elsif (beresp.ttl <= 0s) { - # Varnish determined the object was not cacheable - set beresp.http.X-Cacheable = "NO:Not Cacheable"; - - } elsif (beresp.http.set-cookie) { - # You don't wish to cache content for logged in users - set beresp.http.X-Cacheable = "NO:Set-Cookie"; - set beresp.uncacheable = true; - return (deliver); - - } elsif (beresp.http.Cache-Control ~ "private") { - # You are respecting the Cache-Control=private header from the backend - set beresp.http.X-Cacheable = "NO:Cache-Control=private"; - set beresp.uncacheable = true; - return (deliver); - - } else { - # Varnish determined the object was cacheable - set beresp.http.X-Cacheable = "YES"; - - # Remove Expires from backend, it's not long enough - unset beresp.http.expires; + } - # Set the clients TTL on this object - set beresp.http.cache-control = "max-age=900"; + # Purge logic to remove objects from the cache. + # Tailored to the Proxy Cache Purge WordPress plugin + # See https://wordpress.org/plugins/varnish-http-purge/ + if(req.method == "PURGE") { + if(!client.ip ~ purge) { + return(synth(405,"PURGE not allowed for this IP address")); + } + if (req.http.X-Purge-Method == "regex") { + ban("obj.http.x-url ~ " + req.url + " && obj.http.x-host == " + req.http.host); + return(synth(200, "Purged")); + } + ban("obj.http.x-url == " + req.url + " && obj.http.x-host == " + req.http.host); + return(synth(200, "Purged")); + } - # Set how long Varnish will keep it - set beresp.ttl = 1w; + # Only handle relevant HTTP request methods + if ( + req.method != "GET" && + req.method != "HEAD" && + req.method != "PUT" && + req.method != "POST" && + req.method != "PATCH" && + req.method != "TRACE" && + req.method != "OPTIONS" && + req.method != "DELETE" + ) { + return (pipe); + } - # marker for vcl_deliver to reset Age: - set beresp.http.magicmarker = "1"; + # Remove tracking query string parameters used by analytics tools + if (req.url ~ "(\?|&)(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=") { + set req.url = regsuball(req.url, "&(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=([A-z0-9_\-\.%25]+)", ""); + set req.url = regsuball(req.url, "\?(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=([A-z0-9_\-\.%25]+)", "?"); + set req.url = regsub(req.url, "\?&", "?"); + set req.url = regsub(req.url, "\?$", ""); } - # unset cookies from backendresponse - if (!(bereq.url ~ "(wp-login|wp-admin)")) { - set beresp.http.X-UnsetCookies = "TRUE"; - unset beresp.http.set-cookie; - set beresp.ttl = 1h; - } + # Only cache GET and HEAD requests + if (req.method != "GET" && req.method != "HEAD") { + set req.http.X-Cacheable = "NO:REQUEST-METHOD"; + return(pass); + } - # long ttl for assets - if (bereq.url ~ "\.(gif|jpg|jpeg|swf|ttf|css|js|flv|mp3|mp4|pdf|ico|png)(\?.*|)$") { - set beresp.ttl = 365d; + # Mark static files with the X-Static-File header, and remove any cookies + # X-Static-File is also used in vcl_backend_response to identify static files + 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)(\?.*)?$") { + set req.http.X-Static-File = "true"; + unset req.http.Cookie; + return(hash); + } - } - set beresp.grace = 1w; + # No caching of special URLs, logged in users and some plugins + if ( + req.http.Cookie ~ "wordpress_(?!test_)[a-zA-Z0-9_]+|wp-postpass|comment_author_[a-zA-Z0-9_]+|woocommerce_cart_hash|woocommerce_items_in_cart|wp_woocommerce_session_[a-zA-Z0-9]+|wordpress_logged_in_|comment_author|PHPSESSID" || + req.http.Authorization || + req.url ~ "add_to_cart" || + req.url ~ "edd_action" || + req.url ~ "nocache" || + req.url ~ "^/addons" || + req.url ~ "^/bb-admin" || + req.url ~ "^/bb-login.php" || + req.url ~ "^/bb-reset-password.php" || + req.url ~ "^/cart" || + req.url ~ "^/checkout" || + req.url ~ "^/control.php" || + req.url ~ "^/login" || + req.url ~ "^/logout" || + req.url ~ "^/lost-password" || + req.url ~ "^/my-account" || + req.url ~ "^/product" || + req.url ~ "^/register" || + req.url ~ "^/register.php" || + req.url ~ "^/server-status" || + req.url ~ "^/signin" || + req.url ~ "^/signup" || + req.url ~ "^/stats" || + req.url ~ "^/wc-api" || + req.url ~ "^/wp-admin" || + req.url ~ "^/wp-comments-post.php" || + req.url ~ "^/wp-cron.php" || + req.url ~ "^/wp-login.php" || + req.url ~ "^/wp-activate.php" || + req.url ~ "^/wp-mail.php" || + req.url ~ "^/wp-login.php" || + req.url ~ "^\?add-to-cart=" || + req.url ~ "^\?wc-api=" || + req.url ~ "^/preview=" || + req.url ~ "^/\.well-known/acme-challenge/" + ) { + set req.http.X-Cacheable = "NO:Logged in/Got Sessions"; + if(req.http.X-Requested-With == "XMLHttpRequest") { + set req.http.X-Cacheable = "NO:Ajax"; + } + return(pass); + } + # Remove any cookies left + unset req.http.Cookie; + return(hash); } sub vcl_hash { - if ( req.http.X-Forwarded-Proto ) { - hash_data( req.http.X-Forwarded-Proto ); - } - } - - sub vcl_backend_error { - # display custom error page if backend down - if (beresp.status == 503 && bereq.retries == 3) { - synthetic(std.fileread("/etc/varnish/error503.html")); - return(deliver); - } - } - - sub vcl_synth { - # redirect for http - if (resp.status == 750) { - set resp.status = 301; - set resp.http.Location = req.http.x-redir; - return(deliver); - } - # display custom error page if backend down - if (resp.status == 503) { - synthetic(std.fileread("/etc/varnish/error503.html")); - return(deliver); + if(req.http.X-Forwarded-Proto) { + # Create cache variations depending on the request protocol + hash_data(req.http.X-Forwarded-Proto); } } + sub vcl_backend_response { + # Inject URL & Host header into the object for asynchronous banning purposes + set beresp.http.x-url = bereq.url; + set beresp.http.x-host = bereq.http.host; - sub vcl_deliver { - # oh noes backend is down - if (resp.status == 503) { - return(restart); + # If we dont get a Cache-Control header from the backend + # we default to 1h cache for all objects + if (!beresp.http.Cache-Control) { + set beresp.ttl = 1h; + set beresp.http.X-Cacheable = "YES:Forced"; } - if (resp.http.magicmarker) { - # Remove the magic marker - unset resp.http.magicmarker; - # By definition we have a fresh object - set resp.http.age = "0"; + # If the file is marked as static we cache it for 1 day + if (bereq.http.X-Static-File == "true") { + unset beresp.http.Set-Cookie; + set beresp.http.X-Cacheable = "YES:Forced"; + set beresp.ttl = 1d; } - if (obj.hits > 0) { - set resp.http.X-Cache = "HIT"; - } else { - set resp.http.X-Cache = "MISS"; - } - set resp.http.Access-Control-Allow-Origin = "*"; - } - sub vcl_hit { - if (req.method == "PURGE") { - return(synth(200,"OK")); + + # Remove the Set-Cookie header when a specific Wordfence cookie is set + if (beresp.http.Set-Cookie ~ "wfvt_|wordfence_verifiedHuman") { + unset beresp.http.Set-Cookie; } + + if (beresp.http.Set-Cookie) { + set beresp.http.X-Cacheable = "NO:Got Cookies"; + } elseif(beresp.http.Cache-Control ~ "private") { + set beresp.http.X-Cacheable = "NO:Cache-Control=private"; + } } - sub vcl_miss { - if (req.method == "PURGE") { - return(synth(404,"Not cached")); - } - } \ No newline at end of file + sub vcl_deliver { + # Debug header + if(req.http.X-Cacheable) { + set resp.http.X-Cacheable = req.http.X-Cacheable; + } elseif(obj.uncacheable) { + if(!resp.http.X-Cacheable) { + set resp.http.X-Cacheable = "NO:UNCACHEABLE"; + } + } elseif(!resp.http.X-Cacheable) { + set resp.http.X-Cacheable = "YES"; + } + + # Cleanup of headers + unset resp.http.x-url; + unset resp.http.x-host; + }