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/06/03 15:16] – walbaum | server:traefik [2025/09/25 15:05] (aktuell) – walbaum | ||
|---|---|---|---|
| Zeile 1: | Zeile 1: | ||
| ====== Traefik ====== | ====== Traefik ====== | ||
| - | ; URL : [[https:// | + | ; URL : [[https:// |
| - | ; User : smns-tr | + | ; User : smns-tr |
| ; Passwort : < | ; Passwort : < | ||
| - | ; Production : hetzner:/ | + | ; Production : hetzner:/ |
| - | + | < | |
| - | ├── 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 | ||
| + | </ | ||
| In .env steht die URL traefik.smns-bw.org sowie die Zugangsdaten für diese Seite für docker-compose.yml | In .env steht die URL traefik.smns-bw.org sowie die Zugangsdaten für diese Seite für docker-compose.yml | ||
| In die einzelnen docker-compose.yml Files der Container kommt dann sowas (Beispiel Sammlungskatalog): | In die einzelnen docker-compose.yml Files der Container kommt dann sowas (Beispiel Sammlungskatalog): | ||
| - | |||
| < | < | ||
| + | |||
| labels: | labels: | ||
| - " | - " | ||
| Zeile 36: | Zeile 37: | ||
| - " | - " | ||
| - " | - " | ||
| + | |||
| </ | </ | ||
| - | 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: |
| + | <codedoc toggle docker-compose.yml> | ||
| + | services: | ||
| + | traefik: | ||
| + | container_name: | ||
| + | image: " | ||
| + | restart: always | ||
| + | # read_only: true | ||
| + | command: | ||
| + | - --configfile=/ | ||
| + | mem_limit: 2G | ||
| + | cpus: 0.75 | ||
| + | ports: | ||
| + | - " | ||
| + | - " | ||
| + | volumes: | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | # - " | ||
| + | # - " | ||
| + | - " | ||
| + | depends_on: | ||
| + | - " | ||
| + | security_opt: | ||
| + | - " | ||
| + | networks: | ||
| + | - " | ||
| + | - " | ||
| + | labels: | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | # API service | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | healthcheck: | ||
| + | test: [" | ||
| + | interval: 30s | ||
| + | timeout: 5s | ||
| + | retries: 3 | ||
| + | start_period: | ||
| + | |||
| + | dockerproxy: | ||
| + | build: | ||
| + | context: . | ||
| + | container_name: | ||
| + | command: | ||
| + | - ' | ||
| + | - ' | ||
| + | - ' | ||
| + | - ' | ||
| + | - ' | ||
| + | restart: unless-stopped | ||
| + | user: " | ||
| + | read_only: true | ||
| + | mem_limit: 64M | ||
| + | cap_drop: | ||
| + | - ALL | ||
| + | security_opt: | ||
| + | - no-new-privileges | ||
| + | volumes: | ||
| + | - / | ||
| + | networks: | ||
| + | - " | ||
| + | - " | ||
| + | healthcheck: | ||
| + | test: [ " | ||
| + | interval: 1m | ||
| + | timeout: 3s | ||
| + | retries: 3 | ||
| + | |||
| + | error-pages-webportal: | ||
| + | container_name: | ||
| + | image: nginx: | ||
| + | restart: always | ||
| + | volumes: | ||
| + | - ./ | ||
| + | - ./ | ||
| + | - ./ | ||
| + | labels: | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | # - " | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | - " | ||
| + | networks: | ||
| + | - " | ||
| + | healthcheck: | ||
| + | test: [" | ||
| + | interval: 30s | ||
| + | timeout: 5s | ||
| + | retries: 3 | ||
| + | start_period: | ||
| + | |||
| + | networks: | ||
| + | proxy: | ||
| + | external: true | ||
| + | docker-proxynet: | ||
| + | driver: bridge | ||
| + | internal: true | ||
| + | </ | ||
| + | \\ | ||
| + | <codedoc toggle traefik.yml> | ||
| + | entryPoints: | ||
| + | http: | ||
| + | address: ": | ||
| + | http: | ||
| + | redirections: | ||
| + | entryPoint: | ||
| + | to: ': | ||
| + | scheme: https | ||
| + | https: | ||
| + | address: ": | ||
| + | # http3: | ||
| + | # advertisedPort: | ||
| + | postgres: | ||
| + | address: ": | ||
| + | ssh: | ||
| + | address: ": | ||
| + | healthcheck: | ||
| + | address: ": | ||
| + | log: | ||
| + | level: INFO | ||
| + | filePath: "/ | ||
| + | format: json | ||
| + | |||
| + | accessLog: | ||
| + | filePath: "/ | ||
| + | bufferingSize: | ||
| + | |||
| + | api: {} | ||
| + | |||
| + | ping: | ||
| + | entryPoint: healthcheck | ||
| + | |||
| + | providers: | ||
| + | docker: | ||
| + | # endpoint: " | ||
| + | endpoint: " | ||
| + | # endpoint: " | ||
| + | watch: true | ||
| + | network: proxy | ||
| + | # exposedByDefault: | ||
| + | file: | ||
| + | directory: "/ | ||
| + | watch: true | ||
| + | |||
| + | certificatesResolvers: | ||
| + | leresolver: | ||
| + | acme: | ||
| + | email: " | ||
| + | storage: "/ | ||
| + | caServer: " | ||
| + | tlsChallenge: | ||
| + | |||
| + | experimental: | ||
| + | plugins: | ||
| + | traefik-plugin-cookie-path-prefix: | ||
| + | moduleName: " | ||
| + | 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 58: | Zeile 831: | ||
| === Step-by-Step Setup === | === Step-by-Step Setup === | ||
| - | == 1. Add a dedicated HTTP (non-TLS) entrypoint for health | + | == 1. Add a dedicated HTTP (non-TLS) entrypoint for health == |
| Add this to your '' | Add this to your '' | ||
| - | <code yaml> | + | <code yaml> |
| - | entryPoints: | + | entryPoints: |
| - | healthcheck: | + | healthcheck: |
| address: ": | address: ": | ||
| - | | + | |
| ping: | ping: | ||
| entryPoint: healthcheck | entryPoint: healthcheck | ||
| + | |||
| </ | </ | ||
| Zeile 74: | Zeile 848: | ||
| * Do not enable TLS or configure HTTP redirection for this entrypoint. | * Do not enable TLS or configure HTTP redirection for this entrypoint. | ||
| - | == 2. Update '' | + | == 2. Update '' |
| + | |||
| + | <code yaml> | ||
| + | healthcheck: | ||
| + | test: [ " | ||
| + | interval: 30s | ||
| + | timeout: 5s | ||
| + | retries: 3 | ||
| + | start_period: | ||
| - | <code yaml> | ||
| - | healthcheck: | ||
| - | test: [ " | ||
| - | interval: 30s | ||
| - | timeout: 5s | ||
| - | retries: 3 | ||
| - | start_period: | ||
| </ | </ | ||
| - | == 3. Recreate the container (!important) | + | == 3. Recreate the container (!important) == |
| After editing the healthcheck, | After editing the healthcheck, | ||
| - | <code bash> docker compose down docker compose up -d </ | + | <code bash> |
| + | docker compose down docker compose up -d | ||
| + | |||
| + | </ | ||
| + | |||
| + | or for just the traefik service: | ||
| + | |||
| + | <code bash> | ||
| + | docker compose rm traefik docker compose up -d traefik | ||
| + | |||
| + | </ | ||
| - | == 4. Confirm it's working | + | == 4. Confirm it's working == |
| * Check status with: | * Check status with: | ||
| - | | + | |
| + | <code bash> | ||
| + | docker inspect traefik | grep Health -A 10 | ||
| + | |||
| + | </ | ||
| * Look for: | * Look for: | ||
| - | | + | |
| - | * The test pointing at '' | + | * The test pointing at '' |
| * You can also exec into the container: | * You can also exec into the container: | ||
| - | | + | |
| + | <code bash> | ||
| + | wget --spider http:// | ||
| + | |||
| + | </ | ||
| * and expect “remote file exists” or HTTP 200. | * and expect “remote file exists” or HTTP 200. | ||
| Zeile 105: | Zeile 900: | ||
| * If you see ''' | * If you see ''' | ||
| - | | + | |
| - | * Logs for ping endpoint registration (grep -i ping < | + | * Logs for ping endpoint registration (grep -i ping < |
| - | * Healthcheck in the running container is updated (see '' | + | * Healthcheck in the running container is updated (see '' |
| * If the healthcheck is still using the old endpoint (e.g., port 443), the container must be removed and recreated. | * If the healthcheck is still using the old endpoint (e.g., port 443), the container must be removed and recreated. | ||
| Zeile 113: | Zeile 908: | ||
| * Q: Why not use /ping on :443? | * Q: Why not use /ping on :443? | ||
| - | | + | |
| * Q: Do I need to expose port 8082 externally? | * Q: Do I need to expose port 8082 externally? | ||
| - | | + | |
| * Q: Can I combine ping and redirect on the same entrypoint? | * Q: Can I combine ping and redirect on the same entrypoint? | ||
| - | | + | |
| === References === | === References === | ||
| Zeile 125: | Zeile 920: | ||
| * [[https:// | * [[https:// | ||
| - | Authored for SMNS IT by Chattie and AI Programmer | + | Authored for SMNS IT by Chattie and AI Programmer |
| + | |||