Metainformationen zur Seite
  •  

Unterschiede

Hier werden die Unterschiede zwischen zwei Versionen angezeigt.

Link zu dieser Vergleichsansicht

Beide Seiten der vorigen RevisionVorhergehende Überarbeitung
Nächste Überarbeitung
Vorhergehende Überarbeitung
server:traefik [2025/09/12 10:44] walbaumserver:traefik [2025/09/25 15:05] (aktuell) walbaum
Zeile 9: Zeile 9:
 ├── traefik ├── traefik
 │ ├── configfiles │ ├── configfiles
-│ │ ├── {{:server:config.yml|config.yml}}  +│ │ ├── config.yml 
-│ │ ├── {{:server:middleware-chains.yml|middleware-chains.yml}}  +│ │ ├── middleware-chains.yml 
-│ │ ├── {{:server:middlewares.yml|middlewares.yml}}  +│ │ ├── middlewares.yml 
-│ │ ├── {{:server:tls-opts.yml|tls-opts.yml}}+│ │ ├── tls-opts.yml
 │ ├── docker-compose.yml │ ├── docker-compose.yml
 │ ├── .env │ ├── .env
Zeile 211: Zeile 211:
       version: "v0.0.3"       version: "v0.0.3"
 </codedoc> </codedoc>
 +\\
 <codedoc toggle config.yml> <codedoc toggle config.yml>
 http: http:
Zeile 275: Zeile 275:
       chain:       chain:
         middlewares:         middlewares:
-          - "security-headers" +          - "security-headers-base" 
-          - "CSP-global"+          - "cors-default
 +          - "CSP-base"
  
     secure-traefik:     secure-traefik:
       chain:       chain:
         middlewares:         middlewares:
-          - "security-headers" +          - "traefik-auth"               # auth first, so errors still get headers/CSP 
-          - "CSP-global+          - "security-headers-base
-          - "traefik-auth"+          - "cors-default
 +          - "CSP-base"
  
     secure-idservice:     secure-idservice:
       chain:       chain:
         middlewares:         middlewares:
-          - "security-headers"+          - "security-headers-base" 
 +          - "cors-default"
           - "CSP-idservice"           - "CSP-idservice"
  
Zeile 294: Zeile 297:
       chain:       chain:
         middlewares:         middlewares:
-          - "security-headers"+          - "security-headers-base" 
 +          - "cors-default"
           - "CSP-biocase"           - "CSP-biocase"
  
Zeile 300: Zeile 304:
       chain:       chain:
         middlewares:         middlewares:
-          - "security-headers"+          - "security-headers-base" 
 +          - "cors-default"
           - "CSP-webmin"           - "CSP-webmin"
  
Zeile 306: Zeile 311:
       chain:       chain:
         middlewares:         middlewares:
-          - "security-headers"+          - "security-headers-base" 
 +          - "cors-default"
           - "CSP-prestashop"           - "CSP-prestashop"
  
Zeile 312: Zeile 318:
       chain:       chain:
         middlewares:         middlewares:
-          - "security-headers"+          - "security-headers-base" 
 +          - "cors-default"
           - "CSP-ent"           - "CSP-ent"
  
Zeile 318: Zeile 325:
       chain:       chain:
         middlewares:         middlewares:
-          - "security-headers-collection"+          - "webportal-errors"            # moved before headers so error pages get hardened 
 +          - "security-headers-hsts-subdomains" 
 +          - "security-headers-allowframes" 
 +          - "cors-collections"
           - "CSP-collections"           - "CSP-collections"
-          - "webportal-errors" 
  
     secure-digiphyll:     secure-digiphyll:
       chain:       chain:
         middlewares:         middlewares:
 +          - "security-headers-base"
           - "security-headers-allowframes"           - "security-headers-allowframes"
 +          - "cors-default"
           - "CSP-digiphyll"           - "CSP-digiphyll"
  
Zeile 331: Zeile 342:
       chain:       chain:
         middlewares:         middlewares:
-          - "security-headers-h5p"+          - "security-headers-hsts-subdomains" 
 +          - "security-headers-allowframes" 
 +          - "cors-default"
           - "CSP-dokuwiki"           - "CSP-dokuwiki"
  
Zeile 337: Zeile 350:
       chain:       chain:
         middlewares:         middlewares:
-          - "security-headers"+          - "security-headers-base" 
 +          - "cors-default"
           - "CSP-geometroidea"           - "CSP-geometroidea"
  
Zeile 343: Zeile 357:
       chain:       chain:
         middlewares:         middlewares:
 +          - "security-headers-base"
           - "security-headers-allowframes"           - "security-headers-allowframes"
 +          - "cors-default"
           - "CSP-h5p"           - "CSP-h5p"
  
