initial traefik layout

main
pipistrello 2026-06-05 14:21:01 +03:00
parent 57725520e4
commit 6e3b73f822
5 changed files with 247 additions and 0 deletions

73
traefik/README.md Normal file
View File

@ -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 `<https kdcproxy.osk.team />`
(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.

View File

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

View File

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

52
traefik/dynamic/web.yml Normal file
View File

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

49
traefik/traefik.yml Normal file
View File

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