This commit is contained in:
Mateusz Gruszczyński
2025-09-25 12:17:15 +02:00
parent afaafe8546
commit 4c7c11ce99
3 changed files with 175 additions and 301 deletions

View File

@@ -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

View File

@@ -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 }

View File

@@ -1,40 +1,66 @@
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;
}
.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 {
# --- 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";
# Accept-Encoding normalize (wyłącz dla plików binarnych/statycznych)
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; }
}
# --- 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 niecacheowalnych ---
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|bmp|tiff|jiff|svg|webp|ico|js|css|html?|txt|eot|ttf|woff2?)$") {
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";
@@ -45,67 +71,15 @@ sub vcl_recv {
}
}
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ż cacheowalne → 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 niecacheowalne (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
# --- 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ł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, ";[^ ][^;]*", "");
# 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);
}
if (req.http.Cookie ~ "^\s*$") { unset req.http.Cookie; }
else { return (pass); } # zostało coś istotnego → omijamy cache
}
return (hash);
@@ -116,39 +90,30 @@ sub vcl_hash {
}
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;
}
# retry na błędach backendu
if (beresp.status == 500 || beresp.status == 503) {
return (retry);
}
# retry na 5xx (bez pętli)
if (beresp.status == 500 || beresp.status == 503) { return (retry); }
# 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)$") {
# 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;
}
# 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") {
# 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+") {
@@ -156,32 +121,16 @@ sub vcl_backend_response {
} 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)
# fallback (lekko agresywny, prosto)
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
}
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; }
}
}
# 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)$") {
# 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;
}
@@ -191,70 +140,64 @@ sub vcl_backend_response {
set beresp.do_esi = true;
}
# grace: 10% TTL (min 10m, max 2h); keep: min(TTL, 1h)
# grace/keep
if (beresp.ttl > 0s) {
set beresp.grace = beresp.ttl / 10;
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; }
if (beresp.ttl > 1h) {
set beresp.keep = 1h;
} else {
set beresp.keep = beresp.ttl;
}
set beresp.keep = (beresp.ttl > 1h) ? 1h : 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;
# 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";
# 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
}
unset resp.http.Expires;
unset resp.http.Pragma;
if (obj.hits > 0) {
set resp.http.X-Cache = req.http.X-Cache;
# 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;
}
unset resp.http.X-Varnish;
# 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";
# 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";