Zeile 349: Zeile 365:
       chain:       chain:
         middlewares:         middlewares:
-          - "security-headers"+          - "security-headers-base" 
 +          - "cors-default"
           - "CSP-awstats"           - "CSP-awstats"
  
Zeile 355: Zeile 372:
       chain:       chain:
         middlewares:         middlewares:
-          - "security-headers-librechat"+          - "security-headers-noindex" 
 +          - "cors-default"
           - "CSP-librechat"           - "CSP-librechat"
  
Zeile 361: Zeile 379:
       chain:       chain:
         middlewares:         middlewares:
 +          - "security-headers-base"
           - "security-headers-allowframes"           - "security-headers-allowframes"
-          - "CSP-global"+          - "cors-default" 
 +          - "CSP-base"
 </codedoc> </codedoc>
 \\ \\
Zeile 371: Zeile 391:
       ipWhiteList:       ipWhiteList:
         sourceRange:         sourceRange:
-        - "127.0.0.1/32" +          - "127.0.0.1/32" 
-        - "172.19.0.0/24"+          - "172.19.0.0/24"
  
     traefik-auth:     traefik-auth:
Zeile 379: Zeile 399:
           - "smns-tr:$2y$10$mUF04Rf7852wnP5xGxewte1hW4X/LcEN2c7of5xPOEyzbAXGVrEAi"           - "smns-tr:$2y$10$mUF04Rf7852wnP5xGxewte1hW4X/LcEN2c7of5xPOEyzbAXGVrEAi"
  
 +    # Core security baseline used by most services
 +    security-headers-base:
 +      headers: &SEC_BASE
 +      addVaryHeader: true
 +
 +      # Response header hardening
 +      contentTypeNosniff: true
 +      browserXssFilter: true
 +      referrerPolicy: "same-origin"
 +      permissionsPolicy: fullscreen=(self "https://smns-bw.org"), geolocation=*, midi=(), camera=(), usb=(), magnetometer=(), accelerometer=(), vr=(), speaker=(), ambient-light-sensor=(), gyroscope=(), microphone=(), payment=()
 +
 +      # HSTS
 +      forceSTSHeader: true
 +      stsSeconds: 31536000
 +      stsPreload: false
 +      # Keep includeSubdomains off in base to avoid accidental lock-in; use variant below when desired.
 +      # stsIncludeSubdomains: false
 +
 +      # X-Frame-Options — deny by default (use CSP frame-ancestors for granular control)
 +      frameDeny: true
 +      # customFrameOptionsValue: "SAMEORIGIN"  # optional alternative if you want XFO too
 +
 +      # TLS/Proxy awareness
 +      sslRedirect: true                # FIX: belongs at headers root (not inside sslProxyHeaders)
 +      sslProxyHeaders:
 +        X-Forwarded-Proto: "https"
 +      hostsProxyHeaders:
 +        - "X-Forwarded-Host"
 +
 +      # Standard response scrubbing
 +      customResponseHeaders: &RESP_BASE
 +        Server: ""                     # FIX: unify case
 +        X-Powered-By: ""               # FIX: unify case
 +        X-Robots-Tag: "index, follow"  # override via -noindex variant
 +
 +      # Upstream request adjustments (to your apps)
 +      customRequestHeaders: &REQ_BASE
 +        X-Forwarded-Proto: "https"     # OK to keep here
 +
 +    # Variant: noindex
     security-headers-noindex:     security-headers-noindex:
       headers:       headers:
-        accessControlAllowMethods+      <<*SEC_BASE 
-          - GET +      customResponseHeaders
-          - OPTIONS +        <<*RESP_BASE 
-          - PUT +        X-Robots-Tag: "none,noarchive,nosnippet,notranslate,noimageindex"
-        accessControlMaxAge100 +
-        addVaryHeadertrue +
-        customResponseHeaders: +
-          X-Robots-Tag: "none,noarchive,nosnippet,notranslate,noimageindex" +
-          X-Forwarded-Proto: "https" +
-          Server: "" +
-          x-powered-by: "" +
-        customRequestHeaders: +
-          X-Forwarded-Proto: "https" +
-          Cache-Control: public +
-          customFrameOptionsValue: SAMEORIGIN +
-        sslProxyHeaders: +
-          X-Forwarded-Proto: "https" +
-          sslRedirect: true +
-        referrerPolicy: "same-origin" +
-        hostsProxyHeaders: +
-          - "X-Forwarded-Host" +
-        contentTypeNosniff: true +
-        browserXssFilter: true +
-        forceSTSHeader: true +
-        stsSeconds: 31536000 +
-        stsPreload: false +
-        permissionsPolicy: fullscreen=(self "https://smns-bw.org"), geolocation=*, midi=(), camera=(), usb=(), magnetometer=(), accelerometer=(), vr=(), speaker=(), ambient-light-sensor=(), gyroscope=(), microphone=(), payment=() +
-        frameDeny: true+
  
-    security-headers:+    # Variant: allow framing (prefer CSP frame-ancestors to control who can frame you) 
 +    security-headers-allowframes:
       headers:       headers:
-        accessControlMaxAge100 +      <<: *SEC_BASE 
-        addVaryHeader: true +      frameDenyfalse 
-        customResponseHeaders: +      # customFrameOptionsValue is mostly ignored by modern browsers except SAMEORIGIN/DENY
-          X-Robots-Tag: "index, follow" +      # "ALLOW-FROMis deprecated; rely on CSP if you need granular control. 
-          X-Forwarded-Proto: "https" +      customFrameOptionsValue: "SAMEORIGIN"
-          Server: "" +
-          accessControlAllowMethods: +
-            - GET +
-            - OPTIONS +
-            - PUT +
-          accessControlAllowHeaders: "*" +
-          X-Powered-By"" +
-        customRequestHeaders: +
-          X-Forwarded-Proto: "https" +
-          Cache-Control: public +
-          customFrameOptionsValue: "ALLOW-FROM https://smns-bw.org" +
-        sslProxyHeaders: +
-          X-Forwarded-Proto: "https" +
-          sslRedirect: true +
-        referrerPolicy: "same-origin+
-        hostsProxyHeaders: +
-          - "X-Forwarded-Host" +
-        contentTypeNosniff: true +
-        browserXssFilter: true +
-        forceSTSHeader: true +
-        stsSeconds: 31536000 +
-        stsPreload: false +
-        permissionsPolicy: fullscreen=(self "https://smns-bw.org"), geolocation=*, midi=(), camera=(), usb=(), magnetometer=(), accelerometer=(), vr=(), speaker=(), ambient-light-sensor=(), gyroscope=(), microphone=(), payment=() +
-        frameDeny: true+
  
-    security-headers-allowframes:+    # Variant: HSTS include subdomains, longer max-age 
 +    security-headers-hsts-subdomains:
       headers:       headers:
-        accessControlMaxAge100 +      <<*SEC_BASE 
-        addVaryHeader: true +      stsIncludeSubdomains: true 
-        customResponseHeaders: +      stsSeconds: 63072000
-          X-Robots-Tag: "index, follow" +
-          accessControlAllowMethods: +
-            - GET +
-            - OPTIONS +
-            - PUT +
-          X-Forwarded-Proto: "https" +
-          Server: "" +
-          X-Powered-By: "" +
-        customRequestHeaders: +
-          X-Forwarded-Proto: "https" +
-          Cache-Control: public +
-          CustomFrameOptionsValue: "ALLOW-FROM https://smns-bw.org" +
-        sslProxyHeaders: +
-          X-Forwarded-Proto: "https" +
-          sslRedirect: true +
-        referrerPolicy: "same-origin" +
-        hostsProxyHeaders: +
-          - "X-Forwarded-Host" +
-        contentTypeNosniff: true +
-        browserXssFilter: true +
-        forceSTSHeader: true +
-        stsIncludeSubdomains: true +
-        stsSeconds: 63072000 +
-        stsPreload: false +
-        permissionsPolicy: fullscreen=(self "https://smns-bw.org"), geolocation=*, midi=(), camera=(), usb=(), magnetometer=(), accelerometer=(), vr=(), speaker=(), ambient-light-sensor=(), gyroscope=(), microphone=(), payment=()+
  
-    security-headers-collection:+    # Optional: shared Cache-Control for static-ish services 
 +    cache-public:
       headers:       headers:
-        addVaryHeader: true +      customResponseHeaders: 
-        customResponseHeaders: +        Cache-Control: "public"         # FIXwas (incorrectlyin customRequestHeaders
-          X-Robots-Tag: "index, follow" +
-          X-Forwarded-Proto: "https" +
-          Server: "" +
-          accessControlAllowMethods: +
-            - GET +
-            - OPTIONS +
-            - PUT +
-          accessControlAllowHeaders: "*" +
-          accessControlAllowOriginList: +
-            - https://pydeepzoom.smns-bw.org +
-            - https://pictures.smns-bw.org +
-          accessControlMaxAge: 100 +
-#          customFrameOptionsValue: "ALLOW-FROM https://pictures.smns-bw.org" +
-        customRequestHeaders: +
-          X-Forwarded-Proto: "https" +
-          Cache-Control: public +
-        sslProxyHeaders: +
-          X-Forwarded-Proto: "https" +
-          sslRedirect: true +
-        referrerPolicy: "same-origin" +
-        hostsProxyHeaders: +
-          - "X-Forwarded-Host" +
-        contentTypeNosniff: true +
-        browserXssFilter: true +
-        forceSTSHeader: true +
-        stsIncludeSubdomains: true +
-        stsSeconds: 63072000 +
-        stsPreload: false +
-        permissionsPolicyfullscreen=(self "https://smns-bw.org"), geolocation=*, midi=(), camera=(), usb=(), magnetometer=(), accelerometer=(), vr=(), speaker=(), ambient-light-sensor=(), gyroscope=(), microphone=(), payment=() +
-#        x-frame-options: ALLOW-FROM https://pictures.smns-bw.org+
  
-    security-headers-librechat:+    # CORS: compose these with the security headers as needed 
 +    cors-default: 
 +      headers: &CORS_DEFAULT 
 +      accessControlAllowMethods: 
 +        - GET 
 +        - OPTIONS 
 +        - PUT 
 +      accessControlAllowHeaders: "*" 
 +      accessControlMaxAge: 100 
 +      addVaryHeader: true 
 + 
 +    cors-collections:
       headers:       headers:
-        accessControlAllowMethods+      <<*CORS_DEFAULT 
-          - GET +      accessControlAllowOriginList
-          - OPTIONS +        - https://pydeepzoom.smns-bw.org 
-          - PUT +        - https://pictures.smns-bw.org
-        accessControlMaxAge100 +
-        addVaryHeader: true +
-        customResponseHeaders: +
-          X-Robots-Tag: "none,noarchive,nosnippet,notranslate,noimageindex" +
-          X-Forwarded-Proto: "https+
-          server"" +
-          x-powered-by: "" +
-        customRequestHeaders: +
-          X-Forwarded-Proto: "https" +
-          Cache-Control: public +
-          customFrameOptionsValue: SAMEORIGIN +
-        sslProxyHeaders: +
-          X-Forwarded-Proto: "https" +
-          sslRedirect: true +
-        referrerPolicy: "same-origin" +
-        hostsProxyHeaders: +
-          - "X-Forwarded-Host" +
-        contentTypeNosniff: true +
-        browserXssFilter: true +
-        forceSTSHeader: true +
-#        stsIncludeSubdomains: true +
-#        stsSeconds: 63072000 +
-        stsPreload: false +
-        permissionsPolicy: fullscreen=(self "https://smns-bw.org"), geolocation=*, midi=(), camera=(), usb=(), magnetometer=(), accelerometer=(), vr=(), ambient-light-sensor=(), gyroscope=(), payment=() +
-        frameDeny: true+
  
-    security-headers-h5p:+    # Base CSP used by services that only need same-origin + *.smns-bw.org 
 +    CSP-base:
       headers:       headers:
-        accessControlAllowMethods: +      contentSecurityPolicy&CSP_BASE > 
-          - GET +        default-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org; 
-          - OPTIONS +        frame-ancestors 'self' *.smns-bw.org; 
-          - PUT +        frame-src *.smns-bw.org; 
-        accessControlMaxAge: 100 +        base-uri 'self'; 
-        addVaryHeader: true +        form-action 'self'; 
-        customResponseHeaders: +        img-src 'self' data:; 
-          X-Robots-Tag: "none,noarchive,nosnippet,notranslate,noimageindex" +        connect-src 'self'; 
-          X-Forwarded-Proto: "https" +        font-src 'self'; 
-          server: "" +        style-src 'self' 'unsafe-inline'; 
-        customRequestHeaders: +        script-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org;
-          X-Forwarded-Proto: "https" +
-          Cache-Control: public +
-          customFrameOptionsValue: SAMEORIGIN +
-        sslProxyHeaders: +
-          X-Forwarded-Proto: "https" +
-          sslRedirect: true +
-        referrerPolicy: "same-origin" +
-        hostsProxyHeaders: +
-          "X-Forwarded-Host" +
-        contentTypeNosnifftrue +
-        browserXssFilter: true +
-        forceSTSHeader: true +
-        stsIncludeSubdomains: true +
-        stsSeconds: 63072000 +
-        stsPreload: false +
-        permissionsPolicy: fullscreen=(self "https://smns-bw.org"), geolocation=*, midi=(), camera=(), usb=(), magnetometer=(), accelerometer=(), vr=(), speaker=(), ambient-light-sensor=(), gyroscope=(), microphone=(), payment=() +
-        frameDeny: false+
  
 +    # If you still reference CSP-global anywhere, just point it to the base:
     CSP-global:     CSP-global:
       headers:       headers:
