Skip to content

lss 2.0: Traefik as edge proxy - #28

Draft
VectoDE wants to merge 12 commits into
feat/lss-5from
feat/lss-traefik
Draft

VectoDE wants to merge 12 commits into
feat/lss-5from
feat/lss-traefik

Conversation

@VectoDE

@VectoDE VectoDE commented Oct 15, 2026

Copy link
Copy Markdown
Owner

Adds Traefik as an optional edge proxy in the web group (issue #27).

  • modules/web/traefik.sh: Traefik 3.7.15 from the upstream release (pinned, checksum in data/checksums.txt, amd64 and arm64), own user lss-traefik, systemd sandbox with CAP_NET_BIND_SERVICE only. Owns 80/tcp, 443/tcp and 443/udp (HTTP/3), redirects to HTTPS, applies the web_headers set and HSTS, JSON access log, dashboard and API on 127.0.0.1 only.
  • WAF: LSS_TRAEFIK_WAF=coraza|modsecurity|both|off (default coraza). Coraza is a pinned, checksum-verified local WASM plugin with CRS 4; verify expects 403 for an SQL injection and a path traversal.
  • Backend mode: nginx, Apache or Caddy move to 127.0.0.1:8008/8443 and keep vhosts, PHP and admin tools; real client IP via realip / mod_remoteip / trusted_proxies. Site files no longer carry listen ports, so switching on install and removal only rewrites the server's own config.
  • web_proxy_route NAME DOMAIN UPSTREAM in lib/web.sh: records a route and writes a Traefik file-provider route, or a reverse-proxy site of the web server without Traefik. Routes move on Traefik install and removal.
  • Docker provider opt-in, only through a read-only socket proxy (wollomatic/socket-proxy, pinned digest, no network, no capabilities).
  • fail2ban jails and CrowdSec acquisition for Traefik's access log; tls deploy hook restarts Traefik after renewals.
  • Tests: tests/unit/traefik.bats, additions to web.bats; integration group edge (every web server and WAF mode), Docker provider in the VM container runs, traefik in profiles/all.conf. run.sh accepts LSS_KEY=VALUE inside a comma set.

Refs #27

🤖 Generated with Claude Code

VectoDE and others added 12 commits October 15, 2026 21:48
Applications and sites need one place that owns ports 80, 443 and
443/udp, applies the same headers, HSTS, redirect and WAF to every
host name, and keeps the web server for what it does best (vhosts,
PHP, admin tools). The new traefik module installs the pinned,
checksum-verified upstream binary with its own user and a systemd
sandbox that keeps only CAP_NET_BIND_SERVICE, loads the Coraza WAF
with the Core Rule Set as a pinned local plugin (or leaves blocking to
ModSecurity behind it), and moves nginx, Apache or Caddy to 127.0.0.1
with the client address taken from X-Forwarded-For.

Site files no longer carry their listen ports, so switching the mode
rewrites only the web server's own configuration. web_proxy_route
records application routes once and applies them to Traefik's file
provider or, without Traefik, to a reverse-proxy site, so app modules
need not know which one is in front. The Docker provider is opt-in and
only ever sees a read-only socket proxy.

Refs #27

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
The site now includes listen-http.conf and listen-https.conf, so a
plain 'listen' pattern matched the include lines themselves.

Refs #27

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
Traefik restarted in a loop on AlmaLinux 10 and Ubuntu 22.04 with the
Docker provider, and the diagnostics only list failed units, which a
restarting one is not. Its own last lines name the reason.

Refs #27

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
A host name without a site gets no answer from the web server (nginx
returns 444), so the probe through Traefik for such a name timed out
behind nginx 1.18 on Ubuntu 22.04 instead of showing anything about the
backend. The sites of the vhost module are checked through Traefik
already, with their headers.

Refs #27

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
On Ubuntu 26.04, Debian 13, Ubuntu 22.04 and AlmaLinux 10 Traefik 3.7.15
died about a second after its start with a fatal error inside the
WebAssembly runtime of the Coraza plugin (unsafe.Slice: len out of
range, called from compiled guest code), while lss wrote routes and
certificates right after the start. Each change of the dynamic
configuration starts the plugin anew; at most one every 10 seconds
gives each start time to finish. A crash that still happens is
followed by a restart after 2 seconds, and route checks wait up to 30.

Refs #27

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
Every Traefik release (3.7.15, 3.6.25, master) embeds wazero 1.8.0. Its
amd64 compiler calls runtime.memmove without reserving DX, which Go 1.24
and later clobber there (wazero/wazero#2378, fixed in 1.9.0). With the
Coraza plugin loaded Traefik therefore dies at random below memmove
("unsafe.Slice: len out of range", "split stack overflow"), which is what
broke the VM and profile jobs on amd64; arm64 passed.

On amd64 lss now leaves the plugin out, uses ModSecurity in the backend
web server instead when the modsecurity module is installed and warns
either way. TRAEFIK_CORAZA_AMD64 switches it back on together with a
Traefik version that carries the fix. Also repairs a cp1252 dash in
docs/hardening.md that was not valid UTF-8.

Refs #27

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
SonarCloud flags positional parameters used deep inside a function body
and the curl status format repeated for every socket proxy check. Named
locals make the route and verify helpers easier to read, and one helper
now asks the socket proxy for a status.

Refs #27

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
Removing traefik dropped the copy of lss-admin in the web server's TLS
directory but kept certbot's lineage. On Rocky and AlmaLinux, where
removing certbot leaves /etc/letsencrypt behind, the next tls install
then found a live certificate the web server lacks and failed verify.
The lineage now goes with the admin host, as vhost does for its sites.

Also fills the README resource row for traefik from the CI RESOURCES
lines: about 100 MiB resident without Coraza, 1.75 GiB with it (arm64).

Refs #27

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
On amd64, Coraza has to stay out of Traefik while Traefik ships wazero
1.8.0. Until now that left the applications without a WAF, and the web
server's sites were filtered only if someone happened to install
modsecurity. The owner chose this instead: with LSS_TRAEFIK_WAF=coraza
(the default) or both on amd64, lss installs the modsecurity module for
nginx or Apache itself, once, and removes it again with Traefik unless
the user installed it. It also sends the routes of the applications
through the web server (Traefik -> nginx/Apache with ModSecurity -> app),
so every request passes a WAF. A "traefik:" warning in the log says why.

Caddy has no ModSecurity connector, so behind Caddy the routes stay
direct and lss warns that nothing filters. The same holds without a web
server and for Docker-provider containers. When a web server is removed,
its routes go straight to the applications again. Explicit modsecurity,
both on arm64, and off keep their meaning, and TRAEFIK_CORAZA_AMD64
brings Coraza and direct routes back on amd64.

verify now expects a 403 for an SQL injection through Traefik on every
vhost site and every route whenever a WAF is in their path.

Refs #27

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
verify now sends an SQL injection to every vhost site through Traefik and
expects 403, so it no longer accepts any answer. That showed Apache
2.4.65 (Debian 12 and 13) answering 421 for every site except the
default one. Traefik connects to 127.0.0.1 without SNI, so the handshake
runs in the default site, and Apache's new SNI vhost policy refuses the
switch to the site named in Host when the TLS settings differ. In
backend mode lss now sets SSLVHostSNIPolicy authonly, which still
refuses differing client authentication (lss configures none), guarded
by IfDirective for Apache releases without the directive.

Refs #27

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: VectoDE <system.bot.discord.utility@gmail.com>
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant