refactor(nginx): proxy upstream modules

This commit is contained in:
arcnmx 2024-04-21 17:17:07 -07:00
parent f7e00a2e64
commit 586efcae0e
21 changed files with 844 additions and 370 deletions

View file

@ -51,7 +51,10 @@
enableRecommended = mkIf cfg.enabled (mkAlmostOptionDefault true);
rewriteReferer.enable = mkIf cfg.enabled (mkAlmostOptionDefault true);
};
redirect.enable = mkIf cfg.enabled (mkAlmostOptionDefault true);
redirect = mkIf cfg.enabled {
enable = mkAlmostOptionDefault true;
fromScheme = mkAlmostOptionDefault xvars.get.proxy_scheme;
};
};
fastcgi = {
passHeaders = {

View file

@ -1,11 +1,12 @@
let
locationModule = { config, name, virtualHost, xvars, gensokyo-zone, lib, ... }: let
locationModule = { config, nixosConfig, name, virtualHost, xvars, gensokyo-zone, lib, ... }: let
inherit (gensokyo-zone.lib) mkJustBefore mkJustAfter mkAlmostOptionDefault mapOptionDefaults coalesce parseUrl;
inherit (lib.options) mkOption mkEnableOption;
inherit (lib.modules) mkIf mkMerge mkBefore mkOptionDefault;
inherit (lib.attrsets) filterAttrs mapAttrsToList;
inherit (lib.strings) hasPrefix removeSuffix concatStringsSep;
inherit (lib.strings) hasPrefix removeSuffix optionalString concatStringsSep;
inherit (lib.trivial) mapNullable;
inherit (nixosConfig.services) nginx;
cfg = config.proxy;
in {
options = with lib.types; {
@ -15,6 +16,10 @@ let
type = bool;
readOnly = true;
};
inheritServerDefaults = mkOption {
type = bool;
default = true;
};
url = mkOption {
type = str;
};
@ -25,7 +30,21 @@ let
type = nullOr str;
};
websocket.enable = mkEnableOption "websocket proxy" // {
default = virtualHost.proxy.websocket.enable;
default = cfg.inheritServerDefaults && virtualHost.proxy.websocket.enable;
};
ssl = {
enabled = mkOption {
type = bool;
};
verify = mkEnableOption "proxy_ssl_verify";
sni = mkEnableOption "proxy_ssl_server_name" // {
default = cfg.ssl.host != null;
};
host = mkOption {
type = nullOr str;
default = null;
example = "xvars.get.proxy_host";
};
};
parsed = {
scheme = mkOption {
@ -37,9 +56,6 @@ let
host = mkOption {
type = nullOr str;
};
hostport = mkOption {
type = nullOr str;
};
port = mkOption {
type = nullOr int;
};
@ -71,8 +87,10 @@ let
config = let
emitHeaders = setHeaders' != { };
url = parseUrl config.proxyPass;
upstream = nginx.upstreams'.${cfg.upstream};
upstreamServer = upstream.servers.${upstream.defaultServerName};
recommendedHeaders = {
Host = if cfg.host == null then xvars.get.proxy_host else cfg.host;
Host = if cfg.host == null then xvars.get.proxy_hostport else cfg.host;
Referer = xvars.get.referer;
X-Real-IP = xvars.get.remote_addr;
X-Forwarded-For = xvars.get.forwarded_for;
@ -80,21 +98,52 @@ let
X-Forwarded-Host = xvars.get.host;
X-Forwarded-Server = xvars.get.forwarded_server;
};
initProxyVars = ''
${xvars.init "proxy_scheme" cfg.parsed.scheme}
${xvars.init "proxy_host" "$proxy_host"}
if (${xvars.get.proxy_host} = "") {
${xvars.init "proxy_host" cfg.parsed.hostport}
}
'';
schemePort = {
http = 80;
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}";
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;
initUpstream = ''
${initScheme}
${initHost}
${initPort}
${initHostPort}
'';
initDynamic = ''
${initScheme}
${xvars.init "proxy_host" "$proxy_host"}
if (${xvars.get.proxy_host} = "") {
${initHost}
}
${xvars.init "proxy_port" "$proxy_port"}
if (${xvars.get.proxy_port} = "") {
${initPort}
}
${xvars.init "proxy_hostport" "${xvars.get.proxy_host}:${xvars.get.proxy_port}"}
if (${xvars.get.proxy_port} = ${toString schemePort}) {
${xvars.init "proxy_hostport" xvars.get.proxy_host}
}
if (${xvars.get.proxy_port} = "") {
${xvars.init "proxy_hostport" xvars.get.proxy_host}
}
'';
init = if cfg.upstream != null then initUpstream else initDynamic;
in init;
hostHeader = coalesce [
cfg.headers.set.Host or null
cfg.host
xvars.get.proxy_host
xvars.get.proxy_hostport
];
rewriteReferer = ''
if (${xvars.get.referer_host} = $host) {
${xvars.init "referer" "${config.proxy.parsed.scheme}://${hostHeader}${xvars.get.referer_path}"}
${xvars.init "referer" "${xvars.get.proxy_scheme}://${hostHeader}${xvars.get.referer_path}"}
}
'';
redirect = ''
@ -105,15 +154,19 @@ let
name: value: "proxy_set_header ${name} ${xvars.escapeString value};"
) setHeaders');
in {
xvars.enable = mkIf cfg.headers.rewriteReferer.enable true;
xvars.enable = mkIf (cfg.headers.rewriteReferer.enable || (cfg.enable && cfg.upstream != null)) true;
proxy = {
enabled = mkOptionDefault (config.proxyPass != null);
path = mkIf (hasPrefix "/" name) (mkOptionDefault name);
url = mkIf (virtualHost.proxy.url != null) (mkOptionDefault virtualHost.proxy.url);
url = mkIf (cfg.inheritServerDefaults && virtualHost.proxy.url != null) (mkOptionDefault virtualHost.proxy.url);
ssl = {
enabled = mkOptionDefault (cfg.parsed.scheme == "https");
};
headers = {
enableRecommended = mkOptionDefault (
if cfg.enable && virtualHost.proxy.headers.enableRecommended != false then true
else virtualHost.proxy.headers.enableRecommended
if cfg.enable && (!cfg.inheritServerDefaults || virtualHost.proxy.headers.enableRecommended != false) then true
else if cfg.inheritServerDefaults then virtualHost.proxy.headers.enableRecommended
else if nginx.recommendedProxySettings then "nixpkgs" else false
);
set = mkMerge [
(mkOptionDefault { })
@ -131,7 +184,7 @@ let
];
};
host = mkOptionDefault (
if virtualHost.proxy.host != null then virtualHost.proxy.host
if cfg.inheritServerDefaults && virtualHost.proxy.host != null then virtualHost.proxy.host
else if cfg.headers.enableRecommended == false then null
else xvars.get.host
);
@ -143,29 +196,33 @@ let
mapNullable (_: url.path) config.proxyPass
);
host = mkOptionDefault (
mapNullable (_: url.host) config.proxyPass
);
hostport = mkOptionDefault (
mapNullable (_: url.hostport) config.proxyPass
if cfg.upstream != null then assert url.host == upstream.name; upstreamServer.addr
else mapNullable (_: url.host) config.proxyPass
);
port = mkOptionDefault (
mapNullable (_: url.port) config.proxyPass
if cfg.upstream != null && url.port == null then assert url.host == upstream.name; upstreamServer.port
else mapNullable (_: url.port) config.proxyPass
);
};
};
proxyPass = mkIf cfg.enable (mkAlmostOptionDefault (removeSuffix "/" cfg.url + cfg.path));
recommendedProxySettings = mkAlmostOptionDefault (cfg.headers.enableRecommended == "nixpkgs");
extraConfig = mkMerge [
(mkIf (cfg.enabled && virtualHost.xvars.enable) (mkJustBefore initProxyVars))
(mkIf (cfg.enabled && cfg.headers.rewriteReferer.enable) (mkJustBefore rewriteReferer))
(mkIf (cfg.enabled && cfg.redirect.enable) (mkBefore redirect))
(mkIf (cfg.enabled && emitHeaders) (mkJustAfter setHeaders))
];
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))
(mkIf (cfg.ssl.enabled && cfg.ssl.sni) "proxy_ssl_server_name on;")
(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;")
]);
};
};
hostModule = { config, nixosConfig, lib, ... }: let
hostModule = { config, nixosConfig, gensokyo-zone, lib, ... }: let
inherit (gensokyo-zone.lib) mapAlmostOptionDefaults;
inherit (lib.options) mkOption mkEnableOption;
inherit (lib.modules) mkIf;
inherit (lib.modules) mkIf mkMerge;
inherit (lib.attrsets) attrValues;
inherit (lib.lists) any;
inherit (nixosConfig.services) nginx;
@ -181,6 +238,10 @@ let
type = nullOr str;
default = null;
};
copyFromVhost = mkOption {
type = nullOr str;
default = null;
};
websocket.enable = mkEnableOption "websocket proxy";
headers.enableRecommended = mkOption {
type = enum [ true false "nixpkgs" ];
@ -196,8 +257,21 @@ let
};
config = let
needsReferer = loc: loc.proxy.enabled && loc.proxy.headers.rewriteReferer.enable;
confCopy = let
proxyHost = nginx.virtualHosts.${cfg.copyFromVhost};
in mapAlmostOptionDefaults {
inherit (proxyHost.proxy) host url upstream;
} // {
websocket = mapAlmostOptionDefaults {
inherit (proxyHost.proxy.websocket) enable;
};
headers = mapAlmostOptionDefaults {
inherit (proxyHost.proxy.headers) enableRecommended;
};
};
in {
xvars.parseReferer = mkIf (any needsReferer (attrValues config.locations)) true;
proxy = mkIf (cfg.copyFromVhost != null) confCopy;
};
};
in {

View file

@ -120,6 +120,7 @@
useACMEHost = mkAlmostOptionDefault cfg.cert.name;
sslCertificate = mkIf (cfg.cert.path != null) (mkAlmostOptionDefault cfg.cert.path);
sslCertificateKey = mkIf (cfg.cert.keyPath != null) (mkAlmostOptionDefault cfg.cert.keyPath);
kTLS = mkAlmostOptionDefault true;
xvars.enable = mkIf emitForce true;
extraConfig = mkIf emitForce (forceRedirectConfig config);
@ -129,6 +130,11 @@
cfg = config.ssl;
in {
imports = [ sslModule ];
options.ssl = with lib.types; {
kTLS = mkEnableOption "kTLS support" // {
default = true;
};
};
config = {
ssl.cert = let
cert = nixosConfig.security.acme.certs.${cfg.cert.name};
@ -137,10 +143,11 @@
keyPath = mkIf (cfg.cert.name != null) (mkAlmostDefault "${cert.directory}/key.pem");
};
#listen.ssl = mkIf cfg.enable { ssl = true; };
extraConfig = mkMerge [
extraConfig = mkIf cfg.enable (mkMerge [
(mkIf (cfg.cert.path != null) "ssl_certificate ${cfg.cert.path};")
(mkIf (cfg.cert.keyPath != null) "ssl_certificate_key ${cfg.cert.keyPath};")
];
(mkIf cfg.kTLS "ssl_conf_command Options KTLS;")
]);
};
};
in {

View file

@ -1,109 +1,13 @@
{
gensokyo-zone,
config,
lib,
gensokyo-zone,
...
}: let
inherit (gensokyo-zone.lib) mkAddress6;
inherit (lib.options) mkOption mkEnableOption;
inherit (lib.modules) mkIf mkMerge mkBefore mkOptionDefault;
inherit (lib.modules) mkIf mkMerge mkAfter mkOptionDefault;
inherit (lib.attrsets) mapAttrsToList;
inherit (lib.lists) optional;
cfg = config.services.nginx.stream;
upstreamServerModule = {config, name, ...}: {
options = with lib.types; {
enable = mkEnableOption "upstream server" // {
default = true;
};
addr = mkOption {
type = str;
default = name;
};
port = mkOption {
type = port;
};
server = mkOption {
type = str;
example = "unix:/tmp/backend3";
};
settings = mkOption {
type = attrsOf (oneOf [ int str ]);
default = { };
};
extraConfig = mkOption {
type = str;
default = "";
};
serverConfig = mkOption {
type = separatedString " ";
internal = true;
};
serverDirective = mkOption {
type = str;
internal = true;
};
};
config = let
settings = mapAttrsToList (key: value: "${key}=${toString value}") config.settings;
in {
server = mkOptionDefault "${mkAddress6 config.addr}:${toString config.port}";
serverConfig = mkMerge (
[ (mkBefore config.server) ]
++ settings
++ optional (config.extraConfig != "") config.extraConfig
);
serverDirective = mkOptionDefault "server ${config.serverConfig};";
};
};
upstreamModule = {config, name, nixosConfig, ...}: {
options = with lib.types; let
upstreamServer = submoduleWith {
modules = [ upstreamServerModule ];
specialArgs = {
inherit nixosConfig;
upstream = config;
};
};
in {
enable = mkEnableOption "upstream block" // {
default = true;
};
name = mkOption {
type = str;
default = name;
};
servers = mkOption {
type = attrsOf upstreamServer;
};
ssl = {
enable = mkEnableOption "ssl upstream";
};
extraConfig = mkOption {
type = lines;
default = "";
};
streamConfig = mkOption {
type = lines;
internal = true;
};
upstreamBlock = mkOption {
type = lines;
internal = true;
};
};
config = {
streamConfig = mkMerge (
mapAttrsToList (_: server: mkIf server.enable server.serverDirective) config.servers
++ [ config.extraConfig ]
);
upstreamBlock = mkOptionDefault ''
upstream ${config.name} {
${config.streamConfig}
}
'';
};
};
serverModule = {config, ...}: {
options = with lib.types; {
enable = mkEnableOption "stream server block" // {
@ -125,37 +29,33 @@
preread.enable = mkEnableOption "ngx_stream_ssl_preread_module";
};
proxy = {
upstream = mkOption {
type = nullOr str;
default = null;
ssl = {
enable = mkEnableOption "ssl upstream";
verify = mkEnableOption "proxy_ssl_verify";
};
url = mkOption {
type = nullOr str;
default = null;
};
};
};
config = {
proxy = {
url = mkOptionDefault (
if config.proxy.upstream != null then cfg.upstreams.${config.proxy.upstream}.name
else null
);
};
streamConfig = let
proxyUpstream = cfg.upstreams.${config.proxy.upstream};
in mkMerge [
proxy.ssl.enable = mkIf config.ssl.preread.enable false;
streamConfig = mkMerge [
config.extraConfig
(mkIf config.ssl.preread.enable ''
ssl_preread on;
'')
(mkIf (config.proxy.upstream != null && !config.ssl.preread.enable && proxyUpstream.ssl.enable) ''
proxy_ssl on;
proxy_ssl_verify off;
'')
(mkIf (config.proxy.url != null) ''
proxy_pass ${config.proxy.url};
'')
(mkIf config.ssl.preread.enable
"ssl_preread on;"
)
(mkIf config.proxy.ssl.enable
"proxy_ssl on;"
)
(mkIf (config.proxy.ssl.enable && config.proxy.ssl.verify)
"proxy_ssl_verify on;"
)
(mkIf (config.proxy.url != null) (mkAfter
"proxy_pass ${config.proxy.url};"
))
];
serverBlock = mkOptionDefault ''
server {
@ -171,16 +71,7 @@ in {
modules = [serverModule];
shorthandOnlyDefinesConfig = false;
specialArgs = {
nixosConfig = config;
};
});
default = { };
};
upstreams = mkOption {
type = attrsOf (submoduleWith {
modules = [upstreamModule];
shorthandOnlyDefinesConfig = false;
specialArgs = {
inherit gensokyo-zone;
nixosConfig = config;
};
});
@ -189,8 +80,7 @@ in {
};
config.services.nginx = {
streamConfig = mkMerge (
mapAttrsToList (_: upstream: mkIf upstream.enable upstream.upstreamBlock) cfg.upstreams
++ mapAttrsToList (_: server: mkIf server.enable server.serverBlock) cfg.servers
mapAttrsToList (_: server: mkIf server.enable server.serverBlock) cfg.servers
);
};
}

View file

@ -0,0 +1,342 @@
let
upstreamServerAccessModule = {config, nixosConfig, name, gensokyo-zone, lib, upstreamKind, ...}: let
inherit (lib.options) mkOption;
inherit (lib.modules) mkIf mkMerge mkOptionDefault;
inherit (gensokyo-zone.lib) mkAlmostOptionDefault;
inherit (lib.attrsets) attrValues;
inherit (lib.lists) findSingle;
inherit (lib.trivial) mapNullable;
inherit (nixosConfig.lib) access;
cfg = config.accessService;
system = access.systemFor cfg.system;
service = system.exports.services.${cfg.name};
port = service.ports.${cfg.port};
in {
options = with lib.types; {
accessService = {
enable = mkOption {
type = bool;
};
name = mkOption {
type = nullOr str;
default = null;
};
system = mkOption {
type = nullOr str;
};
id = mkOption {
type = nullOr str;
default = null;
};
port = mkOption {
type = str;
default = "default";
};
network = mkOption {
type = str;
default = "lan";
};
};
};
config = let
confAccess.accessService = {
enable = mkOptionDefault (cfg.id != null || cfg.name != null);
name = mkIf (cfg.id != null) (mkAlmostOptionDefault (
(findSingle (s: s.id == cfg.id) null null (attrValues system.exports.services)).name
));
system = mkMerge [
(mkIf (cfg.id != null) (mkAlmostOptionDefault (access.systemForServiceId cfg.id).name))
(mkOptionDefault (mapNullable (serviceName: (access.systemForService serviceName).name) cfg.name))
];
};
conf = {
enable = lib.warnIf (!port.enable) "${cfg.system}.exports.services.${cfg.name}.ports.${cfg.port} isn't enabled" (
mkAlmostOptionDefault port.enable
);
addr = mkAlmostOptionDefault (access.getAddressFor system.name cfg.network);
port = mkOptionDefault port.port;
ssl.enable = mkIf port.ssl (mkAlmostOptionDefault true);
};
in mkMerge [
confAccess
(mkIf cfg.enable conf)
];
};
upstreamServerModule = {config, name, gensokyo-zone, lib, upstreamKind, ...}: let
inherit (gensokyo-zone.lib) mkAddress6;
inherit (lib.options) mkOption mkEnableOption;
inherit (lib.modules) mkIf mkMerge mkBefore mkOptionDefault;
inherit (lib.attrsets) mapAttrsToList;
inherit (lib.lists) optional;
inherit (lib.strings) optionalString;
inherit (lib.trivial) isBool;
in {
options = with lib.types; {
enable = mkEnableOption "upstream server" // {
default = true;
};
addr = mkOption {
type = str;
default = name;
};
port = mkOption {
type = nullOr port;
};
ssl = {
enable = mkEnableOption "ssl upstream server";
};
server = mkOption {
type = str;
example = "unix:/tmp/backend3";
};
settings = mkOption {
type = attrsOf (oneOf [ int str bool ]);
default = { };
};
extraConfig = mkOption {
type = str;
default = "";
};
serverConfig = mkOption {
type = separatedString " ";
internal = true;
};
serverDirective = mkOption {
type = str;
internal = true;
};
};
config = let
mapSetting = key: value:
if isBool value then mkIf value key
else "${key}=${toString value}";
settings = mapAttrsToList mapSetting config.settings;
port = optionalString (config.port != null) ":${toString config.port}";
in {
server = mkOptionDefault "${mkAddress6 config.addr}${port}";
serverConfig = mkMerge (
[ (mkBefore config.server) ]
++ settings
++ optional (config.extraConfig != "") config.extraConfig
);
serverDirective = mkOptionDefault "server ${config.serverConfig};";
};
};
upstreamModule = {config, name, nixosConfig, gensokyo-zone, lib, upstreamKind, ...}: let
inherit (gensokyo-zone.lib) mkAlmostOptionDefault unmerged;
inherit (lib.options) mkOption mkEnableOption;
inherit (lib.modules) mkIf mkMerge mkOptionDefault;
inherit (lib.attrsets) filterAttrs attrNames attrValues mapAttrsToList mapAttrs' nameValuePair;
inherit (lib.lists) findSingle any;
inherit (lib.strings) replaceStrings;
in {
options = with lib.types; let
upstreamServer = submoduleWith {
modules = [ upstreamServerModule upstreamServerAccessModule ];
specialArgs = {
inherit nixosConfig gensokyo-zone upstreamKind;
upstream = config;
};
};
in {
enable = mkEnableOption "upstream block" // {
default = true;
};
name = mkOption {
type = str;
default = replaceStrings [ "'" ] [ "_" ] name;
};
servers = mkOption {
type = attrsOf upstreamServer;
};
ssl = {
enable = mkEnableOption "ssl upstream";
};
defaultServerName = mkOption {
type = nullOr str;
};
extraConfig = mkOption {
type = lines;
default = "";
};
upstreamConfig = mkOption {
type = lines;
internal = true;
};
upstreamBlock = mkOption {
type = lines;
internal = true;
};
upstreamSettings = mkOption {
type = unmerged.types.attrs;
internal = true;
};
};
config = let
enabledServers = filterAttrs (_: server: server.enable) config.servers;
assertServers = v: assert enabledServers != { }; v;
in {
ssl.enable = mkIf (any (server: server.ssl.enable) (attrValues enabledServers)) (mkAlmostOptionDefault true);
defaultServerName = findSingle (_: true) null null (attrNames enabledServers);
upstreamConfig = mkMerge (
mapAttrsToList (_: server: mkIf server.enable server.serverDirective) config.servers
++ [ config.extraConfig ]
);
upstreamBlock = mkOptionDefault ''
upstream ${config.name} {
${assertServers config.upstreamConfig}
}
'';
upstreamSettings = assertServers (mkOptionDefault {
#extraConfig = config.upstreamConfig;
extraConfig = config.extraConfig;
servers = mapAttrs' (name: server: nameValuePair (if server.enable then server.server else "disabled_${name}") (mkIf server.enable (mkMerge [
server.settings
(mkIf (server.extraConfig != "") {
${config.extraConfig} = true;
})
]))) config.servers;
});
};
};
serverModule = {config, nixosConfig, gensokyo-zone, lib, ...}: let
inherit (gensokyo-zone.lib) mkAlmostOptionDefault;
inherit (lib.options) mkOption;
inherit (lib.modules) mkIf;
inherit (nixosConfig.services) nginx;
in {
options = with lib.types; {
proxy = {
upstream = mkOption {
type = nullOr str;
default = null;
};
};
};
config = let
proxyUpstream = nginx.stream.upstreams.${config.proxy.upstream};
in {
proxy = {
url = mkIf (config.proxy.upstream != null) (mkAlmostOptionDefault (assert proxyUpstream.enable;
proxyUpstream.name
));
ssl.enable = mkIf (config.proxy.upstream != null && proxyUpstream.ssl.enable) (mkAlmostOptionDefault true);
};
};
};
proxyUpstreamModule = {config, nixosConfig, lib, ...}: let
inherit (lib.options) mkOption;
in {
options = with lib.types; {
proxy = {
upstream = mkOption {
type = nullOr str;
};
};
};
};
locationModule = {config, nixosConfig, virtualHost, gensokyo-zone, lib, ...}: let
inherit (gensokyo-zone.lib) mkAlmostOptionDefault;
inherit (lib.modules) mkIf mkOptionDefault;
inherit (nixosConfig.services) nginx;
in {
imports = [ proxyUpstreamModule ];
config = let
proxyUpstream = nginx.upstreams'.${config.proxy.upstream};
proxyScheme = if proxyUpstream.ssl.enable then "https" else "http";
in {
proxy = {
upstream = mkOptionDefault virtualHost.proxy.upstream;
enable = mkIf (config.proxy.upstream != null && virtualHost.proxy.upstream == null) true;
url = mkIf (config.proxy.upstream != null) (mkAlmostOptionDefault (assert proxyUpstream.enable;
"${proxyScheme}://${proxyUpstream.name}"
));
};
};
};
hostModule = {config, nixosConfig, lib, ...}: let
inherit (lib.options) mkOption;
inherit (lib.modules) mkOptionDefault;
in {
imports = [ proxyUpstreamModule ];
options = with lib.types; {
locations = mkOption {
type = attrsOf (submodule locationModule);
};
};
config = {
proxy = {
upstream = mkOptionDefault null;
};
};
};
in {
config,
lib,
gensokyo-zone,
...
}: let
inherit (gensokyo-zone.lib) unmerged;
inherit (lib.options) mkOption;
inherit (lib.modules) mkIf mkMerge;
inherit (lib.attrsets) mapAttrsToList;
cfg = config.services.nginx;
in {
options.services.nginx = with lib.types; {
upstreams' = mkOption {
type = attrsOf (submoduleWith {
modules = [upstreamModule];
shorthandOnlyDefinesConfig = false;
specialArgs = {
inherit gensokyo-zone;
nixosConfig = config;
upstreamKind = "virtualHost";
};
});
default = { };
};
virtualHosts = mkOption {
type = attrsOf (submodule hostModule);
};
stream = {
upstreams = mkOption {
type = attrsOf (submoduleWith {
modules = [upstreamModule];
shorthandOnlyDefinesConfig = false;
specialArgs = {
inherit gensokyo-zone;
nixosConfig = config;
upstreamKind = "stream";
};
});
default = { };
};
servers = mkOption {
type = attrsOf (submoduleWith {
modules = [serverModule];
shorthandOnlyDefinesConfig = false;
});
};
};
};
config.services.nginx = let
confStream.streamConfig = mkMerge (
mapAttrsToList (_: upstream: mkIf upstream.enable upstream.upstreamBlock) cfg.stream.upstreams
);
useUpstreams = true;
confUpstreams.upstreams = mkMerge (mapAttrsToList (_: upstream: mkIf upstream.enable {
${upstream.name} = unmerged.mergeAttrs upstream.upstreamSettings;
}) cfg.upstreams');
confBlock.commonHttpConfig = mkMerge (
mapAttrsToList (_: upstream: mkIf upstream.enable upstream.upstreamBlock) cfg.upstreams'
);
in mkMerge [
confStream
(if useUpstreams then confUpstreams else confBlock)
];
}

