From 1c35520b56476202005cc07a026e56c184695749 Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Thu, 30 May 2024 21:21:39 +0200 Subject: linde: add HTTP security headers to caddy virtual hosts --- system/linde.nix | 382 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 228 insertions(+), 154 deletions(-) (limited to 'system') 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"; -- cgit 1.4.1