Metainformationen zur Seite
Unterschiede
Hier werden die Unterschiede zwischen zwei Versionen angezeigt.
| Beide Seiten der vorigen RevisionVorhergehende ÜberarbeitungNächste Überarbeitung | Vorhergehende Überarbeitung | ||
| server:traefik [2025/09/12 10:32] – walbaum | server:traefik [2025/09/25 15:05] (aktuell) – walbaum | ||
|---|---|---|---|
| Zeile 7: | Zeile 7: | ||
| < | < | ||
| - | ├── traefik\\ | + | ├── traefik |
| - | │ ├── configfiles\\ | + | │ ├── configfiles |
| - | │ │ ├── | + | │ │ ├── config.yml |
| - | │ │ ├── | + | │ │ ├── middleware-chains.yml |
| - | │ │ ├── | + | │ │ ├── middlewares.yml |
| - | │ │ ├── | + | │ │ ├── tls-opts.yml |
| - | │ ├── | + | │ ├── docker-compose.yml |
| - | │ ├── .env\\ | + | │ ├── .env |
| - | │ ├── traefik.log\\ | + | │ ├── traefik.log |
| - | │ ├── | + | │ ├── traefik.yml |
| │ ├── access.log | │ ├── access.log | ||
| </ | </ | ||
| Zeile 42: | Zeile 42: | ||
| Damit der Docker.sock nicht nach außen exposed ist, ist zusammen mit Traefik ein docker-socket aufgesetzt, der von hier stammt: [[https:// | Damit der Docker.sock nicht nach außen exposed ist, ist zusammen mit Traefik ein docker-socket aufgesetzt, der von hier stammt: [[https:// | ||
| - | docker-compose.yml: | + | <codedoc toggle |
| - | <code> | + | |
| services: | services: | ||
| traefik: | traefik: | ||
| Zeile 151: | Zeile 150: | ||
| driver: bridge | driver: bridge | ||
| internal: true | internal: true | ||
| - | </code> | + | </codedoc> |
| - | + | \\ | |
| - | traefik.yml: | + | <codedoc toggle |
| - | <code> | + | |
| entryPoints: | entryPoints: | ||
| http: | http: | ||
| Zeile 212: | Zeile 210: | ||
| moduleName: " | moduleName: " | ||
| version: " | version: " | ||
| - | </ | + | </ |
| + | \\ | ||
| + | <codedoc toggle config.yml> | ||
| + | http: | ||
| + | routers: | ||
| + | webmin: | ||
| + | entryPoints: | ||
| + | - " | ||
| + | rule: Host(`webmin.smns-bw.org`) | ||
| + | service: webmin | ||
| + | middlewares: | ||
| + | - " | ||
| + | tls: | ||
| + | certResolver: | ||
| + | options: tls-opts | ||
| + | |||
| + | traefik: | ||
| + | rule: Host(`traefik.smns-bw.org`) | ||
| + | service: cookies | ||
| + | |||
| + | smns_stats: | ||
| + | rule: Host(`statistics.smns-bw.org`) | ||
| + | # rule: " | ||
| + | service: smns_stats | ||
| + | entryPoints: | ||
| + | - " | ||
| + | middlewares: | ||
| + | - " | ||
| + | tls: | ||
| + | certResolver: | ||
| + | options: tls-opts | ||
| + | |||
| + | services: | ||
| + | webmin: | ||
| + | loadBalancer: | ||
| + | servers: | ||
| + | - url: " | ||
| + | passHostHeader: | ||
| + | sticky: | ||
| + | cookie: | ||
| + | secure: true | ||
| + | httpOnly: true | ||
| + | sameSite: lax | ||
| + | |||
| + | smns_stats: | ||
| + | loadBalancer: | ||
| + | servers: | ||
| + | - url: " | ||
| + | passHostHeader: | ||
| + | |||
| + | cookies: | ||
| + | loadBalancer: | ||
| + | sticky: | ||
| + | cookie: | ||
| + | secure: true | ||
| + | httpOnly: true | ||
| + | sameSite: lax | ||
| + | </ | ||
| + | \\ | ||
| + | <codedoc toggle middleware-chains.yml> | ||
| + | http: | ||
| + | middlewares: | ||
| + | secure-global: | ||
| + | chain: | ||
| + | middlewares: | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | |||
| + | secure-traefik: | ||
| + | chain: | ||
| + | middlewares: | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | |||
| + | secure-idservice: | ||
| + | chain: | ||
| + | middlewares: | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | |||
| + | secure-biocase: | ||
| + | chain: | ||
| + | middlewares: | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | |||
| + | secure-webmin: | ||
| + | chain: | ||
| + | middlewares: | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | |||
| + | secure-prestashop: | ||
| + | chain: | ||
| + | middlewares: | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | |||
| + | secure-ent: | ||
| + | chain: | ||
| + | middlewares: | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | |||
| + | secure-collections: | ||
| + | chain: | ||
| + | middlewares: | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | |||
| + | secure-digiphyll: | ||
| + | chain: | ||
| + | middlewares: | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | |||
| + | secure-dokuwiki: | ||
| + | chain: | ||
| + | middlewares: | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | |||
| + | secure-geometroidea: | ||
| + | chain: | ||
| + | middlewares: | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | |||
| + | secure-h5p: | ||
| + | chain: | ||
| + | middlewares: | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | |||
| + | secure-awstats: | ||
| + | chain: | ||
| + | middlewares: | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | |||
| + | secure-librechat: | ||
| + | chain: | ||
| + | middlewares: | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | |||
| + | secure-pictures: | ||
| + | chain: | ||
| + | middlewares: | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | </ | ||
| + | \\ | ||
| + | <codedoc toggle middlewares.yml> | ||
| + | http: | ||
| + | middlewares: | ||
| + | default-whitelist: | ||
| + | ipWhiteList: | ||
| + | sourceRange: | ||
| + | - " | ||
| + | - " | ||
| + | |||
| + | traefik-auth: | ||
| + | basicAuth: | ||
| + | users: | ||
| + | - " | ||
| + | |||
| + | # Core security baseline used by most services | ||
| + | security-headers-base: | ||
| + | headers: & | ||
| + | addVaryHeader: | ||
| + | |||
| + | # Response header hardening | ||
| + | contentTypeNosniff: | ||
| + | browserXssFilter: | ||
| + | referrerPolicy: | ||
| + | permissionsPolicy: | ||
| + | |||
| + | # HSTS | ||
| + | forceSTSHeader: | ||
| + | stsSeconds: 31536000 | ||
| + | stsPreload: false | ||
| + | # Keep includeSubdomains off in base to avoid accidental lock-in; use variant below when desired. | ||
| + | # stsIncludeSubdomains: | ||
| + | |||
| + | # X-Frame-Options — deny by default (use CSP frame-ancestors for granular control) | ||
| + | frameDeny: true | ||
| + | # customFrameOptionsValue: | ||
| + | |||
| + | # TLS/Proxy awareness | ||
| + | sslRedirect: | ||
| + | sslProxyHeaders: | ||
| + | X-Forwarded-Proto: | ||
| + | hostsProxyHeaders: | ||
| + | - " | ||
| + | |||
| + | # Standard response scrubbing | ||
| + | customResponseHeaders: | ||
| + | Server: "" | ||
| + | X-Powered-By: | ||
| + | X-Robots-Tag: | ||
| + | |||
| + | # Upstream request adjustments (to your apps) | ||
| + | customRequestHeaders: | ||
| + | X-Forwarded-Proto: | ||
| + | |||
| + | # Variant: noindex | ||
| + | security-headers-noindex: | ||
| + | headers: | ||
| + | <<: *SEC_BASE | ||
| + | customResponseHeaders: | ||
| + | <<: *RESP_BASE | ||
| + | X-Robots-Tag: | ||
| + | |||
| + | # Variant: allow framing (prefer CSP frame-ancestors to control who can frame you) | ||
| + | security-headers-allowframes: | ||
| + | headers: | ||
| + | <<: *SEC_BASE | ||
| + | frameDeny: false | ||
| + | # customFrameOptionsValue is mostly ignored by modern browsers except SAMEORIGIN/ | ||
| + | # " | ||
| + | customFrameOptionsValue: | ||
| + | |||
| + | # Variant: HSTS include subdomains, longer max-age | ||
| + | security-headers-hsts-subdomains: | ||
| + | headers: | ||
| + | <<: *SEC_BASE | ||
| + | stsIncludeSubdomains: | ||
| + | stsSeconds: 63072000 | ||
| + | |||
| + | # Optional: shared Cache-Control for static-ish services | ||
| + | cache-public: | ||
| + | headers: | ||
| + | customResponseHeaders: | ||
| + | Cache-Control: | ||
| + | |||
| + | # CORS: compose these with the security headers as needed | ||
| + | cors-default: | ||
| + | headers: & | ||
| + | accessControlAllowMethods: | ||
| + | - GET | ||
| + | - OPTIONS | ||
| + | - PUT | ||
| + | accessControlAllowHeaders: | ||
| + | accessControlMaxAge: | ||
| + | addVaryHeader: | ||
| + | |||
| + | cors-collections: | ||
| + | headers: | ||
| + | <<: *CORS_DEFAULT | ||
| + | accessControlAllowOriginList: | ||
| + | - https:// | ||
| + | - https:// | ||
| + | |||
| + | # Base CSP used by services that only need same-origin + *.smns-bw.org | ||
| + | CSP-base: | ||
| + | headers: | ||
| + | contentSecurityPolicy: | ||
| + | default-src ' | ||
| + | frame-ancestors ' | ||
| + | frame-src *.smns-bw.org; | ||
| + | base-uri ' | ||
| + | form-action ' | ||
| + | img-src ' | ||
| + | connect-src ' | ||
| + | font-src ' | ||
| + | style-src ' | ||
| + | script-src ' | ||
| + | |||
| + | # If you still reference CSP-global anywhere, just point it to the base: | ||
| + | CSP-global: | ||
| + | headers: | ||
| + | contentSecurityPolicy: | ||
| + | |||
| + | # Deltas below (only where different from base) | ||
| + | |||
| + | CSP-dokuwiki: | ||
| + | headers: | ||
| + | contentSecurityPolicy: | ||
| + | default-src ' | ||
| + | frame-ancestors ' | ||
| + | frame-src *.smns-bw.org *.google.com; | ||
| + | base-uri ' | ||
| + | form-action ' | ||
| + | img-src ' | ||
| + | connect-src ' | ||
| + | font-src ' | ||
| + | style-src ' | ||
| + | script-src ' | ||
| + | |||
| + | CSP-idservice: | ||
| + | headers: | ||
| + | contentSecurityPolicy: | ||
| + | default-src ' | ||
| + | frame-ancestors ' | ||
| + | frame-src *.smns-bw.org; | ||
| + | base-uri ' | ||
| + | form-action ' | ||
| + | img-src ' | ||
| + | connect-src ' | ||
| + | font-src ' | ||
| + | style-src ' | ||
| + | script-src ' | ||
| + | |||
| + | CSP-librechat: | ||
| + | headers: | ||
| + | contentSecurityPolicy: | ||
| + | default-src ' | ||
| + | frame-ancestors ' | ||
| + | frame-src *.smns-bw.org; | ||
| + | base-uri ' | ||
| + | form-action ' | ||
| + | img-src ' | ||
| + | connect-src ' | ||
| + | font-src ' | ||
| + | style-src ' | ||
| + | script-src ' | ||
| + | |||
| + | # Fixed: removed invalid ' | ||
| + | CSP-awstats: | ||
| + | headers: | ||
| + | contentSecurityPolicy: | ||
| + | default-src ' | ||
| + | frame-ancestors ' | ||
| + | frame-src *.smns-bw.org; | ||
| + | base-uri ' | ||
| + | form-action ' | ||
| + | img-src ' | ||
| + | connect-src ' | ||
| + | font-src ' | ||
| + | style-src ' | ||
| + | style-src-elem https:// | ||
| + | script-src ' | ||
| + | script-src-elem https:// | ||
| + | |||
| + | CSP-h5p: | ||
| + | headers: | ||
| + | contentSecurityPolicy: | ||
| + | default-src ' | ||
| + | frame-ancestors https:// | ||
| + | frame-src *.smns-bw.org; | ||
| + | base-uri ' | ||
| + | form-action ' | ||
| + | img-src ' | ||
| + | connect-src ' | ||
| + | font-src ' | ||
| + | style-src ' | ||
| + | script-src ' | ||
| + | |||
| + | CSP-biocase: | ||
| + | headers: | ||
| + | contentSecurityPolicy: | ||
| + | default-src ' | ||
| + | frame-ancestors ' | ||
| + | frame-src *.smns-bw.org; | ||
| + | base-uri ' | ||
| + | form-action ' | ||
| + | img-src ' | ||
| + | connect-src ' | ||
| + | font-src ' | ||
| + | style-src ' | ||
| + | script-src ' | ||
| + | |||
| + | CSP-prestashop: | ||
| + | headers: | ||
| + | contentSecurityPolicy: | ||
| + | default-src ' | ||
| + | frame-ancestors ' | ||
| + | frame-src *.smns-bw.org; | ||
| + | base-uri ' | ||
| + | form-action ' | ||
| + | img-src ' | ||
| + | connect-src ' | ||
| + | font-src ' | ||
| + | style-src ' | ||
| + | style-src-elem https:// | ||
| + | script-src ' | ||
| + | |||
| + | # Base + broader font-src | ||
| + | CSP-webmin: | ||
| + | headers: | ||
| + | contentSecurityPolicy: | ||
| + | default-src ' | ||
| + | base-uri ' | ||
| + | img-src ' | ||
| + | font-src ' | ||
| + | style-src ' | ||
| + | script-src ' | ||
| + | |||
| + | # Fixed: stray " | ||
| + | CSP-collections: | ||
| + | headers: | ||
| + | contentSecurityPolicy: | ||
| + | default-src ' | ||
| + | frame-ancestors ' | ||
| + | frame-src *.smns-bw.org; | ||
| + | base-uri ' | ||
| + | form-action ' | ||
| + | style-src ' | ||
| + | connect-src ' | ||
| + | font-src ' | ||
| + | img-src ' | ||
| + | script-src ' | ||
| + | |||
| + | # Fixed: removed invalid ' | ||
| + | CSP-ent: | ||
| + | headers: | ||
| + | contentSecurityPolicy: | ||
| + | default-src ' | ||
| + | base-uri ' | ||
| + | form-action ' | ||
| + | style-src-elem https:// | ||
| + | connect-src ' | ||
| + | script-src-elem https:// | ||
| + | img-src ' | ||
| + | |||
| + | CSP-digiphyll: | ||
| + | headers: | ||
| + | contentSecurityPolicy: | ||
| + | default-src ' | ||
| + | frame-ancestors ' | ||
| + | frame-src *.smns-bw.org https:// | ||
| + | base-uri ' | ||
| + | form-action ' | ||
| + | img-src ' | ||
| + | connect-src ' | ||
| + | font-src ' | ||
| + | style-src ' | ||
| + | script-src ' | ||
| + | |||
| + | # Fixed: removed invalid ' | ||
| + | CSP-geometroidea: | ||
| + | headers: | ||
| + | contentSecurityPolicy: | ||
| + | default-src ' | ||
| + | style-src ' | ||
| + | font-src ' | ||
| + | img-src ' | ||
| + | |||
| + | my-traefik-plugin-cookie-path-prefix: | ||
| + | plugin: | ||
| + | traefik-plugin-cookie-path-prefix: | ||
| + | prefix: smns | ||
| + | |||
| + | webportal-errors: | ||
| + | errors: | ||
| + | status: | ||
| + | service: error-pages-webportal@docker | ||
| + | - " | ||
| + | query: "/ | ||
| + | |||
| + | # another-service-errors: | ||
| + | # errors: | ||
| + | # status: | ||
| + | # - " | ||
| + | # service: error-pages-another-service | ||
| + | # query: "/ | ||
| + | </ | ||
| + | \\ | ||
| + | <codedoc toggle tls-opts.yml> | ||
| + | tls: | ||
| + | options: | ||
| + | tls-opts: | ||
| + | minVersion: VersionTLS12 | ||
| + | cipherSuites: | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | curvePreferences: | ||
| + | - CurveP521 | ||
| + | - CurveP384 | ||
| + | # sniStrict: true | ||
| + | </ | ||
| + | |||
| + | ===== Traefik security headers and CSP (DRY config) ===== | ||
| + | |||
| + | Last updated: 25.09.2025 | ||
| + | |||
| + | ==== High‑level design ==== | ||
| + | |||
| + | * One baseline for security headers: security-headers-base | ||
| + | * Small header variants you can compose: -noindex, -allowframes, | ||
| + | * Separate CORS middlewares: | ||
| + | * One base CSP: CSP-base (+ per‑service CSP middlewares that only add what’s different) | ||
| + | * Chains compose the above into per‑service “one-liners” you reference from docker-compose labels. | ||
| + | |||
| + | ==== Security headers ==== | ||
| + | |||
| + | === Baseline (security-headers-base) === | ||
| + | |||
| + | Sets the shared hardening and sane defaults. Highlights: | ||
| + | |||
| + | * addVaryHeader: | ||
| + | * contentTypeNosniff: | ||
| + | * browserXssFilter: | ||
| + | * referrerPolicy: | ||
| + | * permissionsPolicy: | ||
| + | * HSTS: forceSTSHeader: | ||
| + | * X-Frame-Options: | ||
| + | * TLS/proxy awareness: sslRedirect: | ||
| + | * Scrub response headers (customResponseHeaders): | ||
| + | * Upstream request header: customRequestHeaders.X-Forwarded-Proto: | ||
| + | Rationale: | ||
| + | |||
| + | * Default deny framing (clickjacking defense); enable per service via -allowframes and control who via CSP. | ||
| + | * Keep includeSubdomains off by default to avoid accidental HSTS lock-in; use the variant when needed. | ||
| + | |||
| + | ==== Variants you can compose ==== | ||
| + | |||
| + | * security-headers-noindex | ||
| + | * Same as base, but X-Robots-Tag: | ||
| + | * security-headers-allowframes | ||
| + | * Same as base, but frameDeny: false (use CSP frame-ancestors to specify who can frame) | ||
| + | * security-headers-hsts-subdomains | ||
| + | * Same as base, but stsIncludeSubdomains: | ||
| + | * cache-public | ||
| + | * Adds Cache-Control: | ||
| + | |||
| + | ==== CORS middlewares ==== | ||
| + | |||
| + | * cors-default | ||
| + | * accessControlAllowMethods: | ||
| + | * accessControlAllowHeaders: | ||
| + | * accessControlMaxAge: | ||
| + | * addVaryHeader: | ||
| + | * cors-collections | ||
| + | * Inherits cors-default + accessControlAllowOriginList: | ||
| + | * https:// | ||
| + | * https:// | ||
| + | |||
| + | ==== Content Security Policy (CSP) ==== | ||
| + | |||
| + | === Base CSP (CSP-base) === | ||
| + | |||
| + | Applies to most services unchanged. | ||
| + | |||
| + | <code yaml> CSP-base: headers: contentSecurityPolicy: | ||
| + | |||
| + | Notes: | ||
| + | |||
| + | * Only one CSP header is honored. Don’t stack multiple CSP middlewares in a single chain; “last header wins.” | ||
| + | * Use Report-Only during testing by duplicating to contentSecurityPolicyReportOnly. | ||
| + | |||
| + | ==== How to add a new service ==== | ||
| + | |||
| + | * If defaults are enough: | ||
| + | * Use secure-global chain. | ||
| + | * If it needs framing: | ||
| + | * Use security-headers-allowframes in the chain and set CSP frame-ancestors to the exact allowlist. | ||
| + | * If it needs special CORS: | ||
| + | * Add or create a cors-< | ||
| + | * If it needs special CSP: | ||
| + | * Copy CSP-base to CSP-< | ||
| + | * Wire it in docker-compose: | ||
| + | * traefik.http.routers.< | ||
| + | ==== Verification (quick commands) ==== | ||
| + | |||
| + | * Check headers: | ||
| + | * curl -sI https://< | ||
| + | * Check CSP is present (and only once): | ||
| + | * curl -sI https://< | ||
| + | * Check CORS (preflight example): | ||
| + | * curl -sI -X OPTIONS https://< | ||
| + | * Check robots indexing: | ||
| + | * curl -sI https://< | ||
| + | |||
| + | ==== Operational notes ==== | ||
| + | |||
| + | * Prefer CSP frame-ancestors over X-Frame-Options for precise embedding control. | ||
| + | * Consider a Report-Only CSP during rollouts (duplicate middleware with contentSecurityPolicyReportOnly). | ||
| + | * HSTS includeSubdomains is opt-in via variant to avoid unintentional hard lock-in. | ||
| + | * If a page doesn’t render: check the browser console for CSP violations first; add only the specific host/type needed. | ||
| + | ==== Appendix: Anchor usage ==== | ||
| + | We used YAML anchors to stay DRY: | ||
| + | |||
| + | * & | ||
| + | * & | ||
| + | This keeps the source config compact while allowing targeted overrides. | ||
| ===== Traefik v3 Healthcheck (Docker) ===== | ===== Traefik v3 Healthcheck (Docker) ===== | ||
| Zeile 320: | Zeile 920: | ||
| * [[https:// | * [[https:// | ||
| - | Authored for SMNS IT by Chattie and AI Programmer | + | Authored for SMNS IT by Chattie and AI Programmer |