diff --git a/modules/nixos/nginx/headers.nix b/modules/nixos/nginx/headers.nix new file mode 100644 index 00000000..8723e2ef --- /dev/null +++ b/modules/nixos/nginx/headers.nix @@ -0,0 +1,76 @@ +let + locationModule = { config, virtualHost, xvars, gensokyo-zone, lib, ... }: let + inherit (gensokyo-zone.lib) mapOptionDefaults; + inherit (lib.options) mkOption; + inherit (lib.modules) mkIf mkMerge mkAfter mkOptionDefault; + inherit (lib.attrsets) mapAttrsToList; + inherit (lib.lists) isList; + cfg = config.headers; + in { + options.headers = with lib.types; { + inheritServerDefaults = mkOption { + type = bool; + default = true; + }; + set = mkOption { + type = attrsOf (nullOr (oneOf [ str (listOf str) ])); + }; + }; + config = let + mkHeader = name: value: + if isList value then mkMerge (map (mkHeader name) value) + else mkAfter "add_header ${name} ${xvars.escapeString value};"; + setHeaders = mapAttrsToList (name: value: mkIf (value != null) (mkHeader name value)) cfg.set; + in { + headers = { + set = mkMerge [ + (mkOptionDefault { }) + (mkIf cfg.inheritServerDefaults (mapOptionDefaults virtualHost.headers.set)) + ]; + }; + extraConfig = mkMerge setHeaders; + }; + }; + hostModule = { config, nixosConfig, gensokyo-zone, lib, ... }: let + inherit (gensokyo-zone.lib) mapOptionDefaults; + inherit (lib.options) mkOption; + inherit (nixosConfig.services) nginx; + in { + options = with lib.types; { + headers = { + set = mkOption { + type = attrsOf (nullOr (oneOf [ str (listOf str) ])); + }; + }; + locations = mkOption { + type = attrsOf (submoduleWith { + modules = [ locationModule ]; + shorthandOnlyDefinesConfig = true; + }); + }; + }; + config = { + headers = { + set = mapOptionDefaults nginx.headers.set; + }; + }; + }; +in { + lib, + ... +}: let + inherit (lib.options) mkOption; +in { + options.services.nginx = with lib.types; { + headers = { + set = mkOption { + type = attrsOf (nullOr (oneOf [ str (listOf str) ])); + default = { + }; + }; + }; + virtualHosts = mkOption { + type = attrsOf (submodule [hostModule]); + }; + }; +} diff --git a/modules/nixos/nginx/proxy.nix b/modules/nixos/nginx/proxy.nix index 7d8e6104..df312f5d 100644 --- a/modules/nixos/nginx/proxy.nix +++ b/modules/nixos/nginx/proxy.nix @@ -68,6 +68,10 @@ let set = mkOption { type = attrsOf (nullOr str); }; + hide = mkOption { + type = attrsOf bool; + default = { }; + }; }; redirect = { enable = mkEnableOption "proxy_redirect"; @@ -103,12 +107,12 @@ let https = 443; }.${cfg.parsed.scheme} or (throw "unsupported proxy_scheme ${toString cfg.parsed.scheme}"); port = coalesce [ cfg.parsed.port schemePort ]; - hostport = cfg.parsed.host + optionalString (port != schemePort) ":${toString cfg.parsed.port}"; + hostport = cfg.parsed.host + optionalString (port != schemePort) ":${toString port}"; initProxyVars = let - initScheme = xvars.init "proxy_scheme" cfg.parsed.scheme; - initHost = xvars.init "proxy_host" cfg.parsed.host; - initPort = xvars.init "proxy_port" port; - initHostPort = xvars.init "proxy_hostport" hostport; + initScheme = xvars.init "proxy_scheme" config.xvars.defaults.proxy_scheme; + initHost = xvars.init "proxy_host" config.xvars.defaults.proxy_host; + initPort = xvars.init "proxy_port" config.xvars.defaults.proxy_port; + initHostPort = xvars.init "proxy_hostport" config.xvars.defaults.proxy_hostport; initUpstream = '' ${initScheme} ${initHost} @@ -153,8 +157,17 @@ let setHeaders = concatStringsSep "\n" (mapAttrsToList ( name: value: "proxy_set_header ${name} ${xvars.escapeString value};" ) setHeaders'); + hideHeaders = mapAttrsToList (header: hide: mkIf hide "proxy_hide_header ${xvars.escapeString header};") cfg.headers.hide; in { - xvars.enable = mkIf (cfg.headers.rewriteReferer.enable || (cfg.enable && cfg.upstream != null)) true; + xvars = { + enable = mkIf cfg.headers.rewriteReferer.enable true; + defaults = mkIf cfg.enabled (mapOptionDefaults { + proxy_scheme = cfg.parsed.scheme; + proxy_host = cfg.parsed.host; + proxy_port = toString port; + proxy_hostport = hostport; + }); + }; proxy = { enabled = mkOptionDefault (config.proxyPass != null); path = mkIf (hasPrefix "/" name) (mkOptionDefault name); @@ -207,8 +220,8 @@ let }; proxyPass = mkIf cfg.enable (mkAlmostOptionDefault (removeSuffix "/" cfg.url + cfg.path)); recommendedProxySettings = mkAlmostOptionDefault (cfg.headers.enableRecommended == "nixpkgs"); - extraConfig = mkIf cfg.enabled (mkMerge [ - (mkIf (virtualHost.xvars.enable) (mkJustBefore initProxyVars)) + extraConfig = mkIf cfg.enabled (mkMerge ([ + (mkIf virtualHost.xvars.enable (mkJustBefore initProxyVars)) (mkIf (cfg.headers.rewriteReferer.enable) (mkJustBefore rewriteReferer)) (mkIf (cfg.redirect.enable) (mkBefore redirect)) (mkIf (emitHeaders) (mkJustAfter setHeaders)) @@ -216,17 +229,18 @@ let (mkIf (cfg.ssl.enabled && cfg.ssl.host != null) "proxy_ssl_name ${cfg.ssl.host};") (mkIf (cfg.ssl.enabled && cfg.ssl.verify) "proxy_ssl_verify on;") (mkIf cfg.websocket.enable "proxy_cache_bypass $http_upgrade;") - ]); + ] ++ hideHeaders)); }; }; hostModule = { config, nixosConfig, gensokyo-zone, lib, ... }: let - inherit (gensokyo-zone.lib) mapAlmostOptionDefaults; + inherit (gensokyo-zone.lib) mapOptionDefaults mapAlmostOptionDefaults; inherit (lib.options) mkOption mkEnableOption; - inherit (lib.modules) mkIf mkMerge; + inherit (lib.modules) mkIf mkOptionDefault; inherit (lib.attrsets) attrValues; inherit (lib.lists) any; inherit (nixosConfig.services) nginx; cfg = config.proxy; + anyLocations = f: any (loc: loc.enable && f loc) (attrValues config.locations); in { options = with lib.types; { proxy = { @@ -270,7 +284,15 @@ let }; }; in { - xvars.parseReferer = mkIf (any needsReferer (attrValues config.locations)) true; + xvars = { + parseReferer = mkIf (anyLocations needsReferer) true; + defaults = mkIf (anyLocations (loc: loc.proxy.enabled)) (mkOptionDefault (mapOptionDefaults rec { + proxy_scheme = null; + proxy_host = "$proxy_host"; + proxy_port = "$proxy_port"; + proxy_hostport = "${proxy_host}:${proxy_port}"; + })); + }; proxy = mkIf (cfg.copyFromVhost != null) confCopy; }; }; diff --git a/modules/nixos/nginx/xvars.nix b/modules/nixos/nginx/xvars.nix index 09856f6a..8cb20f6b 100644 --- a/modules/nixos/nginx/xvars.nix +++ b/modules/nixos/nginx/xvars.nix @@ -1,24 +1,48 @@ let locationModule = { config, virtualHost, lib, ... }: let - inherit (lib.options) mkEnableOption; + inherit (lib.options) mkEnableOption mkOption; + inherit (lib.attrsets) mapAttrs; cfg = config.xvars; in { options.xvars = with lib.types; { enable = mkEnableOption "$x_variables"; + defaults = mkOption { + type = attrsOf (nullOr str); + default = { }; + }; + lib = mkOption { + type = attrs; + }; }; - config = let - in { + config = { + xvars = { + lib = let + xvars = virtualHost.xvars.lib; + get = mapAttrs (name: default: if virtualHost.xvars.enable then "$x_${name}" else assert default != null; default) cfg.defaults; + in xvars // { + get = xvars.get // get; + }; + }; + _module.args.xvars = config.xvars.lib; }; }; hostModule = { config, nixosConfig, gensokyo-zone, xvars, lib, ... }: let inherit (gensokyo-zone.lib) mkJustBefore; inherit (lib.options) mkOption mkEnableOption; inherit (lib.modules) mkIf mkMerge mkOptionDefault; - inherit (lib.strings) concatStringsSep; inherit (lib.attrsets) attrValues filterAttrs mapAttrs mapAttrsToList; inherit (lib.lists) any; + inherit (lib.strings) concatStringsSep hasPrefix hasInfix; + inherit (lib.trivial) isInt; cfg = config.xvars; - escapeString = value: if value == "" then ''""'' else toString value; + escapeString = value: + if value == "" then ''""'' + else if isInt value then toString value + else if hasPrefix ''"'' value || hasPrefix "'" value then value # already escaped, may include trailing arguments + else if hasInfix ''"'' value then "'${value}'" + else if hasInfix " " value || hasInfix ";" value || hasInfix "'" value then ''"${value}"'' + else value; + anyLocations = f: any (loc: loc.enable && f loc) (attrValues config.locations); in { options = with lib.types; { xvars = { @@ -34,10 +58,6 @@ let host = "$host"; referer = "$http_referer"; https = "$https"; - proxy_host = "$proxy_host"; - proxy_port = "$proxy_port"; - proxy_hostport = "${proxy_host}:${proxy_port}"; - proxy_scheme = null; }; }; lib = mkOption { @@ -49,7 +69,7 @@ let modules = [ locationModule ]; shorthandOnlyDefinesConfig = true; specialArgs = { - inherit nixosConfig gensokyo-zone xvars; + inherit nixosConfig gensokyo-zone; virtualHost = config; }; }); @@ -70,7 +90,7 @@ let in { xvars = { enable = mkMerge [ - (mkIf (any (loc: loc.xvars.enable) (attrValues config.locations)) true) + (mkIf (anyLocations (loc: loc.xvars.enable)) true) (mkIf cfg.parseReferer true) ]; defaults = mkIf cfg.parseReferer (mkOptionDefault { diff --git a/nixos/access/freepbx.nix b/nixos/access/freepbx.nix index 80e50925..dc6629a2 100644 --- a/nixos/access/freepbx.nix +++ b/nixos/access/freepbx.nix @@ -37,11 +37,9 @@ in { proxy = { enable = true; websocket.enable = true; + headers.hide.Access-Control-Allow-Origin = true; }; - extraConfig = '' - proxy_hide_header Access-Control-Allow-Origin; - add_header Access-Control-Allow-Origin ${xvars.get.scheme}://${virtualHost.serverName}; - ''; + headers.set.Access-Control-Allow-Origin = "${xvars.get.scheme}://${virtualHost.serverName}"; }; }; allLocations = mkMerge [ diff --git a/nixos/access/invidious.nix b/nixos/access/invidious.nix index 93193cb1..53f88dec 100644 --- a/nixos/access/invidious.nix +++ b/nixos/access/invidious.nix @@ -40,11 +40,10 @@ in { proxy = { enable = true; websocket.enable = true; - headers.enableRecommended = true; + headers.hide.content-security-policy = true; }; + headers.set.content-security-policy = contentSecurityPolicy; extraConfig = '' - proxy_hide_header content-security-policy; - add_header content-security-policy "${contentSecurityPolicy}"; proxy_cookie_domain ${virtualHosts.invidious.serverName} ${xvars.get.host}; ''; }; diff --git a/nixos/nginx.nix b/nixos/nginx.nix index e90f5ddc..915c6548 100644 --- a/nixos/nginx.nix +++ b/nixos/nginx.nix @@ -16,19 +16,21 @@ with lib; { recommendedOptimisation = true; recommendedProxySettings = true; recommendedTlsSettings = false; + headers.set = { + Referrer-Policy = mkDefault "origin-when-cross-origin"; + #Strict-Transport-Security = "$hsts_header"; + #Content-Security-Policy = ''"script-src 'self'; object-src 'none'; base-uri 'none';" always''; + #X-Frame-Options = "DENY"; + #X-Content-Type-Options = "nosniff"; + #X-XSS-Protection = "1; mode=block"; + }; commonHttpConfig = '' map $scheme $hsts_header { https "max-age=31536000; includeSubdomains; preload"; } - #add_header Strict-Transport-Security $hsts_header; - #add_header Content-Security-Policy "script-src 'self'; object-src 'none'; base-uri 'none';" always; - add_header 'Referrer-Policy' 'origin-when-cross-origin'; - #add_header X-Frame-Options DENY; - #add_header X-Content-Type-Options nosniff; - #add_header X-XSS-Protection "1; mode=block"; #proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict"; ''; - clientMaxBodySize = "512m"; + clientMaxBodySize = mkDefault "512m"; virtualHosts.fallback = { serverName = null; default = mkDefault true;