Unverified Commit d74e5f4a authored by Ryan Lahfa's avatar Ryan Lahfa Committed by GitHub
Browse files

Merge pull request #213510 from RaitoBezarius/nginx-proxyprotocol

nixos/nginx: first-class PROXY protocol support
parents 3133de79 69bb0f94
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -17,3 +17,5 @@
## Other Notable Changes {#sec-release-23.11-notable-changes}

- A new option was added to the virtualisation module that enables specifying explicitly named network interfaces in QEMU VMs. The existing `virtualisation.vlans` is still supported for cases where the name of the network interface is irrelevant.

- `services.nginx` gained a `defaultListen` option at server-level with support for PROXY protocol listeners, also `proxyProtocol` is now exposed in `services.nginx.virtualHosts.<name>.listen` option. It is now possible to run PROXY listeners and non-PROXY listeners at a server-level, see [#213510](https://github.com/NixOS/nixpkgs/pull/213510/) for more details.
+94 −6
Original line number Diff line number Diff line
@@ -309,36 +309,54 @@ let
        onlySSL = vhost.onlySSL || vhost.enableSSL;
        hasSSL = onlySSL || vhost.addSSL || vhost.forceSSL;

        # First evaluation of defaultListen based on a set of listen lines.
        mkDefaultListenVhost = listenLines:
          # If this vhost has SSL or is a SSL rejection host.
          # We enable a TLS variant for lines without explicit ssl or ssl = true.
          optionals (hasSSL || vhost.rejectSSL)
            (map (listen: { port = cfg.defaultSSLListenPort; ssl = true; } // listen)
            (filter (listen: !(listen ? ssl) || listen.ssl) listenLines))
          # If this vhost is supposed to serve HTTP
          # We provide listen lines for those without explicit ssl or ssl = false.
          ++ optionals (!onlySSL)
            (map (listen: { port = cfg.defaultHTTPListenPort; ssl = false; } // listen)
            (filter (listen: !(listen ? ssl) || !listen.ssl) listenLines));

        defaultListen =
          if vhost.listen != [] then vhost.listen
          else
          if cfg.defaultListen != [] then mkDefaultListenVhost
            # Cleanup nulls which will mess up with //.
            # TODO: is there a better way to achieve this? i.e. mergeButIgnoreNullPlease?
            (map (listenLine: filterAttrs (_: v: (v != null)) listenLine) cfg.defaultListen)
          else
            let addrs = if vhost.listenAddresses != [] then vhost.listenAddresses else cfg.defaultListenAddresses;
            in optionals (hasSSL || vhost.rejectSSL) (map (addr: { inherit addr; port = cfg.defaultSSLListenPort; ssl = true; }) addrs)
              ++ optionals (!onlySSL) (map (addr: { inherit addr; port = cfg.defaultHTTPListenPort; ssl = false; }) addrs);
            in mkDefaultListenVhost (map (addr: { inherit addr; }) addrs);


        hostListen =
          if vhost.forceSSL
            then filter (x: x.ssl) defaultListen
            else defaultListen;

        listenString = { addr, port, ssl, extraParameters ? [], ... }:
        listenString = { addr, port, ssl, proxyProtocol ? false, extraParameters ? [], ... }:
          # UDP listener for QUIC transport protocol.
          (optionalString (ssl && vhost.quic) ("
            listen ${addr}:${toString port} quic "
          + optionalString vhost.default "default_server "
          + optionalString vhost.reuseport "reuseport "
          + optionalString (extraParameters != []) (concatStringsSep " " (
            let inCompatibleParameters = [ "ssl" "proxy_protocol" "http2" ];
          + optionalString (extraParameters != []) (concatStringsSep " "
            (let inCompatibleParameters = [ "ssl" "proxy_protocol" "http2" ];
                isCompatibleParameter = param: !(any (p: p == param) inCompatibleParameters);
            in filter isCompatibleParameter extraParameters))
          + ";"))
          + "

            listen ${addr}:${toString port} "
          + optionalString (ssl && vhost.http2) "http2 "
          + optionalString ssl "ssl "
          + optionalString vhost.default "default_server "
          + optionalString vhost.reuseport "reuseport "
          + optionalString proxyProtocol "proxy_protocol "
          + optionalString (extraParameters != []) (concatStringsSep " " extraParameters)
          + ";";

@@ -539,6 +557,49 @@ in
        '';
      };

      defaultListen = mkOption {
        type = with types; listOf (submodule {
          options = {
            addr = mkOption {
              type = str;
              description = lib.mdDoc "IP address.";
            };
            port = mkOption {
              type = nullOr port;
              description = lib.mdDoc "Port number.";
              default = null;
            };
            ssl  = mkOption {
              type = nullOr bool;
              default = null;
              description = lib.mdDoc "Enable SSL.";
            };
            proxyProtocol = mkOption {
              type = bool;
              description = lib.mdDoc "Enable PROXY protocol.";
              default = false;
            };
            extraParameters = mkOption {
              type = listOf str;
              description = lib.mdDoc "Extra parameters of this listen directive.";
              default = [ ];
              example = [ "backlog=1024" "deferred" ];
            };
          };
        });
        default = [];
        example = literalExpression ''[
          { addr = "10.0.0.12"; proxyProtocol = true; ssl = true; }
          { addr = "0.0.0.0"; }
          { addr = "[::0]"; }
        ]'';
        description = lib.mdDoc ''
          If vhosts do not specify listen, use these addresses by default.
          This option takes precedence over {option}`defaultListenAddresses` and
          other listen-related defaults options.
        '';
      };

      defaultListenAddresses = mkOption {
        type = types.listOf types.str;
        default = [ "0.0.0.0" ] ++ optional enableIPv6 "[::0]";
@@ -546,6 +607,7 @@ in
        example = literalExpression ''[ "10.0.0.12" "[2002:a00:1::]" ]'';
        description = lib.mdDoc ''
          If vhosts do not specify listenAddresses, use these addresses by default.
          This is akin to writing `defaultListen = [ { addr = "0.0.0.0" } ]`.
        '';
      };

@@ -1078,6 +1140,32 @@ in
          which can be achieved by setting `services.nginx.package = pkgs.nginxQuic;`.
        '';
      }

      {
        # The idea is to understand whether there is a virtual host with a listen configuration
        # that requires ACME configuration but has no HTTP listener which will make deterministically fail
        # this operation.
        # Options' priorities are the following at the moment:
        # listen (vhost) > defaultListen (server) > listenAddresses (vhost) > defaultListenAddresses (server)
        assertion =
        let
          hasAtLeastHttpListener = listenOptions: any (listenLine: if listenLine ? proxyProtocol then !listenLine.proxyProtocol else true) listenOptions;
          hasAtLeastDefaultHttpListener = if cfg.defaultListen != [] then hasAtLeastHttpListener cfg.defaultListen else (cfg.defaultListenAddresses != []);
        in
          all (host:
            let
              hasAtLeastVhostHttpListener = if host.listen != [] then hasAtLeastHttpListener host.listen else (host.listenAddresses != []);
              vhostAuthority = host.listen != [] || (cfg.defaultListen == [] && host.listenAddresses != []);
            in
              # Either vhost has precedence and we need a vhost specific http listener
              # Either vhost set nothing and inherit from server settings
              host.enableACME -> ((vhostAuthority && hasAtLeastVhostHttpListener) || (!vhostAuthority && hasAtLeastDefaultHttpListener))
          ) (attrValues virtualHosts);
        message = ''
          services.nginx.virtualHosts.<name>.enableACME requires a HTTP listener
          to answer to ACME requests.
        '';
      }
    ] ++ map (name: mkCertOwnershipAssertion {
      inherit (cfg) group user;
      cert = config.security.acme.certs.${name};
+30 −7
Original line number Diff line number Diff line
@@ -27,12 +27,35 @@ with lib;
    };

    listen = mkOption {
      type = with types; listOf (submodule { options = {
        addr = mkOption { type = str;  description = lib.mdDoc "IP address.";  };
        port = mkOption { type = port;  description = lib.mdDoc "Port number."; default = 80; };
        ssl  = mkOption { type = bool; description = lib.mdDoc "Enable SSL.";  default = false; };
        extraParameters = mkOption { type = listOf str; description = lib.mdDoc "Extra parameters of this listen directive."; default = []; example = [ "backlog=1024" "deferred" ]; };
      }; });
      type = with types; listOf (submodule {
        options = {
          addr = mkOption {
            type = str;
            description = lib.mdDoc "IP address.";
          };
          port = mkOption {
            type = port;
            description = lib.mdDoc "Port number.";
            default = 80;
          };
          ssl = mkOption {
            type = bool;
            description = lib.mdDoc "Enable SSL.";
            default = false;
          };
          proxyProtocol = mkOption {
            type = bool;
            description = lib.mdDoc "Enable PROXY protocol.";
            default = false;
          };
          extraParameters = mkOption {
            type = listOf str;
            description = lib.mdDoc "Extra parameters of this listen directive.";
            default = [ ];
            example = [ "backlog=1024" "deferred" ];
          };
        };
      });
      default = [];
      example = [
        { addr = "195.154.1.1"; port = 443; ssl = true; }
@@ -45,7 +68,7 @@ with lib;
        and `onlySSL`.

        If you only want to set the addresses manually and not
        the ports, take a look at `listenAddresses`
        the ports, take a look at `listenAddresses`.
      '';
    };

+1 −0
Original line number Diff line number Diff line
@@ -521,6 +521,7 @@ in {
  nginx-sandbox = handleTestOn ["x86_64-linux"] ./nginx-sandbox.nix {};
  nginx-sso = handleTest ./nginx-sso.nix {};
  nginx-variants = handleTest ./nginx-variants.nix {};
  nginx-proxyprotocol = handleTest ./nginx-proxyprotocol {};
  nifi = handleTestOn ["x86_64-linux"] ./web-apps/nifi.nix {};
  nitter = handleTest ./nitter.nix {};
  nix-ld = handleTest ./nix-ld.nix {};
+20 −0
Original line number Diff line number Diff line
-----BEGIN CERTIFICATE-----
MIIDLjCCAhagAwIBAgIIP2+4GFxOYMgwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE
AxMVbWluaWNhIHJvb3QgY2EgNGU3NTJiMB4XDTIzMDEzMDAzNDExOFoXDTQzMDEz
MDAzNDExOFowFTETMBEGA1UEAwwKKi50ZXN0Lm5peDCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAMarJSCzelnzTMT5GMoIKA/MXBNk5j277uI2Gq2MCky/
DlBpx+tjSsKsz6QLBduKMF8OH5AgjrVAKQAtsVPDseY0Qcyx/5dgJjkdO4on+DFb
V0SJ3ZhYPKACrqQ1SaoG+Xup37puw7sVR13J7oNvP6fAYRcjYqCiFC7VMjJNG4dR
251jvWWidSc7v5CYw2AxrngtBgHeQuyG9QCJ1DRH8h6ioV7IeonwReN7noYtTWh8
NDjGnw9HH2nYMcL91E+DWCxWVmbC9/orvYOT7u0Orho0t1w9BB0/zzcdojwQpMCv
HahEmFQmdGbWTuI4caBeaDBJVsSwKlTcxLSS4MAZ0c8CAwEAAaN3MHUwDgYDVR0P
AQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMB
Af8EAjAAMB8GA1UdIwQYMBaAFGyXySYI3gL88d7GHnGMU6wpiBf2MBUGA1UdEQQO
MAyCCioudGVzdC5uaXgwDQYJKoZIhvcNAQELBQADggEBAJ/DpwiLVBgWyozsn++f
kR4m0dUjnuCgpHo2EMoMZh+9og+OC0vq6WITXHaJytB3aBMxFOUTim3vwxPyWPXX
/vy+q6jJ6QMLx1J3VIWZdmXsT+qLGbVzL/4gNoaRsLPGO06p3yVjhas+OBFx1Fee
6kTHb82S/dzBojOJLRRo18CU9yw0FUXOPqN7HF7k2y+Twe6+iwCuCKGSFcvmRjxe
bWy11C921bTienW0Rmq6ppFWDaUNYP8kKpMN2ViAvc0tyF6wwk5lyOiqCR+pQHJR
H/J4qSeKDchYLKECuzd6SySz8FW/xPKogQ28zba+DBD86hpqiEJOBzxbrcN3cjUn
7N4=
-----END CERTIFICATE-----
Loading