From 6e3b73f8224d7773a92b242f131089ccebe9f864 Mon Sep 17 00:00:00 2001 From: pipistrello Date: Fri, 5 Jun 2026 14:21:01 +0300 Subject: [PATCH] initial traefik layout --- traefik/README.md | 73 +++++++++++++++++++++++++++++++++ traefik/docker-compose.yaml | 22 ++++++++++ traefik/dynamic/passthrough.yml | 51 +++++++++++++++++++++++ traefik/dynamic/web.yml | 52 +++++++++++++++++++++++ traefik/traefik.yml | 49 ++++++++++++++++++++++ 5 files changed, 247 insertions(+) create mode 100644 traefik/README.md create mode 100644 traefik/docker-compose.yaml create mode 100644 traefik/dynamic/passthrough.yml create mode 100644 traefik/dynamic/web.yml create mode 100644 traefik/traefik.yml diff --git a/traefik/README.md b/traefik/README.md new file mode 100644 index 0000000..8f9f7f3 --- /dev/null +++ b/traefik/README.md @@ -0,0 +1,73 @@ +# traefik — ОСК reverse proxy / SNI router + +Replaces **nginx-proxy-manager** on serverweb. One `:443` entrypoint that does +**both**: + +- **TLS pass-through (Layer 4)** for the Windows services that use Windows-Integrated + auth — TLS terminates at the backend, native Kerberos/NTLM/Negotiate is preserved + (NPM/openresty cannot do this): + - `workfolders.osk.team` → serverfile `192.168.0.11:443` (Work Folders) + - `mail.osk.team`, `autodiscover.osk.team` → servermail `192.168.0.6:443` (Exchange 2019) + - `kdcproxy.osk.team` → serverwsus `192.168.0.7:443` (KDC proxy) +- **TLS termination + Let's Encrypt** for the web dashboards: + - `start.osk.team` (flame), `portainer.osk.team`, `traefik.osk.team` (dashboard) + +## Files + +| File | Purpose | +|------|---------| +| `docker-compose.yaml` | Traefik v3 service, binds 80/443, mounts config + acme | +| `traefik.yml` | static config (entrypoints, providers, ACME) | +| `dynamic/passthrough.yml` | the TCP SNI-passthrough routers → Windows hosts | +| `dynamic/web.yml` | HTTP routers (dashboards) + dashboard basic-auth | + +Routing config lives in git and redeploys on push (file provider, `watch: true`). +`acme.json` is **not** in git — it holds private keys and persists on the host. + +## One-time host prep (serverweb) + +```bash +mkdir -p /mnt/containers/traefik/container-data/acme +touch /mnt/containers/traefik/container-data/acme/acme.json +chmod 600 /mnt/containers/traefik/container-data/acme/acme.json +``` + +## Before deploying — edit + +1. `traefik.yml`: set the real ACME `email`. +2. `dynamic/web.yml`: set a real `dash-auth` hash (`htpasswd -nbB admin 'pass'`), + and confirm the portainer backend (add portainer to `reverseproxy-nw`, or point + at `https://192.168.0.8:9443`). + +## Deploy (Portainer GitOps) + +- Decommission NPM first (it owns 80/443): stop/remove the `nginx-proxy-manager` + stack. **Do not** delete its data yet (rollback insurance). +- Add a new Portainer **Git** stack, compose path `traefik/docker-compose.yaml`, + same repo. Deploy. +- Test while the edge still points 443 → serverfile: from inside the LAN, hit each + SNI name against serverweb and confirm routing/cert. +- **Flip the edge** last: + - `TCP/80 → serverweb 192.168.0.8` (ACME http-01 + OWA http→https) + - `TCP/443 → serverweb 192.168.0.8` (was → serverfile) + - `UDP/443 → serverfile 192.168.0.11` (SMB over QUIC — unchanged, bypasses Traefik) + +## Recommended consolidation + +Once Traefik fronts 443, **retire the `kdcproxy` `:8443` hack**: route +`kdcproxy.osk.team` through Traefik on 443 (passthrough above) and revert the +client GPO `KdcProxy\ProxyServers\CORP.LOCAL` back to `` +(no port). Then the edge `TCP/8443` forward can be removed. + +## Caveats + +- **SNI required.** Passthrough routes only by SNI; any client not sending SNI + can't be routed (all modern Outlook/ActiveSync/Work Folders do send it). +- **Passthrough backends keep their own certs.** Traefik's LE only covers the + dashboards it terminates — **serverfile's Work Folders LE cert still renews on + serverfile** (the Sept-3 expiry is unchanged), and Exchange/KDC-proxy keep theirs. +- **Exchange cert** on servermail must cover `mail.osk.team` + `autodiscover.osk.team` + (and any other published names). +- **QUIC** stays a direct edge forward to serverfile; Traefik does not touch UDP/443. +- The dashboards' LE certs need public `:80` reachability for http-01. If they are + internal-only, use a DNS-01 challenge or a default cert instead. diff --git a/traefik/docker-compose.yaml b/traefik/docker-compose.yaml new file mode 100644 index 0000000..0f37161 --- /dev/null +++ b/traefik/docker-compose.yaml @@ -0,0 +1,22 @@ +services: + traefik: + image: traefik:v3 + container_name: traefik + restart: always + # Traefik replaces nginx-proxy-manager on 80/443. Decommission NPM before + # deploying this (both can't bind 80/443). UDP/443 (SMB-over-QUIC) is NOT + # handled here — the router forwards UDP/443 straight to serverfile. + ports: + - "80:80" + - "443:443" + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - ./traefik.yml:/etc/traefik/traefik.yml:ro,z + - ./dynamic:/etc/traefik/dynamic:ro,z + - /mnt/containers/traefik/container-data/acme:/acme:z + networks: + - reverseproxy-nw + +networks: + reverseproxy-nw: + external: true diff --git a/traefik/dynamic/passthrough.yml b/traefik/dynamic/passthrough.yml new file mode 100644 index 0000000..8425ccf --- /dev/null +++ b/traefik/dynamic/passthrough.yml @@ -0,0 +1,51 @@ +# SNI TLS pass-through (Layer 4) to the Windows backends. +# +# Traefik does NOT terminate TLS for these — it reads the SNI from the TLS +# ClientHello and forwards the raw connection to the backend, which terminates +# TLS itself and performs its own NATIVE auth (Kerberos / NTLM / Negotiate). +# This is the whole point: it preserves Windows-Integrated auth that an +# HTTP-terminating proxy (NPM/openresty) breaks. +# +# Requirements: +# - Clients MUST send SNI (all modern Outlook / ActiveSync / Work Folders do). +# - Each backend must present a cert valid for its own hostname(s): +# serverfile -> files.osk.team + workfolders.osk.team (LE, 9b279156…) +# servermail -> mail.osk.team + autodiscover.osk.team (Exchange cert) +# serverwsus -> kdcproxy.osk.team (self-signed, 02B9ADB3…) + +tcp: + routers: + workfolders: + entryPoints: ["websecure"] + rule: "HostSNI(`workfolders.osk.team`)" + tls: + passthrough: true + service: serverfile-wf + + exchange: + entryPoints: ["websecure"] + rule: "HostSNI(`mail.osk.team`, `autodiscover.osk.team`)" + tls: + passthrough: true + service: servermail-ex + + kdcproxy: + entryPoints: ["websecure"] + rule: "HostSNI(`kdcproxy.osk.team`)" + tls: + passthrough: true + service: serverwsus-kdc + + services: + serverfile-wf: + loadBalancer: + servers: + - address: "192.168.0.11:443" # serverfile — Work Folders + servermail-ex: + loadBalancer: + servers: + - address: "192.168.0.6:443" # servermail — Exchange 2019 + serverwsus-kdc: + loadBalancer: + servers: + - address: "192.168.0.7:443" # serverwsus — KDC proxy diff --git a/traefik/dynamic/web.yml b/traefik/dynamic/web.yml new file mode 100644 index 0000000..baa36bc --- /dev/null +++ b/traefik/dynamic/web.yml @@ -0,0 +1,52 @@ +# HTTP routers for the web dashboards — Traefik TERMINATES TLS here and +# auto-issues Let's Encrypt certs (resolver "le"). These hosts have no +# Windows-Integrated auth, so termination is fine. +# +# ACME http-01 requires each Host below to be publicly resolvable and reachable +# on :80 through the edge. For internal-only dashboards, use a dnsChallenge or a +# default cert instead (see traefik.yml). + +http: + routers: + flame: + entryPoints: ["websecure"] + rule: "Host(`start.osk.team`)" + service: flame + tls: + certResolver: le + + portainer: + entryPoints: ["websecure"] + rule: "Host(`portainer.osk.team`)" + service: portainer + tls: + certResolver: le + + traefik-dashboard: + entryPoints: ["websecure"] + rule: "Host(`traefik.osk.team`)" + service: api@internal + middlewares: ["dash-auth"] + tls: + certResolver: le + + services: + flame: + loadBalancer: + servers: + - url: "http://flame:5005" + portainer: + # Portainer must share a network with Traefik. Either add the portainer + # container to reverseproxy-nw, or point this at the host IP instead: + # - url: "https://192.168.0.8:9443" (+ serversTransport insecureSkipVerify) + loadBalancer: + servers: + - url: "https://192.168.0.8:9443" + + middlewares: + dash-auth: + basicAuth: + # Generate: htpasswd -nbB admin 'yourpassword' (escape $ as $$ only in + # docker-compose labels — in this YAML file use the raw single-$ hash). + users: + - "admin:$2y$05$HjhBPjFYOxYTWS37DScedenZRiRZ.qbxMsf10XQVujzCljE9VbQfG" diff --git a/traefik/traefik.yml b/traefik/traefik.yml new file mode 100644 index 0000000..a042951 --- /dev/null +++ b/traefik/traefik.yml @@ -0,0 +1,49 @@ +# Traefik v3 static configuration (ОСК reverse proxy / SNI router) +# Mounted read-only at /etc/traefik/traefik.yml + +global: + checkNewVersion: false + sendAnonymousUsage: false + +log: + level: INFO +accessLog: {} + +api: + dashboard: true # exposed via dynamic/web.yml router (traefik.osk.team) with basic-auth + +entryPoints: + web: + address: ":80" + http: + redirections: + entryPoint: + to: websecure + scheme: https + websecure: + address: ":443" + # NOTE: TLS-passthrough TCP routers and TLS-terminating HTTP routers coexist + # on :443 — Traefik matches specific HostSNI(...) TCP routers first, and + # everything else falls through to the HTTP routers. + +providers: + # Docker labels (for local containers that opt in with traefik.enable=true) + docker: + exposedByDefault: false + network: reverseproxy-nw + # File provider = all the static routing (SNI passthrough + dashboards) + file: + directory: /etc/traefik/dynamic + watch: true + +# Let's Encrypt — only for hosts Traefik TERMINATES (dashboards). +# Passthrough hosts (workfolders/mail/kdcproxy) keep their own backend certs. +certificatesResolvers: + le: + acme: + email: gamroot@osk.team # <-- CHANGE to a real address + storage: /acme/acme.json + httpChallenge: + entryPoint: web + # For internal-only dashboards not reachable on :80 from the internet, + # switch to a dnsChallenge or a default self-signed cert instead.