-        contentSecurityPolicy: +      contentSecurityPolicy: *CSP_BASE 
-          default-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org; frame-ancestors 'self' *.smns-bw.org; + 
-          frame-src *.smns-bw.org; base-uri 'self'; +    # Deltas below (only where different from base)
-          form-action 'self'; img-src 'self' data:; +
-          connect-src 'self'; font-src 'self'; +
-          style-src 'self' 'unsafe-inline'; +
-          script-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org;+
  
     CSP-dokuwiki:     CSP-dokuwiki:
       headers:       headers:
-        contentSecurityPolicy:+      contentSecurityPolicy:
-          default-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org; +        default-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org; 
-          frame-ancestors 'self' *.smns-bw.org; +        frame-ancestors 'self' *.smns-bw.org; 
-          frame-src *.smns-bw.org *.google.com; +        frame-src *.smns-bw.org *.google.com; 
-          base-uri 'self'; +        base-uri 'self'; 
-          form-action 'self'; +        form-action 'self'; 
-          img-src 'self' https://www.dokuwiki.org https://www.gravatar.com data:; +        img-src 'self' https://www.dokuwiki.org https://www.gravatar.com data:; 
-          connect-src 'self'; +        connect-src 'self'; 
-          font-src 'self'; +        font-src 'self'; 
-          style-src 'self' 'unsafe-inline'; +        style-src 'self' 'unsafe-inline'; 
-          script-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org;+        script-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org;
  
     CSP-idservice:     CSP-idservice:
       headers:       headers:
-        contentSecurityPolicy:+      contentSecurityPolicy:
-          default-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org; +        default-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org; 
-          frame-ancestors 'self' *.smns-bw.org; frame-src *.smns-bw.org; +        frame-ancestors 'self' *.smns-bw.org; 
-          base-uri 'self'; +        frame-src *.smns-bw.org; 
-          form-action 'self'; +        base-uri 'self'; 
-          img-src 'self' https://physalia.evolution.uni-bonn.de data:; +        form-action 'self'; 
-          connect-src 'self'; +        img-src 'self' https://physalia.evolution.uni-bonn.de data:; 
-          font-src 'self'; +        connect-src 'self'; 
-          style-src 'self' 'unsafe-inline'; +        font-src 'self'; 
-          script-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org https://physalia.evolution.uni-bonn.de;+        style-src 'self' 'unsafe-inline'; 
 +        script-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org https://physalia.evolution.uni-bonn.de;
  
     CSP-librechat:     CSP-librechat:
       headers:       headers:
-        contentSecurityPolicy:+      contentSecurityPolicy:
-          default-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org; +        default-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org; 
-          frame-ancestors 'self' *.smns-bw.org; +        frame-ancestors 'self' *.smns-bw.org; 
-          frame-src *.smns-bw.org; +        frame-src *.smns-bw.org; 
-          base-uri 'self'; +        base-uri 'self'; 
-          form-action 'self'; +        form-action 'self'; 
-          img-src 'self' blob: data:; +        img-src 'self' blob: data:; 
-          connect-src 'self'; +        connect-src 'self'; 
-          font-src 'self'; +        font-src 'self'; 
-          style-src 'self' 'unsafe-inline'; +        style-src 'self' 'unsafe-inline'; 
-          script-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org;+        script-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org;
  
 +    # Fixed: removed invalid 'unsafe-inline' and 'unsafe-eval' from -elem directives
     CSP-awstats:     CSP-awstats:
       headers:       headers:
-        contentSecurityPolicy:+      contentSecurityPolicy:
-          default-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org; +        default-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org; 
-          frame-ancestors 'self' *.smns-bw.org; +        frame-ancestors 'self' *.smns-bw.org; 
-          frame-src *.smns-bw.org; +        frame-src *.smns-bw.org; 
-          base-uri 'self'; +        base-uri 'self'; 
-          form-action 'self'; +        form-action 'self'; 
-          img-src 'self' https://chart.googleapis.com; +        img-src 'self' https://chart.googleapis.com; 
-          connect-src 'self' https://www.gstatic.com/charts/; +        connect-src 'self' https://www.gstatic.com/charts/; 
-          font-src 'self'; +        font-src 'self'; 
-          style-src 'self' 'unsafe-inline'; +        style-src 'self' 'unsafe-inline'; 
-          style-src-elem https://www.gstatic.com/charts/ 'unsafe-inline'+        style-src-elem https://www.gstatic.com/charts/; 
-          script-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org; +        script-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org; 
-          script-src-elem https://www.google.com/jsapi https://www.gstatic.com/charts/ 'unsafe-inline' 'unsafe-eval';+        script-src-elem https://www.google.com/jsapi https://www.gstatic.com/charts/;
  
     CSP-h5p:     CSP-h5p:
       headers:       headers:
-        contentSecurityPolicy:+      contentSecurityPolicy:
-          default-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org; +        default-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org; 
-          frame-ancestors https://www.naturkundemuseum-bw.de; +        frame-ancestors https://www.naturkundemuseum-bw.de; 
-          frame-src *.smns-bw.org; +        frame-src *.smns-bw.org; 
-          base-uri 'self'; +        base-uri 'self'; 
-          form-action 'self'; +        form-action 'self'; 
-          img-src 'self' data:; +        img-src 'self' data:; 
-          connect-src 'self'; +        connect-src 'self'; 
-          font-src 'self'; +        font-src 'self'; 
-          style-src 'self' 'unsafe-inline'; +        style-src 'self' 'unsafe-inline'; 
-          script-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org;+        script-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org;
  
     CSP-biocase:     CSP-biocase:
       headers:       headers:
-        contentSecurityPolicy:+      contentSecurityPolicy:
-          default-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org; +        default-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org; 
-          frame-ancestors 'self' *.smns-bw.org; +        frame-ancestors 'self' *.smns-bw.org; 
-          frame-src *.smns-bw.org; +        frame-src *.smns-bw.org; 
-          base-uri 'self'; +        base-uri 'self'; 
-          form-action 'self'; +        form-action 'self'; 
-          img-src 'self' https://unpkg.com/ https://*.tile.osm.org data:; +        img-src 'self' https://unpkg.com/ https://*.tile.osm.org data:; 
-          connect-src 'self'; +        connect-src 'self'; 
-          font-src 'self'; +        font-src 'self'; 
-          style-src 'self' 'unsafe-inline' https://unpkg.com/leaflet@1.3.3/dist/leaflet.css; +        style-src 'self' 'unsafe-inline' https://unpkg.com/leaflet@1.3.3/dist/leaflet.css; 
-          script-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org https://unpkg.com/leaflet@1.3.3/dist/leaflet.js https://code.jquery.com/jquery-1.7.min.js;+        script-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org https://unpkg.com/leaflet@1.3.3/dist/leaflet.js https://code.jquery.com/jquery-1.7.min.js;
  
     CSP-prestashop:     CSP-prestashop:
       headers:       headers:
-        contentSecurityPolicy:+      contentSecurityPolicy:
-          default-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org; +        default-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org; 
-          frame-ancestors 'self' *.smns-bw.org; +        frame-ancestors 'self' *.smns-bw.org; 
-          frame-src *.smns-bw.org; +        frame-src *.smns-bw.org; 
-          base-uri 'self'; +        base-uri 'self'; 
-          form-action 'self'; +        form-action 'self'; 
-          img-src 'self' data:; +        img-src 'self' data:; 
-          connect-src 'self'; +        connect-src 'self'; 
-          font-src 'self'; +        font-src 'self'; 
-          style-src 'self' 'unsafe-inline'; +        style-src 'self' 'unsafe-inline'; 
-          style-src-elem https://market.smns-bw.org/+        style-src-elem https://market.smns-bw.org; 
-          script-src 'self' 'unsafe-inline' 'unsafe-eval' https://market.smns-bw.org/;+        script-src 'self' 'unsafe-inline' 'unsafe-eval' https://market.smns-bw.org;
  
 +    # Base + broader font-src
     CSP-webmin:     CSP-webmin:
       headers:       headers:
-        contentSecurityPolicy:+      contentSecurityPolicy:
-          default-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org; +        default-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org; 
-          img-src 'self' data:; +        base-uri 'self'; 
-          font-src 'self' *.smns-bw.org data:;+        img-src 'self' data:; 
 +        font-src 'self' *.smns-bw.org data:
 +        style-src 'self' 'unsafe-inline'; 
 +        script-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org;
  
 +    # Fixed: stray "data" token removed, keep single "data:" at end
     CSP-collections:     CSP-collections:
       headers:       headers:
