From b0a3da835c74c59bc3dd48110010f3741d2f3dc6 Mon Sep 17 00:00:00 2001 From: arcnmx Date: Tue, 23 Apr 2024 13:18:47 -0700 Subject: [PATCH] refactor(nginx): ssl preread --- modules/nixos/nginx/preread.nix | 103 +++++++++++++++++++++++++++++++ modules/nixos/nginx/proxy.nix | 6 +- modules/nixos/nginx/stream.nix | 7 --- modules/nixos/nginx/upstream.nix | 27 +++++--- nixos/access/freeipa.nix | 71 +++++++-------------- nixos/access/nginx.nix | 7 ++- nixos/nginx.nix | 15 +++-- 7 files changed, 162 insertions(+), 74 deletions(-) create mode 100644 modules/nixos/nginx/preread.nix diff --git a/modules/nixos/nginx/preread.nix b/modules/nixos/nginx/preread.nix new file mode 100644 index 00000000..3c572621 --- /dev/null +++ b/modules/nixos/nginx/preread.nix @@ -0,0 +1,103 @@ +let + serverModule = {config, nixosConfig, name, gensokyo-zone, lib, ...}: let + inherit (gensokyo-zone.lib) mkAlmostOptionDefault; + inherit (lib.options) mkOption mkEnableOption; + inherit (lib.modules) mkIf mkBefore mkOptionDefault; + inherit (lib.attrsets) mapAttrsToList; + inherit (lib.lists) optional; + inherit (lib.strings) concatStringsSep replaceStrings; + cfg = config.ssl.preread; + inherit (nixosConfig.services) nginx; + in { + options.ssl.preread = with lib.types; { + enable = mkEnableOption "ngx_stream_ssl_preread_module"; + upstream = mkOption { + type = str; + default = "$preread_" + replaceStrings [ "'" ] [ "_" ] name; + }; + upstreams = mkOption { + type = nullOr (attrsOf str); + }; + streamConfig = mkOption { + type = lines; + }; + }; + config = let + inherit (nginx.stream) upstreams; + mkUpstream = host: upstream: "${host} ${upstreams.${upstream}.name};"; + upstreams' = removeAttrs cfg.upstreams [ "default" ]; + upstreamLines = mapAttrsToList mkUpstream upstreams' + ++ optional (cfg.upstreams ? default) (mkUpstream "default" cfg.upstreams.default); + in { + ssl.preread = { + streamConfig = mkIf (cfg.upstreams != null) '' + map $ssl_preread_server_name ${cfg.upstream} { + hostnames; + ${concatStringsSep "\n " upstreamLines} + } + ''; + }; + proxy = mkIf cfg.enable { + ssl.enable = false; + upstream = mkAlmostOptionDefault cfg.upstream; + }; + streamConfig = mkIf cfg.enable "ssl_preread on;"; + serverBlock = mkIf cfg.enable (mkOptionDefault (mkBefore cfg.streamConfig)); + }; + }; +in {config, gensokyo-zone, lib, ...}: let + inherit (gensokyo-zone.lib) mkAlmostOptionDefault; + inherit (lib.options) mkOption mkEnableOption; + inherit (lib.modules) mkIf mkDefault mkOptionDefault; + cfg = config.services.nginx.ssl.preread; +in { + options.services.nginx = with lib.types; { + ssl.preread = { + enable = mkEnableOption "ssl preread"; + listenPort = mkOption { + type = port; + default = 444; + }; + serverPort = mkOption { + type = port; + default = 443; + }; + serverName = mkOption { + type = str; + default = "preread'https"; + }; + upstreamName = mkOption { + type = str; + default = "preread'nginx"; + }; + }; + stream.servers = mkOption { + type = attrsOf (submoduleWith { + modules = [serverModule]; + shorthandOnlyDefinesConfig = false; + }); + }; + }; + config = { + services.nginx = { + defaultSSLListenPort = mkIf cfg.enable cfg.listenPort; + stream = { + upstreams.${cfg.upstreamName} = mkIf cfg.enable { + ssl.enable = true; + servers.access = { + addr = mkDefault "localhost"; + port = mkOptionDefault cfg.listenPort; + }; + }; + servers.${cfg.serverName} = { + enable = mkIf (!cfg.enable) (mkAlmostOptionDefault false); + listen.https.port = cfg.serverPort; + ssl.preread = { + enable = true; + upstreams.default = mkOptionDefault cfg.upstreamName; + }; + }; + }; + }; + }; +} diff --git a/modules/nixos/nginx/proxy.nix b/modules/nixos/nginx/proxy.nix index df312f5d..8240257b 100644 --- a/modules/nixos/nginx/proxy.nix +++ b/modules/nixos/nginx/proxy.nix @@ -93,6 +93,8 @@ let url = parseUrl config.proxyPass; upstream = nginx.upstreams'.${cfg.upstream}; upstreamServer = upstream.servers.${upstream.defaultServerName}; + dynamicUpstream = hasPrefix "$" cfg.upstream; + hasUpstream = cfg.upstream != null && !dynamicUpstream && upstream.defaultServerName != null; recommendedHeaders = { Host = if cfg.host == null then xvars.get.proxy_hostport else cfg.host; Referer = xvars.get.referer; @@ -209,11 +211,11 @@ let mapNullable (_: url.path) config.proxyPass ); host = mkOptionDefault ( - if cfg.upstream != null then assert url.host == upstream.name; upstreamServer.addr + if hasUpstream then assert url.host == upstream.name; upstreamServer.addr else mapNullable (_: url.host) config.proxyPass ); port = mkOptionDefault ( - if cfg.upstream != null && url.port == null then assert url.host == upstream.name; upstreamServer.port + if hasUpstream && url.port == null then assert url.host == upstream.name; upstreamServer.port else mapNullable (_: url.port) config.proxyPass ); }; diff --git a/modules/nixos/nginx/stream.nix b/modules/nixos/nginx/stream.nix index 2f3be334..ff9a6659 100644 --- a/modules/nixos/nginx/stream.nix +++ b/modules/nixos/nginx/stream.nix @@ -25,9 +25,6 @@ type = lines; internal = true; }; - ssl = { - preread.enable = mkEnableOption "ngx_stream_ssl_preread_module"; - }; proxy = { ssl = { enable = mkEnableOption "ssl upstream"; @@ -41,12 +38,8 @@ }; config = { - proxy.ssl.enable = mkIf config.ssl.preread.enable false; streamConfig = mkMerge [ config.extraConfig - (mkIf config.ssl.preread.enable - "ssl_preread on;" - ) (mkIf config.proxy.ssl.enable "proxy_ssl on;" ) diff --git a/modules/nixos/nginx/upstream.nix b/modules/nixos/nginx/upstream.nix index c39a468e..ecabd71c 100644 --- a/modules/nixos/nginx/upstream.nix +++ b/modules/nixos/nginx/upstream.nix @@ -204,6 +204,7 @@ let inherit (gensokyo-zone.lib) mkAlmostOptionDefault; inherit (lib.options) mkOption; inherit (lib.modules) mkIf; + inherit (lib.strings) hasPrefix; inherit (nixosConfig.services) nginx; in { options = with lib.types; { @@ -217,12 +218,15 @@ let config = let proxyUpstream = nginx.stream.upstreams.${config.proxy.upstream}; + dynamicUpstream = hasPrefix "$" config.proxy.upstream; + hasUpstream = config.proxy.upstream != null && !dynamicUpstream; + proxyPass = + if dynamicUpstream then config.proxy.upstream + else assert proxyUpstream.enable; proxyUpstream.name; 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); + url = mkIf (config.proxy.upstream != null) (mkAlmostOptionDefault proxyPass); + ssl.enable = mkIf (hasUpstream && proxyUpstream.ssl.enable) (mkAlmostOptionDefault true); }; }; }; @@ -240,20 +244,27 @@ let locationModule = {config, nixosConfig, virtualHost, gensokyo-zone, lib, ...}: let inherit (gensokyo-zone.lib) mkAlmostOptionDefault; inherit (lib.modules) mkIf mkOptionDefault; + inherit (lib.strings) hasPrefix; inherit (nixosConfig.services) nginx; in { imports = [ proxyUpstreamModule ]; config = let proxyUpstream = nginx.upstreams'.${config.proxy.upstream}; - proxyScheme = if proxyUpstream.ssl.enable then "https" else "http"; + proxyScheme = if config.proxy.ssl.enabled then "https" else "http"; + dynamicUpstream = hasPrefix "$" config.proxy.upstream; + hasUpstream = config.proxy.upstream != null && !dynamicUpstream; + proxyHost = + if dynamicUpstream then config.proxy.upstream + else assert proxyUpstream.enable; proxyUpstream.name; 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}" - )); + url = mkIf (config.proxy.upstream != null) (mkAlmostOptionDefault + "${proxyScheme}://${proxyHost}" + ); + ssl.enabled = mkAlmostOptionDefault (if hasUpstream then proxyUpstream.ssl.enable else false); }; }; }; diff --git a/nixos/access/freeipa.nix b/nixos/access/freeipa.nix index c0bfb455..9254aa59 100644 --- a/nixos/access/freeipa.nix +++ b/nixos/access/freeipa.nix @@ -6,7 +6,7 @@ ... }: let - inherit (gensokyo-zone.lib) mkAddress6; + inherit (gensokyo-zone.lib) mkAddress6 mapOptionDefaults; inherit (lib.options) mkOption mkEnableOption; inherit (lib.modules) mkIf mkMerge mkDefault mkOptionDefault; inherit (config.services) tailscale; @@ -52,13 +52,6 @@ in { type = str; }; preread = { - enable = mkEnableOption "ssl preread" // { - # TODO: default = true; - }; - port = mkOption { - type = port; - default = 444; - }; ldapPort = mkOption { type = port; default = 637; @@ -104,10 +97,10 @@ in { }; config = { services.nginx = { + # TODO: ssl.preread.enable = mkDefault true; access.freeipa = { host = mkOptionDefault (config.lib.access.getAddressFor (config.lib.access.systemForService "freeipa").name "lan"); }; - defaultSSLListenPort = mkIf access.preread.enable access.preread.port; stream = let prereadConf = { upstreams = { @@ -128,28 +121,29 @@ in { port = mkOptionDefault nginx.stream.servers.ldap.listen.ldaps.port; }; }; - nginx = { - ssl.enable = true; - servers.access = { - addr = mkDefault "localhost"; - port = mkOptionDefault nginx.defaultSSLListenPort; - }; - }; }; servers = { - preread'https = { - listen = { - https.port = 443; + ${nginx.ssl.preread.serverName} = { + ssl.preread.upstreams = mapOptionDefaults { + ${virtualHosts.freeipa.serverName} = "freeipa"; + ${virtualHosts.freeipa'ca.serverName} = "freeipa"; + ${nginx.access.ldap.domain} = "ldaps_access"; + ${nginx.access.ldap.localDomain} = "ldaps_access"; + ${nginx.access.ldap.intDomain} = "ldaps_access"; + ${nginx.access.ldap.tailDomain} = "ldaps_access"; }; - ssl.preread.enable = true; - proxy.url = "$https_upstream"; }; preread'ldap = { listen = { - ldaps.port = 636; + ldaps.port = access.ldapPort; + }; + ssl.preread = { + enable = true; + upstreams = mapOptionDefaults { + ${virtualHosts.freeipa.serverName} = "ldaps"; + default = "ldaps_access"; + }; }; - ssl.preread.enable = true; - proxy.url = "$ldap_upstream"; }; }; }; @@ -198,37 +192,16 @@ in { conf.servers = { ldap = { listen = { - ldaps.port = mkIf access.preread.enable (mkDefault access.preread.ldapPort); + ldaps.port = mkIf nginx.ssl.preread.enable (mkDefault access.preread.ldapPort); }; ssl.cert.copyFromVhost = mkDefault "freeipa"; }; }; in mkMerge [ conf - (mkIf access.preread.enable prereadConf) + (mkIf nginx.ssl.preread.enable prereadConf) (mkIf access.kerberos.enable kerberosConf) ]; - streamConfig = let - inherit (nginx.stream) upstreams; - preread = '' - map $ssl_preread_server_name $https_upstream { - hostnames; - ${virtualHosts.freeipa.serverName} ${upstreams.freeipa.name}; - ${virtualHosts.freeipa'ca.serverName} ${upstreams.freeipa.name}; - ${nginx.access.ldap.domain} ${upstreams.ldaps_access.name}; - ${nginx.access.ldap.localDomain} ${upstreams.ldaps_access.name}; - ${nginx.access.ldap.intDomain} ${upstreams.ldaps_access.name}; - ${nginx.access.ldap.tailDomain} ${upstreams.ldaps_access.name}; - default ${upstreams.nginx.name}; - } - - map $ssl_preread_server_name $ldap_upstream { - hostnames; - ${virtualHosts.freeipa.serverName} ${upstreams.ldaps.name}; - default ${upstreams.ldaps_access.name}; - } - ''; - in mkIf access.preread.enable preread; virtualHosts = let name.shortServer = mkDefault "ipa"; in { @@ -292,8 +265,8 @@ in { access.kerberos.ports.kpasswd access.kerberos.ports.kadmin ]) - (mkIf access.preread.enable [ - 636 + (mkIf nginx.ssl.preread.enable [ + access.ldapPort ]) ]; allowedUDPPorts = mkIf access.kerberos.enable [ diff --git a/nixos/access/nginx.nix b/nixos/access/nginx.nix index 4d6f4bf0..7b341098 100644 --- a/nixos/access/nginx.nix +++ b/nixos/access/nginx.nix @@ -1,5 +1,8 @@ -_: { - networking.firewall.allowedTCPPorts = [ +{config, lib, ...}: let + inherit (lib.modules) mkIf; + cfg = config.services.nginx; +in { + networking.firewall.allowedTCPPorts = mkIf cfg.enable [ 443 80 ]; diff --git a/nixos/nginx.nix b/nixos/nginx.nix index 915c6548..4c14855f 100644 --- a/nixos/nginx.nix +++ b/nixos/nginx.nix @@ -1,13 +1,16 @@ { config, lib, - pkgs, ... -}: -with lib; { - networking.firewall.interfaces.local.allowedTCPPorts = [ - 443 - 80 +}: let + inherit (lib.modules) mkIf mkDefault; + cfg = config.services.nginx; +in { + networking.firewall.interfaces.local.allowedTCPPorts = let + inherit (cfg.ssl) preread; + in mkIf cfg.enable [ + (if preread.enable then preread.serverPort else cfg.defaultSSLListenPort) + cfg.defaultHTTPListenPort ]; services.nginx = {