From 4c7c11ce993d6db69a7d710b5b083fe467cc27f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Thu, 25 Sep 2025 12:17:15 +0200 Subject: [PATCH] rebuild --- docker-compose.yml | 47 +----- envoy/envoy.yaml | 40 ----- varnish/default.vcl | 389 +++++++++++++++++++------------------------- 3 files changed, 175 insertions(+), 301 deletions(-) delete mode 100644 envoy/envoy.yaml diff --git a/docker-compose.yml b/docker-compose.yml index 28c83b3..0e16311 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,33 +1,14 @@ services: - envoy: - image: envoyproxy/envoy:v1.30.10 - command: ["-c", "/etc/envoy/envoy.yaml", "--log-level", "info"] - ports: - - 8084:80 - depends_on: - - varnish - volumes: - - ./envoy/envoy.yaml:/etc/envoy/envoy.yaml:ro - networks: [edge, internal] - varnish: - image: varnish:7 - command: - - "-a" - - ":6081" - - "-f" - - "/etc/varnish/default.vcl" - - "-s" - - "malloc,1024m" - - "-p" - - "http_resp_hdr_len=64k" - - "-p" - - "http_resp_size=64k" + image: varnish:latest + environment: + - VARNISH_SIZE=1024m volumes: - ./varnish/default.vcl:/etc/varnish/default.vcl:ro - expose: ["6081"] - depends_on: [plik] - networks: [internal] + ports: + - "8084:80" + depends_on: [ plik ] + restart: unless-stopped plik: image: rootgg/plik:latest @@ -42,20 +23,10 @@ services: expose: - "8080" - "8811" - networks: [internal] healthcheck: - # Wariant z wget (często dostępny): - test: ["CMD-SHELL", "wget -q -O /dev/null http://localhost:8080 || exit 1"] + test: [ "CMD-SHELL", "wget -q -O /dev/null http://localhost:8080 || exit 1" ] interval: 10s timeout: 3s retries: 10 start_period: 10s - # Jeśli w obrazie nie ma wget, użyj curl: - # test: ["CMD-SHELL", "curl -fsS http://localhost:8080 >/dev/null || exit 1"] - -networks: - edge: - driver: bridge - internal: - driver: bridge - + restart: unless-stopped diff --git a/envoy/envoy.yaml b/envoy/envoy.yaml deleted file mode 100644 index acfdc93..0000000 --- a/envoy/envoy.yaml +++ /dev/null @@ -1,40 +0,0 @@ -static_resources: - listeners: - - name: http_listener - address: - socket_address: { address: 0.0.0.0, port_value: 80 } - filter_chains: - - filters: - - name: envoy.filters.network.http_connection_manager - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager - stat_prefix: ingress_http - route_config: - name: local_route - virtual_hosts: - - name: default - domains: ["*"] - routes: - - match: { prefix: "/" } - route: - cluster: varnish - response_headers_to_remove: - - "x-envoy-upstream-service-time" - - "server" - http_filters: - - name: envoy.filters.http.router - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router - - clusters: - - name: varnish - connect_timeout: 2s - type: STRICT_DNS - lb_policy: ROUND_ROBIN - load_assignment: - cluster_name: varnish - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: { address: varnish, port_value: 6081 } diff --git a/varnish/default.vcl b/varnish/default.vcl index 79b0dfd..101cb84 100644 --- a/varnish/default.vcl +++ b/varnish/default.vcl @@ -1,268 +1,211 @@ vcl 4.1; import std; +import vsthrottle; backend default { - .host = "plik"; - .port = "8080"; - .max_connections = 100; - .probe = { - .url = "/"; - .interval = 10s; - .timeout = 5s; - .window = 5; - .threshold = 3; - } - .connect_timeout = 5s; - .first_byte_timeout = 90s; - .between_bytes_timeout = 2s; + .host = "plik"; + .port = "8080"; + .max_connections = 100; + .probe = { .url = "/"; .interval = 10s; .timeout = 5s; .window = 5; .threshold = 3; } + .connect_timeout = 5s; + .first_byte_timeout = 90s; + .between_bytes_timeout = 2s; } -acl purge { - "localhost"; - "127.0.0.1"; - "::1"; -} +acl purge { "localhost"; "127.0.0.1"; "::1"; } sub vcl_recv { - unset req.http.X-Cache; - unset req.http.X-Cache-Hits; + # --- anty-noise / normalizacja --- + unset req.http.X-Cache; + unset req.http.X-Cache-Hits; + set req.http.Host = regsub(req.http.Host, ":[0-9]+", ""); + unset req.http.proxy; + set req.backend_hint = default; + set req.url = std.querysort(req.url); + set req.url = regsub(req.url, "\?$", ""); + set req.http.Surrogate-Capability = "key=ESI/1.0"; - set req.http.Host = regsub(req.http.Host, ":[0-9]+", ""); - unset req.http.proxy; - set req.backend_hint = default; + if (req.restarts == 0) { + if (req.http.X-Forwarded-For) { set req.http.X-Forwarded-For = req.http.X-Forwarded-For + ", " + client.ip; } + else { set req.http.X-Forwarded-For = client.ip; } + } - # Accept-Encoding normalize (wyłącz dla plików binarnych/statycznych) - if (req.http.Accept-Encoding) { - if (req.url ~ "(?i)\.(jpg|jpeg|png|gif|bmp|tiff|jiff|svg|webp|ico|js|css|html?|txt|eot|ttf|woff2?)$") { - unset req.http.Accept-Encoding; - } elseif (req.http.Accept-Encoding ~ "gzip") { - set req.http.Accept-Encoding = "gzip"; - } elseif (req.http.Accept-Encoding ~ "deflate" && req.http.user-agent !~ "MSIE") { - set req.http.Accept-Encoding = "deflate"; - } else { - unset req.http.Accept-Encoding; - } - } - - set req.url = std.querysort(req.url); - set req.url = regsub(req.url, "\?$", ""); - set req.http.Surrogate-Capability = "key=ESI/1.0"; - - if (req.restarts == 0) { - if (req.http.X-Forwarded-For) { - set req.http.X-Forwarded-For = req.http.X-Forwarded-For + ", " + client.ip; - } else { - set req.http.X-Forwarded-For = client.ip; - } - } - - # PURGE - if (req.method == "PURGE") { - if (!client.ip ~ purge) { return (synth(405, "Not allowed.")); } - return (hash); - } - - # BAN (opcjonalnie ban wg host+url) - if (req.method == "BAN") { - if (!client.ip ~ purge) { return (synth(405, "Not allowed.")); } - ban("req.http.host == " + req.http.host + " && req.url ~ " + req.url); - return (synth(200, "Banned")); - } - - # inne metody niż cache’owalne → pipe lub pass - 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); - } - - # cache tylko GET/HEAD i bez Authorization - if ((req.method != "GET" && req.method != "HEAD") || req.http.Authorization) { - return (pass); - } - - # wyjątki niecache’owalne (np. admin/ajax) - if (req.url ~ "^/status\.php$" || - req.url ~ "^/update\.php$" || - req.url ~ "^/admin(?:/.*)?$" || - req.url ~ "^/flag/.*$" || - req.url ~ "^.*/ajax/.*$" || - req.url ~ "^.*/ahah/.*$") { - return (pass); - } - - # cookies: zostaw tylko whitelisted (tu: brak) → jeśli coś zostało, zrób pass - if (req.http.Cookie) { - set req.http.Cookie = ";" + req.http.Cookie; - set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";"); - # (przykład whitelistu – dopasuj pod siebie) - set req.http.Cookie = regsuball(req.http.Cookie, ";(SESS[a-z0-9]+|SSESS[a-z0-9]+|NO_CACHE)=", "; \1="); - set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", ""); - set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", ""); - if (req.http.Cookie ~ "^\s*$") { - unset req.http.Cookie; - } else { - return (pass); - } - } + # --- RATE LIMIT: 100/10s, kara 10s --- + if (vsthrottle.is_denied(client.identity, 100, 10s, 10s)) { + return (synth(429, "Too Many Requests")); + } + # --- metody administracyjne --- + if (req.method == "PURGE") { + if (!client.ip ~ purge) { return (synth(405, "Not allowed.")); } return (hash); + } + if (req.method == "BAN") { + if (!client.ip ~ purge) { return (synth(405, "Not allowed.")); } + ban("req.http.host == " + req.http.host + " && req.url ~ " + req.url); + return (synth(200, "Banned")); + } + + # --- dopuszczalne metody / pass dla niecache’owalnych --- + if (req.method != "GET" && req.method != "HEAD" && req.method != "OPTIONS") { + return (pass); + } + if (req.http.Authorization) { return (pass); } + + # --- wyjątki dynamiczne (np. admin, ajax, status) --- + if (req.url ~ "(?i)/(ajax|ahah)/") { + return (pass); + } + + # --- Accept-Encoding (nie kompresujemy oczywistych statyk po rozszerzeniu) --- + if (req.http.Accept-Encoding) { + if (req.url ~ "(?i)\.(jpg|jpeg|png|gif|webp|ico|svg|woff2?|ttf|eot|mp4|mp3|pdf|zip|7z|gz|bz2)$") { + unset req.http.Accept-Encoding; + } elseif (req.http.Accept-Encoding ~ "gzip") { + set req.http.Accept-Encoding = "gzip"; + } elseif (req.http.Accept-Encoding ~ "deflate" && req.http.user-agent !~ "MSIE") { + set req.http.Accept-Encoding = "deflate"; + } else { + unset req.http.Accept-Encoding; + } + } + + # --- cookies: tylko jeśli naprawdę potrzebne do cache key; inaczej wyczyść --- + if (req.http.Cookie) { + set req.http.Cookie = ";" + req.http.Cookie; + set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";"); + # przykładowy whitelist (dopasuj pod aplikację); tu czyścimy wszystkie + set req.http.Cookie = regsuball(req.http.Cookie, ";[^;]*", ""); + set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", ""); + if (req.http.Cookie ~ "^\s*$") { unset req.http.Cookie; } + else { return (pass); } # zostało coś istotnego → omijamy cache + } + + return (hash); } sub vcl_hash { - hash_data(req.http.X-Forwarded-Proto); + hash_data(req.http.X-Forwarded-Proto); } sub vcl_hit { - set req.http.X-Cache = "hit"; - if (obj.ttl <= 0s && obj.grace > 0s) { - set req.http.X-Cache = "hit graced"; - } + set req.http.X-Cache = "HIT"; + if (obj.ttl <= 0s && obj.grace > 0s) { set req.http.X-Cache = "HIT-GRACE"; } } - -sub vcl_miss { set req.http.X-Cache = "miss"; } -sub vcl_pass { set req.http.X-Cache = "pass"; } -sub vcl_pipe { set req.http.X-Cache = "pipe uncacheable"; } +sub vcl_miss { set req.http.X-Cache = "MISS"; } +sub vcl_pass { set req.http.X-Cache = "PASS"; } sub vcl_backend_response { - set beresp.http.X-Url = bereq.url; - set beresp.http.X-Host = bereq.http.host; + # krótkie TTL dla wybranych statusów + if (beresp.status == 404 || beresp.status == 301 || beresp.status == 500) { + set beresp.ttl = 10m; + } - # krótkie TTL dla wybranych statusów - if (beresp.status == 404 || beresp.status == 301 || beresp.status == 500) { - set beresp.ttl = 10m; - } + # retry na 5xx (bez pętli) + if (beresp.status == 500 || beresp.status == 503) { return (retry); } - # retry na błędach backendu - if (beresp.status == 500 || beresp.status == 503) { - return (retry); - } + # kompresja + if (bereq.url ~ "(?i)\.(jpg|jpeg|png|gif|webp|ico|svg|mp4|mp3|pdf|zip|7z|gz|bz2)$") { + set beresp.do_gzip = false; + } else { + set beresp.do_gzip = true; + } - # kompresja (wyłącz dla oczywistych binariów po URL) - if (bereq.url ~ "(?i)\.(3gp|wmv|avi|asf|asx|mpg|mpeg|mp4|pls|mp3|mid|wav|swf|flv|exe|zip|tar|rar|gz|tgz|bz2|uha|7z|doc|docx|xls|xlsx|pdf|iso)$") { - set beresp.do_gzip = false; - } else { - set beresp.do_gzip = true; + # TTL: honoruj Cache-Control; no-store/private = 0 + if (beresp.http.Cache-Control ~ "(?i)no-store|private") { + set beresp.ttl = 0s; + } else { + if (beresp.http.Cache-Control ~ "(?i)s-maxage=\d+") { + set beresp.ttl = std.duration(regsub(beresp.http.Cache-Control, ".*(?i)s-maxage=(\d+).*", "\1") + "s", 0s); + } elseif (beresp.http.Cache-Control ~ "(?i)max-age=\d+") { + set beresp.ttl = std.duration(regsub(beresp.http.Cache-Control, ".*(?i)max-age=(\d+).*", "\1") + "s", 0s); } + # fallback (lekko agresywny, prosto) + if (beresp.ttl <= 0s) { + if (beresp.http.Content-Type ~ "(?i)^image/|^font/|/javascript|/css") { set beresp.ttl = 7d; } + elseif (beresp.http.Content-Type ~ "(?i)^text/|^application/json") { set beresp.ttl = 1d; } + else { set beresp.ttl = 1h; } + } + } - # TTL: honoruj Cache-Control (s-maxage > max-age), no-store/private → 0 - if (beresp.http.Cache-Control ~ "(?i)no-store" || beresp.http.Cache-Control ~ "(?i)private") { - set beresp.ttl = 0s; - } else { - if (beresp.http.Cache-Control ~ "(?i)s-maxage=\d+") { - set beresp.ttl = std.duration(regsub(beresp.http.Cache-Control, ".*(?i)s-maxage=(\d+).*", "\1") + "s", 0s); - } elseif (beresp.http.Cache-Control ~ "(?i)max-age=\d+") { - set beresp.ttl = std.duration(regsub(beresp.http.Cache-Control, ".*(?i)max-age=(\d+).*", "\1") + "s", 0s); - } - # fallback wg Content-Type (jeśli brak max-age) - if (beresp.ttl <= 0s) { - if (beresp.http.Content-Type ~ "(?i)^video/(mp4|webm|ogg|x-msvideo|x-matroska|mpeg|quicktime|3gpp|3gpp2|x-flv|avi|x-ms-wmv)($|;)") { - set beresp.ttl = 259200s; # 3d - } elseif (beresp.http.Content-Type ~ "(?i)^audio/(mpeg|mp3|ogg|wav|x-wav|webm|aac|flac|midi|x-midi|x-aiff|aiff|x-mpegurl|x-ms-wma)($|;)") { - set beresp.ttl = 259200s; # 3d - } elseif (beresp.http.Content-Type ~ "(?i)^application/(zip|x-tar|rar|x-7z-compressed|gzip|x-bzip2|x-bzip|octet-stream|x-rar-compressed|x-gzip|x-xz|x-lzma|x-iso9660-image)($|;)") { - set beresp.ttl = 3600s; # 1h - } elseif (beresp.http.Content-Type ~ "(?i)^image/(jpeg|jpg|jpe|png|gif|bmp|webp|svg(\+xml)?|tiff|tif|x-icon|vnd\.microsoft\.icon|heic|heif|avif|jp2|jpx|j2k|j2c)($|;)") { - set beresp.ttl = 759200s; # ~8.8d - } elseif ( - beresp.http.Content-Type ~ "(?i)^text/(plain|csv|css|html?|xml|javascript|markdown|x-markdown|tab-separated-values|richtext)" - || beresp.http.Content-Type ~ "(?i)^application/(json|xml|x-yaml|x-tar|x-latex|x-tex|x-bibtex|x-sql|x-javascript|x-lua|x-perl|x-python|x-ruby|x-csh|x-php|x-httpd-php|x-shellscript|x-javascript-config)" - ) { - set beresp.ttl = 2592000s; # 30d - } else { - set beresp.ttl = 1d; # domyślnie - } - } - } + # usuń ciasteczka dla statyk/binariów + if (bereq.url ~ "(?i)\.(jpg|jpeg|png|gif|webp|ico|svg|js|css|woff2?|ttf|eot|pdf|zip|7z|gz|bz2|mp4|mp3)$") { + unset beresp.http.Set-Cookie; + } - # wyczyść Set-Cookie dla statyk (obrazy, css/js, fonty, binaria) - if (bereq.url ~ "(?i)\.(jpg|jpeg|png|gif|bmp|tiff|svg|webp|ico|js|css|html?|txt|eot|ttf|woff2?)$") { - unset beresp.http.Set-Cookie; - } - if (bereq.url ~ "(?i)\.(3gp|wmv|avi|asf|asx|mpg|mpeg|mp4|pls|mp3|mid|wav|swf|flv|exe|zip|tar|rar|gz|tgz|bz2|uha|7z|doc|docx|xls|xlsx|pdf|iso)$") { - unset beresp.http.Set-Cookie; - } + # ESI + if (beresp.http.Surrogate-Control ~ "ESI/1.0") { + unset beresp.http.Surrogate-Control; + set beresp.do_esi = true; + } - # ESI - if (beresp.http.Surrogate-Control ~ "ESI/1.0") { - unset beresp.http.Surrogate-Control; - set beresp.do_esi = true; - } + # grace/keep + if (beresp.ttl > 0s) { + set beresp.grace = std.duration(std.tostring( beresp.ttl / 10 ), 10m); + if (beresp.grace < 10m) { set beresp.grace = 10m; } + if (beresp.grace > 2h) { set beresp.grace = 2h; } + set beresp.keep = (beresp.ttl > 1h) ? 1h : beresp.ttl; + } else { + set beresp.grace = 0s; + set beresp.keep = 0s; + } - # grace: 10% TTL (min 10m, max 2h); keep: min(TTL, 1h) - if (beresp.ttl > 0s) { - set beresp.grace = beresp.ttl / 10; - if (beresp.grace < 10m) { set beresp.grace = 10m; } - if (beresp.grace > 2h) { set beresp.grace = 2h; } - - if (beresp.ttl > 1h) { - set beresp.keep = 1h; - } else { - set beresp.keep = beresp.ttl; - } - } else { - set beresp.grace = 0s; - set beresp.keep = 0s; - } + # streaming dużych odpowiedzi (>1 MiB) + if (beresp.http.Content-Length && std.integer(beresp.http.Content-Length, 0) > 1048576) { + set beresp.do_stream = true; + } } sub vcl_deliver { - unset resp.http.X-Url; - unset resp.http.X-Host; - unset resp.http.Cache-Tags; - unset resp.http.X-Drupal-Cache-Contexts; + # polityka Cache-Control po typie (frontend) + if (resp.http.Content-Type ~ "(?i)^image/|^font/|/javascript|/css") { + set resp.http.Cache-Control = "public, max-age=604800"; # 7d + } elseif (resp.http.Content-Type ~ "(?i)^text/|^application/json") { + set resp.http.Cache-Control = "public, max-age=86400"; # 1d + } - # Ustal Cache-Control po Content-Type (frontend-policy) - if (resp.http.Content-Type ~ "(?i)^video/(mp4|webm|ogg|x-msvideo|x-matroska|mpeg|quicktime|3gpp|3gpp2|x-flv|avi|x-ms-wmv)$") { - set resp.http.Cache-Control = "public, max-age=259200"; - } elseif (resp.http.Content-Type ~ "(?i)^audio/(mpeg|mp3|ogg|wav|x-wav|webm|aac|flac|midi|x-midi|x-aiff|aiff|x-mpegurl|x-ms-wma)$") { - set resp.http.Cache-Control = "public, max-age=259200"; - } elseif (resp.http.Content-Type ~ "(?i)^application/(zip|x-tar|rar|x-7z-compressed|gzip|x-bzip2|x-bzip|octet-stream|x-rar-compressed|x-gzip|x-xz|x-lzma|x-iso9660-image)$") { - set resp.http.Cache-Control = "public, max-age=3600"; - } elseif (resp.http.Content-Type ~ "(?i)^image/(jpeg|jpg|jpe|png|gif|bmp|webp|svg(\+xml)?|tiff|tif|x-icon|vnd\.microsoft\.icon|heic|heif|avif|jp2|jpx|j2k|j2c)$") { - set resp.http.Cache-Control = "public, max-age=759200"; - } elseif ( - resp.http.Content-Type ~ "(?i)^text/(plain|csv|css|html?|xml|javascript|markdown|x-markdown|tab-separated-values|richtext)" - || resp.http.Content-Type ~ "(?i)^application/(json|xml|x-yaml|x-tar|x-latex|x-tex|x-bibtex|x-sql|x-javascript|x-lua|x-perl|x-python|x-ruby|x-csh|x-php|x-httpd-php|x-shellscript|x-javascript-config)" - ) { - set resp.http.Cache-Control = "public, max-age=2592000"; - } + unset resp.http.Expires; + unset resp.http.Pragma; - unset resp.http.Expires; - unset resp.http.Pragma; + # metryki cache + if (obj.uncacheable) { + set resp.http.X-Cache = "PASS"; + unset resp.http.Age; + } elseif (obj.hits > 0) { + set resp.http.X-Cache = "HIT"; + set resp.http.X-Cache-Hits = obj.hits; + } else { + set resp.http.X-Cache = "MISS"; + unset resp.http.Age; + } - if (obj.hits > 0) { - set resp.http.X-Cache = req.http.X-Cache; - set resp.http.X-Cache-Hits = obj.hits; - } else { - unset resp.http.Age; - } + # twarde usunięcie sygnatur serwera + unset resp.http.X-Url; + unset resp.http.X-Host; + unset resp.http.Via; + unset resp.http.X-Varnish; + unset resp.http.Server; + set resp.http.X-Frame-Options = "SAMEORIGIN"; - unset resp.http.X-Varnish; - unset resp.http.Via; - unset resp.http.Server; - - if (resp.status == 403 || resp.status == 404 || resp.status == 500 || resp.status == 503) { - return (synth(800, "Maintenance page")); - } + # strona serwisowa dla wybranych statusów + if (resp.status == 403 || resp.status == 404 || resp.status == 500 || resp.status == 503) { + return (synth(800, "Maintenance page")); + } } sub vcl_synth { - set req.http.X-Cache = "synth"; + set resp.http.X-Cache = "SYNTH"; - if (resp.status == 503 && req.restarts < 4) { - return (restart); - } + if (resp.status == 503 && req.restarts < 4) { return (restart); } - if (resp.status == 800) { - set resp.http.Content-Type = "text/html; charset=utf-8"; - set resp.status = 404; - set resp.http.Cache-Control = "no-store, no-cache, must-revalidate, max-age=0"; - synthetic({" + if (resp.status == 800) { + set resp.http.Content-Type = "text/html; charset=utf-8"; + set resp.status = 404; + set resp.http.Cache-Control = "no-store, no-cache, must-revalidate, max-age=0"; + synthetic({" "} + resp.status + " " + resp.reason + {"

Error "} + resp.status + {"

"} + resp.reason + {"

"}); - return (deliver); - } + return (deliver); + } }