summary refs log tree commit diff stats
path: root/system
diff options
context:
space:
mode:
authorAlan Pearce2024-05-30 21:21:39 +0200
committerAlan Pearce2024-05-30 21:21:39 +0200
commit1c35520b56476202005cc07a026e56c184695749 (patch)
tree1e4c4e79b1f4d41135df6833a60c237beaa164ce /system
parente017d0070f5c2d25e708978d63cc7d89dc0bfd81 (diff)
downloadnixfiles-1c35520b56476202005cc07a026e56c184695749.tar.lz
nixfiles-1c35520b56476202005cc07a026e56c184695749.tar.zst
nixfiles-1c35520b56476202005cc07a026e56c184695749.zip
linde: add HTTP security headers to caddy virtual hosts
Diffstat (limited to 'system')
-rw-r--r--system/linde.nix382
1 files changed, 228 insertions, 154 deletions
diff --git a/system/linde.nix b/system/linde.nix
index f7ae9f9c..a55abb06 100644
--- a/system/linde.nix
+++ b/system/linde.nix
@@ -628,184 +628,258 @@ in
       auto_https disable_certs
       default_bind ${net-ip6} ${net-ip4}
     '';
-    virtualHosts = {
-      "http://" = {
-        # Needed for HTTP->HTTPS servers
-      };
-      "${hostname}.alanpearce.eu" = {
-        serverAliases = [ "https://" ];
-        useACMEHost = "alanpearce.eu";
-        extraConfig = ''
-          respond * 204
-        '';
-      };
-      "pdns.alanpearce.eu" = {
-        useACMEHost = "alanpearce.eu";
-        extraConfig = ''
-          log {
-            output discard
+    virtualHosts =
+      let
+        subValue = v:
+          if builtins.isList v
+          then
+            builtins.concatStringsSep " "
+              (builtins.map
+                (v:
+                  (if lib.strings.hasPrefix "http" v
+                  then v
+                  else "'${v}'"))
+                v)
+          else toString v;
+
+        headerValue = sep: val:
+          if builtins.isAttrs val
+          then
+            builtins.concatStringsSep "; "
+              (lib.attrsets.mapAttrsToList
+                (k: v:
+                  if builtins.isBool v then k else
+                  "${k}${sep}${subValue v}"
+                )
+                val)
+          else toString val;
+        genHeader = header:
+          let
+            sep = if header == "content-security-policy" then " " else "=";
+          in
+          value: "${header} \"${headerValue sep value}\"";
+
+        headers = matcher: headers: ''
+          header ${matcher} {
+            ${builtins.concatStringsSep "\n"
+              (lib.attrsets.mapAttrsToList genHeader headers)}
           }
-          reverse_proxy 127.0.0.1:8081
         '';
-      };
-      "id.alanpearce.eu" = {
-        useACMEHost = "alanpearce.eu";
-        extraConfig = ''
-          encode zstd gzip
-          reverse_proxy http://${config.services.dex.settings.web.http}
-        '';
-      };
-      "dns.alanpearce.eu" = {
-        useACMEHost = "alanpearce.eu";
-        extraConfig = ''
-          log {
-            output discard
-          }
-          reverse_proxy localhost:443 {
-            transport http {
-              tls_server_name dns.alanpearce.eu
+        security-headers = { matcher ? "", overrides ? { } }: headers matcher ({
+          strict-transport-security = {
+            max-age = 2 * 365 * 24 * 60 * 60;
+          };
+          x-content-type-options = "nosniff";
+          x-frame-options = "DENY";
+        } // overrides);
+      in
+      {
+        "http://" = {
+          # Needed for HTTP->HTTPS servers
+        };
+        "${hostname}.alanpearce.eu" = {
+          serverAliases = [ "https://" ];
+          useACMEHost = "alanpearce.eu";
+          extraConfig = ''
+            respond * 204
+            ${security-headers {}}
+          '';
+        };
+        "pdns.alanpearce.eu" = {
+          useACMEHost = "alanpearce.eu";
+          extraConfig = ''
+            log {
+              output discard
             }
-          }
-        '';
-      };
-      "files.alanpearce.eu" = {
-        useACMEHost = "alanpearce.eu";
-        extraConfig = ''
-          encode zstd gzip
-          root * /srv/http/files
-          file_server browse
-        '';
-      };
-      "git.alanpearce.eu" =
-        let
-          fcgi = config.services.fcgiwrap;
-          fcgisocket = "${fcgi.socketType}/${fcgi.socketAddress}";
-        in
-        {
+            reverse_proxy 127.0.0.1:8081
+          '';
+        };
+        "id.alanpearce.eu" = {
           useACMEHost = "alanpearce.eu";
           extraConfig = ''
-            root * ${pkgs.cgit-pink}/cgit/
             encode zstd gzip
-            handle_path /custom/* {
-              file_server {
-                root /srv/http/cgit/
-              }
+            ${security-headers {}}
+            reverse_proxy http://${config.services.dex.settings.web.http}
+          '';
+        };
+        "dns.alanpearce.eu" = {
+          useACMEHost = "alanpearce.eu";
+          extraConfig = ''
+            log {
+              output discard
             }
-            rewrite /robots.txt /assets/robots.txt
-            handle_path /assets/* {
-              file_server  {
-                hide cgit.cgi
+            encode zstd gzip
+            reverse_proxy localhost:443 {
+              transport http {
+                tls_server_name dns.alanpearce.eu
               }
             }
-            @git_http_backend path_regexp "^.*/(HEAD|info/refs|objects/info/[^/]+|git-upload-pack)$"
-            handle @git_http_backend {
-              reverse_proxy ${fcgisocket} {
-                transport fastcgi {
-                  env SCRIPT_FILENAME ${pkgs.git}/libexec/git-core/git-http-backend
-                  env GIT_PROJECT_ROOT ${config.services.gitolite.dataDir}/repositories
+          '';
+        };
+        "files.alanpearce.eu" = {
+          useACMEHost = "alanpearce.eu";
+          extraConfig = ''
+            encode zstd gzip
+            ${security-headers {}}
+            root * /srv/http/files
+            file_server browse
+          '';
+        };
+        "git.alanpearce.eu" =
+          let
+            fcgi = config.services.fcgiwrap;
+            fcgisocket = "${fcgi.socketType}/${fcgi.socketAddress}";
+          in
+          {
+            useACMEHost = "alanpearce.eu";
+            extraConfig = ''
+              root * ${pkgs.cgit-pink}/cgit/
+              encode zstd gzip
+              ${security-headers {
+                overrides.content-security-policy = {
+                  default-src = [ "none" ];
+                  base-uri = [ "none" ];
+                  style-src = [ "self" "unsafe-inline" ];
+                  script-src = [ "self" "unsafe-inline" ];
+                  form-action = [ "self" ];
+                  connect-src = [ "self" ];
+                  img-src = [ "https" ];
+                  object-src = [ "none" ];
+                };
+              }}
+              handle_path /custom/* {
+                file_server {
+                  root /srv/http/cgit/
                 }
               }
-            }
-            handle {
-              reverse_proxy ${fcgisocket} {
-                transport fastcgi {
-                  env       SCRIPT_FILENAME  {http.vars.root}/cgit.cgi
-                  env       CGIT_CONFIG      ${pkgs.writeText "cgitrc" ''
-                    head-include=/srv/http/cgit/responsive-cgit-css-master/head.html
-                    css=/custom/responsive-cgit-css-master/cgit.css
-                    virtual-root=/
-                    logo=
-                    readme=:README.md
-                    source-filter=${pkgs.cgit-pink}/lib/cgit/filters/syntax-highlighting.py
-                    about-filter=${pkgs.cgit-pink}/lib/cgit/filters/about-formatting.sh
-                    enable-git-config=1
-                    enable-index-owner=0
-                    enable-index-links=1
-                    enable-follow-links=0
-                    enable-log-linecount=1
-                    max-stats=year
-                    snapshots=tar.lz tar.zst zip
-                    cache-size=10240
-                    enable-http-clone=1
-                    enable-commit-graph=1
-                    mimetype-file=${pkgs.nginx}/conf/mime.types
-                    section-from-path=1
-                    noplainemail=1
-                    repository-sort=age
-                    root-title=my personal projects
-                    clone-url=git://git.alanpearce.eu/$CGIT_REPO_URL https://git.alanpearce.eu/$CGIT_REPO_URL
-                    remove-suffix=1
-                    strict-export=git-daemon-export-ok
-                    scan-path=${config.services.gitolite.dataDir}/repositories/
-                  ''}
+              rewrite /robots.txt /assets/robots.txt
+              handle_path /assets/* {
+                file_server  {
+                  hide cgit.cgi
+                }
+              }
+              @git_http_backend path_regexp "^.*/(HEAD|info/refs|objects/info/[^/]+|git-upload-pack)$"
+              handle @git_http_backend {
+                reverse_proxy ${fcgisocket} {
+                  transport fastcgi {
+                    env SCRIPT_FILENAME ${pkgs.git}/libexec/git-core/git-http-backend
+                    env GIT_PROJECT_ROOT ${config.services.gitolite.dataDir}/repositories
                   }
                 }
-            }
+              }
+              handle {
+                reverse_proxy ${fcgisocket} {
+                  transport fastcgi {
+                    env       SCRIPT_FILENAME  {http.vars.root}/cgit.cgi
+                    env       CGIT_CONFIG      ${pkgs.writeText "cgitrc" ''
+                      head-include=/srv/http/cgit/responsive-cgit-css-master/head.html
+                      css=/custom/responsive-cgit-css-master/cgit.css
+                      virtual-root=/
+                      logo=
+                      readme=:README.md
+                      source-filter=${pkgs.cgit-pink}/lib/cgit/filters/syntax-highlighting.py
+                      about-filter=${pkgs.cgit-pink}/lib/cgit/filters/about-formatting.sh
+                      enable-git-config=1
+                      enable-index-owner=0
+                      enable-index-links=1
+                      enable-follow-links=0
+                      enable-log-linecount=1
+                      max-stats=year
+                      snapshots=tar.lz tar.zst zip
+                      cache-size=10240
+                      enable-http-clone=1
+                      enable-commit-graph=1
+                      mimetype-file=${pkgs.nginx}/conf/mime.types
+                      section-from-path=1
+                      noplainemail=1
+                      repository-sort=age
+                      root-title=my personal projects
+                      clone-url=git://git.alanpearce.eu/$CGIT_REPO_URL https://git.alanpearce.eu/$CGIT_REPO_URL
+                      remove-suffix=1
+                      strict-export=git-daemon-export-ok
+                      scan-path=${config.services.gitolite.dataDir}/repositories/
+                    ''}
+                    }
+                  }
+              }
+            '';
+          };
+        "ntfy.alanpearce.eu" = {
+          useACMEHost = "alanpearce.eu";
+          extraConfig = ''
+            encode zstd gzip
+            ${security-headers {}}
+            reverse_proxy localhost${config.services.ntfy-sh.settings.listen-http}
           '';
         };
-      "ntfy.alanpearce.eu" = {
-        useACMEHost = "alanpearce.eu";
-        extraConfig = ''
-          encode zstd gzip
-          reverse_proxy localhost${config.services.ntfy-sh.settings.listen-http}
-        '';
-      };
-      "searchix.alanpearce.eu" = {
-        useACMEHost = "alanpearce.eu";
-        extraConfig = ''
-          reverse_proxy localhost:${toString config.services.searchix.settings.web.port} {
-            health_uri /health
-            health_status 2xx
-          }
-          encode zstd gzip {
-            match {
-              header Content-Type text/*
-              header Content-Type application/json*
-              header Content-Type application/javascript*
-              header Content-Type application/opensearchdescription+xml
-              header Content-Type application/atom+xml*
-              header Content-Type application/rss+xml*
-              header Content-Type image/svg+xml*
-            }
-          }
-        '';
-      };
-      "legit.alanpearce.eu" =
-        let
-          server = config.services.legit.settings.server;
-        in
-        {
+        "searchix.alanpearce.eu" = {
           useACMEHost = "alanpearce.eu";
           extraConfig = ''
+            reverse_proxy localhost:${toString config.services.searchix.settings.web.port} {
+              health_uri /health
+              health_status 2xx
+            }
+            encode zstd gzip {
+              match {
+                header Content-Type text/*
+                header Content-Type application/json*
+                header Content-Type application/javascript*
+                header Content-Type application/opensearchdescription+xml
+                header Content-Type application/atom+xml*
+                header Content-Type application/rss+xml*
+                header Content-Type image/svg+xml*
+              }
+            }
+          '';
+        };
+        "legit.alanpearce.eu" =
+          let
+            server = config.services.legit.settings.server;
+          in
+          {
+            useACMEHost = "alanpearce.eu";
+            extraConfig = ''
+              encode zstd gzip
+              handle_path /static/* {
+                root * /srv/http/legit/src/static
+                file_server
+              }
+              ${security-headers {
+                overrides.content-security-policy = {
+                  default-src = [ "none" ];
+                  base-uri = [ "none" ];
+                  style-src = [ "self" ];
+                  script-src = [ "none" ];
+                  form-action = [ "self" ];
+                  connect-src = [ "self" ];
+                  img-src = [ "https" ];
+                  object-src = [ "none" ];
+                };
+              }}
+              reverse_proxy ${server.host}:${toString server.port}
+            '';
+          };
+        "papers.alanpearce.eu" = {
+          extraConfig = ''
             encode zstd gzip
             handle_path /static/* {
-              root * /srv/http/legit/src/static
+              root * ${config.services.paperless.package}/lib/paperless-ngx/static
               file_server
             }
-            reverse_proxy ${server.host}:${toString server.port}
-          '';
-        };
-      "papers.alanpearce.eu" = {
-        extraConfig = ''
-          encode zstd gzip
-          handle_path /static/* {
-            root * ${config.services.paperless.package}/lib/paperless-ngx/static
-            file_server
-          }
-          reverse_proxy localhost:${toString config.services.paperless.port}
+            reverse_proxy localhost:${toString config.services.paperless.port}
 
-        '';
-      };
-      "binarycache.alanpearce.eu" =
-        let
-          ns = config.services.nix-serve;
-        in
-        {
-          extraConfig = ''
-            reverse_proxy ${ns.bindAddress}:${toString ns.port}
           '';
         };
-    };
+        "binarycache.alanpearce.eu" =
+          let
+            ns = config.services.nix-serve;
+          in
+          {
+            extraConfig = ''
+              reverse_proxy ${ns.bindAddress}:${toString ns.port}
+            '';
+          };
+      };
   };
   systemd.services.caddy.serviceConfig = {
     UMask = "007";