View file

@ -1,12 +1,13 @@
{
config,
system,
lib,
inputs,
gensokyo-zone,
...
}: let
inherit (inputs.self.lib.lib) mkAlmostOptionDefault;
inherit (gensokyo-zone.lib) mkAlmostOptionDefault;
inherit (lib.options) mkOption mkEnableOption;
inherit (lib.modules) mkIf mkMerge mkBefore mkAfter mkOptionDefault;
inherit (lib.modules) mkIf mkMerge mkBefore mkAfter mkOptionDefault mkDefault;
inherit (lib.attrsets) mapAttrsToList;
inherit (lib.strings) toLower replaceStrings removePrefix;
inherit (config) networking;
@ -23,7 +24,7 @@
};
config = let
enableVouchLocal = virtualHost.vouch.localSso.enable;
enableVouchTail = enableVouchLocal && tailscale.enable;
enableVouchTail = enableVouchLocal && tailscale.enable && false;
allowOrigin = url: "add_header Access-Control-Allow-Origin ${url};";
in mkIf config.vouch.requireAuth {
lua = mkIf virtualHost.vouch.auth.lua.enable {
@ -183,9 +184,14 @@
${cfg.auth.requestLocation} = { config, xvars, ... }: {
proxy = {
enable = true;
url = vouch.proxyOrigin;
inheritServerDefaults = false;
upstream = mkDefault (
if vouch.doubleProxy.enable then "vouch'proxy"
else if cfg.localSso.enable then "vouch'auth'local"
else "vouch'auth"
);
# nginx-proxied vouch must use X-Forwarded-Host, but vanilla vouch requires Host
host = if vouch.doubleProxy.enable
host = if config.proxy.upstream == "vouch'proxy"
then (if cfg.localSso.enable then vouch.doubleProxy.localServerName else vouch.doubleProxy.serverName)
else xvars.get.host;
headers = {
@ -205,18 +211,11 @@ in {
services.nginx = {
vouch = {
enable = mkEnableOption "vouch auth proxy";
enableLocal = mkEnableOption "use local vouch instance" // {
default = true;
};
localSso = {
enable = mkEnableOption "lan-local auth" // {
default = true;
};
};
proxyOrigin = mkOption {
type = str;
default = "https://login.local.${networking.domain}";
};
doubleProxy = {
enable = mkOption {
type = bool;
@ -254,25 +253,69 @@ in {
};
};
config.services.nginx = {
vouch = mkMerge [
{
proxyOrigin = mkIf (tailscale.enable && !vouch-proxy.enable) (
mkAlmostOptionDefault "http://login.tail.${networking.domain}"
);
}
(mkIf (vouch.enableLocal && vouch-proxy.enable) {
proxyOrigin = let
inherit (vouch-proxy.settings.vouch) listen port;
host =
if listen == "0.0.0.0" || listen == "[::]"
then "localhost"
else listen;
in
mkAlmostOptionDefault "http://${host}:${toString port}";
authUrl = mkAlmostOptionDefault vouch-proxy.authUrl;
url = mkAlmostOptionDefault vouch-proxy.url;
doubleProxy.enable = mkAlmostOptionDefault false;
})
];
upstreams' = let
localVouch = let
inherit (vouch-proxy.settings.vouch) listen port;
host =
if listen == "0.0.0.0" || listen == "[::]"
then "localhost"
else listen;
in {
# TODO: serviceAccess.exportedId = "login";
enable = mkAlmostOptionDefault vouch-proxy.enable;
port = mkIf vouch-proxy.enable (mkOptionDefault port);
addr = mkIf vouch-proxy.enable (mkAlmostOptionDefault host);
};
in {
vouch'auth = {
enable = vouch.enable;
servers = {
local = localVouch;
service = { upstream, ... }: {
enable = mkIf upstream.servers.local.enable false;
accessService = {
name = "vouch-proxy";
id = "login";
};
};
};
};
vouch'auth'local = {
enable = vouch.enable && vouch.localSso.enable;
servers = {
local = localVouch // {
enable = mkAlmostOptionDefault false;
};
service = { upstream, ... }: {
enable = mkIf upstream.servers.local.enable false;
accessService = {
name = "vouch-proxy";
id = "login.local";
};
};
};
};
vouch'proxy = {
enable = vouch.enable && vouch.doubleProxy.enable;
servers = {
lan = { upstream, ... }: {
enable = mkAlmostOptionDefault (!upstream.servers.int.enable);
addr = mkAlmostOptionDefault "login.local.${networking.domain}";
port = mkOptionDefault null;
ssl.enable = mkAlmostOptionDefault true;
};
int = { upstream, ... }: {
enable = mkAlmostOptionDefault system.network.networks.int.enable or false;
addr = mkAlmostOptionDefault "login.int.${networking.domain}";
port = mkOptionDefault null;
};
tail = { upstream, ... }: {
enable = mkAlmostOptionDefault (tailscale.enable && !upstream.servers.lan.enable && !upstream.servers.int.enable);
addr = mkAlmostOptionDefault "login.tail.${networking.domain}";
port = mkOptionDefault null;
};
};
};
};
};
}

View file

@ -18,7 +18,7 @@ let
inherit (lib.attrsets) attrValues filterAttrs mapAttrs mapAttrsToList;
inherit (lib.lists) any;
cfg = config.xvars;
escapeString = value: if value == "" then ''""'' else value;
escapeString = value: if value == "" then ''""'' else toString value;
in {
options = with lib.types; {
xvars = {
@ -34,7 +34,9 @@ let
host = "$host";
referer = "$http_referer";
https = "$https";
proxy_host = null;
proxy_host = "$proxy_host";
proxy_port = "$proxy_port";
proxy_hostport = "${proxy_host}:${proxy_port}";
proxy_scheme = null;
};
};