From 586efcae0e2b5d39fa354417d736937d3fb998b2 Mon Sep 17 00:00:00 2001 From: arcnmx Date: Sun, 21 Apr 2024 17:17:07 -0700 Subject: [PATCH] refactor(nginx): proxy upstream modules --- modules/nixos/nginx/proxied.nix | 5 +- modules/nixos/nginx/proxy.nix | 142 ++++++++++--- modules/nixos/nginx/ssl.nix | 11 +- modules/nixos/nginx/stream.nix | 154 ++------------ modules/nixos/nginx/upstream.nix | 342 +++++++++++++++++++++++++++++++ modules/nixos/nginx/vouch.nix | 109 +++++++--- modules/nixos/nginx/xvars.nix | 6 +- nixos/access/barcodebuddy.nix | 1 + nixos/access/freeipa.nix | 29 ++- nixos/access/freepbx.nix | 7 +- nixos/access/grocy.nix | 1 + nixos/access/home-assistant.nix | 71 ++++--- nixos/access/invidious.nix | 29 ++- nixos/access/keycloak.nix | 42 ++-- nixos/access/kitchencam.nix | 5 +- nixos/access/ldap.nix | 42 ++-- nixos/access/plex.nix | 73 ++++--- nixos/access/unifi.nix | 38 +++- nixos/access/vouch.nix | 41 ++-- nixos/access/zigbee2mqtt.nix | 60 ++++-- systems/hakurei/nixos.nix | 6 +- 21 files changed, 844 insertions(+), 370 deletions(-) create mode 100644 modules/nixos/nginx/upstream.nix diff --git a/modules/nixos/nginx/proxied.nix b/modules/nixos/nginx/proxied.nix index e1d070b2..addcfe15 100644 --- a/modules/nixos/nginx/proxied.nix +++ b/modules/nixos/nginx/proxied.nix @@ -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 = { diff --git a/modules/nixos/nginx/proxy.nix b/modules/nixos/nginx/proxy.nix index a21f01a4..7d8e6104 100644 --- a/modules/nixos/nginx/proxy.nix +++ b/modules/nixos/nginx/proxy.nix @@ -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 { diff --git a/modules/nixos/nginx/ssl.nix b/modules/nixos/nginx/ssl.nix index f1113a2a..f22422e2 100644 --- a/modules/nixos/nginx/ssl.nix +++ b/modules/nixos/nginx/ssl.nix @@ -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 { diff --git a/modules/nixos/nginx/stream.nix b/modules/nixos/nginx/stream.nix index 938d5aa0..2f3be334 100644 --- a/modules/nixos/nginx/stream.nix +++ b/modules/nixos/nginx/stream.nix @@ -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 ); }; } diff --git a/modules/nixos/nginx/upstream.nix b/modules/nixos/nginx/upstream.nix new file mode 100644 index 00000000..c39a468e --- /dev/null +++ b/modules/nixos/nginx/upstream.nix @@ -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) + ]; +} diff --git a/modules/nixos/nginx/vouch.nix b/modules/nixos/nginx/vouch.nix index bc1f3ec1..7da00d14 100644 --- a/modules/nixos/nginx/vouch.nix +++ b/modules/nixos/nginx/vouch.nix @@ -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; + }; + }; + }; + }; }; } diff --git a/modules/nixos/nginx/xvars.nix b/modules/nixos/nginx/xvars.nix index c316bea6..09856f6a 100644 --- a/modules/nixos/nginx/xvars.nix +++ b/modules/nixos/nginx/xvars.nix @@ -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; }; }; diff --git a/nixos/access/barcodebuddy.nix b/nixos/access/barcodebuddy.nix index 23939079..1fb416b7 100644 --- a/nixos/access/barcodebuddy.nix +++ b/nixos/access/barcodebuddy.nix @@ -8,6 +8,7 @@ name.shortServer = mkDefault "bbuddy"; serverName = "@bbuddy_internal"; in { + config.services.nginx.vouch.enable = true; config.services.nginx.virtualHosts = { barcodebuddy'php = mkIf barcodebuddy.enable { inherit serverName; diff --git a/nixos/access/freeipa.nix b/nixos/access/freeipa.nix index da502d43..c0bfb455 100644 --- a/nixos/access/freeipa.nix +++ b/nixos/access/freeipa.nix @@ -17,12 +17,13 @@ let extraConfig = '' ssl_verify_client optional_no_ca; ''; - locations' = domain: { + locations = { "/" = { config, xvars, ... }: { proxy = { enable = true; url = mkDefault access.proxyPass; - host = mkDefault domain; + host = mkDefault virtualHosts.freeipa.serverName; + ssl.host = mkDefault config.proxy.host; headers = { rewriteReferer.enable = true; set = { @@ -37,15 +38,8 @@ let }; proxyPass = mkDefault access.proxyPass; recommendedProxySettings = false; - extraConfig = '' - proxy_ssl_server_name on; - proxy_ssl_name ${domain}; - ''; }; }; - locations = locations' virtualHosts.freeipa.serverName; - caLocations = locations' virtualHosts.freeipa'ca.serverName; - kTLS = mkDefault true; in { imports = let inherit (meta) nixos; @@ -200,12 +194,12 @@ in { kticket4 = mkKrb5Server null "ticket4"; }; }; + conf.upstreams.ldap'access.servers.ldaps.enable = false; conf.servers = { ldap = { listen = { ldaps.port = mkIf access.preread.enable (mkDefault access.preread.ldapPort); }; - proxy.upstream = mkDefault "ldap"; ssl.cert.copyFromVhost = mkDefault "freeipa"; }; }; @@ -240,7 +234,7 @@ in { in { freeipa = { name.shortServer = mkDefault "idp"; - inherit locations extraConfig kTLS; + inherit locations extraConfig; ssl.force = mkDefault true; }; freeipa'web = { @@ -248,21 +242,26 @@ in { force = mkDefault virtualHosts.freeipa.ssl.force; cert.copyFromVhost = "freeipa"; }; - inherit name locations extraConfig kTLS; + inherit name locations extraConfig; }; freeipa'ca = { name.shortServer = mkDefault "idp-ca"; - locations = caLocations; + locations."/" = mkMerge [ + locations."/" + { + proxy.host = virtualHosts.freeipa'ca.serverName; + } + ]; ssl = { force = mkDefault virtualHosts.freeipa.ssl.force; cert.copyFromVhost = "freeipa"; }; - inherit extraConfig kTLS; + inherit extraConfig; }; freeipa'web'local = { ssl.cert.copyFromVhost = "freeipa'web"; local.enable = true; - inherit name locations kTLS; + inherit name locations; }; freeipa'ldap = { serverName = mkDefault ldap.domain; diff --git a/nixos/access/freepbx.nix b/nixos/access/freepbx.nix index 20084c1e..80e50925 100644 --- a/nixos/access/freepbx.nix +++ b/nixos/access/freepbx.nix @@ -51,14 +51,13 @@ in { } ]; name.shortServer = mkDefault "pbx"; - kTLS = mkDefault true; in { freepbx = { vouch.enable = mkDefault true; ssl.force = true; proxy.url = mkDefault url; locations = allLocations; - inherit name extraConfig kTLS; + inherit name extraConfig; }; freepbx'ucp = { serverName = mkDefault nginx.virtualHosts.freepbx.serverName; @@ -83,7 +82,7 @@ in { locations = { inherit (locations) "/socket.io"; }; - inherit extraConfig kTLS; + inherit extraConfig; }; freepbx'local = { listen' = { @@ -101,7 +100,7 @@ in { proxy.url = mkDefault nginx.virtualHosts.freepbx.proxy.url; local.enable = true; locations = allLocations; - inherit name extraConfig kTLS; + inherit name extraConfig; }; }; }; diff --git a/nixos/access/grocy.nix b/nixos/access/grocy.nix index 76dc135c..737cdb80 100644 --- a/nixos/access/grocy.nix +++ b/nixos/access/grocy.nix @@ -48,6 +48,7 @@ in { config.services.nginx = { lua.http.enable = true; + vouch.enable = true; virtualHosts = { grocy'php = mkIf grocy.enable { inherit serverName; diff --git a/nixos/access/home-assistant.nix b/nixos/access/home-assistant.nix index 4b18f65b..25cb3e5c 100644 --- a/nixos/access/home-assistant.nix +++ b/nixos/access/home-assistant.nix @@ -1,11 +1,10 @@ { config, lib, - access, ... }: let - inherit (lib.modules) mkDefault; - inherit (config.services) home-assistant nginx; + inherit (lib.modules) mkIf mkDefault; + inherit (config.services) nginx home-assistant; name.shortServer = mkDefault "home"; listen' = { http = { }; @@ -16,35 +15,55 @@ extraParameters = [ "default_server" ]; }; }; + upstreamName = "home-assistant'access"; in { - config.services.nginx.virtualHosts = { - home-assistant = { - inherit name; - locations."/" = { - proxy = { - websocket.enable = true; - headers.enableRecommended = true; + config.services.nginx = { + upstreams'.${upstreamName}.servers = { + local = { + enable = mkDefault home-assistant.enable; + addr = mkDefault "localhost"; + port = mkIf home-assistant.enable (mkDefault home-assistant.config.http.server_port); + }; + service = { upstream, ... }: { + enable = mkIf upstream.servers.local.enable (mkDefault false); + accessService = { + name = "home-assistant"; }; - proxyPass = mkDefault ( - if home-assistant.enable then "http://localhost:${toString home-assistant.config.http.server_port}" - else access.proxyUrlFor { serviceName = "home-assistant"; } - ); }; }; - home-assistant'local = { - inherit name listen'; - ssl.cert.copyFromVhost = "home-assistant"; - local.enable = mkDefault true; - locations."/" = { - proxy = { - websocket.enable = true; - headers.enableRecommended = true; + virtualHosts = let + copyFromVhost = mkDefault "home-assistant"; + locations = { + "/" = { + proxy.enable = true; }; - proxyPass = (mkDefault - nginx.virtualHosts.home-assistant.locations."/".proxyPass - ); + "/api/websocket" = { + proxy = { + enable = true; + websocket.enable = true; + }; + }; + }; + in { + home-assistant = { + inherit name locations; + proxy.upstream = mkDefault upstreamName; + }; + home-assistant'local = { + inherit name listen' locations; + ssl.cert = { + inherit copyFromVhost; + }; + proxy = { + inherit copyFromVhost; + }; + local.enable = mkDefault true; }; }; }; - config.networking.firewall.allowedTCPPorts = [ home-assistant.config.http.server_port ]; + config.networking.firewall.allowedTCPPorts = let + inherit (nginx.virtualHosts.home-assistant'local) listen'; + in mkIf nginx.virtualHosts.home-assistant'local.enable [ + (mkIf listen'.hass.enable listen'.hass.port) + ]; } diff --git a/nixos/access/invidious.nix b/nixos/access/invidious.nix index 53b88674..93193cb1 100644 --- a/nixos/access/invidious.nix +++ b/nixos/access/invidious.nix @@ -1,15 +1,28 @@ { config, - access, lib, ... }: let - inherit (lib.modules) mkMerge mkBefore mkDefault; + inherit (lib.modules) mkIf mkMerge mkBefore mkDefault; inherit (lib.strings) replaceStrings concatStringsSep concatMapStringsSep escapeRegex; inherit (config.services.nginx) virtualHosts; cfg = config.services.invidious; + upstreamName = "invidious'access"; in { config.services.nginx = { + upstreams'.${upstreamName}.servers = { + local = { + enable = mkDefault cfg.enable; + addr = mkDefault "localhost"; + port = mkIf cfg.enable (mkDefault cfg.port); + }; + service = { upstream, ... }: { + enable = mkIf upstream.servers.local.enable (mkDefault false); + accessService = { + name = "invidious"; + }; + }; + }; virtualHosts = let invidiousDomains = virtualHosts.invidious.allServerNames @@ -36,12 +49,11 @@ in { ''; }; name.shortServer = mkDefault "yt"; - kTLS = mkDefault true; localDomains = virtualHosts.invidious'local.allServerNames; in { invidious = { # lua can't handle HTTP 2.0 requests, so layer it behind another proxy... - inherit name extraConfig kTLS; + inherit name extraConfig; proxy = { url = mkDefault "http://localhost:${toString config.services.nginx.defaultHTTPListenPort}"; host = mkDefault virtualHosts.invidious'int.serverName; @@ -88,10 +100,7 @@ in { }; proxy = { host = mkDefault xvars.get.host; - url = mkDefault (if cfg.enable - then "http://localhost:${toString cfg.port}" - else access.proxyUrlFor { serviceName = "invidious"; } - ); + upstream = mkDefault upstreamName; }; locations = { "/" = mkMerge [ @@ -107,11 +116,11 @@ in { local.enable = true; ssl.cert.copyFromVhost = "invidious"; proxy = { + copyFromVhost = mkDefault "invidious'int"; host = mkDefault xvars.get.host; - url = mkDefault virtualHosts.invidious'int.proxy.url; }; locations."/" = location; - inherit name extraConfig kTLS; + inherit name extraConfig; }; }; lua.http.enable = true; diff --git a/nixos/access/keycloak.nix b/nixos/access/keycloak.nix index d92e0696..0f598c8b 100644 --- a/nixos/access/keycloak.nix +++ b/nixos/access/keycloak.nix @@ -1,33 +1,49 @@ { config, lib, - access, ... }: let - inherit (lib.modules) mkDefault; + inherit (lib.modules) mkIf mkDefault; cfg = config.services.keycloak; - inherit (config.services) nginx; + upstreamName = "keycloak'access"; + locations."/".proxy.enable = true; + name.shortServer = mkDefault "sso"; + copyFromVhost = mkDefault "keycloak"; in { config.services.nginx = { + upstreams'.${upstreamName}.servers = { + local = mkIf cfg.enable { + enable = mkDefault true; + addr = mkDefault "localhost"; + port = mkDefault cfg.port; + ssl.enable = mkIf (cfg.protocol == "https") true; + }; + access = { upstream, ... }: { + enable = mkDefault (!upstream.servers.local.enable or false); + accessService = { + name = "keycloak"; + port = "https"; + }; + }; + }; virtualHosts = { keycloak = { - name.shortServer = mkDefault "sso"; + inherit name locations; ssl.force = mkDefault true; - locations."/".proxyPass = let - url = mkDefault "${cfg.protocol}://localhost:${toString cfg.port}"; - in mkDefault ( - if cfg.enable then url - else access.proxyUrlFor { serviceName = "keycloak"; portName = "https"; } - ); + proxy.upstream = mkDefault upstreamName; }; keycloak'local = { - name.shortServer = mkDefault "sso"; + inherit name locations; ssl = { force = mkDefault true; - cert.copyFromVhost = "keycloak"; + cert = { + inherit copyFromVhost; + }; }; local.enable = true; - locations."/".proxyPass = mkDefault nginx.virtualHosts.keycloak.locations."/".proxyPass; + proxy = { + inherit copyFromVhost; + }; }; }; }; diff --git a/nixos/access/kitchencam.nix b/nixos/access/kitchencam.nix index 5ca8c47d..3f43a85a 100644 --- a/nixos/access/kitchencam.nix +++ b/nixos/access/kitchencam.nix @@ -40,14 +40,13 @@ in { }; }; name.shortServer = mkDefault "kitchen"; - kTLS = mkDefault true; in { kitchencam = { - inherit name locations listen' kTLS; + inherit name locations listen'; vouch.enable = true; }; kitchencam'local = { - inherit name listen' kTLS; + inherit name listen'; ssl.cert.copyFromVhost = "kitchencam"; local.enable = true; locations = mapAttrs (name: location: location // { diff --git a/nixos/access/ldap.nix b/nixos/access/ldap.nix index 5e45216d..1741a9b5 100644 --- a/nixos/access/ldap.nix +++ b/nixos/access/ldap.nix @@ -12,8 +12,7 @@ let inherit (config.services) nginx; portPlaintext = 389; portSsl = 636; - system = access.systemForService "ldap"; - inherit (system.exports.services) ldap; + upstreamName = "ldap'access"; in { options.services.nginx.access.ldap = with lib.types; { domain = mkOption { @@ -36,19 +35,32 @@ in { config = { services.nginx = { stream = { - upstreams = let - addr = mkAlmostOptionDefault (access.getAddressFor system.name "lan"); - in { - ldap.servers.access = { - inherit addr; - port = mkOptionDefault ldap.ports.default.port; + upstreams = { + ${upstreamName}.servers = { + ldaps = { + accessService = { + inherit (nginx.stream.upstreams.ldaps.servers.access.accessService) system name id port; + }; + }; + ldap = { upstream, ... }: { + enable = mkIf upstream.servers.ldaps.enable false; + accessService = { + inherit (nginx.stream.upstreams.ldap.servers.access.accessService) system name id port; + }; + }; }; - ldaps = { - enable = mkAlmostOptionDefault ldap.ports.ssl.enable; - ssl.enable = mkAlmostOptionDefault true; + ldap.servers.access = { + accessService = { + name = "ldap"; + }; + }; + ldaps = { config, ... }: { + enable = mkAlmostOptionDefault config.servers.access.enable; servers.access = { - inherit addr; - port = mkOptionDefault ldap.ports.ssl.port; + accessService = { + name = "ldap"; + port = "ssl"; + }; }; }; }; @@ -60,9 +72,7 @@ in { ssl = true; }; }; - proxy.upstream = mkAlmostOptionDefault ( - if nginx.stream.upstreams.ldaps.enable then "ldaps" else "ldap" - ); + proxy.upstream = mkAlmostOptionDefault upstreamName; }; }; }; diff --git a/nixos/access/plex.nix b/nixos/access/plex.nix index 24a314fb..4294f5b1 100644 --- a/nixos/access/plex.nix +++ b/nixos/access/plex.nix @@ -7,41 +7,63 @@ inherit (lib.modules) mkIf mkDefault; inherit (config.services) nginx; cfg = config.services.plex; + upstreamName = "plex'access"; in { config.services.nginx = { + upstreams'.${upstreamName}.servers = { + local = { + enable = mkDefault cfg.enable; + addr = mkDefault "localhost"; + port = mkDefault cfg.port; + }; + access = { upstream, ... }: { + enable = mkDefault (!upstream.servers.local.enable); + accessService.name = "plex"; + }; + }; virtualHosts = let extraConfig = '' # Some players don't reopen a socket and playback stops totally instead of resuming after an extended pause send_timeout 100m; - # Plex headers - proxy_set_header X-Plex-Client-Identifier $http_x_plex_client_identifier; - proxy_set_header X-Plex-Device $http_x_plex_device; - proxy_set_header X-Plex-Device-Name $http_x_plex_device_name; - proxy_set_header X-Plex-Platform $http_x_plex_platform; - proxy_set_header X-Plex-Platform-Version $http_x_plex_platform_version; - proxy_set_header X-Plex-Product $http_x_plex_product; - proxy_set_header X-Plex-Token $http_x_plex_token; - proxy_set_header X-Plex-Version $http_x_plex_version; - proxy_set_header X-Plex-Nocache $http_x_plex_nocache; - proxy_set_header X-Plex-Provides $http_x_plex_provides; - proxy_set_header X-Plex-Device-Vendor $http_x_plex_device_vendor; - proxy_set_header X-Plex-Model $http_x_plex_model; # Buffering off send to the client as soon as the data is received from Plex. proxy_redirect off; proxy_buffering off; ''; - locations."/" = { - proxy.websocket.enable = mkDefault true; - proxyPass = mkDefault (if cfg.enable - then "http://localhost:${toString cfg.port}" - else access.proxyUrlFor { serviceName = "plex"; } - ); + headers.set = { + X-Plex-Client-Identifier = "$http_x_plex_client_identifier"; + X-Plex-Device = "$http_x_plex_device"; + X-Plex-Device-Name = "$http_x_plex_device_name"; + X-Plex-Platform = "$http_x_plex_platform"; + X-Plex-Platform-Version = "$http_x_plex_platform_version"; + X-Plex-Product = "$http_x_plex_product"; + X-Plex-Token = "$http_x_plex_token"; + X-Plex-Version = "$http_x_plex_version"; + X-Plex-Nocache = "$http_x_plex_nocache"; + X-Plex-Provides = "$http_x_plex_provides"; + X-Plex-Device-Vendor = "$http_x_plex_device_vendor"; + X-Plex-Model = "$http_x_plex_model"; + }; + locations = { + "/" = { + proxy = { + enable = true; + inherit headers; + }; + }; + "/websockets/" = { + proxy = { + enable = true; + websocket.enable = true; + inherit headers; + }; + }; }; name.shortServer = mkDefault "plex"; - kTLS = mkDefault true; + copyFromVhost = mkDefault "plex"; in { plex = { - inherit name locations extraConfig kTLS; + inherit name locations extraConfig; + proxy.upstream = mkDefault upstreamName; listen' = { http = { }; https.ssl = true; @@ -53,8 +75,13 @@ in { }; }; plex'local = { - inherit name locations extraConfig kTLS; - ssl.cert.copyFromVhost = "plex"; + inherit name locations extraConfig; + ssl.cert = { + inherit copyFromVhost; + }; + proxy = { + inherit copyFromVhost; + }; local.enable = true; }; }; diff --git a/nixos/access/unifi.nix b/nixos/access/unifi.nix index 6a4cf0f6..636cfb18 100644 --- a/nixos/access/unifi.nix +++ b/nixos/access/unifi.nix @@ -1,14 +1,29 @@ { config, lib, - access, ... }: let inherit (lib.modules) mkDefault; - inherit (config.services) nginx; cfg = config.services.unifi; + upstreamName = "unifi'access"; in { config.services.nginx = { + vouch.enable = true; + upstreams'.${upstreamName}.servers = { + local = { + enable = mkDefault cfg.enable; + addr = mkDefault "localhost"; + port = mkDefault 8443; + ssl.enable = mkDefault true; + }; + access = { upstream, ... }: { + enable = mkDefault (!upstream.servers.local.enable); + accessService = { + name = "unifi"; + port = "management"; + }; + }; + }; virtualHosts = let extraConfig = '' proxy_redirect off; @@ -26,22 +41,23 @@ in { }; }; name.shortServer = mkDefault "unifi"; - kTLS = mkDefault true; + copyFromVhost = mkDefault "unifi"; in { unifi = { - inherit name extraConfig kTLS locations; + inherit name extraConfig locations; vouch.enable = mkDefault true; ssl.force = mkDefault true; - proxy.url = mkDefault (if cfg.enable - then "https://localhost:8443" - else access.proxyUrlFor { serviceName = "unifi"; portName = "management"; } - ); + proxy.upstream = mkDefault upstreamName; }; unifi'local = { - inherit name extraConfig kTLS locations; - ssl.cert.copyFromVhost = "unifi"; + inherit name extraConfig locations; + ssl.cert = { + inherit copyFromVhost; + }; local.enable = true; - proxy.url = mkDefault nginx.virtualHosts.unifi.proxy.url; + proxy = { + inherit copyFromVhost; + }; }; }; }; diff --git a/nixos/access/vouch.nix b/nixos/access/vouch.nix index c705b145..ed929baf 100644 --- a/nixos/access/vouch.nix +++ b/nixos/access/vouch.nix @@ -1,15 +1,23 @@ { config, lib, - access, ... }: let - inherit (lib.modules) mkIf mkMerge mkDefault; - inherit (config) networking; + inherit (lib.modules) mkIf mkDefault; inherit (config.services) tailscale nginx; cfg = config.services.vouch-proxy; in { config.services.nginx = { + upstreams'.vouch'access.servers.access = { + accessService = { + inherit (nginx.upstreams'.vouch'auth.servers.service.accessService) system name id port; + }; + }; + upstreams'.vouch'access'local.servers.access = { + accessService = { + inherit (nginx.upstreams'.vouch'auth'local.servers.service.accessService) system name id port; + }; + }; virtualHosts = let locations = { "/" = { @@ -25,13 +33,6 @@ in { local.denyGlobal = true; }; }; - localLocations = kanidmDomain: mkIf (nginx.vouch.localSso.enable && false) { - "/" = { xvars, ... }: { - extraConfig = '' - proxy_redirect https://sso.${networking.domain}/ ${xvars.get.scheme}://${kanidmDomain}/; - ''; - }; - }; name.shortServer = mkDefault "login"; in { vouch = { xvars, ... }: { @@ -39,9 +40,7 @@ in { serverAliases = [ nginx.vouch.doubleProxy.serverName ]; proxied.enable = true; proxy = { - url = mkDefault ( - access.proxyUrlFor { serviceName = "vouch-proxy"; serviceId = "login"; } - ); + upstream = mkDefault "vouch'access"; host = mkDefault xvars.get.host; }; local.denyGlobal = true; @@ -54,9 +53,7 @@ in { serverAliases = mkIf cfg.enable [ nginx.vouch.doubleProxy.localServerName ]; proxied.enable = true; proxy = { - url = mkDefault ( - access.proxyUrlFor { serviceName = "vouch-proxy"; serviceId = "login.local"; } - ); + upstream = mkDefault "vouch'access'local"; host = mkDefault xvars.get.host; }; local.enable = true; @@ -64,10 +61,7 @@ in { force = true; cert.copyFromVhost = "vouch"; }; - locations = mkMerge [ - locations - (localLocations "sso.local.${networking.domain}") - ]; + inherit locations; }; vouch'tail = { xvars, ... }: { enable = mkDefault (tailscale.enable && !nginx.virtualHosts.vouch'local.name.includeTailscale); @@ -78,13 +72,10 @@ in { }; local.enable = true; proxy = { - url = mkDefault nginx.virtualHosts.vouch'local.locations."/".proxyPass; + upstream = mkDefault nginx.virtualHosts.vouch'local.proxy.upstream; host = mkDefault xvars.get.host; }; - locations = mkMerge [ - locations - (localLocations "sso.tail.${networking.domain}") - ]; + inherit locations; }; }; }; diff --git a/nixos/access/zigbee2mqtt.nix b/nixos/access/zigbee2mqtt.nix index 2778d225..cbb261e9 100644 --- a/nixos/access/zigbee2mqtt.nix +++ b/nixos/access/zigbee2mqtt.nix @@ -1,34 +1,56 @@ { config, lib, - access, ... }: let - inherit (lib.modules) mkDefault; + inherit (lib.modules) mkIf mkDefault; inherit (config.services) nginx zigbee2mqtt; - name.shortServer = mkDefault "z2m"; + upstreamName = "zigbee2mqtt'access"; in { config.services.nginx = { - virtualHosts = { - zigbee2mqtt = { - locations."/" = { - proxy.websocket.enable = true; - proxyPass = mkDefault ( - if zigbee2mqtt.enable then "http://localhost:${toString zigbee2mqtt.settings.frontend.port}" - else access.proxyUrlFor { serviceName = "zigbee2mqtt"; } - ); + vouch.enable = mkIf nginx.virtualHosts.zigbee2mqtt.enable true; + upstreams'.${upstreamName}.servers = { + local = { + enable = mkDefault zigbee2mqtt.enable; + addr = mkDefault "localhost"; + port = mkIf zigbee2mqtt.enable (mkDefault zigbee2mqtt.settings.frontend.port); + }; + service = { upstream, ... }: { + enable = mkIf upstream.servers.local.enable (mkDefault false); + accessService = { + name = "zigbee2mqtt"; }; - inherit name; + }; + }; + virtualHosts = let + locations = { + "/" = { + proxy.enable = true; + }; + "/api" = { + proxy = { + enable = true; + websocket.enable = true; + }; + }; + }; + name.shortServer = mkDefault "z2m"; + copyFromVhost = mkDefault "zigbee2mqtt"; + in { + zigbee2mqtt = { + proxy = { + upstream = mkDefault upstreamName; + }; + inherit name locations; vouch.enable = true; }; zigbee2mqtt'local = { - inherit name; - ssl.cert.copyFromVhost = "zigbee2mqtt"; - locations."/" = { - proxy.websocket.enable = true; - proxyPass = mkDefault ( - nginx.virtualHosts.zigbee2mqtt.locations."/".proxyPass - ); + inherit name locations; + ssl.cert = { + inherit copyFromVhost; + }; + proxy = { + inherit copyFromVhost; }; local.enable = true; }; diff --git a/systems/hakurei/nixos.nix b/systems/hakurei/nixos.nix index 54694bd3..fdbdc213 100644 --- a/systems/hakurei/nixos.nix +++ b/systems/hakurei/nixos.nix @@ -215,7 +215,11 @@ in { }; services.nginx = { - vouch.enableLocal = false; + vouch.enable = true; + upstreams' = { + vouch'auth.servers.local.enable = false; + vouch'auth'local.servers.local.enable = true; + }; stream.servers = { mosquitto.ssl.cert.name = "mosquitto"; };