refactor(nginx): headers and proxy vars

This commit is contained in:
arcnmx 2024-04-23 11:20:19 -07:00
parent 692d3aacbd
commit 418caefe64
6 changed files with 154 additions and 37 deletions

View file

@ -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]);
};
};
}

View file

@ -68,6 +68,10 @@ let
set = mkOption { set = mkOption {
type = attrsOf (nullOr str); type = attrsOf (nullOr str);
}; };
hide = mkOption {
type = attrsOf bool;
default = { };
};
}; };
redirect = { redirect = {
enable = mkEnableOption "proxy_redirect"; enable = mkEnableOption "proxy_redirect";
@ -103,12 +107,12 @@ let
https = 443; https = 443;
}.${cfg.parsed.scheme} or (throw "unsupported proxy_scheme ${toString cfg.parsed.scheme}"); }.${cfg.parsed.scheme} or (throw "unsupported proxy_scheme ${toString cfg.parsed.scheme}");
port = coalesce [ cfg.parsed.port schemePort ]; 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 initProxyVars = let
initScheme = xvars.init "proxy_scheme" cfg.parsed.scheme; initScheme = xvars.init "proxy_scheme" config.xvars.defaults.proxy_scheme;
initHost = xvars.init "proxy_host" cfg.parsed.host; initHost = xvars.init "proxy_host" config.xvars.defaults.proxy_host;
initPort = xvars.init "proxy_port" port; initPort = xvars.init "proxy_port" config.xvars.defaults.proxy_port;
initHostPort = xvars.init "proxy_hostport" hostport; initHostPort = xvars.init "proxy_hostport" config.xvars.defaults.proxy_hostport;
initUpstream = '' initUpstream = ''
${initScheme} ${initScheme}
${initHost} ${initHost}
@ -153,8 +157,17 @@ let
setHeaders = concatStringsSep "\n" (mapAttrsToList ( setHeaders = concatStringsSep "\n" (mapAttrsToList (
name: value: "proxy_set_header ${name} ${xvars.escapeString value};" name: value: "proxy_set_header ${name} ${xvars.escapeString value};"
) setHeaders'); ) setHeaders');
hideHeaders = mapAttrsToList (header: hide: mkIf hide "proxy_hide_header ${xvars.escapeString header};") cfg.headers.hide;
in { 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 = { proxy = {
enabled = mkOptionDefault (config.proxyPass != null); enabled = mkOptionDefault (config.proxyPass != null);
path = mkIf (hasPrefix "/" name) (mkOptionDefault name); path = mkIf (hasPrefix "/" name) (mkOptionDefault name);
@ -207,8 +220,8 @@ let
}; };
proxyPass = mkIf cfg.enable (mkAlmostOptionDefault (removeSuffix "/" cfg.url + cfg.path)); proxyPass = mkIf cfg.enable (mkAlmostOptionDefault (removeSuffix "/" cfg.url + cfg.path));
recommendedProxySettings = mkAlmostOptionDefault (cfg.headers.enableRecommended == "nixpkgs"); recommendedProxySettings = mkAlmostOptionDefault (cfg.headers.enableRecommended == "nixpkgs");
extraConfig = mkIf cfg.enabled (mkMerge [ extraConfig = mkIf cfg.enabled (mkMerge ([
(mkIf (virtualHost.xvars.enable) (mkJustBefore initProxyVars)) (mkIf virtualHost.xvars.enable (mkJustBefore initProxyVars))
(mkIf (cfg.headers.rewriteReferer.enable) (mkJustBefore rewriteReferer)) (mkIf (cfg.headers.rewriteReferer.enable) (mkJustBefore rewriteReferer))
(mkIf (cfg.redirect.enable) (mkBefore redirect)) (mkIf (cfg.redirect.enable) (mkBefore redirect))
(mkIf (emitHeaders) (mkJustAfter setHeaders)) (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.host != null) "proxy_ssl_name ${cfg.ssl.host};")
(mkIf (cfg.ssl.enabled && cfg.ssl.verify) "proxy_ssl_verify on;") (mkIf (cfg.ssl.enabled && cfg.ssl.verify) "proxy_ssl_verify on;")
(mkIf cfg.websocket.enable "proxy_cache_bypass $http_upgrade;") (mkIf cfg.websocket.enable "proxy_cache_bypass $http_upgrade;")
]); ] ++ hideHeaders));
}; };
}; };
hostModule = { config, nixosConfig, gensokyo-zone, lib, ... }: let 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.options) mkOption mkEnableOption;
inherit (lib.modules) mkIf mkMerge; inherit (lib.modules) mkIf mkOptionDefault;
inherit (lib.attrsets) attrValues; inherit (lib.attrsets) attrValues;
inherit (lib.lists) any; inherit (lib.lists) any;
inherit (nixosConfig.services) nginx; inherit (nixosConfig.services) nginx;
cfg = config.proxy; cfg = config.proxy;
anyLocations = f: any (loc: loc.enable && f loc) (attrValues config.locations);
in { in {
options = with lib.types; { options = with lib.types; {
proxy = { proxy = {
@ -270,7 +284,15 @@ let
}; };
}; };
in { 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; proxy = mkIf (cfg.copyFromVhost != null) confCopy;
}; };
}; };

View file

@ -1,24 +1,48 @@
let let
locationModule = { config, virtualHost, lib, ... }: let locationModule = { config, virtualHost, lib, ... }: let
inherit (lib.options) mkEnableOption; inherit (lib.options) mkEnableOption mkOption;
inherit (lib.attrsets) mapAttrs;
cfg = config.xvars; cfg = config.xvars;
in { in {
options.xvars = with lib.types; { options.xvars = with lib.types; {
enable = mkEnableOption "$x_variables"; enable = mkEnableOption "$x_variables";
defaults = mkOption {
type = attrsOf (nullOr str);
default = { };
};
lib = mkOption {
type = attrs;
};
}; };
config = let config = {
in { 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 hostModule = { config, nixosConfig, gensokyo-zone, xvars, lib, ... }: let
inherit (gensokyo-zone.lib) mkJustBefore; inherit (gensokyo-zone.lib) mkJustBefore;
inherit (lib.options) mkOption mkEnableOption; inherit (lib.options) mkOption mkEnableOption;
inherit (lib.modules) mkIf mkMerge mkOptionDefault; inherit (lib.modules) mkIf mkMerge mkOptionDefault;
inherit (lib.strings) concatStringsSep;
inherit (lib.attrsets) attrValues filterAttrs mapAttrs mapAttrsToList; inherit (lib.attrsets) attrValues filterAttrs mapAttrs mapAttrsToList;
inherit (lib.lists) any; inherit (lib.lists) any;
inherit (lib.strings) concatStringsSep hasPrefix hasInfix;
inherit (lib.trivial) isInt;
cfg = config.xvars; 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 { in {
options = with lib.types; { options = with lib.types; {
xvars = { xvars = {
@ -34,10 +58,6 @@ let
host = "$host"; host = "$host";
referer = "$http_referer"; referer = "$http_referer";
https = "$https"; https = "$https";
proxy_host = "$proxy_host";
proxy_port = "$proxy_port";
proxy_hostport = "${proxy_host}:${proxy_port}";
proxy_scheme = null;
}; };
}; };
lib = mkOption { lib = mkOption {
@ -49,7 +69,7 @@ let
modules = [ locationModule ]; modules = [ locationModule ];
shorthandOnlyDefinesConfig = true; shorthandOnlyDefinesConfig = true;
specialArgs = { specialArgs = {
inherit nixosConfig gensokyo-zone xvars; inherit nixosConfig gensokyo-zone;
virtualHost = config; virtualHost = config;
}; };
}); });
@ -70,7 +90,7 @@ let
in { in {
xvars = { xvars = {
enable = mkMerge [ enable = mkMerge [
(mkIf (any (loc: loc.xvars.enable) (attrValues config.locations)) true) (mkIf (anyLocations (loc: loc.xvars.enable)) true)
(mkIf cfg.parseReferer true) (mkIf cfg.parseReferer true)
]; ];
defaults = mkIf cfg.parseReferer (mkOptionDefault { defaults = mkIf cfg.parseReferer (mkOptionDefault {

View file

@ -37,11 +37,9 @@ in {
proxy = { proxy = {
enable = true; enable = true;
websocket.enable = true; websocket.enable = true;
headers.hide.Access-Control-Allow-Origin = true;
}; };
extraConfig = '' headers.set.Access-Control-Allow-Origin = "${xvars.get.scheme}://${virtualHost.serverName}";
proxy_hide_header Access-Control-Allow-Origin;
add_header Access-Control-Allow-Origin ${xvars.get.scheme}://${virtualHost.serverName};
'';
}; };
}; };
allLocations = mkMerge [ allLocations = mkMerge [

View file

@ -40,11 +40,10 @@ in {
proxy = { proxy = {
enable = true; enable = true;
websocket.enable = true; websocket.enable = true;
headers.enableRecommended = true; headers.hide.content-security-policy = true;
}; };
headers.set.content-security-policy = contentSecurityPolicy;
extraConfig = '' extraConfig = ''
proxy_hide_header content-security-policy;
add_header content-security-policy "${contentSecurityPolicy}";
proxy_cookie_domain ${virtualHosts.invidious.serverName} ${xvars.get.host}; proxy_cookie_domain ${virtualHosts.invidious.serverName} ${xvars.get.host};
''; '';
}; };

View file

@ -16,19 +16,21 @@ with lib; {
recommendedOptimisation = true; recommendedOptimisation = true;
recommendedProxySettings = true; recommendedProxySettings = true;
recommendedTlsSettings = false; 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 = '' commonHttpConfig = ''
map $scheme $hsts_header { map $scheme $hsts_header {
https "max-age=31536000; includeSubdomains; preload"; 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"; #proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";
''; '';
clientMaxBodySize = "512m"; clientMaxBodySize = mkDefault "512m";
virtualHosts.fallback = { virtualHosts.fallback = {
serverName = null; serverName = null;
default = mkDefault true; default = mkDefault true;