From dc2c7f46ae65c62a9c9a2fe1bc1232dc2e48a65f Mon Sep 17 00:00:00 2001 From: Gbanyan Date: Sat, 7 Feb 2026 02:15:13 +0800 Subject: [PATCH] traefik: harden websecure defaults (crowdsec, headers, tls12) --- .gitignore | 3 +++ README.md | 2 +- docker-compose.yaml | 6 ++++++ dynamic.d/middlewares/label-auth.yml | 6 ++++++ dynamic.d/middlewares/secure-headers.yml | 12 ++++++++++++ dynamic.d/routers/label.yml | 18 ++++++++++++++++++ dynamic.d/routers/usher.yml | 16 ++++++++++++++++ dynamic.d/tls/options.yml | 5 +++++ dynamic.d/transports/fast-upstreams.yml | 5 +++++ traefik.yml | 18 ++++++++++++++++++ 10 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 dynamic.d/middlewares/label-auth.yml create mode 100644 dynamic.d/middlewares/secure-headers.yml create mode 100644 dynamic.d/routers/label.yml create mode 100644 dynamic.d/routers/usher.yml create mode 100644 dynamic.d/tls/options.yml diff --git a/.gitignore b/.gitignore index 6009da9..0af72a8 100644 --- a/.gitignore +++ b/.gitignore @@ -25,5 +25,8 @@ node_modules/ # Ignore generated secrets dynamic.d/middlewares/crowdsec.yml +# Local secrets directory (htpasswd, etc.) +secrets/ + # Ignore backup files *.~* diff --git a/README.md b/README.md index 4d04133..42a2e67 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ labels: - traefik.http.routers.myapp.rule=Host(`app.example.com`) - traefik.http.routers.myapp.entrypoints=websecure - traefik.http.routers.myapp.tls.certresolver=letsencrypt - - traefik.http.routers.myapp.middlewares=crowdsec@file,retry-fast@file,compress-middleware@file + - traefik.http.routers.myapp.middlewares=myapp-custom@docker - traefik.http.services.myapp.loadbalancer.serversTransport=fast-upstreams@file - traefik.http.services.myapp.loadbalancer.server.port=3000 - traefik.docker.network=traefik_default diff --git a/docker-compose.yaml b/docker-compose.yaml index 43c8375..7225e82 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -16,6 +16,7 @@ services: volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./certs:/letsencrypt + - ./secrets:/secrets:ro #- ./dashboard_authfile:/dashboard_authfile:ro - ./dynamic.d:/dynamic.d - ./traefik.yml:/traefik.yml @@ -30,6 +31,11 @@ services: - "traefik.http.routers.traefik.entrypoints=internal_websecure" - "traefik.http.routers.traefik.tls.certresolver=letsencrypt" - "traefik.http.routers.traefik.service=api@internal" + # CrowdSec bouncer middleware (defined via Docker provider so the LAPI key isn't stored in git-tracked files). + - "traefik.http.middlewares.crowdsec.plugin.bouncer.enabled=true" + - "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdsecMode=stream" + - "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdsecLapiHost=localhost:8080" + - "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdsecLapiKey=${CROWDSEC_LAPI_KEY}" - "com.centurylinklabs.watchtower.enable=true" # Added label for Watchtower # "traefik.http.middlewares.auth.basicauth.usersfile=/dashboard_authfile" - "traefik.http.services.traefik.loadbalancer.server.port=9090" diff --git a/dynamic.d/middlewares/label-auth.yml b/dynamic.d/middlewares/label-auth.yml new file mode 100644 index 0000000..760db08 --- /dev/null +++ b/dynamic.d/middlewares/label-auth.yml @@ -0,0 +1,6 @@ +http: + middlewares: + label-auth: + basicAuth: + # Keep actual user hashes out of git. + usersFile: /secrets/label.htpasswd diff --git a/dynamic.d/middlewares/secure-headers.yml b/dynamic.d/middlewares/secure-headers.yml new file mode 100644 index 0000000..c8f9701 --- /dev/null +++ b/dynamic.d/middlewares/secure-headers.yml @@ -0,0 +1,12 @@ +http: + middlewares: + secure-headers: + headers: + contentTypeNosniff: true + frameDeny: true + referrerPolicy: "strict-origin-when-cross-origin" + # Intentionally no HSTS (per requirement). + customResponseHeaders: + server: "" + x-powered-by: "" + diff --git a/dynamic.d/routers/label.yml b/dynamic.d/routers/label.yml new file mode 100644 index 0000000..233202e --- /dev/null +++ b/dynamic.d/routers/label.yml @@ -0,0 +1,18 @@ +http: + routers: + label: + rule: Host(`label.gbanyan.net`) + entryPoints: + - websecure + tls: + certResolver: letsencrypt + middlewares: + - label-auth + service: label + + services: + label: + loadBalancer: + passHostHeader: true + servers: + - url: "http://127.0.0.1:5004" diff --git a/dynamic.d/routers/usher.yml b/dynamic.d/routers/usher.yml new file mode 100644 index 0000000..8e092d4 --- /dev/null +++ b/dynamic.d/routers/usher.yml @@ -0,0 +1,16 @@ +http: + routers: + usher: + rule: Host(`member.usher.org.tw`) + entryPoints: + - websecure + tls: + certResolver: letsencrypt + service: usher + + services: + usher: + loadBalancer: + passHostHeader: true + servers: + - url: "http://10.0.0.225:8000" diff --git a/dynamic.d/tls/options.yml b/dynamic.d/tls/options.yml new file mode 100644 index 0000000..e84af8d --- /dev/null +++ b/dynamic.d/tls/options.yml @@ -0,0 +1,5 @@ +tls: + options: + default: + minVersion: VersionTLS12 + diff --git a/dynamic.d/transports/fast-upstreams.yml b/dynamic.d/transports/fast-upstreams.yml index 35f4244..4471818 100644 --- a/dynamic.d/transports/fast-upstreams.yml +++ b/dynamic.d/transports/fast-upstreams.yml @@ -5,3 +5,8 @@ http: forwardingTimeouts: idleConnTimeout: 30s responseHeaderTimeout: 15s + gitea-upstreams: + maxIdleConnsPerHost: 64 + forwardingTimeouts: + idleConnTimeout: 10m + responseHeaderTimeout: 10m diff --git a/traefik.yml b/traefik.yml index 4285f1d..688e244 100644 --- a/traefik.yml +++ b/traefik.yml @@ -59,8 +59,14 @@ entryPoints: entryPoint: to: "websecure" # The target element scheme: "https" + permanent: true websecure: address: "10.0.0.225:443" + transport: + respondingTimeouts: + readTimeout: 10m + writeTimeout: 10m + idleTimeout: 10m forwardedHeaders: trustedIPs: - "173.245.48.0/20" @@ -85,6 +91,12 @@ entryPoints: - "2405:8100::/32" - "2a06:98c0::/29" - "2c0f:f248::/32" + http: + middlewares: + - crowdsec@docker + - secure-headers@file + - compress-middleware@file + - retry-fast@file internal_web: address: "192.168.50.4:80" http: @@ -92,8 +104,14 @@ entryPoints: entryPoint: to: "internal_websecure" # The target element scheme: "https" + permanent: true internal_websecure: address: "192.168.50.4:443" + transport: + respondingTimeouts: + readTimeout: 10m + writeTimeout: 10m + idleTimeout: 10m metrics: address: ":8082" dashboard: