nixos/nghttpx: add module for the nghttpx proxy server (#31680)

* nghttpx: Add a new NixOS module for the nghttpx proxy server

This change also adds a global `uid` and `gid` for a `nghttpx` user
and group as well as an integration test.

* nixos/nghttpx: fix building manual

authored by

Parnell Springmeyer and committed by
Jörg Thalheim
cb11bf73 8bd10a17

+642
+1
nixos/modules/module-list.nix
··· 484 484 ./services/networking/networkmanager.nix 485 485 ./services/networking/nftables.nix 486 486 ./services/networking/ngircd.nix 487 + ./services/networking/nghttpx/default.nix 487 488 ./services/networking/nix-serve.nix 488 489 ./services/networking/nntp-proxy.nix 489 490 ./services/networking/nsd.nix
+131
nixos/modules/services/networking/nghttpx/backend-params-submodule.nix
··· 1 + { lib, ...}: 2 + { options = { 3 + proto = lib.mkOption { 4 + type = lib.types.enum [ "h2" "http/1.1" ]; 5 + default = "http/1.1"; 6 + description = '' 7 + This option configures the protocol the backend server expects 8 + to use. 9 + 10 + Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-b 11 + for more detail. 12 + ''; 13 + }; 14 + 15 + tls = lib.mkOption { 16 + type = lib.types.bool; 17 + default = false; 18 + description = '' 19 + This option determines whether nghttpx will negotiate its 20 + connection with a backend server using TLS or not. The burden 21 + is on the backend server to provide the TLS certificate! 22 + 23 + Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-b 24 + for more detail. 25 + ''; 26 + }; 27 + 28 + sni = lib.mkOption { 29 + type = lib.types.nullOr lib.types.str; 30 + default = null; 31 + description = '' 32 + Override the TLS SNI field value. This value (in nghttpx) 33 + defaults to the host value of the backend configuration. 34 + 35 + Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-b 36 + for more detail. 37 + ''; 38 + }; 39 + 40 + fall = lib.mkOption { 41 + type = lib.types.int; 42 + default = 0; 43 + description = '' 44 + If nghttpx cannot connect to the backend N times in a row, the 45 + backend is assumed to be offline and is excluded from load 46 + balancing. If N is 0 the backend is never excluded from load 47 + balancing. 48 + 49 + Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-b 50 + for more detail. 51 + ''; 52 + }; 53 + 54 + rise = lib.mkOption { 55 + type = lib.types.int; 56 + default = 0; 57 + description = '' 58 + If the backend is excluded from load balancing, nghttpx will 59 + periodically attempt to make a connection to the backend. If 60 + the connection is successful N times in a row the backend is 61 + re-included in load balancing. If N is 0 a backend is never 62 + reconsidered for load balancing once it falls. 63 + 64 + Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-b 65 + for more detail. 66 + ''; 67 + }; 68 + 69 + affinity = lib.mkOption { 70 + type = lib.types.enum [ "ip" "none" ]; 71 + default = "none"; 72 + description = '' 73 + If "ip" is given, client IP based session affinity is 74 + enabled. If "none" is given, session affinity is disabled. 75 + 76 + Session affinity is enabled (by nghttpx) per-backend 77 + pattern. If at least one backend has a non-"none" affinity, 78 + then session affinity is enabled for all backend servers 79 + sharing the same pattern. 80 + 81 + It is advised to set affinity on all backends explicitly if 82 + session affinity is desired. The session affinity may break if 83 + one of the backend gets unreachable, or backend settings are 84 + reloaded or replaced by API. 85 + 86 + Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-b 87 + for more detail. 88 + ''; 89 + }; 90 + 91 + dns = lib.mkOption { 92 + type = lib.types.bool; 93 + default = false; 94 + description = '' 95 + Name resolution of a backends host name is done at start up, 96 + or configuration reload. If "dns" is true, name resolution 97 + takes place dynamically. 98 + 99 + This is useful if a backends address changes frequently. If 100 + "dns" is true, name resolution of a backend's host name at 101 + start up, or configuration reload is skipped. 102 + 103 + Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-b 104 + for more detail. 105 + ''; 106 + }; 107 + 108 + redirect-if-not-tls = lib.mkOption { 109 + type = lib.types.bool; 110 + default = false; 111 + description = '' 112 + If true, a backend match requires the frontend connection be 113 + TLS encrypted. If it is not, nghttpx responds to the request 114 + with a 308 status code and https URI the client should use 115 + instead in the Location header. 116 + 117 + The port number in the redirect URI is 443 by default and can 118 + be changed using 'services.nghttpx.redirect-https-port' 119 + option. 120 + 121 + If at least one backend has "redirect-if-not-tls" set to true, 122 + this feature is enabled for all backend servers with the same 123 + pattern. It is advised to set "redirect-if-no-tls" parameter 124 + to all backends explicitly if this feature is desired. 125 + 126 + Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-b 127 + for more detail. 128 + ''; 129 + }; 130 + }; 131 + }
+50
nixos/modules/services/networking/nghttpx/backend-submodule.nix
··· 1 + { lib, ... }: 2 + { options = { 3 + server = lib.mkOption { 4 + type = 5 + lib.types.either 6 + (lib.types.submodule (import ./server-options.nix)) 7 + (lib.types.path); 8 + example = { 9 + host = "127.0.0.1"; 10 + port = 8888; 11 + }; 12 + default = { 13 + host = "127.0.0.1"; 14 + port = 80; 15 + }; 16 + description = '' 17 + Backend server location specified as either a host:port pair 18 + or a unix domain docket. 19 + ''; 20 + }; 21 + 22 + patterns = lib.mkOption { 23 + type = lib.types.listOf lib.types.str; 24 + example = [ 25 + "*.host.net/v1/" 26 + "host.org/v2/mypath" 27 + "/somepath" 28 + ]; 29 + default = []; 30 + description = '' 31 + List of nghttpx backend patterns. 32 + 33 + Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-b 34 + for more information on the pattern syntax and nghttpxs behavior. 35 + ''; 36 + }; 37 + 38 + params = lib.mkOption { 39 + type = lib.types.nullOr (lib.types.submodule (import ./backend-params-submodule.nix)); 40 + example = { 41 + proto = "h2"; 42 + tls = true; 43 + }; 44 + default = null; 45 + description = '' 46 + Parameters to configure a backend. 47 + ''; 48 + }; 49 + }; 50 + }
+117
nixos/modules/services/networking/nghttpx/default.nix
··· 1 + {config, pkgs, lib, ...}: 2 + let 3 + cfg = config.services.nghttpx; 4 + 5 + # renderHost :: Either ServerOptions Path -> String 6 + renderHost = server: 7 + if builtins.isString server 8 + then "unix://${server}" 9 + else "${server.host},${builtins.toString server.port}"; 10 + 11 + # Filter out submodule parameters whose value is null or false or is 12 + # the key _module. 13 + # 14 + # filterParams :: ParamsSubmodule -> ParamsSubmodule 15 + filterParams = p: 16 + lib.filterAttrs 17 + (n: v: ("_module" != n) && (null != v) && (false != v)) 18 + (lib.optionalAttrs (null != p) p); 19 + 20 + # renderBackend :: BackendSubmodule -> String 21 + renderBackend = backend: 22 + let 23 + host = renderHost backend.server; 24 + patterns = lib.concatStringsSep ":" backend.patterns; 25 + 26 + # Render a set of backend parameters, this is somewhat 27 + # complicated because nghttpx backend patterns can be entirely 28 + # omitted and the params may be given as a mixed collection of 29 + # 'key=val' pairs or atoms (e.g: 'proto=h2;tls') 30 + params = 31 + lib.mapAttrsToList 32 + (n: v: 33 + if builtins.isBool v 34 + then n 35 + else if builtins.isString v 36 + then "${n}=${v}" 37 + else "${n}=${builtins.toString v}") 38 + (filterParams backend.params); 39 + 40 + # NB: params are delimited by a ";" which is the same delimiter 41 + # to separate the host;[pattern];[params] sections of a backend 42 + sections = 43 + builtins.filter (e: "" != e) ([ 44 + host 45 + patterns 46 + ]++params); 47 + formattedSections = lib.concatStringsSep ";" sections; 48 + in 49 + "backend=${formattedSections}"; 50 + 51 + # renderFrontend :: FrontendSubmodule -> String 52 + renderFrontend = frontend: 53 + let 54 + host = renderHost frontend.server; 55 + params0 = 56 + lib.mapAttrsToList 57 + (n: v: if builtins.isBool v then n else v) 58 + (filterParams frontend.params); 59 + 60 + # NB: nghttpx doesn't accept "tls", you must omit "no-tls" for 61 + # the default behavior of turning on TLS. 62 + params1 = lib.remove "tls" params0; 63 + 64 + sections = [ host] ++ params1; 65 + formattedSections = lib.concatStringsSep ";" sections; 66 + in 67 + "frontend=${formattedSections}"; 68 + 69 + configurationFile = pkgs.writeText "nghttpx.conf" '' 70 + ${lib.optionalString (null != cfg.tls) ("private-key-file="+cfg.tls.key)} 71 + ${lib.optionalString (null != cfg.tls) ("certificate-file="+cfg.tls.crt)} 72 + 73 + user=nghttpx 74 + 75 + ${lib.concatMapStringsSep "\n" renderFrontend cfg.frontends} 76 + ${lib.concatMapStringsSep "\n" renderBackend cfg.backends} 77 + 78 + backlog=${builtins.toString cfg.backlog} 79 + backend-address-family=${cfg.backend-address-family} 80 + 81 + workers=${builtins.toString cfg.workers} 82 + rlimit-nofile=${builtins.toString cfg.rlimit-nofile} 83 + 84 + ${lib.optionalString cfg.single-thread "single-thread=yes"} 85 + ${lib.optionalString cfg.single-process "single-process=yes"} 86 + 87 + ${cfg.extraConfig} 88 + ''; 89 + in 90 + { imports = [ 91 + ./nghttpx-options.nix 92 + ]; 93 + 94 + config = lib.mkIf cfg.enable { 95 + 96 + users.groups.nghttpx = { }; 97 + users.users.nghttpx = { 98 + group = config.users.groups.nghttpx.name; 99 + }; 100 + 101 + 102 + systemd.services = { 103 + nghttpx = { 104 + wantedBy = [ "multi-user.target" ]; 105 + after = [ "network.target" ]; 106 + script = '' 107 + ${pkgs.nghttp2}/bin/nghttpx --conf=${configurationFile} 108 + ''; 109 + 110 + serviceConfig = { 111 + Restart = "on-failure"; 112 + RestartSec = 60; 113 + }; 114 + }; 115 + }; 116 + }; 117 + }
+64
nixos/modules/services/networking/nghttpx/frontend-params-submodule.nix
··· 1 + { lib, ...}: 2 + { options = { 3 + tls = lib.mkOption { 4 + type = lib.types.enum [ "tls" "no-tls" ]; 5 + default = "tls"; 6 + description = '' 7 + Enable or disable TLS. If true (enabled) the key and 8 + certificate must be configured for nghttpx. 9 + 10 + Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-f 11 + for more detail. 12 + ''; 13 + }; 14 + 15 + sni-fwd = lib.mkOption { 16 + type = lib.types.bool; 17 + default = false; 18 + description = '' 19 + When performing a match to select a backend server, SNI host 20 + name received from the client is used instead of the request 21 + host. See --backend option about the pattern match. 22 + 23 + Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-f 24 + for more detail. 25 + ''; 26 + }; 27 + 28 + api = lib.mkOption { 29 + type = lib.types.bool; 30 + default = false; 31 + description = '' 32 + Enable API access for this frontend. This enables you to 33 + dynamically modify nghttpx at run-time therefore this feature 34 + is disabled by default and should be turned on with care. 35 + 36 + Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-f 37 + for more detail. 38 + ''; 39 + }; 40 + 41 + healthmon = lib.mkOption { 42 + type = lib.types.bool; 43 + default = false; 44 + description = '' 45 + Make this frontend a health monitor endpoint. Any request 46 + received on this frontend is responded to with a 200 OK. 47 + 48 + Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-f 49 + for more detail. 50 + ''; 51 + }; 52 + 53 + proxyproto = lib.mkOption { 54 + type = lib.types.bool; 55 + default = false; 56 + description = '' 57 + Accept PROXY protocol version 1 on frontend connection. 58 + 59 + Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-f 60 + for more detail. 61 + ''; 62 + }; 63 + }; 64 + }
+36
nixos/modules/services/networking/nghttpx/frontend-submodule.nix
··· 1 + { lib, ... }: 2 + { options = { 3 + server = lib.mkOption { 4 + type = 5 + lib.types.either 6 + (lib.types.submodule (import ./server-options.nix)) 7 + (lib.types.path); 8 + example = { 9 + host = "127.0.0.1"; 10 + port = 8888; 11 + }; 12 + default = { 13 + host = "127.0.0.1"; 14 + port = 80; 15 + }; 16 + description = '' 17 + Frontend server interface binding specification as either a 18 + host:port pair or a unix domain docket. 19 + 20 + NB: a host of "*" listens on all interfaces and includes IPv6 21 + addresses. 22 + ''; 23 + }; 24 + 25 + params = lib.mkOption { 26 + type = lib.types.nullOr (lib.types.submodule (import ./frontend-params-submodule.nix)); 27 + example = { 28 + tls = "tls"; 29 + }; 30 + default = null; 31 + description = '' 32 + Parameters to configure a backend. 33 + ''; 34 + }; 35 + }; 36 + }
+142
nixos/modules/services/networking/nghttpx/nghttpx-options.nix
··· 1 + { config, lib, ... }: 2 + { options.services.nghttpx = { 3 + enable = lib.mkEnableOption "nghttpx"; 4 + 5 + frontends = lib.mkOption { 6 + type = lib.types.listOf (lib.types.submodule (import ./frontend-submodule.nix)); 7 + description = '' 8 + A list of frontend listener specifications. 9 + ''; 10 + example = [ 11 + { server = { 12 + host = "*"; 13 + port = 80; 14 + }; 15 + 16 + params = { 17 + tls = "no-tls"; 18 + }; 19 + } 20 + ]; 21 + }; 22 + 23 + backends = lib.mkOption { 24 + type = lib.types.listOf (lib.types.submodule (import ./backend-submodule.nix)); 25 + description = '' 26 + A list of backend specifications. 27 + ''; 28 + example = [ 29 + { server = { 30 + host = "172.16.0.22"; 31 + port = 8443; 32 + }; 33 + patterns = [ "/" ]; 34 + params = { 35 + proto = "http/1.1"; 36 + redirect-if-not-tls = true; 37 + }; 38 + } 39 + ]; 40 + }; 41 + 42 + tls = lib.mkOption { 43 + type = lib.types.nullOr (lib.types.submodule (import ./tls-submodule.nix)); 44 + default = null; 45 + description = '' 46 + TLS certificate and key paths. Note that this does not enable 47 + TLS for a frontend listener, to do so, a frontend 48 + specification must set <literal>params.tls</literal> to true. 49 + ''; 50 + example = { 51 + key = "/etc/ssl/keys/server.key"; 52 + crt = "/etc/ssl/certs/server.crt"; 53 + }; 54 + }; 55 + 56 + extraConfig = lib.mkOption { 57 + type = lib.types.lines; 58 + default = ""; 59 + description = '' 60 + Extra configuration options to be appended to the generated 61 + configuration file. 62 + ''; 63 + }; 64 + 65 + single-process = lib.mkOption { 66 + type = lib.types.bool; 67 + default = false; 68 + description = '' 69 + Run this program in a single process mode for debugging 70 + purpose. Without this option, nghttpx creates at least 2 71 + processes: master and worker processes. If this option is 72 + used, master and worker are unified into a single 73 + process. nghttpx still spawns additional process if neverbleed 74 + is used. In the single process mode, the signal handling 75 + feature is disabled. 76 + 77 + Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx--single-process 78 + ''; 79 + }; 80 + 81 + backlog = lib.mkOption { 82 + type = lib.types.int; 83 + default = 65536; 84 + description = '' 85 + Listen backlog size. 86 + 87 + Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx--backlog 88 + ''; 89 + }; 90 + 91 + backend-address-family = lib.mkOption { 92 + type = lib.types.enum [ 93 + "auto" 94 + "IPv4" 95 + "IPv6" 96 + ]; 97 + default = "auto"; 98 + description = '' 99 + Specify address family of backend connections. If "auto" is 100 + given, both IPv4 and IPv6 are considered. If "IPv4" is given, 101 + only IPv4 address is considered. If "IPv6" is given, only IPv6 102 + address is considered. 103 + 104 + Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx--backend-address-family 105 + ''; 106 + }; 107 + 108 + workers = lib.mkOption { 109 + type = lib.types.int; 110 + default = 1; 111 + description = '' 112 + Set the number of worker threads. 113 + 114 + Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-n 115 + ''; 116 + }; 117 + 118 + single-thread = lib.mkOption { 119 + type = lib.types.bool; 120 + default = false; 121 + description = '' 122 + Run everything in one thread inside the worker process. This 123 + feature is provided for better debugging experience, or for 124 + the platforms which lack thread support. If threading is 125 + disabled, this option is always enabled. 126 + 127 + Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx--single-thread 128 + ''; 129 + }; 130 + 131 + rlimit-nofile = lib.mkOption { 132 + type = lib.types.int; 133 + default = 0; 134 + description = '' 135 + Set maximum number of open files (RLIMIT_NOFILE) to &lt;N&gt;. If 0 136 + is given, nghttpx does not set the limit. 137 + 138 + Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx--rlimit-nofile 139 + ''; 140 + }; 141 + }; 142 + }
+18
nixos/modules/services/networking/nghttpx/server-options.nix
··· 1 + { lib, ... }: 2 + { options = { 3 + host = lib.mkOption { 4 + type = lib.types.str; 5 + example = "127.0.0.1"; 6 + description = '' 7 + Server host address. 8 + ''; 9 + }; 10 + port = lib.mkOption { 11 + type = lib.types.int; 12 + example = 5088; 13 + description = '' 14 + Server host port. 15 + ''; 16 + }; 17 + }; 18 + }
+21
nixos/modules/services/networking/nghttpx/tls-submodule.nix
··· 1 + {lib, ...}: 2 + { options = { 3 + key = lib.mkOption { 4 + type = lib.types.str; 5 + example = "/etc/ssl/keys/mykeyfile.key"; 6 + default = "/etc/ssl/keys/server.key"; 7 + description = '' 8 + Path to the TLS key file. 9 + ''; 10 + }; 11 + 12 + crt = lib.mkOption { 13 + type = lib.types.str; 14 + example = "/etc/ssl/certs/mycert.crt"; 15 + default = "/etc/ssl/certs/server.crt"; 16 + description = '' 17 + Path to the TLS certificate file. 18 + ''; 19 + }; 20 + }; 21 + }
+1
nixos/release.nix
··· 303 303 tests.nfs3 = callTest tests/nfs.nix { version = 3; }; 304 304 tests.nfs4 = callTest tests/nfs.nix { version = 4; }; 305 305 tests.nginx = callTest tests/nginx.nix { }; 306 + tests.nghttpx = callTest tests/nghttpx.nix { }; 306 307 tests.leaps = callTest tests/leaps.nix { }; 307 308 tests.nsd = callTest tests/nsd.nix {}; 308 309 tests.openssh = callTest tests/openssh.nix {};
+61
nixos/tests/nghttpx.nix
··· 1 + let 2 + nginxRoot = "/var/run/nginx"; 3 + in 4 + import ./make-test.nix ({...}: { 5 + name = "nghttpx"; 6 + nodes = { 7 + webserver = { 8 + networking.firewall.allowedTCPPorts = [ 80 ]; 9 + systemd.services.nginx = { 10 + preStart = '' 11 + mkdir -p ${nginxRoot} 12 + echo "Hello world!" > ${nginxRoot}/hello-world.txt 13 + ''; 14 + }; 15 + 16 + services.nginx = { 17 + enable = true; 18 + virtualHosts."server" = { 19 + locations."/".root = nginxRoot; 20 + }; 21 + }; 22 + }; 23 + 24 + proxy = { 25 + networking.firewall.allowedTCPPorts = [ 80 ]; 26 + services.nghttpx = { 27 + enable = true; 28 + frontends = [ 29 + { server = { 30 + host = "*"; 31 + port = 80; 32 + }; 33 + 34 + params = { 35 + tls = "no-tls"; 36 + }; 37 + } 38 + ]; 39 + backends = [ 40 + { server = { 41 + host = "webserver"; 42 + port = 80; 43 + }; 44 + patterns = [ "/" ]; 45 + params.proto = "http/1.1"; 46 + } 47 + ]; 48 + }; 49 + }; 50 + 51 + client = {}; 52 + }; 53 + 54 + testScript = '' 55 + startAll; 56 + 57 + $webserver->waitForOpenPort("80"); 58 + $proxy->waitForOpenPort("80"); 59 + $client->waitUntilSucceeds("curl -s --fail http://proxy/hello-world.txt"); 60 + ''; 61 + })