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 {
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;
};
};

View file

@ -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 {

View file

@ -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 [

View file

@ -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};
'';
};

View file

@ -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;