-        contentSecurityPolicy:+      contentSecurityPolicy:
-          default-src 'none'; +        default-src 'none'; 
-          frame-ancestors 'self' *.smns-bw.org pictures.smns-bw.org; +        frame-ancestors 'self' *.smns-bw.org pictures.smns-bw.org; 
-          frame-src *.smns-bw.org; +        frame-src *.smns-bw.org; 
-          base-uri 'self'; +        base-uri 'self'; 
-          form-action 'self'; +        form-action 'self'; 
-          style-src 'self' 'unsafe-inline' *.smns-bw.org https://cloud.ccm19.de; +        style-src 'self' 'unsafe-inline' *.smns-bw.org https://cloud.ccm19.de; 
-          connect-src 'self' https://cloud.ccm19.de https://matomo.naturkundemuseum-bw.de/; +        connect-src 'self' https://cloud.ccm19.de https://matomo.naturkundemuseum-bw.de/; 
-          font-src 'self' *.smns-bw.org; +        font-src 'self' *.smns-bw.org; 
-          img-src 'self' data *.smns-bw.org https://a.tile.openstreetmap.org https://b.tile.openstreetmap.org https://c.tile.openstreetmap.org https://cloud.ccm19.de data:; +        img-src 'self' *.smns-bw.org https://a.tile.openstreetmap.org https://b.tile.openstreetmap.org https://c.tile.openstreetmap.org https://cloud.ccm19.de data:; 
-          script-src 'self' 'unsafe-inline' 'unsafe-eval' https://pydeepzoom.smns-bw.org https://matomo.naturkundemuseum-bw.de https://cloud.ccm19.de;+        script-src 'self' 'unsafe-inline' 'unsafe-eval' https://pydeepzoom.smns-bw.org https://matomo.naturkundemuseum-bw.de https://cloud.ccm19.de;
  
 +    # Fixed: removed invalid 'unsafe-inline' from connect-src and from script/style -elem
     CSP-ent:     CSP-ent:
       headers:       headers:
-        contentSecurityPolicy:+      contentSecurityPolicy:
-          default-src 'none'; +        default-src 'none'; 
-          base-uri 'self'; +        base-uri 'self'; 
-          form-action 'self'; +        form-action 'self'; 
-          style-src-elem https://ent.smns-bw.org/static/css/ 'unsafe-inline'+        style-src-elem https://ent.smns-bw.org/static/css/; 
-          connect-src 'self' 'unsafe-inline' https://matomo.naturkundemuseum-bw.de; +        connect-src 'self' https://matomo.naturkundemuseum-bw.de; 
-          script-src-elem 'self' 'unsafe-inline' 'unsafe-eval' https://matomo.naturkundemuseum-bw.de https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js https://openseadragon.github.io/openseadragon/openseadragon.js; +        script-src-elem https://matomo.naturkundemuseum-bw.de https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js https://openseadragon.github.io/openseadragon/openseadragon.js; 
-          img-src 'self' https://openseadragon.github.io/openseadragon/images/;+        img-src 'self' https://openseadragon.github.io/openseadragon/images/;
  
     CSP-digiphyll:     CSP-digiphyll:
       headers:       headers:
-        contentSecurityPolicy:+      contentSecurityPolicy:
-          default-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org; +        default-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org; 
-          frame-ancestors 'self' *.smns-bw.org; +        frame-ancestors 'self' *.smns-bw.org; 
-          frame-src *.smns-bw.org https://144.41.33.40/; +        frame-src *.smns-bw.org https://144.41.33.40/; 
-          base-uri 'self'; +        base-uri 'self'; 
-          form-action 'self'; +        form-action 'self'; 
-          img-src 'self' https://a.tile.openstreetmap.org https://b.tile.openstreetmap.org https://c.tile.openstreetmap.org https://unpkg.com/ data:; +        img-src 'self' https://a.tile.openstreetmap.org https://b.tile.openstreetmap.org https://c.tile.openstreetmap.org https://unpkg.com/ data:; 
-          connect-src 'self'; +        connect-src 'self'; 
-          font-src 'self' data:; +        font-src 'self' data:; 
-          style-src 'self' 'unsafe-inline' data:; +        style-src 'self' 'unsafe-inline' data:; 
-          script-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org https://mathjax.rstudio.com/latest/ http://144.41.33.40/ data:;+        script-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org https://mathjax.rstudio.com/latest/ http://144.41.33.40/ data:;
  
 +    # Fixed: removed invalid 'unsafe-eval' from style-src
     CSP-geometroidea:     CSP-geometroidea:
       headers:       headers:
-        contentSecurityPolicy:+      contentSecurityPolicy:
-          default-src 'self'; +        default-src 'self'; 
-          style-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css https://fonts.googleapis.com/css2; +        style-src 'self' 'unsafe-inline' *.smns-bw.org https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css https://fonts.googleapis.com/css2; 
-          font-src 'self' https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/ https://fonts.gstatic.com/s/nunito/v25/; +        font-src 'self' https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/webfonts/ https://fonts.gstatic.com/s/nunito/v25/; 
-          img-src 'self' data:;+        img-src 'self' data:;
  
     my-traefik-plugin-cookie-path-prefix:     my-traefik-plugin-cookie-path-prefix:
Zeile 743: Zeile 679:
       errors:       errors:
         status:         status:
-          - "404-503" +          service: error-pages-webportal@docker 
-        service: error-pages-webportal@docker +            - "404-503" 
-        query: "/{status}.html"+          query: "/{status}.html"
  
 #    another-service-errors: #    another-service-errors:
Zeile 772: Zeile 708:
 #      sniStrict: true #      sniStrict: true
 </codedoc> </codedoc>
 +
 +===== 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, -hsts-subdomains, cache-public
 +  * Separate CORS middlewares: cors-default, cors-collections
 +  * 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: true
 +  * contentTypeNosniff: true
 +  * browserXssFilter: true
 +  * referrerPolicy: same-origin
 +  * permissionsPolicy: fullscreen=(self "https://smns-bw.org"), geolocation=*, midi=(), camera=(), usb=(), magnetometer=(), accelerometer=(), vr=(), speaker=(), ambient-light-sensor=(), gyroscope=(), microphone=(), payment=()
 +  * HSTS: forceSTSHeader: true, stsSeconds: 31536000, stsPreload: false
 +  * X-Frame-Options: frameDeny: true by default (prefer CSP frame-ancestors for allowlists)
 +  * TLS/proxy awareness: sslRedirect: true; sslProxyHeaders.X-Forwarded-Proto: https
 +  * Scrub response headers (customResponseHeaders): Server: "", X-Powered-By: "", X-Robots-Tag: "index, follow"
 +  * Upstream request header: customRequestHeaders.X-Forwarded-Proto: https
 +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: "none,noarchive,nosnippet,notranslate,noimageindex"
 +  * 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: true, stsSeconds: 63072000
 +  * cache-public
 +    * Adds Cache-Control: public to customResponseHeaders
 +
 +==== CORS middlewares ====
 +
 +  * cors-default
 +    * accessControlAllowMethods: GET, OPTIONS, PUT
 +    * accessControlAllowHeaders: "*"
 +    * accessControlMaxAge: 100
 +    * addVaryHeader: true
 +  * cors-collections
 +    * Inherits cors-default + accessControlAllowOriginList:
 +      * https://pydeepzoom.smns-bw.org
 +      * https://pictures.smns-bw.org
 +
 +==== Content Security Policy (CSP) ====
 +
 +=== Base CSP (CSP-base) ===
 +
 +Applies to most services unchanged.
 +
 +<code yaml> CSP-base: headers: contentSecurityPolicy: &CSP_BASE > default-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org; frame-ancestors 'self' *.smns-bw.org; frame-src *.smns-bw.org; base-uri 'self'; form-action 'self'; img-src 'self' data:; connect-src 'self'; font-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' 'unsafe-eval' *.smns-bw.org; </code>
 +
 +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-<service> middleware and include it in the chain.
 +  * If it needs special CSP:
 +    * Copy CSP-base to CSP-<service> and append only the minimal deltas (new hosts/tokens).
 +  * Wire it in docker-compose:
 +    * traefik.http.routers.<name>.middlewares=secure-<name>@file
 +==== Verification (quick commands) ====
 +
 +  * Check headers:
 +    * curl -sI https://<host> | egrep -i "strict-transport-security|x-frame-options|x-content-type-options|referrer-policy|permissions-policy|x-robots-tag"
 +    * Check CSP is present (and only once):
 +    * curl -sI https://<host> | grep -i "content-security-policy"
 +  * Check CORS (preflight example):
 +    * curl -sI -X OPTIONS https://<host>/ -H "Origin: https://example.com" -H "Access-Control-Request-Method: GET"
 +  * Check robots indexing:
 +    * curl -sI https://<host>/ | grep -i x-robots-tag
 +
 +==== 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:
 +
 +  * &SEC_BASE / *SEC_BASE for baseline headers.
 +  * &CSP_BASE / *CSP_BASE for base CSP string.
 +This keeps the source config compact while allowing targeted overrides.
  
 ===== Traefik v3 Healthcheck (Docker) ===== ===== Traefik v3 Healthcheck (Docker) =====
Zeile 879: Zeile 920:
   * [[https://docs.docker.com/reference/compose-file/services/#healthcheck|Docker Compose healthcheck docs]]   * [[https://docs.docker.com/reference/compose-file/services/#healthcheck|Docker Compose healthcheck docs]]
  
-Authored for SMNS IT by Chattie and AI Programmer — [[:server:date?media=server:date|]]+Authored for SMNS IT by Chattie and AI Programmer